資料缺失、混亂、重複怎麼辦?最全資料清洗指南讓你所向披靡

杜佳豪發表於2020-04-20
要獲得優秀的模型,首先需要清洗資料。這是一篇如何在 Python 中執行資料清洗的分步指南。

資料缺失、混亂、重複怎麼辦?最全資料清洗指南讓你所向披靡

在擬合機器學習或統計模型之前,我們通常需要清洗資料。用雜亂資料訓練出的模型無法輸出有意義的結果。

資料清洗:從記錄集、表或資料庫中檢測和修正(或刪除)受損或不準確記錄的過程。它識別出資料中不完善、不準確或不相關的部分,並替換、修改或刪除這些髒亂的資料。

「資料清洗」光定義就這麼長,執行過程肯定既枯燥又耗時。

資料缺失、混亂、重複怎麼辦?最全資料清洗指南讓你所向披靡
圖源:https://www.kdnuggets.com/2017/09/cartoon-machine-learning-class.html

為了將資料清洗簡單化,本文介紹了一種新型完備分步指南,支援在 Python 中執行資料清洗流程。讀者可以學習找出並清洗以下資料的方法:

  • 缺失資料;

  • 不規則資料(異常值);

  • 不必要資料:重複資料(repetitive data)、複製資料(duplicate data)等;

  • 不一致資料:大寫、地址等;


該指南使用的資料集是 Kaggle 競賽 Sberbank 俄羅斯房地產價值預測競賽資料(該專案的目標是預測俄羅斯的房價波動)。本文並未使用全部資料,僅選取了其中的一部分樣本。
資料缺失、混亂、重複怎麼辦?最全資料清洗指南讓你所向披靡
本文兩位作者 Lianne & Justin。

在進入資料清洗流程之前,我們先來看一下資料概況。

# import packages
import pandas as pd
import numpy as np
import seaborn as sns

import matplotlib.pyplot as plt
import matplotlib.mlab as mlab
import matplotlib
plt.style.use('ggplot')
from matplotlib.pyplot import figure

%matplotlib inline
matplotlib.rcParams['figure.figsize'] = (12,8)

pd.options.mode.chained_assignment = None



# read the data
df = pd.read_csv('sberbank.csv')

# shape and data types of the data
print(df.shape)
print(df.dtypes)

# select numeric columns
df_numeric = df.select_dtypes(include=[np.number])
numeric_cols = df_numeric.columns.values
print(numeric_cols)

# select non numeric columns
df_non_numeric = df.select_dtypes(exclude=[np.number])
non_numeric_cols = df_non_numeric.columns.values
print(non_numeric_cols)

從以上結果中,我們可以看到該資料集共有 30,471 行、292 列,還可以辨別特徵屬於數值變數還是分類變數。這些都是有用的資訊。

現在,我們可以瀏覽「髒」資料型別檢查清單,並一一攻破。

開始吧!

缺失資料

處理缺失資料/缺失值是資料清洗中最棘手也最常見的部分。很多模型可以與其他資料問題和平共處,但大多數模型無法接受缺失資料問題。

如何找出缺失資料?

本文將介紹三種方法,幫助大家更多地瞭解資料集中的缺失資料。

方法 1:缺失資料熱圖

當特徵數量較少時,我們可以透過熱圖對缺失資料進行視覺化。

cols = df.columns[:30] # first 30 columns
colours = ['#000099', '#ffff00'] # specify the colours - yellow is missing. blue is not missing.
sns.heatmap(df[cols].isnull(), cmap=sns.color_palette(colours))

下表展示了前 30 個特徵的缺失資料模式。橫軸表示特徵名,縱軸表示觀察值/行數,黃色表示缺失資料,藍色表示非缺失資料。

例如,下圖中特徵 life_sq 在多個行中存在缺失值。而特徵 floor 只在第 7000 行左右出現零星缺失值。

資料缺失、混亂、重複怎麼辦?最全資料清洗指南讓你所向披靡缺失資料熱圖

方法 2:缺失資料百分比列表

當資料集中存在很多特徵時,我們可以為每個特徵列出缺失資料的百分比。

# if it's a larger dataset and the visualization takes too long can do this.
# % of missing.
for col in df.columns:
    pct_missing = np.mean(df[col].isnull())
    print('{} - {}%'.format(col, round(pct_missing*100)))

得到如下列表,該表展示了每個特徵的缺失值百分比。

具體而言,我們可以從下表中看到特徵 life_sq 有 21% 的缺失資料,而特徵 floor 僅有 1% 的缺失資料。該列表有效地總結了每個特徵的缺失資料百分比情況,是對熱圖視覺化的補充。

