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