# 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,不管原始蒐集的資料為何,都能透過一系列的轉換&品質控制,獲得乾淨的資料。 * 你應該已經學會下面的功能: * 資料讀入及寫出 * 資料清理 * 簡單資料合併 * 熟練的利用迴圈做大量重複的事 * 對於資料處理時的事前規劃 * 驗證規則的設定