資料缺失、混亂、重複怎麼辦?最全資料清洗指南讓你所向披靡
前 30 個特徵的缺失資料百分比列表

方法 3:缺失資料直方圖

在存在很多特徵時,缺失資料直方圖也不失為一種有效方法。

要想更深入地瞭解觀察值中的缺失值模式,我們可以用直方圖的形式進行視覺化。

# first create missing indicator for features with missing data
for col in df.columns:
    missing = df[col].isnull()
    num_missing = np.sum(missing)
    
    if num_missing > 0:  
        print('created missing indicator for: {}'.format(col))
        df['{}_ismissing'.format(col)] = missing


# then based on the indicator, plot the histogram of missing values
ismissing_cols = [col for col in df.columns if 'ismissing' in col]
df['num_missing'] = df[ismissing_cols].sum(axis=1)

df['num_missing'].value_counts().reset_index().sort_values(by='index').plot.bar(x='index', y='num_missing')

直方圖可以幫助在 30,471 個觀察值中識別缺失值狀況。

例如,從下圖中可以看到,超過 6000 個觀察值不存在缺失值,接近 4000 個觀察值具備一個缺失值。

資料缺失、混亂、重複怎麼辦?最全資料清洗指南讓你所向披靡
缺失資料直方圖

如何處理缺失資料?

這方面沒有統一的解決方案。我們必須研究特定特徵和資料集,據此決定處理缺失資料的最佳方式。

下面介紹了四種最常用的缺失資料處理方法。不過,如果情況較為複雜,我們需要創造性地使用更復雜的方法,如缺失資料建模。

解決方案 1:丟棄觀察值

在統計學中,該方法叫做成列刪除(listwise deletion),需要丟棄包含缺失值的整列觀察值。

只有在我們確定缺失資料無法提供資訊時,才可以執行該操作。否則,我們應當考慮其他解決方案。

此外,還存在其他標準。

例如,從缺失資料直方圖中,我們可以看到只有少量觀察值的缺失值數量超過 35。因此,我們可以建立一個新的資料集 df_less_missing_rows,該資料集刪除了缺失值數量超過 35 的觀察值。

# drop rows with a lot of missing values.
ind_missing = df[df['num_missing'] > 35].index
df_less_missing_rows = df.drop(ind_missing, axis=0)

解決方案 2:丟棄特徵

與解決方案 1 類似,我們只在確定某個特徵無法提供有用資訊時才丟棄它。

例如,從缺失資料百分比列表中,我們可以看到 hospital_beds_raion 具備較高的缺失值百分比——47%,因此我們丟棄這一整個特徵。

# hospital_beds_raion has a lot of missing.
# If we want to drop.
cols_to_drop = ['hospital_beds_raion']
df_less_hos_beds_raion = df.drop(cols_to_drop, axis=1)

解決方案 3:填充缺失資料

當特徵是數值變數時,執行缺失資料填充。對同一特徵的其他非缺失資料取平均值或中位數,用這個值來替換缺失值。

當特徵是分類變數時,用眾數(最頻值)來填充缺失值。

以特徵 life_sq 為例,我們可以用特徵中位數來替換缺失值。

# replace missing values with the median.
med = df['life_sq'].median()
print(med)
df['life_sq'] = df['life_sq'].fillna(med)

此外,我們還可以對所有數值特徵一次性應用同樣的填充策略。

# impute the missing values and create the missing value indicator variables for each numeric column.
df_numeric = df.select_dtypes(include=[np.number])
numeric_cols = df_numeric.columns.values

for col in numeric_cols:
    missing = df[col].isnull()
    num_missing = np.sum(missing)
    
    if num_missing > 0:  # only do the imputation for the columns that have missing values.
        print('imputing missing values for: {}'.format(col))
        df['{}_ismissing'.format(col)] = missing
        med = df[col].median()
        df[col] = df[col].fillna(med)
資料缺失、混亂、重複怎麼辦?最全資料清洗指南讓你所向披靡
很幸運,本文使用的資料集中的分類特徵沒有缺失值。不然,我們也可以對所有分類特徵一次性應用眾數填充策略。

# impute the missing values and create the missing value indicator variables for each non-numeric column.
df_non_numeric = df.select_dtypes(exclude=[np.number])
non_numeric_cols = df_non_numeric.columns.values

