數據缺失、混亂、重複怎麼辦?最全數據清洗指南


作者:機器之心

本文約5000字,建議閱讀10分鐘

本文爲你介紹如何在 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 SosenskoepOseleNie 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(正確值)。

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

已知這些類別應僅有四個值:torontovancouvermontrealcalgary。計算所有值與單詞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

編輯:黃繼彥

校對:林亦霖

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章