在進行一個數據分析案例時,都是一些散落的點兒妇菱,東做一點西做一點兒承粤,思路不特別清晰暴区。結合網上的學習,對照采用線性回歸進行汽車價格預測這一案例辛臊,結合自己的理解仙粱,搭建了一個分析的框架,作為一個checklist浪讳。面對一個新的任務缰盏、新的數據集時,以比較順暢的執(zhí)行淹遵。更換模型時口猜,則只需要在對應部分進行替換即可。希望能給需要的人有所幫助透揣。
準備工作:導入相關包
此處主要列出了常用的一些济炎,在使用過程中可根據需要靈活添加
# 導入相關包
import numpy as np
import pandas as pd
# 導入可視化包
import matplotlib.pyplot as plt
import seaborn as sns
import missingno as msno
# 缺失數據可視化的一個小工具包
# 統(tǒng)計函數
from statsmodels.distributions.empirical_distribution import ECDF
from sklearn.metrics import mean_squared_error, r2_score
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.linear_model import LinearRegression, Lasso, LassoCV
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.ensemble import RandomForestRegressor
seed = 123
獲取數據
實際中有多種多樣的方式,此處只簡單的以在文件中獲取舉例辐真,如果有調整须尚,只需要在此處變化即可。
有網友提供了一個網盤侍咱,可以下載數據:
https://pan.baidu.com/s/1H7RWWMmb_mXXm2gKjd2E5w 提取碼:9fbq
csv_dir = r'線性回歸_汽車數據.csv'
# 注意耐床,引處需要指定na_values,否則在缺失值可視化時不能正常顯示
# data = pd.read_csv(csv_dir)
data = pd.read_csv(csv_dir, na_values='?')
探索數據
根據《商業(yè)數據分析指南》中給出的建議楔脯,探索數據的過程主要包括以下幾個部分:
- 0 了解數據類型及基本情況
- 1 數據質量檢查:主要包括檢查數據中是否有錯誤撩轰,如性別類型,是否會有拼寫錯誤的昧廷,把female 拼寫為fmale等等堪嫂,諸如此類
- 2 異常值檢測:主要通過
數據概覽
這些可以理解為數據字典,是基于業(yè)務而得到的數據取值范圍及類型木柬,后面在檢查時需對照是否在這些范圍內皆串。
當然,基于此數據集眉枕,有些給出的范圍是實際數據集的恶复,而不是從業(yè)務角度給出的可能范圍。注意做好一定的區(qū)分即可速挑。
主要包括3類指標:
- 汽車的各種特性.
保險風險評級:(-3, -2, -1, 0, 1, 2, 3).
每輛保險車輛年平均相對損失支付.
- 類別屬性
make: 汽車的商標(奧迪谤牡,寶馬。梗摇。拓哟。)
fuel-type: 汽油還是天然氣
aspiration: 渦輪
num-of-doors: 兩門還是四門
body-style: 硬頂車想许、轎車伶授、掀背車断序、敞篷車
drive-wheels: 驅動輪
engine-location: 發(fā)動機位置
engine-type: 發(fā)動機類型
num-of-cylinders: 幾個氣缸
fuel-system: 燃油系統(tǒng)
- 連續(xù)指標
bore: continuous from 2.54 to 3.94.
stroke: continuous from 2.07 to 4.17.
compression-ratio: continuous from 7 to 23.
horsepower: continuous from 48 to 288.
peak-rpm: continuous from 4150 to 6600.
city-mpg: continuous from 13 to 49.
highway-mpg: continuous from 16 to 54.
price: continuous from 5118 to 45400.
# 分析數據類型,看哪些是分類數據糜烹,哪些是數據數據违诗,有沒有數據類型需要轉換等等
data.dtypes
symboling int64
normalized-losses float64
make object
fuel-type object
aspiration object
num-of-doors object
body-style object
drive-wheels object
engine-location object
wheel-base float64
length float64
width float64
height float64
curb-weight int64
engine-type object
num-of-cylinders object
engine-size int64
fuel-system object
bore float64
stroke float64
compression-ratio float64
horsepower float64
peak-rpm float64
city-mpg int64
highway-mpg int64
price float64
dtype: object
print(data.shape)
data.head(5)
(205, 26)
print(data.columns)
# 對數據進行描述統(tǒng)計
# 會返回一個DataFrame結構的數據
data_desc = data.describe()
data_desc
Index(['symboling', 'normalized-losses', 'make', 'fuel-type', 'aspiration',
'num-of-doors', 'body-style', 'drive-wheels', 'engine-location',
'wheel-base', 'length', 'width', 'height', 'curb-weight', 'engine-type',
'num-of-cylinders', 'engine-size', 'fuel-system', 'bore', 'stroke',
'compression-ratio', 'horsepower', 'peak-rpm', 'city-mpg',
'highway-mpg', 'price'],
dtype='object')
檢查數據取值
對分類數據,查看其所有可能的取值疮蹦,是否有錯漏
classes = ['make', 'fuel-type', 'aspiration', 'num-of-doors',
'body-style', 'drive-wheels', 'engine-location',
'engine-type', 'num-of-cylinders', 'fuel-system']
for each in classes:
print(each + ':\n')
print(list(data[each].drop_duplicates()))
print('\n')
make:
['alfa-romero', 'audi', 'bmw', 'chevrolet', 'dodge', 'honda', 'isuzu', 'jaguar', 'mazda', 'mercedes-benz', 'mercury', 'mitsubishi', 'nissan', 'peugot', 'plymouth', 'porsche', 'renault', 'saab', 'subaru', 'toyota', 'volkswagen', 'volvo']
fuel-type:
['gas', 'diesel']
aspiration:
['std', 'turbo']
num-of-doors:
['two', 'four', nan]
body-style:
['convertible', 'hatchback', 'sedan', 'wagon', 'hardtop']
drive-wheels:
['rwd', 'fwd', '4wd']
engine-location:
['front', 'rear']
engine-type:
['dohc', 'ohcv', 'ohc', 'l', 'rotor', 'ohcf', 'dohcv']
num-of-cylinders:
['four', 'six', 'five', 'three', 'twelve', 'two', 'eight']
fuel-system:
['mpfi', '2bbl', 'mfi', '1bbl', 'spfi', '4bbl', 'idi', 'spdi']
缺失值處理
缺失值處理方法:
1诸迟、缺失值較少時,1%以下愕乎,可以直接去掉nan阵苇;
2、用已有的值取平均值或眾數感论;
3绅项、用已知的數做回歸模型,進行預測比肄。
觀測異常值的缺失情況快耿,可通過missingno提供的可視化工具,也可以以計數的形式芳绩,查看缺失值及所占比例
處理完異常值后掀亥,就沒有缺失值了。如果采用文中的方法妥色,應該先處理缺失值
# 通過圖示查看缺失值
# missing values?
#darkgrid 黑色網格(默認)
#whitegrid 白色網格
#dark 黑色背景
#white 白色背景
#ticks
sns.set(style='ticks') #設置sns的樣式背景
# 注意搪花,在讀入csv數據時,需將缺失值指定相關參數 垛膝,如na_values='?',否則不能顯示
msno.matrix(data)
# 根據以上數據可以看出鳍侣,只有nrmaized-losses列缺失值比較多,其余的缺失值很少
# 看一下具體缺失多少
null_cols = ['normalized-losses', 'num-of-doors', 'bore', 'stroke', 'horsepower', 'peak-rpm', 'price']
total_rows = data.shape[0]
# 看一下是否還有缺失
# # 看一下具體缺失多少
# for each_col in data.columns:
# print('{}:{}'.format(each_col, data[pd.isnull(data[each_col])].shape))
for each_col in null_cols:
print('{}:{}'.format(each_col, data[pd.isnull(data[each_col])].shape[0] / total_rows))
# print(data[pd.isnull(data[each_col])].head())
normalized-losses:0.2
num-of-doors:0.00975609756097561
bore:0.01951219512195122
stroke:0.01951219512195122
horsepower:0.00975609756097561
peak-rpm:0.00975609756097561
price:0.01951219512195122
# 看一下nrmaized-losses的分布情況
sns.set(style='darkgrid')
plt.figure(figsize=(12,5))
plt.subplot(121)
# 累計分布曲線
cdf = ECDF(data['normalized-losses'])
cdf = [[each_x, each_y] for each_x, each_y in zip(cdf.x, cdf.y)]
cdf = pd.DataFrame(cdf, columns=['x','y'])
sns.lineplot(x="x", y="y",data=cdf)
plt.subplot(122)
# 直方圖
x = data['normalized-losses'].dropna()
sns.distplot(x, hist=True, kde=True, kde_kws={"color": "k", "lw": 3, "label": "KDE"},
hist_kws={"histtype": "step", "linewidth": 3,
"alpha": 1, "color": "g"})
# 可以發(fā)現 80% 的 normalized losses 是低于200 并且絕大多數低于125.
# 一個基本的想法就是用中位數來進行填充吼拥,但直觀理解倚聚,這個值應該是和保險等級直接相關,如果分組填充應該會更精確一些凿可。
# 惑折!為啥會想到這點?還是基于業(yè)務理解吧枯跑,即數據分析最基本的出發(fā)點:為了什么而分析惨驶。時刻應該有這種意識
# 首先來看一下對于不同保險情況的統(tǒng)計指標:(這種判斷是基于業(yè)務出的,所以實際問題實際分析)
data.groupby('symboling')['normalized-losses'].describe()
# 可以看出敛助,不同的保險級別粗卜,區(qū)別還是比較明顯的
# 其他維度的缺失值較小,直接刪除
sub_set = ['num-of-doors', 'bore', 'stroke', 'horsepower', 'peak-rpm', 'price']
data = data.dropna(subset=sub_set).reset_index(drop=True)
# 用分組的平均值進行填充(此處掌握groupby + transform的用法纳击,非常好用)
data['normalized-losses'] = data.groupby('symboling')['normalized-losses'].transform(lambda x: x.fillna(x.mean()))
print(data.shape)
data.head()
(193, 26)
檢測異常值(本例中未實際采用)
常對于數值型數據進行
一般異常值的檢測方法有基于統(tǒng)計的方法续扔,基于聚類的方法攻臀,以及一些專門檢測異常值的方法等。
對于非專門的異常檢測任務纱昧,這里采用基于統(tǒng)計的方法刨啸。
- 基于正態(tài)分布
數據需要服從正態(tài)分布。在3?原則下识脆,異常值如超過3倍標準差设联,則認為是異常值 - 基于四分位矩
利用箱型圖的四分位距(IQR)對異常值進行檢測,也叫Tukey‘s test灼捂。四分位距(IQR)就是上四分位與下四分位的差值离例。而我們通過IQR的1.5倍為標準,規(guī)定:超過上四分位+1.5倍IQR距離悉稠,或者下四分位-1.5倍IQR距離的點為異常值粘招。
也可以直接畫出箱線圖,通過肉眼識別是否有異常值偎球,再進行處理洒扎。
num = ['symboling', 'normalized-losses', 'length', 'width', 'height', 'horsepower', 'wheel-base',
'bore', 'stroke','compression-ratio', 'peak-rpm','engine-size','highway-mpg']
# 可以一次性繪制出所有的箱線圖,但由于其度量并不一致衰絮,可以分別繪制.
# 用sns繪制時袍冷,需要考慮到缺失值的情況,這里直接用dataframe的功能繪制
for each in num:
plt.figure()
x = data[each]
x.plot.box()
# 在箱線圖中可以直接觀測到離群點猫牡,一般應將其刪除
# 異常值的處理
# for each in num:
# ## 是否可以直接用data_desc.loc[行索引名胡诗,列索引名進行] data_desc.loc['25%', 'price']
# #定義一個下限
# lower = data[each].quantile(0.25)-1.5*(data[each].quantile(0.75)-data[each].quantile(0.25))
# #定義一個上限
# upper = data[each].quantile(0.25)+1.5*(data[each].quantile(0.75)-data[each].quantile(0.25))
# #重新加入一列,用于判斷
# data['qutlier'] = (data[each] < lower) | (data[each] > upper)
# #篩選異常數據
# data[data['qutlier'] ==True]
# #過濾掉異常數據
# data = data[data['qutlier'] ==False]
# plt.figure()
# data[each].plot.box()
# data = data.drop('qutlier',axis=1)
檢查數據的相關性
cor_matrix = data.corr()
# 返回的仍是dataframe類型數據淌友,可以直接引用
cor_matrix
# 轉化為一維表
#返回函數的上三角矩陣煌恢,把對角線上的置0,讓他們不是最高的震庭。
#np.tri()生成下三角矩陣瑰抵,k=-1即對角線向下偏移一個單位,對角線及以上元素全都置零
#.T矩陣轉置器联,下三角矩陣轉置變成上三角矩陣
cor_matrix *= np.tri(*cor_matrix.values.shape, k=-1).T
cor_matrix = cor_matrix.stack()#在用pandas進行數據重排二汛,stack:以列為索引進行堆積,unstack:以行為索引展開拨拓。
cor_matrix = cor_matrix.reindex(cor_matrix.abs().sort_values(ascending=False).index).reset_index()
cor_matrix.columns = ["FirstVariable", "SecondVariable", "Correlation"]
cor_matrix.head(10)
# 除了單純的相關性分析外肴颊,還可分析特征之間是否存在一些邏輯關系,比如本例中數據中長寬高三個特征渣磷,可以將其拼接為一個體積變量
#對于相關性極高的變量婿着,沒什么區(qū)別,二者選其一就可以了
# 同時,要矩陣圖中竟宋,除了要檢查不同變量之間的相關關系外奥务,還需要看每個變量的分布情況,看是否有特殊的規(guī)律性內容袜硫。
# 根據結果,city-mpg highway-mpg之間相似度過高挡篓,只保留一個即可
# 繪制矩陣圖
sns.pairplot(data, hue='fuel-type', palette='husl')
# 處理完畢異常值后婉陷,fuel-type只剩下了一種取值
# sns.pairplot(data, hue='fuel-type', palette='husl', markers=['o', 's'])
# 仔細分析一下與幾個指標之間的關系
sns.lmplot(x='price',y='horsepower',data=data, hue='fuel-type', col='fuel-type', row='num-of-doors', palette='plasma', fit_reg=True)
cor_matrix = data.corr()
# 需重新計算一次,因上一組變更了其取值
# 再通過熱力圖直觀了解一下相關系數的情況
# Generate a mask for the upper triangle
#構造一個蒙版布爾型官研,同corr_all一致的零矩陣秽澳,然后從中取上三角矩陣。去下三角矩陣是np.tril_indices_from(mask)
#其目的是剔除冗余映射戏羽,只取一半就好
mask = np.zeros_like(cor_matrix, dtype = np.bool)
mask[np.triu_indices_from(mask)] = True
# Set up the matplotlib figure
#plt.subplots() 返回一個 Figure實例fig 和一個 AxesSubplot實例ax 担神。這個很好理解,fig代表整個圖像始花,ax代表坐標軸和畫的圖妄讯。
f, ax = plt.subplots(figsize = (11, 9))
# Draw the heatmap with the mask and correct aspect ratio
#mask=True ,上三角單元將自動被屏蔽
sns.heatmap(cor_matrix, mask=mask,
square = True, linewidths = .5, ax = ax, cmap = "YlOrRd")
檢查數據是否滿足模型的基本假設
對線性回歸,數據分布需滿足正態(tài)分布
# 分析汽車價格的分布是否符合正態(tài)
# data['price'] = np.log(data['price'])
x = data['price']
sns.distplot(x, hist=True, kde=True, kde_kws={"color": "k", "lw": 3, "label": "KDE"},
hist_kws={"histtype": "stepfilled", "linewidth": 3,
"alpha": 1, "color": "g"})
# 認為基本滿足酷宵,但有點兒偏態(tài)分布了亥贸,可以需要通過其他的方式進行加工處理。
數據預處理
特征融合
# 數據預處理
data2 = data.copy()
data2['volume'] = data2.length * data2.width * data2.height
#drop默認刪除行元素浇垦,刪除列需加 axis = 1
data2.drop(['width', 'length', 'height',
'curb-weight', 'city-mpg'],
axis = 1, # 1 for columns
inplace = True)
數值型特征的標準化
# 提取預測值
target = data2['price']
target = data2.price
# 剔除預測值后的其他變量
features = data2.drop(columns=['price'])
# 取數字值有
num = ['symboling', 'normalized-losses', 'volume', 'horsepower', 'wheel-base',
'bore', 'stroke','compression-ratio', 'peak-rpm','engine-size','highway-mpg']
standard_scaler = StandardScaler()
features[num] = standard_scaler.fit_transform(features[num])
features.head(10)
# 繪制箱線圖看數據分布() 但由于數據的度量范圍不一致炕置,差距比較大,應在進行歸一化后再識別
# https://cloud.tencent.com/developer/article/1441795(如何解決標簽重疊的問題)
# plt.figure(figsize = (100, 50)) 試圖通過拉長畫布解決男韧,但在標簽很多的情況下朴摊,還是無法實現
features.plot.box(title="Auto-Car", vert=False)
plt.xticks(rotation=-20)
plt.figure()
sns.boxplot(data=features, linewidth=2.5, orient='h')
類別數據編碼
# 類別屬性的獨熱編碼
classes = ['make', 'fuel-type', 'aspiration', 'num-of-doors',
'body-style', 'drive-wheels', 'engine-location',
'engine-type', 'num-of-cylinders', 'fuel-system']
# 將數據轉化成獨熱編碼, 即對非數值類型的字符進行分類轉換成數字。用0-1表示此虑,這就將許多指標劃分成若干子列甚纲,因此現在總共是66列
dummies = pd.get_dummies(features[classes])
#將分類處理后的數據列添加進列表中同時刪除處理前的列
# 采用這種方式的好處:每列的名稱不是無意義的
features3 = features.join(dummies).drop(classes, axis = 1)
print(features.columns)
# one_hot_encoder = OneHotEncoder()
# # 小心,有兩組數據不用進行標準化
# features3 = pd.concat([features[num],pd.DataFrame(one_hot_encoder.fit_transform(features[classes]).toarray())], axis=1)
features3.head()
Index(['symboling', 'normalized-losses', 'make', 'fuel-type', 'aspiration',
'num-of-doors', 'body-style', 'drive-wheels', 'engine-location',
'wheel-base', 'engine-type', 'num-of-cylinders', 'engine-size',
'fuel-system', 'bore', 'stroke', 'compression-ratio', 'horsepower',
'peak-rpm', 'highway-mpg', 'volume'],
dtype='object')
數據建模
劃分數據集
# sklearn.model_selection.train_test_split隨機劃分訓練集和測試集
X_train, X_test, y_train, y_test = train_test_split(features3, target,
test_size = 0.3,
random_state = seed)
模型調參
相關運行中參數的可視化輸出
方法一:建一個for循環(huán)
alphas = 2. ** np.arange(2, 12) #array([2,3,4,5,6,7,8,9,10,11])
scores = np.empty_like(alphas)
c = '#366DE8'
#用不同的alphas值做模型
for i, a in enumerate(alphas): #i:alphas索引序列朦前,a:alphas
lasso = Lasso(random_state = seed) #指定lasso模型
lasso.set_params(alpha = a) #確定alpha值
lasso.fit(X_train, y_train) #.fit(x,y) 執(zhí)行
scores[i] = lasso.score(X_test, y_test) #用測試值計算
方法二:常用交叉驗證
#lassocv:交叉驗證模型贩疙,
#lassocv返回擬合優(yōu)度這一統(tǒng)計學指標,越趨近1况既,擬合程度越好
lassocv = LassoCV(cv = 10, random_state=seed)#制定模型这溅,將訓練集平均切10分,9份用來做訓練棒仍,1份用來做驗證悲靴,可設置alphas=[]是多少(序列格式),默認不設置則找適合訓練集最優(yōu)alpha
lassocv.fit(features3, target) #輸入數據訓練訓練模型
lassocv_score = lassocv.score(features3, target) #測試模型莫其,返回r^2
lassocv_alpha = lassocv.alpha_ #即確定出最佳懲罰系數 入
plt.figure(figsize = (10, 4))
plt.plot(alphas, scores, '-ko')
#在圖上添加一條水平線
plt.axhline(lassocv_score, color = c)
plt.xlabel(r'$\alpha$')
plt.ylabel('CV Score')
plt.xscale('log', basex = 2)
#兩個坐標軸與圖像的偏移距離
sns.despine(offset = 15)
print('CV results:', lassocv_score, lassocv_alpha)
模型訓練
# 看一下權重的分布
# lassocv 回歸系數 lassocv.coef_是參數向量w
#pd.Series(data,index)
coefs = pd.Series(lassocv.coef_, index = features3.columns) # .coef_ 可以返回經過學習后的所有 feature 的參數癞尚。
# prints out the number of picked/eliminated features
print("Lasso picked " + str(sum(coefs != 0)) + " features and eliminated the other " + \
str(sum(coefs == 0)) + " features.")
#在汽車價格預測中耸三,最重要的正向 feature 是 volume,這個比較貼近現實
# takes first and last 10
coefs = pd.concat([coefs.sort_values().head(5), coefs.sort_values().tail(5)]) #將相同字段首尾相接
plt.figure(figsize = (10, 4))
coefs.plot(kind = "barh", color = c)
# sns.barplot(data=coefs)
plt.title("Coefficients in the Lasso Model")
plt.show()
模型測試
# 測試集
model_l1 = LassoCV(alphas=alphas, cv=10, random_state=seed).fit(X_train, y_train)
y_pred_l1 = model_l1.predict(X_test)
model_l1.score(X_test, y_test)
0.8302697453896324
# residual plot
plt.rcParams['figure.figsize'] = (6.0, 6.0)
preds = pd.DataFrame({"preds": model_l1.predict(X_train), "true": y_train})
preds["residuals"] = preds["true"] - preds["preds"]
sns.scatterplot(x='preds',y="residuals",data=preds)
print(mean_squared_error(y_test, y_pred_l1), r2_score(y_test, y_pred_l1))
3935577.218151757 0.8302697453896325
# 預測結果與實際結果對照
d = {'true' : y_test,
'predicted' : y_pred_l1
}
pd.DataFrame(d).head()
模型應用
給定未知數據并輸出
如同模型測試中的方法一致浇揩,使用predict方法