for col in non_numeric_cols:
    missing = df[col].isnull()
    num_missing = np.sum(missing)
    
    if num_missing > 0:  # only do the imputation for the columns that have missing values.
        print('imputing missing values for: {}'.format(col))
        df['{}_ismissing'.format(col)] = missing
        
        top = df[col].describe()['top'] # impute with the most frequent value.
        df[col] = df[col].fillna(top)

解決方案 4:替換缺失值

對於分類特徵,我們可以新增新的帶值類別,如 _MISSING_。對於數值特徵,我們可以用特定值(如-999)來替換缺失值。

這樣,我們就可以保留缺失值,使之提供有價值的資訊。

# categorical
df['sub_area'] = df['sub_area'].fillna('_MISSING_')


# numeric
df['life_sq'] = df['life_sq'].fillna(-999)

不規則資料(異常值)

異常值指與其他觀察值具備顯著差異的資料,它們可能是真的異常值也可能是錯誤。

如何找出異常值?

根據特徵的屬性(數值或分類),使用不同的方法來研究其分佈,進而檢測異常值。

方法 1:直方圖/箱形圖

當特徵是數值變數時,使用直方圖和箱形圖來檢測異常值。

下圖展示了特徵 life_sq 的直方圖。

# histogram of life_sq.
df['life_sq'].hist(bins=100)

由於資料中可能存在異常值,因此下圖中資料高度偏斜。
資料缺失、混亂、重複怎麼辦?最全資料清洗指南讓你所向披靡
直方圖

為了進一步研究特徵,我們來看一下箱形圖。

# box plot.
df.boxplot(column=['life_sq'])

從下圖中我們可以看到,異常值是一個大於 7000 的數值。
資料缺失、混亂、重複怎麼辦?最全資料清洗指南讓你所向披靡
箱形圖

方法 2:描述統計學

對於數值特徵,當異常值過於獨特時,箱形圖無法顯示該值。因此,我們可以檢視其描述統計學。

例如,對於特徵 life_sq,我們可以看到其最大值是 7478,而上四分位數(資料的第 75 個百分位資料)是 43。因此值 7478 是異常值。

df['life_sq'].describe()

資料缺失、混亂、重複怎麼辦?最全資料清洗指南讓你所向披靡

方法 3:條形圖

當特徵是分類變數時,我們可以使用條形圖來了解其類別和分佈。

例如,特徵 ecology 具備合理的分佈。但如果某個類別「other」僅有一個值,則它就是異常值。

# bar chart -  distribution of a categorical variable
df['ecology'].value_counts().plot.bar()
資料缺失、混亂、重複怎麼辦?最全資料清洗指南讓你所向披靡條形圖

其他方法:還有很多方法可以找出異常值,如散點圖、z 分數和聚類,本文不過多探討全部方法。

如何處理異常值?

儘管異常值不難檢測,但我們必須選擇合適的處理辦法。而這高度依賴於資料集和專案目標。

處理異常值的方法與處理缺失值有些類似:要麼丟棄,要麼修改,要麼保留。(讀者可以返回上一章節處理缺失值的部分檢視相關解決方案。)

不必要資料

處理完缺失資料和異常值,現在我們來看不必要資料,處理不必要資料的方法更加直接。

輸入到模型中的所有資料應服務於專案目標。不必要資料即無法增加價值的資料。

這裡將介紹三種主要的不必要資料型別。

不必要資料型別 1:資訊不足/重複

有時一個特徵不提供資訊,是因為它擁有太多具備相同值的行。

如何找出重複資料?

我們可以為具備高比例相同值的特徵建立一個列表。

例如,下圖展示了 95% 的行是相同值的特徵。

num_rows = len(df.index)
low_information_cols = [] #

for col in df.columns:
    cnts = df[col].value_counts(dropna=False)
    top_pct = (cnts/num_rows).iloc[0]
    
    if top_pct > 0.95:
        low_information_cols.append(col)
        print('{0}: {1:.5f}%'.format(col, top_pct*100))
        print(cnts)
        print()

我們可以逐一檢視這些變數,確認它們是否提供有用資訊。(此處不再詳述。)
資料缺失、混亂、重複怎麼辦?最全資料清洗指南讓你所向披靡
如何處理重複資料?

我們需要了解重複特徵背後的原因。當它們的確無法提供有用資訊時,我們就可以丟棄它。

不必要資料型別 2:不相關

再次強調,資料需要為專案提供有價值的資訊。如果特徵與專案試圖解決的問題無關,則這些特徵是不相關資料。

如何找出不相關資料?

瀏覽特徵,找出不相關的資料。

