入門Python資料分析最好的實戰專案(二)

路遠發表於2019-02-16

作者:xiaoyu

微信公眾號:Python資料科學

知乎:python資料分析師


上一篇和大家分享了一個入門資料分析的一個小專案 北京二手房房價分析,連結如下:

文章在sf釋出之後看到有不少感興趣的朋友給我點了贊,感謝大家的支援了。

圖片描述

本篇將繼續上一篇資料分析之後進行資料探勘建模預測,這兩部分構成了一個簡單的完整專案。結合兩篇文章通過資料分析和挖掘的方法可以達到二手房屋價格預測的效果。

下面從特徵工程開始講述。

特徵工程

特徵工程包括的內容很多,有特徵清洗,預處理,監控等,而預處理根據單一特徵或多特徵又分很多種方法,如歸一化,降維,特徵選擇,特徵篩選等等。這麼多的方法,為的是什麼呢?其目的是讓這些特徵更友好的作為模型的輸入,處理資料的好壞會嚴重的影響模型效能,而好的特徵工程有的時候甚至比建模調參更重要。

下面是繼上一次分析之後對資料進行的特徵工程,博主將一個一個幫大家解讀。

"""
特徵工程
"""
# 移除結構型別異常值和房屋大小異常值
df = df[(df[`Layout`]!=`疊拼別墅`)&(df[`Size`]<1000)]

# 去掉錯誤資料“南北”,因為爬蟲過程中一些資訊位置為空,導致“Direction”的特徵出現在這裡,需要清除或替換
df[`Renovation`] = df.loc[(df[`Renovation`] != `南北`), `Renovation`]

# 由於存在個別型別錯誤,如簡裝和精裝,特徵值錯位,故需要移除
df[`Elevator`] = df.loc[(df[`Elevator`] == `有電梯`)|(df[`Elevator`] == `無電梯`), `Elevator`]

# 填補Elevator缺失值
df.loc[(df[`Floor`]>6)&(df[`Elevator`].isnull()), `Elevator`] = `有電梯`
df.loc[(df[`Floor`]<=6)&(df[`Elevator`].isnull()), `Elevator`] = `無電梯`

# 只考慮“室”和“廳”,將其它少數“房間”和“衛”移除
df = df.loc[df[`Layout`].str.extract(`^d(.*?)d.*?`) == `室`]

# 提取“室”和“廳”建立新特徵
df[`Layout_room_num`] = df[`Layout`].str.extract(`(^d).*`, expand=False).astype(`int64`)
df[`Layout_hall_num`] = df[`Layout`].str.extract(`^d.*?(d).*`, expand=False).astype(`int64`)

# 按中位數對“Year”特徵進行分箱
df[`Year`] = pd.qcut(df[`Year`],8).astype(`object`)

# 對“Direction”特徵
d_list_one = [`東`,`西`,`南`,`北`]
d_list_two = [`東西`,`東南`,`東北`,`西南`,`西北`,`南北`]
d_list_three = [`東西南`,`東西北`,`東南北`,`西南北`]
d_list_four = [`東西南北`]    
df[`Direction`] = df[`Direction`].apply(direct_func)
df = df.loc[(df[`Direction`]!=`no`)&(df[`Direction`]!=`nan`)]

# 根據已有特徵建立新特徵
df[`Layout_total_num`] = df[`Layout_room_num`] + df[`Layout_hall_num`]
df[`Size_room_ratio`] = df[`Size`]/df[`Layout_total_num`]

# 刪除無用特徵
df = df.drop([`Layout`,`PerPrice`,`Garden`],axis=1)

# 對於object特徵進行onehot編碼
df,df_cat = one_hot_encoder(df)

由於一些清洗處理在上一篇文章已經提到,博主從17行程式碼開始。

Layout

先來看看沒經處理的Layout特徵值是什麼樣的。

df[`Layout`].value_counts()

圖片描述

大家也都看到了,特徵值並不是像想象中的那麼理想。有兩種格式的資料,一種是"xx室xx廳",另一種是"xx房間xx衛",但是絕大多數都是xx室xx廳的資料。而對於像"11房間3衛"或者"5房間0衛"這些的Layout明顯不是民住的二手房(不在我們的考慮範圍之內),因此最後決定將所有”xx房間xx衛”格式的資料都移除掉,只保留"xx室xx廳"的資料。

Layout特徵的處理如下:

第2行的意思是隻保留”xx室xx廳”資料,但是保留這種格式的資料也是不能作為模型的輸入的,我們不如干脆將”室”和”廳”都提取出來,單獨作為兩個新特徵(如第5和6行),這樣效果可能更好。

