Data Cleansing

資料清理(Data Cleaning)指的是一個檢測資料品質的過程,在這個過程中去檢查、校正、轉換,甚至移除錯誤資料。資料清理不僅僅是一個修正錯誤的過程,同時它也是在確保資料的一致性,避免不同的資料科學家在分析的過程中使用到不同的資料,進而導致結果的分歧。

一開始提案單位所提供的原始資料集是從資料庫中的不同的資料表所撈出來的系統資料,而系統資料有可能會因為當初設計時沒有驗證使用者輸入的資料,抑或是沒有強制要求輸入時一定要填行政流程中的必填欄位,導致原始資料集中會有重複資料、資料缺陷及格式錯誤等等的問題。另外,再依據分析的過程中的需要進行資料格式的轉換、資料合併及資料的延伸等等。

重複資料

一個家戶會有一個獨一無二的卡號,而每一個人也都會有一個獨一無二的個人編號,而在原始資料中發現同一卡號內有重複的個人編號出現,這樣的問題會造成統計家戶人數時的錯誤。

這裡可以使用pandaspandas.DataFrame.drop_duplicates 便可以將重複資料剔除。

df = df.drop_duplicates(subset=['個人編號'], keep='last')
  • subset:可以傳入一個或多個欄位名稱。
  • keep:則是選擇要保留哪一筆重複資料;可選擇 firstlast,或是設定 False 將所有重複值移除。

日期轉換

系統資料的日期是使用民國年月日(e.g. 1070101),但是大部分的程式在視覺化的時候是使用西元的年月日格式,主要可以分為以下步驟:

  1. 字串切割:先將原本的字串(1070101)切割為(107)、(01)、(01)
  2. 型態轉換:再將從字串 parse 成數字
  3. 數字相加:將民國年加上 1911後,得到 2017
  4. 型態轉換:再將 2017從數字parse 回字串
  5. 字串相加:將西元的年月日重新串接起來成為 2018-01-01
  6. 型態轉換:最後再利用 pandas.to_datetime 即可將字串轉換成 python 的時間的資料結構 datetime
import pandas as pd
birthday = '1070101'
print(' 生日:', birthday)
print('資料型態:', type(birthday))
d1 = str(int(birthday[0:3]) + 1911)
d2 = birthday[3:5]
d3 = birthday[5:7]
d4 = d1 + '-' + d2 + '-' + d3

birthday = pd.to_datetime(d4, format='%Y-%m-%d')
print(' 生日:', birthday)
print('資料型態:', type(birthday))
-----------------------------------------------------------------     生日: 1070101
資料型態: <class 'str'>
  生日: 2018-01-01 00:00:00
資料型態: <class 'pandas._libs.tslib.Timestamp'>

欄位資料合併

以分析的角度當然希望原始資料盡可能越詳細越好,但是訓練模型或是視覺化呈現時,同性質的內容應該被歸類為同一類,例如:原始資料中的「住宅情形」中有借住 - 父母借住 - 兄弟姊妹借住 - 親戚借住 - 朋友,這些選項應該是屬於同類借住

  • 借住:借住、借住 - 父母、借住 - 兄弟姊妹、借住 - 親戚、借住 - 朋友
  • 租賃:一般租屋、國宅、公營出租住宅
  • 配住:配住、配住 - 安康平宅、配住 - 延吉平宅、配住 - 福民平宅、配住 - 宿舍
  • 安置機構:大同之家、浩然敬老院、公私立機構

這裡同樣地可以使用pandaspandas.isnull 來判斷物件中是否為空值,以及用 pandas.Series.str.contains 來判斷目標字有沒有在物件中,例如:

living[living.str.contains('租賃')==True][0:10]
-----------------------------------------------------------------
0 租賃 - 一般租屋
1 租賃 - 一般租屋
2 租賃 - 一般租屋
3 租賃 - 一般租屋
4 租賃 - 一般租屋
12 租賃 - 國宅
20 租賃 - 一般租屋
21 租賃 - 一般租屋
22 租賃 - 一般租屋
25 租賃 - 國宅

