---
# System prepended metadata

title: Lesson 8 | 高階資料清理與驗證規則

---

# Lesson 8 | 高階資料清理與驗證規則
## 第一節：資料清理(1)

* 在拿到一筆資料時，第一件事情應該是做資料清理(或稱品質控制)。我們需要先把資料中不應該出現的值給去除，接著再進行分析。

    * 一般來說，資料進來以後我們會設定驗證規則，如果不符合此一規則就刪除或重新檢視。

* 驗證規則主要分為下列幾種：

    * 不正確的數據(Incorrect data)：如年齡>130歲
    * 不準確的數據(Inaccurate data)：如年齡實際為50歲的人，被紀錄為60歲
    * 重複的數據(Duplicate data)：如重複key-in的問卷
    * 不完整的數據(Incomplete data)：不該遺漏而遺漏的數據
    * 不一致的數據(Inconsistent data)：如洗腎患者腎絲球過濾率卻有90 ml/min/1.73m2
    * 違反規則(Rule violations)：如收案日期為2010年以前，卻出現2013年的資料

* 在某些領域中，一間公司能否賺錢的重點並非使用多新的統計分析，其最重要的核心價值可能是在資料清理。這時候若你的驗證規則設定的夠仔細，這樣出錯的機率就較低。

## 第一節：資料清理(2)