例如,記錄多倫多氣溫的特徵無法為俄羅斯房價預測專案提供任何有用資訊。

如何處理不相關資料?

當這些特徵無法服務於專案目標時,刪除之。

不必要資料型別 3:複製

複製資料即,觀察值存在副本。

複製資料有兩個主要型別。

複製資料型別 1:基於所有特徵

如何找出基於所有特徵的複製資料?

這種複製發生在觀察值內所有特徵的值均相同的情況下,很容易找出。

我們需要先刪除資料集中的唯一識別符號 id,然後刪除複製資料得到資料集 df_dedupped。對比 df 和 df_dedupped 這兩個資料集的形態,找出複製行的數量。

# we know that column 'id' is unique, but what if we drop it?
df_dedupped = df.drop('id', axis=1).drop_duplicates()

# there were duplicate rows
print(df.shape)
print(df_dedupped.shape)

我們發現,有 10 行是完全複製的觀察值。

資料缺失、混亂、重複怎麼辦?最全資料清洗指南讓你所向披靡
如何處理基於所有特徵的複製資料?

刪除這些複製資料。

複製資料型別 2:基於關鍵特徵

如何找出基於關鍵特徵的複製資料?

有時候,最好的方法是刪除基於一組唯一識別符號的複製資料。

例如,相同使用面積、相同價格、相同建造年限的兩次房產交易同時發生的機率接近零。

我們可以設定一組關鍵特徵作為唯一識別符號,比如 timestamp、full_sq、life_sq、floor、build_year、num_room、price_doc。然後基於這些特徵檢查是否存在複製資料。

key = ['timestamp', 'full_sq', 'life_sq', 'floor', 'build_year', 'num_room', 'price_doc']

df.fillna(-999).groupby(key)['id'].count().sort_values(ascending=False).head(20)

基於這組關鍵特徵,我們找到了 16 條複製資料。
資料缺失、混亂、重複怎麼辦?最全資料清洗指南讓你所向披靡

如何處理基於關鍵特徵的複製資料?

刪除這些複製資料。

# drop duplicates based on an subset of variables.

key = ['timestamp', 'full_sq', 'life_sq', 'floor', 'build_year', 'num_room', 'price_doc']
df_dedupped2 = df.drop_duplicates(subset=key)

print(df.shape)
print(df_dedupped2.shape)

刪除 16 條複製資料,得到新資料集 df_dedupped2。

資料缺失、混亂、重複怎麼辦?最全資料清洗指南讓你所向披靡
不一致資料

在擬合模型時,資料集遵循特定標準也是很重要的一點。我們需要使用不同方式來探索資料,找出不一致資料。大部分情況下,這取決於觀察和經驗。不存在執行和修復不一致資料的既定程式碼。

下文介紹了四種不一致資料型別。

不一致資料型別 1:大寫

在類別值中混用大小寫是一種常見的錯誤。這可能帶來一些問題,因為 Python 分析對大小寫很敏感。

如何找出大小寫不一致的資料?

我們來看特徵 sub_area。

df['sub_area'].value_counts(dropna=False)

它儲存了不同地區的名稱,看起來非常標準化。

資料缺失、混亂、重複怎麼辦?最全資料清洗指南讓你所向披靡
但是,有時候相同特徵記憶體在不一致的大小寫使用情況。「Poselenie Sosenskoe」和「pOseleNie sosenskeo」指的是相同的地區。

如何處理大小寫不一致的資料?

為了避免這個問題,我們可以將所有字母設定為小寫(或大寫)。

# make everything lower case.
df['sub_area_lower'] = df['sub_area'].str.lower()
df['sub_area_lower'].value_counts(dropna=False)
資料缺失、混亂、重複怎麼辦?最全資料清洗指南讓你所向披靡

不一致資料型別 2:格式

我們需要執行的另一個標準化是資料格式。比如將特徵從字串格式轉換為 DateTime 格式。

如何找出格式不一致的資料?

特徵 timestamp 在表示日期時是字串格式。

df

資料缺失、混亂、重複怎麼辦?最全資料清洗指南讓你所向披靡

如何處理格式不一致的資料?

使用以下程式碼進行格式轉換,並提取日期或時間值。然後,我們就可以很容易地用年或月的方式分析交易量資料。

df['timestamp_dt'] = pd.to_datetime(df['timestamp'], format='%Y-%m-%d')
df['year'] = df['timestamp_dt'].dt.year
df['month'] = df['timestamp_dt'].dt.month
df['weekday'] = df['timestamp_dt'].dt.weekday

