「數據分析」之零基礎入門數據挖掘

作者:王瑞楠,Datawhale優秀學習者

摘要:對於數據挖掘項目,本文將學習應該從哪些角度分析數據?如何對數據進行整體把握,如何處理異常值與缺失值,從哪些維度進行特徵及預測值分析?

探索性數據分析(Exploratory Data Analysis,EDA)是指對已有數據在盡量少的先驗假設下通過作圖、製表、方程擬合、計算特徵量等手段探索數據的結構和規律的一種數據分析方法。

數據及背景

https://tianchi.aliyun.com/competition/entrance/231784/information(阿里天池-零基礎入門數據挖掘)

EDA的目標

  • 熟悉數據集,了解數據集,對數據集進行驗證來確定所獲得數據集可以用於接下來的機器學習或者深度學習使用。
  • 了解變量間的相互關係以及變量與預測值之間的存在關係。
  • 引導數據科學從業者進行數據處理以及特徵工程的步驟,使數據集的結構和特徵集讓接下來的預測問題更加可靠。

數據載入及總覽

載入各種數據科學以及可視化庫

missingno庫用於可視化缺失值分佈,是基於matplotlib的,接受pandas數據源

import pandas as pd  import numpy as np  import matplotlib.pyplot as plt  import seaborn as sns  import missingno as msno  # 用於可視化缺失值分佈  import scipy.stats as st

載入數據

path = './data/'  Train_data = pd.read_csv(path+'used_car_train_20200313.csv', sep=' ')  Test_data = pd.read_csv(path+'used_car_testA_20200313.csv', sep=' ')

所有特徵集均脫敏處理,脫敏處理後均為label encoding形式,即數字形式

總覽數據

簡略觀察數據head()+shape

Train_data.head().append(Train_data.tail())  Test_data.head().append(Test_data.tail())  Train_data.shape  Test_data.shape

describe()熟悉相關統計量

describe()中包含每列的統計量,個數(count)、平均值(mean)、方差(std)、最小值(min)、中位數(25% 50% 75%)、最大值(max)等。通過觀察以上指標,可以瞬間掌握數據的大概範圍和每個值的異常值的判斷 ,例如有時候會發現999 9999、 -1 等值這些其實都是nan的另外一種表達方式。

Train_data.describe()

info()熟悉數據類型

通過info()來了解數據每列的type,有助於了解是否存在除了nan以外的特殊符號異常。

Train_data.info()

缺失值和異常值

缺失值

查看每列的存在nan情況

Train_data.isnull().sum()  Test_data.isnull().sum()

排序函數sort_values()

可以將數據集依照某個字段中的數據進行排序,該函數即可根據指定列數據也可根據指定行的

通過以下兩句可以很直觀的了解哪些列存在 「nan」, 並可以把nan的個數打印。主要的目的在於 nan存在的個數是否真的很大,如果很小一般選擇填充,如果使用lgb等樹模型可以直接空缺,讓樹自己去優化,但如果nan存在的過多、可以考慮刪掉。

# nan可視化  missing = Train_data.isnull().sum()  missing = missing[missing > 0]  missing.sort_values(inplace=True)  missing.plot.bar()
# 可視化缺省值  msno.matrix(Train_data.sample(250))  msno.bar(Train_data.sample(1000))  msno.matrix(Test_data.sample(250))  msno.bar(Test_data.sample(1000))

從上文Train_data.info()的統計信息可以發現,除了notRepairedDamage 為object類型其他都為數字。接下來將notRepairedDamage中幾個不同的值都進行顯示如下:

Train_data['notRepairedDamage'].value_counts()

可以看出『 – 』也為空缺值,因為很多模型對nan有直接的處理,這裡我們先不做處理,先替換成nan。

Train_data['notRepairedDamage'].replace('-', np.nan, inplace=True)  Train_data['notRepairedDamage'].value_counts()
Train_data.isnull().sum()

異常值