以下就針對每一個家戶的「住宅情形」進行檢查後,把同性質的項目合併。

print(living[0:10])
living[living.str.contains('租賃')==True] = '租賃'
living[living.str.contains('配住')==True] = '配住'
living[living.str.contains('借住')==True] = '借住'
living[
(~pd.isnull(living)) &
(living.str.contains('自有')==False) &
(living.str.contains('租賃')==False) &
(living.str.contains('配住')==False) &
(living.str.contains('借住')==False) &
(living.str.contains('其他')==False)
] = '安置機構'
print(living[0:10])
--------------------------------------------------------------------
0 租賃 - 一般租屋
1 租賃 - 一般租屋
2 租賃 - 一般租屋
3 租賃 - 一般租屋
4 租賃 - 一般租屋
5 借住 - 兄弟姊妹
6 借住 - 親戚
7 借住 - 親戚
8 借住 - 親戚
9 借住 - 親戚
--------------------------------------------------------------------
0 租賃
1 租賃
2 租賃
3 租賃
4 租賃
5 借住
6 借住
7 借住
8 借住
9 借住

延伸欄位

前面有提到在資料分析的過程中,希望原始資料盡可能越詳細越好,但是如果遇到沒有資料的時候,就要自己重新整理後再產生出新欄位囉。

原始資料中有是否就學是否就業的欄位,這些資訊是各家戶在申請成為低收入戶時所填寫,但是這些資訊並不會因為家中成員畢業或找到工作後而更新,因此這些資訊成為無法使用的資料。

這個部分經由專業社工的建議,我們將利用年齡來區分,如:

  • 學齡前人口:小於 6 歲
  • 在學人口:界於 7 歲至 25 歲
  • 工作人口:界於 26 歲至 64 歲
  • 老人人口:65 歲以上

以下先建立資料型態為 datetime 的 生日資料,再定義出要以哪一天作為年齡的計算基準。

birthday = [
pd.to_datetime('2011-10-10', format='%Y-%m-%d'),
pd.to_datetime('1987-06-01', format='%Y-%m-%d'),
pd.to_datetime('1950-12-31', format='%Y-%m-%d')
]
name = ['小白', '阿華', '老劉']
birthdays = pd.DataFrame({'姓名' : name, '生日' : birthday})
print(birthdays)
# 計算年齡基準: 2018/02/10
now = pd.to_datetime('2018-02-10', format='%Y-%m-%d')
--------------------------------------------------------------------
姓名 生日
0 小白 2011-10-10
1 阿華 1987-06-01
2 老劉 1950-12-31

在 Python 裡面,想要知道小白、阿華及老劉的年齡該怎麼做?

不用怕,用力給它相減就對了!

age = now - birthdays
print(age)
--------------------------------------------------------------------
0 2315 days
1 11212 days
2 24513 days
dtype: timedelta64[ns]

雖然成功算出年齡,但是這個結果是從出生到現在總共活了幾天,相對於常見的幾歲有點不太一樣。這裡當然可以把每個數字除以 365 之後就可以得到年齡,但是使用 pandas.Series.astype 就可以簡易把數轉換成數。

print(age.astype('timedelta64[Y]'))
-------------------------------------------------------------------
0 6.0
1 30.0
2 67.0
dtype: float64

得到年齡後,只要將實際年齡依照先前社工所設定的判斷公式比對,就可以獲得每個家戶的人口分布。

age = (now - birthdays).astype('timedelta64[Y]')
preschool = (age <= 6).sum()
schooling = ((7 <= age) & (age <= 25)).sum()
working = ((26 <= age) & (age <= 64)).sum()
elder = (age >= 65).sum()
print('學齡前人口:', preschool)
print(' 在學人口:', schooling)
print(' 工作人口:', working)
print(' 老人人口:', elder)
-------------------------------------------------------------------
學齡前人口: 1
 在學人口: 0
 工作人口: 1
 老人人口: 1