具體的用法就是使用 str.extract() 方法,裡面寫的是正規表示式。

# 只考慮“室”和“廳”,將其它少數“房間”和“衛”移除
df = df.loc[df[`Layout`].str.extract(`^d(.*?)d.*?`) == `室`]

# 提取“室”和“廳”建立新特徵
df[`Layout_room_num`] = df[`Layout`].str.extract(`(^d).*`, expand=False).astype(`int64`)
df[`Layout_hall_num`] = df[`Layout`].str.extract(`^d.*?(d).*`, expand=False).astype(`int64`)

Year

我們還有一個 Year 特徵,為建房的年限時間。年限種類很多,分佈在1950和2018之間,如果每個不同的 Year 值都作為特徵值,我們並不能找出 Year 對 Price 有什麼影響,因為年限劃分的太細了。因此,我們只有將連續數值型特徵 Year 離散化,做分箱處理

如何分箱還要看實際業務需求,博主為了方便並沒有手動分箱,而使用了pandas的 qcut 採用中位數進行分割,分割數為8等份。

# 按中位數對“Year”特徵進行分箱
df[`Year`] = pd.qcut(df[`Year`],8).astype(`object`)

這是將 Year 進行分箱的結果:
圖片描述

Direction

這個特徵沒處理之前更亂,原以為是爬蟲的問題,但是親自到鏈家看過,朝向確實是這樣的。

圖片描述

如上所見,像"西南西北北"或者"東東南南"這樣的朝向是不符合常識的(反正我是理解不了)。因此,我們需要將這些凌亂的資料進行處理,具體實現方式是博主自己寫了一個函式 direct_func,主要思想就是將各種重複但順序不一樣的特徵值合併,比如"西南北""南西北",並將不合理的一些值移除,如"西南西北北"等。

然後通過 apply() 方法將 Direction 資料格式轉換,程式碼如下:

# 對“Direction”特徵
d_list_one = [`東`,`西`,`南`,`北`]
d_list_two = [`東西`,`東南`,`東北`,`西南`,`西北`,`南北`]
d_list_three = [`東西南`,`東西北`,`東南北`,`西南北`]
d_list_four = [`東西南北`]    
df[`Direction`] = df[`Direction`].apply(direct_func)
df = df.loc[(df[`Direction`]!=`no`)&(df[`Direction`]!=`nan`)]

處理完結果如下,所有的內容相同而順序不同的朝向都合併了,異常朝向也被移除了。
圖片描述

建立新特徵

有時候僅靠已有的一些特徵是不夠的,需要根據對業務的理解,定義一些的新特徵,然後嘗試這些新特徵對模型的影響,在實戰中會經常使用這種方法。

這裡嘗試將”室”與”廳”的數量相加作為一個總數量特徵,然後將房屋大小Size與總數量的比值作為一個新特徵,可理解為 "每個房間的平均面積大小"。當然,新特徵不是固定的,可根據自己的理解來靈活的定義。

# 根據已有特徵建立新特徵
df[`Layout_total_num`] = df[`Layout_room_num`] + df[`Layout_hall_num`]
df[`Size_room_ratio`] = df[`Size`]/df[`Layout_total_num`]

# 刪除無用特徵
df = df.drop([`Layout`,`PerPrice`,`Garden`],axis=1)

最後刪除舊的特徵 Layout,PerPrice,Garden。

One-hot coding

這部分是 One-hot 獨熱編碼,因為像 Region,Year(離散分箱後),Direction,Renovation,Elevator等特徵都是定類的非數值型型別,而作為模型的輸入我們需要將這些非數值量化。

在沒有一定順序(定序型別)的情況下,使用獨熱編碼處理定類資料是非常常用的做法,在pandas中非常簡單,就是使用 get_dummies() 方法,而對於像Size這樣的定比資料則不使用獨熱,博主這裡用了一個自己封裝的函式實現了定類資料的自動量化處理。

對於定類,定序,定距,定比這四個非常重要的資料型別相信加入知識星球的夥伴都非常熟悉了,想要了解的同學可以掃描最後二維碼檢視。

# 對於object特徵進行onehot編碼
df,df_cat = one_hot_encoder(df)

以上的特徵工程就完成了。

特徵相關性

下面使用 seabornheatmap 方法對特徵相關性進行視覺化。