以下兩個類別特徵:seller和offerType嚴重傾斜,一般不會對預測有什麼幫助,故這邊先刪掉,當然你也可以繼續挖掘,但是一般意義不大

Train_data["seller"].value_counts()  Train_data["offerType"].value_counts()
del Train_data["seller"]  del Train_data["offerType"]  del Test_data["seller"]  del Test_data["offerType"]

預測值分佈

總體分佈概況

數據整體服從正態分佈,樣本均值和方差則相互獨立,正態分佈具有很多好的性質,很多模型也假設數據服從正態分佈。

例如線性回歸(linear regression),它假設誤差服從正態分佈,從而每個樣本點出現的概率就可以表示為正態分佈形式,將多個樣本點連乘再取對數,就是所有訓練集樣本出現的條件概率,最大化該條件概率就是LR最終求解的問題。這個條件概率的最終表達式的形式就是我們熟悉的誤差平方和。

總之, 機器學習中很多model都假設數據或參數服從正態分佈。當樣本不服從正態分佈時,可以做如下轉換:

  • 線性變化z-scores
  • 使用Boxcox變換
  • 使用yeo-johnson變換

盲目假設變量服從正態分佈可能導致不準確的結果,要結合分析。例如:不能假設股票價格服從正態分佈,因為價格不能為負,故我們可以將股票價格假設為服從對數正態分佈,以確保其值≥0;而股票收益可能是負數,因此收益可以假設服從正態分佈。

當樣本數據表明質量特徵的分佈為非正態時,應用基於正態分佈的方法會作出不正確的判決。約翰遜分佈族即為經約翰(yeo-johnson)變換後服從正態分佈的隨機變量的概率分佈,約翰遜分佈體系建立了三族分佈,分別為有界SB 、對數正態SL和無界SU

本案例的預測值為價格,顯然不符合正態分佈,故分別採用無界約翰遜分佈Johnson SU、正態分佈normal、對數正態分佈lognormal,綜合來看無界約翰遜分佈對price的擬合效果更好。

y = Train_data['price']  plt.figure(1); plt.title('Johnson SU')  sns.distplot(y, kde=False, fit=st.johnsonsu)  plt.figure(2); plt.title('Normal')  sns.distplot(y, kde=False, fit=st.norm)  plt.figure(3); plt.title('Log Normal')  sns.distplot(y, kde=False, fit=st.lognorm)

偏度和峰度

sns.distplot(Train_data['price']);  print("Skewness: %f" % Train_data['price'].skew())  print("Kurtosis: %f" % Train_data['price'].kurt())  sns.distplot(Train_data.skew(),color='blue',axlabel ='Skewness')  sns.distplot(Train_data.kurt(),color='orange',axlabel ='Kurtness')  Train_data.skew(), Train_data.kurt()

預測值頻數

大於20000的值很少,其實該處可將其當作異常值處理填充或刪除,本文中經過log變換之後,分佈較均勻,可據此進行預測,這也是預測問題常用的技巧

plt.hist(Train_data['price'], orientation = 'vertical',histtype = 'bar', color ='red')  plt.show()  plt.hist(np.log(Train_data['price']), orientation = 'vertical',histtype = 'bar', color ='red')  plt.show()

特徵分析

數字特徵

『seller』和『offerType』已被刪除,其他特徵均經過了label coding。若需要處理的數據未label coding,則可通過如下代碼對特徵進行區分:

# 數字特徵  numeric_features = Train_data.select_dtypes(include=[np.number])  numeric_features.columns  # 類型特徵  categorical_features = Train_data.select_dtypes(include=[np.object])  categorical_features.columns

本文數據已經label coding,故採用人工區分方法:

numeric_features = ['power', 'kilometer', 'v_0', 'v_1', 'v_2', 'v_3', 'v_4', 'v_5', 'v_6', 'v_7', 'v_8', 'v_9', 'v_10', 'v_11', 'v_12', 'v_13','v_14' ]  categorical_features = ['name', 'model', 'brand', 'bodyType', 'fuelType', 'gearbox', 'notRepairedDamage', 'regionCode']