print(df['year'].value_counts(dropna=False))
print()
print(df['month'].value_counts(dropna=False))
資料缺失、混亂、重複怎麼辦?最全資料清洗指南讓你所向披靡

相關文章:https://towardsdatascience.com/how-to-manipulate-date-and-time-in-python-like-a-boss-ddea677c6a4d

不一致資料型別 3:類別值

分類特徵的值數量有限。有時由於拼寫錯誤等原因可能出現其他值。

如何找出類別值不一致的資料?

我們需要觀察特徵來找出類別值不一致的情況。舉例來說:

由於本文使用的房地產資料集不存在這類問題,因此我們建立了一個新的資料集。例如,city 的值被錯誤輸入為「torontoo」和「tronto」,其實二者均表示「toronto」(正確值)。

識別它們的一種簡單方式是模糊邏輯(或編輯距離)。該方法可以衡量使一個值匹配另一個值需要更改的字母數量(距離)。

已知這些類別應僅有四個值:「toronto」、「vancouver」、「montreal」和「calgary」。計算所有值與單詞「toronto」(和「vancouver」)之間的距離,我們可以看到疑似拼寫錯誤的值與正確值之間的距離較小,因為它們只有幾個字母不同。

from nltk.metrics import edit_distance

df_city_ex = pd.DataFrame(data={'city': ['torontoo', 'toronto', 'tronto', 'vancouver', 'vancover', 'vancouvr', 'montreal', 'calgary']})


df_city_ex['city_distance_toronto'] = df_city_ex['city'].map(lambda x: edit_distance(x, 'toronto'))
df_city_ex['city_distance_vancouver'] = df_city_ex['city'].map(lambda x: edit_distance(x, 'vancouver'))
df_city_ex
資料缺失、混亂、重複怎麼辦?最全資料清洗指南讓你所向披靡
如何處理類別值不一致的資料?

我們可以設定標準將這些拼寫錯誤轉換為正確值。例如,下列程式碼規定所有值與「toronto」的距離在 2 個字母以內。

msk = df_city_ex['city_distance_toronto'] <= 2
df_city_ex.loc[msk, 'city'] = 'toronto'

msk = df_city_ex['city_distance_vancouver'] <= 2
df_city_ex.loc[msk, 'city'] = 'vancouver'

df_city_ex
資料缺失、混亂、重複怎麼辦?最全資料清洗指南讓你所向披靡
不一致資料型別 4:地址

地址特徵對很多人來說是老大難問題。因為人們往資料庫中輸入資料時通常不會遵循標準格式。

如何找出地址不一致的資料?

用瀏覽的方式可以找出混亂的地址資料。即便有時我們看不出什麼問題,也可以執行程式碼執行標準化。

出於隱私原因,本文采用的房地產資料集沒有地址列。因此我們建立具備地址特徵的新資料集 df_add_ex。

# no address column in the housing dataset. So create one to show the code.
df_add_ex = pd.DataFrame(['123 MAIN St Apartment 15', '123 Main Street Apt 12   ', '543 FirSt Av', '  876 FIRst Ave.'], columns=['address'])
df_add_ex
我們可以看到,地址特徵非常混亂。

資料缺失、混亂、重複怎麼辦?最全資料清洗指南讓你所向披靡
如何處理地址不一致的資料?

執行以下程式碼將所有字母轉為小寫,刪除空格,刪除句號,並將措辭標準化。

df_add_ex['address_std'] = df_add_ex['address'].str.lower()
df_add_ex['address_std'] = df_add_ex['address_std'].str.strip() # remove leading and trailing whitespace.
df_add_ex['address_std'] = df_add_ex['address_std'].str.replace('\\.', '') # remove period.
df_add_ex['address_std'] = df_add_ex['address_std'].str.replace('\\bstreet\\b', 'st') # replace street with st.
df_add_ex['address_std'] = df_add_ex['address_std'].str.replace('\\bapartment\\b', 'apt') # replace apartment with apt.
df_add_ex['address_std'] = df_add_ex['address_std'].str.replace('\\bav\\b', 'ave') # replace apartment with apt.

df_add_ex

現在看起來好多了:

資料缺失、混亂、重複怎麼辦?最全資料清洗指南讓你所向披靡

結束了!我們走過了長長的資料清洗旅程。

現在你可以運用本文介紹的方法清洗所有阻礙你擬合模型的「髒」資料了。

參考連結:https://towardsdatascience.com/data-cleaning-in-python-the-ultimate-guide-2020-c63b88bf0a0d

相關文章