數(shù)據(jù)項(xiàng)目總結(jié) -- 深圳租房數(shù)據(jù)分析!
作者:皮錢超,廈門大學(xué),Datawhale成員
大家好,《上篇》根據(jù)深圳的租房數(shù)據(jù),并從統(tǒng)計(jì)分析和可視化的角度進(jìn)行了分析,受到讀者的歡迎。今天將使用之前的數(shù)據(jù)進(jìn)行數(shù)據(jù)分析和建模,以及模型的可解釋性探索。本文的主要內(nèi)容包含:

導(dǎo)入庫(kù)
導(dǎo)入主要的庫(kù)用于:數(shù)據(jù)處理、可視化、建模、特征可解釋性等。

1、數(shù)據(jù)探索
1)導(dǎo)入數(shù)據(jù)

數(shù)據(jù)在Datawhale后臺(tái)回復(fù) 深圳 可下載
2)數(shù)據(jù)形狀和字段類型

下面是具體的特征解釋:
#?下面是特征屬性
name:小區(qū)名字
layout:幾室?guī)讖d幾衛(wèi)
location:朝向
size:建筑面積
sizeInside:套內(nèi)面積
zhuangxiu:裝修方式
time:小區(qū)建成時(shí)間
zone:行政區(qū)
position:行政區(qū)的具體位置;比如寶安壹方中心,龍華民治等
way:出租方式,整租或者合租
#?最終的目標(biāo)變量y?
money:價(jià)格??3)查看數(shù)據(jù)的缺失值情況
使用的是df.isnull().sum()來(lái)查看:在time字段中存在6個(gè)缺失值

4)缺失值填充
由于缺失量比較少,直接在網(wǎng)上搜索到相應(yīng)的時(shí)間進(jìn)行了人工填充:先定位缺失值的位置,再進(jìn)行填充。

2、字段預(yù)處理
2.1 name
小區(qū)的姓名name直接刪除
df.drop("name",axis=1,inplace=True)
2.2 layout
layout分成3個(gè)具體的屬性:室、廳、衛(wèi)
特殊情況:當(dāng)layout為"商鋪"時(shí),直接刪除~

使用正則解析出室廳衛(wèi):
df1?=?df["layout"].str.extract(r'(?P\d)室(?P\d)廳(?P\d)衛(wèi)' )
#?合并到原數(shù)據(jù)
df?=?pd.concat([df1,df],axis=1)
#?刪除layout
df.drop("layout",axis=1,inplace=True)
#?layout=商鋪正則解析為NaN,直接刪除
df.dropna(subset=["shi","ting","wei"],inplace=True)
df.head()

2.3 location
不同朝向的房子數(shù)量:
df["location"].value_counts()
朝南?????552
朝南北????284
朝北?????241
朝東南????241
朝西南????174
朝西北????142
朝東北????140
朝東?????132
朝西??????92
朝東西??????2
Name:?location,?dtype:?int64
????
#?小提琴圖
fig?=?px.violin(df,y="money",color="location")
fig.show()

小結(jié):可以看到,在朝南北、朝南、朝北的房子數(shù)量比較多,而且整體的價(jià)格分布更為廣泛。
對(duì)不同朝向的房子實(shí)施硬編碼:
#?自定義的順序:根據(jù)朝向和價(jià)格的關(guān)系圖(上面)
location?=?["朝東西","朝東北","朝西","朝西北","朝東","朝西南","朝東南","朝南","朝北","朝南北"]
location_dict?=?{}
for?n,?i?in?enumerate(location):
????location_dict[i]?=?n+1?#?保證序號(hào)從1開始
????
df["location"]?=?df["location"].map(location_dict)
df.head()

2.4 size和sizeInside
建筑面積和套內(nèi)面積提取出數(shù)值部分,提供兩種方法:
#?1、通過(guò)切割的方式來(lái)提取
df["size"]?=?df["size"].apply(lambda?x:?x.split("面積")[1].split("㎡")[0])
#?2、使用正則的方式提取
df["sizeInside"]?=?df["sizeInside"].str.extract(r'面積(?P[\d.]+)' )
df.head()

2.5 zhuangxiu
df["zhuangxiu"].value_counts()
精裝????1172
普裝?????747
豪裝??????62
毛坯??????19
Name:?zhuangxiu,?dtype:?int64
主觀意義上的思路:毛坯的等級(jí)最低,豪裝最高。下面實(shí)施硬編碼過(guò)程:
#?硬編碼
zhuangxiu?=?{"毛坯":1,"普裝":2,?"精裝":3,?"豪裝":4}
zhuangxiu
{'毛坯':?1,?'普裝':?2,?'精裝':?3,?'豪裝':?4}
df["zhuangxiu"]?=?df["zhuangxiu"].map(zhuangxiu)
df.head()