總覽

numeric_features.append('price')  print(numeric_features)

相關性分析

price_numeric = Train_data[numeric_features]  correlation = price_numeric.corr()  print(correlation['price'].sort_values(ascending=False),'n')  f, ax = plt.subplots(figsize = (7, 7))  plt.title('Correlation of Numeric Features with Price',y=1,size=16)  sns.heatmap(correlation,square = True,  vmax=0.8)  del price_numeric['price']

特徵偏度和峰值

for col in numeric_features:      print('{:15}'.format(col),            'Skewness: {:05.2f}'.format(Train_data[col].skew()) ,            '   ' ,            'Kurtosis: {:06.2f}'.format(Train_data[col].kurt())           )

每個數字特徵的分佈可視化

pd.melt():處理數據,透視表格,可將寬數據轉化為長數據,以便於後續分析。形成的數據即為,鍵:各特徵名稱,值:特徵對應的值

sns.FacetGrid() :先sns.FacetGrid()畫出輪廓,再map()填充內容

f = pd.melt(Train_data, value_vars=numeric_features)  g = sns.FacetGrid(f, col="variable",  col_wrap=2, sharex=False, sharey=False)  g = g.map(sns.distplot, "value")

匿名特徵分佈情況

sns.pairplot():展示變量兩兩之間的關係(線性或非線性,有無較為明顯的相關關係):

  • 對角線:各個屬性的直方圖,用diag_kind屬性控制圖類型,可選"scatter"與"reg"
  • 非對角線:兩個不同屬性之間的相關圖,用kind屬性控制圖類型,可選"scatter"與"reg"
  • hue :針對某一字段進行分類
sns.set()  columns = ['price', 'v_12', 'v_8' , 'v_0', 'power', 'v_5',  'v_2', 'v_6', 'v_1', 'v_14']  sns.pairplot(Train_data[columns],size = 2 ,kind ='scatter',diag_kind='kde')  plt.show()

多變量與price的回歸關係

fig, ((ax1, ax2), (ax3, ax4), (ax5, ax6), (ax7, ax8), (ax9, ax10)) = plt.subplots(nrows=5, ncols=2, figsize=(24, 20))  # ['v_12', 'v_8' , 'v_0', 'power', 'v_5',  'v_2', 'v_6', 'v_1', 'v_14']  v_12_scatter_plot = pd.concat([Y_train,Train_data['v_12']],axis = 1)  sns.regplot(x='v_12',y = 'price', data = v_12_scatter_plot,scatter= True, fit_reg=True, ax=ax1)    v_8_scatter_plot = pd.concat([Y_train,Train_data['v_8']],axis = 1)  sns.regplot(x='v_8',y = 'price',data = v_8_scatter_plot,scatter= True, fit_reg=True, ax=ax2)    v_0_scatter_plot = pd.concat([Y_train,Train_data['v_0']],axis = 1)  sns.regplot(x='v_0',y = 'price',data = v_0_scatter_plot,scatter= True, fit_reg=True, ax=ax3)    power_scatter_plot = pd.concat([Y_train,Train_data['power']],axis = 1)  sns.regplot(x='power',y = 'price',data = power_scatter_plot,scatter= True, fit_reg=True, ax=ax4)    v_5_scatter_plot = pd.concat([Y_train,Train_data['v_5']],axis = 1)  sns.regplot(x='v_5',y = 'price',data = v_5_scatter_plot,scatter= True, fit_reg=True, ax=ax5)    v_2_scatter_plot = pd.concat([Y_train,Train_data['v_2']],axis = 1)  sns.regplot(x='v_2',y = 'price',data = v_2_scatter_plot,scatter= True, fit_reg=True, ax=ax6)    v_6_scatter_plot = pd.concat([Y_train,Train_data['v_6']],axis = 1)  sns.regplot(x='v_6',y = 'price',data = v_6_scatter_plot,scatter= True, fit_reg=True, ax=ax7)    v_1_scatter_plot = pd.concat([Y_train,Train_data['v_1']],axis = 1)  sns.regplot(x='v_1',y = 'price',data = v_1_scatter_plot,scatter= True, fit_reg=True, ax=ax8)    v_14_scatter_plot = pd.concat([Y_train,Train_data['v_14']],axis = 1)  sns.regplot(x='v_14',y = 'price',data = v_14_scatter_plot,scatter= True, fit_reg=True, ax=ax9)    v_13_scatter_plot = pd.concat([Y_train,Train_data['v_13']],axis = 1)  sns.regplot(x='v_13',y = 'price',data = v_13_scatter_plot,scatter= True, fit_reg=True, ax=ax10)