# data_corr 
colormap = plt.cm.RdBu
plt.figure(figsize=(20,20))
# plt.title(`Pearson Correlation of Features`, y=1.05, size=15)
sns.heatmap(df.corr(),linewidths=0.1,vmax=1.0, square=True, cmap=colormap, linecolor=`white`, annot=True)

圖片描述

顏色偏紅或者偏藍都說明相關係數較大,即兩個特徵對於目標變數的影響程度相似,即存在嚴重的重複資訊,會造成過擬合現象。因此,通過特徵相關性分析,我們可以找出哪些特徵有嚴重的重疊資訊,然後擇優選擇。

資料建模預測

為了方便理解,博主在建模上做了一些精簡,模型策略方法如下:

  • 使用Cart決策樹的迴歸模型對二手房房價進行分析預測
  • 使用交叉驗證方法充分利用資料集進行訓練,避免資料劃分不均勻的影響。
  • 使用GridSearchCV方法優化模型引數
  • 使用R2評分方法對模型預測評分

上面的建模方法比較簡單,旨在讓大家瞭解建模分析的過程。隨著逐漸的深入瞭解,博主會介紹更多實戰內容。

資料劃分

# 轉換訓練測試集格式為陣列
features = np.array(features)
prices = np.array(prices)

# 匯入sklearn進行訓練測試集劃分
from sklearn.model_selection import train_test_split
features_train, features_test, prices_train, prices_test = train_test_split(features, prices, test_size=0.2, random_state=0)

將以上資料劃分為訓練集和測試集,訓練集用於建立模型,測試集用於測試模型預測準確率。使用sklearn的 model_selection 實現以上劃分功能。

建立模型

from sklearn.model_selection import KFold
from sklearn.tree import DecisionTreeRegressor
from sklearn.metrics import make_scorer
from sklearn.model_selection import GridSearchCV

# 利用GridSearchCV計算最優解
def fit_model(X, y):
    """ 基於輸入資料 [X,y],利於網格搜尋找到最優的決策樹模型"""
    
    cross_validator = KFold(10, shuffle=True)
    regressor = DecisionTreeRegressor()
    
    params = {`max_depth`:[1,2,3,4,5,6,7,8,9,10]}
    scoring_fnc = make_scorer(performance_metric)
    grid = GridSearchCV(estimator = regressor, param_grid = params, scoring = scoring_fnc, cv = cross_validator)

    # 基於輸入資料 [X,y],進行網格搜尋
    grid = grid.fit(X, y)
#     print pd.DataFrame(grid.cv_results_)
    return grid.best_estimator_

# 計算R2分數
def performance_metric(y_true, y_predict):
    """計算並返回預測值相比於預測值的分數"""
    from sklearn.metrics import r2_score
    score = r2_score(y_true, y_predict)

    return score

使用了 KFold 方法減緩過擬合,GridSearchCV 方法進行最優引數自動搜查,最後使用R2評分來給模型打分。

調參優化模型

import visuals as vs

# 分析模型
vs.ModelLearning(features_train, prices_train)
vs.ModelComplexity(features_train, prices_train)

optimal_reg1 = fit_model(features_train, prices_train)

# 輸出最優模型的 `max_depth` 引數
print("最理想模型的引數 `max_depth` 是 {} 。".format(optimal_reg1.get_params()[`max_depth`]))

predicted_value = optimal_reg1.predict(features_test)
r2 = performance_metric(prices_test, predicted_value)

print("最優模型在測試資料上 R^2 分數 {:,.2f}。".format(r2))

由於決策樹容易過擬合的問題,我們這裡採取觀察學習曲線的方法檢視決策樹深度,並判斷模型是否出現了過擬合現象。以下是觀察到的學習曲線圖形:
圖片描述

通過觀察,最理想模型的引數"max_depth"是10,此種情況下達到了偏差與方差的最優平衡,最後模型在測試資料上的R2分數,也即二手房房價預測的準確率為:0.81

總結

以上一個完整的從資料分析到挖掘的專案就結束了,對於專案而言比較簡單,目的是讓大家瞭解整個分析的過程。可提升改進的地方非常多,可以有更好更健壯的方案代替,一些改進思考如下:

  • 獲取更多有價值的特徵資訊,比如學區,附近地鐵,購物中心等
  • 完善特徵工程,如進行有效的特徵選擇
  • 使用更優秀的模型演算法建模或者使用模型融合

完整專案程式碼博主分享在了知識星球中,後續博主將不斷分享更多實戰內容,Kaggle競賽專案,以及網際網路金融風險控制專案

關注微信公眾號:Python資料科學,檢視更多精彩內容。

相關文章