* 我們現在使用一個CKD門診衛教計劃的範例資料，請在[這裡](https://linchin.ndmctsgh.edu.tw/data/validated_example.csv)下載範例資料。

    * 這個資料是由門診護理師Key-in填入，按照計劃目標，所有病人每2個月追蹤一次(低於2個月不能給補助)。

    * 現在你是腎臟醫學會核發衛教補助的承辦人員，你希望了解一下哪些紀錄是有問題的，而找到這些紀錄後你將要通知該醫院的門診護理師，請他再查閱紙本資料後重新Key-in

* 第一步驟都是相同的，先讀進來觀察一下資料。

    ```python=
    import pandas as pd

    data = pd.read_csv("validated_example.csv", encoding = 'CP950') 
    data
    ```
    
## 第一節：資料清理(3)

* 首先第一步，我們先檢查日期，在```Pandas```裡面剛讀進來的檔案中，日期的格式通常需要再進一步進行轉換。比較簡單的做法是將其欄位轉變為```Datetime```，它需要用到函式「.to_datetime()」，可以在[這裡](https://pandas.pydata.org/docs/reference/api/pandas.to_datetime.html)看到相關API的說明。

* 讓我們來嘗試一下如何使用。

    ```python=
    test_date = ["2011/01/05", "2011/09/31", "2011/02/29", "2016/02/29"]
    test_date = pd.to_datetime(test_date, errors = 'coerce')
    test_date
    ```

* 這個步驟可以確保所有內容都成為「日期格式」，如果有非正確的日期，這時候Pandas就會回傳警告，該變數就會變成「NaT」：

* 這個時候我們可以用另一個函式「.isna()」，來告訴我們該變數是否為空值。

    ```python=
    print(pd.isna(test_date))

    print(test_date[pd.isna(test_date) == True])

    print(test_date[pd.isna(test_date) == False])
    ```

## 第一節：資料清理(4)

* 接著，我們就可以將原始的資料多加一個欄位來標註資料的日期是否正確。

    ```python=
    data['wrong_date'] = pd.isna(pd.to_datetime(data['Date'], errors = 'coerce'))
    
    print(data[data['wrong_date'] == True])
    ```

* 在這筆資料中，有兩筆錯誤的日期！

* 另外說明，在```Python```裡面我們常用內建的標準函式庫```datetime```來處理與時間相關的任務，這邊就不贅述，同學們可以按[這裡](https://docs.python.org/zh-tw/3.8/library/datetime.html)查看相關說明。

## 第一節：資料清理(5)

* 我們稍微整理一下規則1的程式碼，老師會把這串程式碼整理成這樣。

    ```python=
    import pandas as pd

    #Read data
    data = pd.read_csv("validated_example.csv", encoding = 'CP950')

    #Rule 1: check date-format
    data['Date'] = pd.to_datetime(data['Date'], errors = 'coerce')
    data['wrong_date'] = pd.isna(data['Date'])
    print(data[data['wrong_date'] == True])    
    
    #Rule 2: ...

    ```

## 第一節：資料清理(6)

* 接著，我們需要檢查各變項的範圍，我們先從類別變項開始。變項『Stage』是指CKD的期別，共有5個期別，所以我們的第二條規則是先確定是否有錯誤的數値。
    
    * 可以利用之前學過的方法「.isin()」，還記得怎麼用嗎?

* 下面是精簡的程式碼，加上```== False```就可以反轉正負，所以wrong_stage為True的就變成是「不屬於1至5的」。

    ```python=
    data['wrong_stage'] = data['Stage'].isin([1,2,3,4,5]) == False
    ```
    
## 第一節：資料清理(7)

* 連續變項的值比較困難一點，我們可以透過簡單的設定區間找出看起來異常的值，如K低於1.5或高於10的我們可以將它找出來

    * 索引函數可以幫助我們設定規則，値得注意的是，未來如果我們想要將連續變項『血壓値』轉換為類別變項『高血壓』時，可以透過類似的方式。
    
        ```python=
        data['wrong_k'] = (data['K'] > 10) | (data['K'] < 1.5)
        ```
    
* 這時候，如果我們去細看某些病人的資料，會發現一件事情：

    * 由於有一些人的K是missing的，但在判斷之後會被填入False。

        ```python=
        data[data['Patient'] == 1514]
        ```
    
    * 可以透過這樣的方式來將該變項為```NaN```的行數，在檢測時也歸類為錯誤。
    
        ```python=
        data.loc[pd.isna(data['K']), 'wrong_k'] = True
    
        print(data[data['Patient'] == 1514])
        ```
        
## 練習1：

* 請同學檢查Stage與MDRD.GFR之關係是否有錯誤

    * 資料表裡面已經有Stage跟MDRD.GFR了，可以這樣觀察。

        ```python=
        print(data[['Stage', 'MDRD.GFR']])
        ```
    * 而正確的Stage與GFR之關係為：1 - 90以上、2 - 60至90、3 - 30至60、4 - 15至30、5 - 15以下。

* 你可以整理完後，將整個檔案輸出，在Excel中看看自己找到的異常値。

    <details>
  <summary>解答</summary>
    
    ```python=
    data = pd.read_csv("validated_example.csv", encoding = 'CP950')
    
    data.loc[data['MDRD.GFR'] > 90, 'my_stage'] = 1
    data.loc[(data['MDRD.GFR'] > 60) & (data['MDRD.GFR'] <= 90), 'my_stage'] = 2
    data.loc[(data['MDRD.GFR'] > 30) & (data['MDRD.GFR'] <= 60), 'my_stage'] = 3
    data.loc[(data['MDRD.GFR'] > 15) & (data['MDRD.GFR'] <= 30), 'my_stage'] = 4
    data.loc[data['MDRD.GFR'] <= 15, 'my_stage'] = 5

    data.loc[data['my_stage'] != data['Stage'], 'wrong_stage'] = True
    ```

    * 或是也可以這樣觀察。
    
    
        ```python=
        data.loc[data['wrong_stage'] == True, ['MDRD.GFR', 'Stage', 'my_stage', "wrong_stage"]]
        ```
    
    </details>
    
## 第二節：簡易驗證規則(1)
    
* 接著我們要設定比較複雜的驗證規則。剛剛有說到同一個人每2個月才能申報一次，如果有人太密集了，那就要把他挑出來。
    
* 先想步驟，好好思考該怎樣做?

## 第二節：簡易驗證規則(2)
    
* 首先，我們需要先創造一個邏輯向量，長度為個案數，填入TRUE或FALSE表示這個人是否需要被Highlight。
    
* 讓我們先留下日期正確的資料。
    
    ```python=
    data = pd.read_csv("validated_example.csv", encoding = 'CP950')

    data['Date'] = pd.to_datetime(data['Date'], errors = 'coerce')
    data['wrong_date'] = pd.isna(data['Date'])
    print(data[data['wrong_date'] == True])

    data = data.loc[data['wrong_date'] == False, :]
    ```
    
* 還記得怎樣了解個案數嗎?可以透過函數「set()」以及函數「list()」的組合。
    
    ```python=
    patient = list(set(data['Patient']))
    
    # 並創建一個可以填答案的list
    highlight = [False] * len(patient)
    ```
    
* 我們可以透過迴圈函數，檢查每個人所有的申報日期

    * 我們先抓出第一個病患的data
    
        ```python=
        i = 0
        sub_data = data.loc[data['Patient'] == patient[i], :]
        sub_data
        ```

## 第二節：簡易驗證規則(3)
        
* 接著，我們要比對日期了，我們可以先排序日期後，再透過後減前計算差距，並把結果儲存成另外一個向量(這裡也可以再做個迴圈)
    
    * 如果個案僅申報一筆資料就不用繼續了，所以必須加入條件判斷
    
        ```python=
        i = 9
        sub_data = data.loc[data['Patient'] == patient[i], :]
        sub_data.sort_values(by='Date')
        sub_data.index = range(sub_data.shape[0])
        
        print(sub_data.shape)
        n_date = sub_data.shape[0]
        if n_date > 1:
            date_diff = [None] * (n_date - 1)
            for k in range(n_date - 1):
                print(k+1, k)
                print(sub_data['Date'][k+1], sub_data['Date'][k])
                date_diff[k] = sub_data['Date'][k+1] - sub_data['Date'][k]

        print(date_diff)
        ```
    
## 第二節：簡易驗證規則(4)
    
* 下一步，我們想要了解有沒有間隔<2個月的，一個比較簡單的想法是檢查差異是否小於60天，並把結果儲存起來。

    ```python=
    i = 1
    sub_data = data.loc[data['Patient'] == patient[i], :]
    sub_data.sort_values(by='Date')
    sub_data.index = range(sub_data.shape[0])

    n_date = sub_data.shape[0]
    if n_date > 1:
        date_diff = [None] * (n_date - 1)
        for k in range(n_date - 1):
            date_diff = sub_data['Date'][k+1] - sub_data['Date'][k]
            # 因為date_diff是pandas的特殊物件，我們可以這樣判斷
            if date_diff < pd.Timedelta(60, unit='days'):
                print(date_diff)
    ```
                                       
* 現在，我們能將這個結果儲存在物件「highlight」內了

    ```python=
    patient = list(set(data['Patient']))

    # 並創建一個可以填答案的list
    highlight = [False] * len(patient)

    i = 1
    sub_data = data.loc[data['Patient'] == patient[i], :]
    sub_data.sort_values(by='Date')
    sub_data.index = range(sub_data.shape[0])

    n_date = sub_data.shape[0]
    if n_date > 1:
        date_diff = [None] * (n_date - 1)
        for k in range(n_date - 1):
            date_diff = sub_data['Date'][k+1] - sub_data['Date'][k]
            # 因為date_diff是pandas的特殊物件，我們可以這樣判斷
            if date_diff < pd.Timedelta(60, unit='days'):
                highlight[i] = True
    ```

## 第二節：簡易驗證規則(5)

* 我們可以將這些組合成一個迴圈了，並且把物件「highlight」整個填滿，完整程式如下。
                                           
    ```python=
    patient = list(set(data['Patient']))
    highlight = [False] * len(patient)

    for i in range(len(patient)):

        sub_data = data.loc[data['Patient'] == patient[i], :]
        sub_data.sort_values(by='Date')
        sub_data.index = range(sub_data.shape[0])

        n_date = sub_data.shape[0]
        if n_date > 1:
            date_diff = [None] * (n_date - 1)
            for k in range(n_date - 1):
                date_diff = sub_data['Date'][k+1] - sub_data['Date'][k]
                if date_diff < pd.Timedelta(60, unit='days'):
                    highlight[i] = True
    ```                              

* 我們可以用這樣的方式來看是哪些人的時間資料可能有誤。
                                           
    ```python=
    x = []
    for i in range(len(patient)):
        if highlight[i]:
            x.append(patient[i])
    print(x)
    ```
                                           
* 最後，增加一個欄位，一口氣把這些人標示出來。
                                           
    ```python=
    data['wrong_date_interval'] = data['Patient'].isin(x)
    ```
                
* 記得，可以找個案例稍微檢查一下。
                                           
    ```python=
    data.loc[data['Patient'] == 710,]
    ```
                                           
## 總結
                                           
* 課程到此為止，同學對於資料整理以及資料品質控制應該是都有辦法得心應手了。
    * 目前為止有跟上進度的同學，應該可以建立一個資料處理pipline，不管原始蒐集的資料為何，都能透過一系列的轉換&品質控制，獲得乾淨的資料。

* 你應該已經學會下面的功能：

    * 資料讀入及寫出
    * 資料清理
    * 簡單資料合併
    * 熟練的利用迴圈做大量重複的事
    * 對於資料處理時的事前規劃
    * 驗證規則的設定