類別特徵

查看nunique分佈

for cat_fea in categorical_features:      print(cat_fea + '特徵分佈如下:')      print('{}特徵有{}個不同的值'.format(cat_fea, Train_data[cat_fea].nunique()))      print(Train_data[cat_fea].value_counts())

查看箱型圖

  • 直觀識別數據中的離群點
  • 直觀判斷數據離散分佈情況,了解數據分佈狀態
categorical_features =['model','brand','bodyType','fuelType','gearbox','notRepairedDamage']  for c in categorical_features:      Train_data[c] = Train_data[c].astype('category')      if Train_data[c].isnull().any():          Train_data[c] = Train_data[c].cat.add_categories(['MISSING'])          Train_data[c] = Train_data[c].fillna('MISSING')    def boxplot(x, y, **kwargs):      sns.boxplot(x=x, y=y)      x=plt.xticks(rotation=90)    f = pd.melt(Train_data, id_vars=['price'], value_vars=categorical_features)  g = sns.FacetGrid(f, col="variable",  col_wrap=2, sharex=False, sharey=False, size=5)  g = g.map(boxplot, "value", "price")

查看小提琴圖

  • 用於顯示數據分佈及概率密度
  • 這種圖表結合了箱形圖和密度圖的特徵,主要用來顯示數據的分佈形狀
catg_list = categorical_features  target = 'icu_los'  for catg in catg_list :      sns.violinplot(x=catg, y=target, data=Train_data)      plt.show()

查看柱形圖

def bar_plot(x, y, **kwargs):      sns.barplot(x=x, y=y)      x=plt.xticks(rotation=90)    f = pd.melt(Train_data, id_vars=['price'], value_vars=categorical_features)  g = sns.FacetGrid(f, col="variable",  col_wrap=2, sharex=False, sharey=False, size=5)  g = g.map(bar_plot, "value", "price")

類別頻數可視化

def count_plot(x,  **kwargs):      sns.countplot(x=x)      x=plt.xticks(rotation=90)    f = pd.melt(Train_data,  value_vars=categorical_features)  g = sns.FacetGrid(f, col="variable",  col_wrap=2, sharex=False, sharey=False, size=5)  g = g.map(count_plot, "value")

生成數據報告

用pandas_profiling生成一個較為全面的可視化和數據報告(較為簡單、方便) 最終打開html文件即可

import pandas_profiling  pfr = pandas_profiling.ProfileReport(Train_data)  pfr.to_file("./example.html")

參考

【1】構建模型時為什麼要盡量將偏態數據轉換為正態分佈數據?

【2】張維銘,施雪忠,樓龍翔.非正態數據變換為正態數據的方法[J].浙江工程學院學報,2000(03):56-59.

【3】偏度與峰度的正態性分佈判斷

【4】數據的偏度和峰度——df.skew()、df.kurt()

【5】Melt函數處理數據,透視表格,寬數據變成長數據

【6】seaborn可視化之FacetGrid()

【7】Seaborn5分鐘入門(七)——pairplot

【8】箱型圖和小提琴圖分析