作者:Lianne & Justin? ?
在擬合機器學習或統計模型之前,我們通常需要清洗數據。用雜亂數據訓練出的模型無法輸出有意義的結果。
數據清洗:從記錄集、表或數據庫中檢測和修正(或刪除)受損或不準確記錄的過程。它識別出數據中不完善、不準確或不相關的部分,并替換、修改或刪除這些臟亂的數據。
「數據清洗」光定義就這么長,執行過程肯定既枯燥又耗時。 ? ? ? ? 為了將數據清洗簡單化,本文介紹了一種新型完備分步指南,支持在 Python 中執行數據清洗流程。讀者可以學習找出并清洗以下數據的方法:
缺失數據;
不規則數據(異常值);
不必要數據:重復數據(repetitive data)、復制數據(duplicate data)等;
不一致數據:大寫、地址等;
該指南使用的數據集是 Kaggle 競賽 Sberbank 俄羅斯房地產價值預測競賽數據(該項目的目標是預測俄羅斯的房價波動)。本文并未使用全部數據,僅選取了其中的一部分樣本。 ? ? ? ? 在進入數據清洗流程之前,我們先來看一下數據概況。
# 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)
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? 現在看起來好多了: ?
? 結束了!我們走過了長長的數據清洗旅程。 ? 現在你可以運用本文介紹的方法清洗所有阻礙你擬合模型的「臟」數據了。
編輯:黃飛
?
評論
查看更多