2.6 numberFloor
中低高樓層和價(jià)格money之間的關(guān)系
#?提取中低高樓層
df["numberFloor"]?=?df["numberFloor"].apply(lambda?x:?x.split("(")[0])
df.head()


小結(jié):中低高3個(gè)樓層在房租上面的影響稍小。直接考慮獨(dú)熱編碼的方式:

df?=?(df.join(pd.get_dummies(df["numberFloor"]))
????.rename(columns={"中樓層":"middleFloor",
????????????????????"低樓層":"lowFloor",
????????????????????"高樓層":"highFloor"}))
df.head()

df.drop("numberFloor",axis=1,inplace=True)??#?刪除原字段
2.7 time
房子的建成時(shí)間處理:
df["time"].value_counts()
#?部分結(jié)果
2003年建成????133
2005年建成????120
2006年建成????114
2004年建成????111
2010年建成????104
......
2019年????????3???#?人工填充的時(shí)間
2022年建成??????2
1983年建成??????1
2003年????????1
2020年????????1
2004年????????1
Name:?time,?dtype:?int64
提取時(shí)間年份:
df["time"]?=?df["time"].str.extract(r'(?P)
#?time轉(zhuǎn)成數(shù)值型
df["time"]?=?df["time"].astype("float")
#?建成時(shí)間和當(dāng)前的差距
df["time"]?=?2022?-?df["time"]
df.head()

2.8 zone+position
行政區(qū)域和地理位置的合并處理
df["zone"].value_counts()
龍崗??????548
福田??????532
龍華??????293
南山??????218
寶安??????173
羅湖??????167
光明???????32
坪山???????31
鹽田????????5
大鵬新區(qū)??????1
Name:?zone,?dtype:?int64
#?行政區(qū)和價(jià)格的關(guān)系
fig?=?px.violin(df,y="money",color="zone")
fig.show()

#?合并字段
df["zone_position"]?=?df["zone"]?+?"_"?+?df["position"]
df.head()

不同行政區(qū)域不同地理位置下的價(jià)格均值統(tǒng)計(jì):
zone_position_mean?=?
(df.groupby("zone_position")["money"].mean()
.reset_index()
.sort_values("money",ascending=False,ignore_index=True))
zone_position_mean

根據(jù)合并的zone_position_mean數(shù)據(jù)框中的money 來(lái)進(jìn)行硬編碼:福田_車公廟 是最高位
zone_position?=?zone_position_mean["zone_position"].tolist()[::-1]
zone_position_dict?=?{}
for?n,?i?in?enumerate(zone_position):
????zone_position_dict[i]?=?n+1?
????
df["zone_position"]?=?df["zone_position"].map(zone_position_dict)
#?刪除原字段
df.drop(["zone","position"],axis=1,inplace=True)
df.head()

2.9 way
出租方式和價(jià)格的關(guān)系探索:
fig?=?px.violin(df,y="money",color="way")
fig.show()

從way的取值來(lái)看:大部分的房子是愿意整租的,而且押一付一和押二付一最為普遍。提取出“整租”和“合租”兩種方式:
df["way"]?=?df["way"].apply(lambda?x:?x.split("?")[0])
#?編碼
df["way"]?=?df["way"].map({"整租":1,"合租":0})
df
終于:算是得到一份比較符合建模的數(shù)據(jù)

3、樣本不均衡
我們查看way字段下的數(shù)據(jù)情況:明顯way=1的取值情況是遠(yuǎn)遠(yuǎn)大于way=0。

解決方法:使用SMOTE算法來(lái)解決解決way=0過(guò)少的問(wèn)題。

解決之后的way=1和way=0的樣本相同:

字段異常處理
1、填充樣本后發(fā)現(xiàn)某些應(yīng)該是整數(shù),卻出現(xiàn)了小數(shù),比如:wei和time等

cols?=?["shi","ting","wei","time"]
for?i?in?cols:
????smote_df[i]?=?smote_df[i].apply(lambda?x:?round(x))
2、類型轉(zhuǎn)化

#?轉(zhuǎn)變數(shù)據(jù)類型
smote_df["size"]?=?smote_df["size"].astype(float)??
smote_df["sizeInside"]?=?smote_df["sizeInside"].astype(float)
4、相關(guān)性分析
4.1 相關(guān)系數(shù)
針對(duì)上面得到的smote_df數(shù)據(jù)進(jìn)行下面的相關(guān)性分析:
#?保存了再讀取
#?smote_df.to_csv("data_new.csv",index=False)
df?=?pd.read_csv("data_new.csv")
1、相關(guān)性
能夠直觀看到money因變量和size與sizeInside相關(guān)性很強(qiáng)
corr?=?df.corr()
f,ax=plt.subplots(figsize=(12,6))
sns.heatmap(corr,vmax=0.8,square=True,fmt='.2f',?cmap='PuBu_r')
plt.show()

4.2 相關(guān)系數(shù)排序
單個(gè)屬性和money之間的相關(guān)性大小排序,可以看到:size(房子面積)、sizeInsize(套內(nèi)面積)和wei(衛(wèi))的相關(guān)型比較強(qiáng)。
單純地從相關(guān)系數(shù)得到的結(jié)論,還是有待考證~

4.3 自變量和因變量關(guān)系

上面舉出3個(gè)字段和money之間的相關(guān)性,在最右側(cè):
- size和sizeInside相對(duì)集中的時(shí)候,money高頻出現(xiàn)0-25000之間
- 在不同的money取值情況,wei字段的取值集中在1-4之間
5、回歸建模
5.1 特征歸一化
下面是針對(duì)數(shù)據(jù)的歸一化過(guò)程,采用的MinMaxScaler方法:
X?=?df.drop("money",axis=1)
y?=?df[["money"]]
#?實(shí)例化
mm?=?MinMaxScaler()
data?=?mm.fit_transform(X)
#?歸一化后的數(shù)據(jù)
X?=?pd.DataFrame(data,columns=X.columns.tolist())
X.head()

5.2 數(shù)據(jù)集切分
按照訓(xùn)練集:測(cè)試集 = 8:2的比例進(jìn)行數(shù)據(jù)集的劃分:
from?sklearn.model_selection?import?train_test_split
X_train,?X_test,?y_train,?y_test?=?train_test_split(
??X,?y,?
??test_size=0.2,??#?比例
??random_state=4??#?隨機(jī)狀態(tài)
)?????????????????????????????????????????????????
5.3 特征選擇
在前面的工作我們從相關(guān)系數(shù)和因變量的相關(guān)性大小進(jìn)行了比較,發(fā)現(xiàn):Size、sizeInside和wei是比較相關(guān)的系數(shù)。
下面是從使用mutual_info_classif來(lái)查看每個(gè)特征的重要性:
from?sklearn.feature_selection?import?mutual_info_classif
imp?=?pd.DataFrame(mutual_info_classif(X,y),
??????????????????index=X.columns)
imp.columns=['importance']
imp.sort_values(by='importance',ascending=False)

我們發(fā)現(xiàn):shi、wei、zhuangxiu、size都是比較重要的特征
5.4 評(píng)價(jià)指標(biāo)

引入多種回歸模型:
#?線性回歸
from?sklearn.linear_model?import?LinearRegression
#?決策樹回歸
from?sklearn.tree?import?DecisionTreeRegressor
#?梯度提升回歸,隨機(jī)森林回歸
from?sklearn.ensemble?import?GradientBoostingRegressor,RandomForestRegressor
5.5 不同模型效果
重點(diǎn)關(guān)注模型的r2值
1、線性回歸-LinearRegression

2、決策樹回歸-DecisionTreeRegressor

3、隨機(jī)森林回歸-RandomForestRegressor

4、梯度提升回歸-GradientBoostingRegressor

通過(guò)3種回歸模型的r2得分比較:GradientBoostingRegressor > DecisionTreeRegressor > RandomForestRegressor > LinearRegression
6、模型可解釋分析
為了更好理解機(jī)器學(xué)習(xí)模型的輸出,下面使用SHAP庫(kù)來(lái)探索調(diào)參后隨機(jī)森林模型的可解釋性。通過(guò)pip install shap即可安裝

6.1 隨機(jī)森林調(diào)參

進(jìn)行fit擬合之后便得到了最優(yōu)的參數(shù)組合:
rf_random.best_params_
#?結(jié)果
{'n_estimators':?120,?'max_features':?'auto',?'max_depth':?20}
調(diào)參后隨機(jī)森林模型的r2系數(shù)略優(yōu)于調(diào)參前:

建立最佳參數(shù)下的模型:
rf_random?=?RandomForestRegressor(
??n_estimators=180,
??max_features="auto",
??max_depth=10)
rf_random.fit(X_train,?y_train)
6.2 計(jì)算shap_values
SHAP value最大的優(yōu)勢(shì)是SHAP能對(duì)于反映出每一個(gè)樣本中的特征的影響力,而且還能夠表現(xiàn)出影響的正負(fù)性。
在SHAP中進(jìn)行模型解釋需要先創(chuàng)建一個(gè)explainer,SHAP支持很多類型的explainer
#?1、傳入調(diào)優(yōu)模型rf_random創(chuàng)建explainer
explainer?=?shap.TreeExplainer(rf_random)
#?2、計(jì)算shap值
shap_values?=?explainer.shap_values(X_test)
shap_values

6.3 均值對(duì)比
1、通過(guò)模型預(yù)測(cè)得到的均值求解
#?y的預(yù)測(cè)值和真實(shí)值比較
y_pred?=?rf_random.predict(X_test)
#?預(yù)測(cè)值
y_test["pred"]?=?y_pred??
#?money部分是真實(shí)值
y_test.head()

其中:money-真實(shí)值,pred-隨機(jī)森林模型的預(yù)測(cè)值。得到的均值為:
#?真實(shí)的平均值
y_test["pred"].mean()
5614.137953764154
2、通過(guò)SHAP得到的基準(zhǔn)值base_value
#?shap得到的基準(zhǔn)值
y_base?=?explainer.expected_value
y_base
array([5487.93357763])
6.4 整體特征重要性
取出每個(gè)特征的shap值的絕對(duì)值的平均值作為該特征的重要性,得到水平的條形圖:
shap.summary_plot(shap_values,?
??????????????????X_test,?
??????????????????plot_type="bar")

shap.summary_plot(shap_values,?X_test)

可以看到,通過(guò)shap值來(lái)看:
- size面積、zone_position區(qū)域位置、shi房間數(shù) 的重要性才是最靠前的,這個(gè)結(jié)果可以和相關(guān)系數(shù)的大小進(jìn)行對(duì)比
- 前5個(gè)特征中,藍(lán)點(diǎn)主要還是集中在SHAP值小于0的區(qū)域
6.5 單個(gè)樣本的SHAP值
從數(shù)據(jù)中隨機(jī)抽查一條樣本查看shap值:
i=18
df0?=?pd.DataFrame()
df0['feature']?=?X_test.columns.tolist()
df0['feature_value']?=?X_test.iloc[i].values
df0['shap_value']?=?shap_values[i]
df0.head(10)

- 第一列是特征名稱
- 第二列是特征的具體數(shù)值
- 第三列是各個(gè)特征在該樣本中對(duì)應(yīng)的SHAP值
注意:一個(gè)樣本中各特征 SHAP 值的和加上基線值應(yīng)該等于該樣本的預(yù)測(cè)值

單條樣本的特征可視化:
#?可視化
shap.initjs()
shap.force_plot(
??explainer.expected_value,?
??shap_values[i],
??X_test.iloc[i])

紅色表示higher部分:表明針對(duì)這條數(shù)據(jù),zone_position和size是重要的特征。下面是換了另一個(gè)樣本的結(jié)果:

不同的樣本具有不同的重要屬性
6.6 部分依賴圖-Partial Dependence Plot
1、單個(gè)變量的影響
shap.dependence_plot('size',?
?????????????????????shap_values,?
?????????????????????X_test,?
?????????????????????interaction_index=None,?
?????????????????????show=False)

通過(guò)圖形我們觀察到:size的取值在0.4以下,size的shap都是相對(duì)平和。一旦size達(dá)到一定值,對(duì)房租價(jià)格的拉升作用相當(dāng)明顯。
2、兩個(gè)變量的交互式影響
SHAP同樣能夠查看兩個(gè)變量的交互式影響,比如size和zone_position。
shap.dependence_plot(
????'size',?
????shap_values,?
????X_test,
????interaction_index='zone_position',??#?指定
????show=False)

當(dāng)指定了interaction_index 的取值情況,我們不僅能夠觀察到size和房租的關(guān)系,還可以看到size和zone_position之間存在的關(guān)系:size在分界點(diǎn)(大致在0.4)前后,one_position的相應(yīng)都比較大(紅色),這同時(shí)表明zone_position是一個(gè)重要性高的特征~
整理不易,點(diǎn)贊三連↓
