大型商場銷售預測
參考1
參考2
%matplotlib inline# 忽略警告提示import warningswarnings.filterwarnings(ignore)import pandas as pdimport numpy as np
載入數據
train=pd.read_csv(Train_UWu5bXk.csv)tests=pd.read_csv(Test_u94Q5KV.csv)
探索數據
train.shape,test.shape((8523, 12), (5681, 11))
訓練數據有8523行,12列,測試數據有5681行,11列數據
#查看數據統計描述信息train.describe()#訓練集和測試集合併到一起處理all_data=pd.concat([train,test],ignore_index=True)all_data.describe()all_data.info()<class pandas.core.frame.DataFrame>RangeIndex: 14204 entries, 0 to 14203Data columns (total 12 columns):Item_Fat_Content 14204 non-null objectItem_Identifier 14204 non-null objectItem_MRP 14204 non-null float64Item_Outlet_Sales 8523 non-null float64Item_Type 14204 non-null objectItem_Visibility 14204 non-null float64Item_Weight 11765 non-null float64Outlet_Establishment_Year 14204 non-null int64Outlet_Identifier 14204 non-null objectOutlet_Location_Type 14204 non-null objectOutlet_Size 10188 non-null objectOutlet_Type 14204 non-null objectdtypes: float64(4), int64(1), object(7)memory usage: 1.3+ MB#查看每列缺失值的數量並排序all_data.isnull().sum().sort_values(ascending=False).head()Item_Outlet_Sales 5681Outlet_Size 4016Item_Weight 2439Outlet_Type 0Outlet_Location_Type 0dtype: int64
- Item_Weight 產品重量存在缺失值,需要處理
- Outlet_Size 商店大小存在缺失值,需要處理
- ItemOutletSales 商品銷售額的缺失值符合測試集的數目,不用處理
- ItemFatContent 商品低脂變數,有LF,low fat,Low Fat,reg,Regula五個類別,從類別描述看可以分為Low Fat和Regular兩個變數,表示低脂和常規
- Item_Visibility 商品的展示百分比數據中存在最低值為0,由於訓練集的每個商品數據都有對應的銷售額,因此這是異常的值,需要處理
- Item_Type 產品類別歸類較多,需要進一步看看能夠生成新的歸類變數
- Item_MRP 給出了商品的標價,能夠計算出對應商品的銷量(相對於銷售額,銷量更易於理解和探索)
- OutletEstablishmentYear 商店的開店年份19xx年,這樣的數據不直觀,可以調整為年數
清洗數據
1、處理缺失值:商品重量
由於每個商品的重量應該是相同的,所以用商品的重量均值補插缺失的值
#商品重量用均值填充——這裡後面試試隨即森林填補all_data[Item_Weight]=all_data[Item_Weight].fillna(all_data[Item_Weight].mean())all_data[Item_Weight].isnull().sum()0
2、缺失值處理:商店大小
- 結果看出每個商店的類型,大小與平均銷售額的關係,似乎商店的大小確實和設想的一樣,與商店的類型有比較強的對應關係
- 可以構建一個決策樹來預測和補插商店大小的缺失值
- 個人理解商店大小是比較關鍵的變數,但因為數據集中的商店數量較少,因此在商店層面的區分效果沒有商店類型變數那麼好
#構建一個決策樹來插補缺失值,特徵用商店類型size_train=all_data[[Outlet_Size,Outlet_Type]]#把大小轉換成數值def size(x): if x==Medium: return 1 elif x==Small: return 2 elif x=="High": return 3 else: return xsize_train.Outlet_Size=size_train.Outlet_Size.map(size)size_train=pd.get_dummies(size_train)known_size=size_train[size_train.Outlet_Size.notnull()].as_matrix()unknown_size=size_train[size_train.Outlet_Size.isnull()].as_matrix()y=known_size[:,0]x=known_size[:,1:]test_size=unknown_size[:,1::]#決策樹#DecisionTreeClassifierfrom sklearn.datasets import load_irisfrom sklearn import treeclf = tree.DecisionTreeClassifier()clf = clf.fit(x, y)pred=clf.predict(test_size)all_data.loc[(all_data.Outlet_Size.isnull()),Outlet_Size]=pred#檢查all_data.Outlet_Size.isnull().value_counts()False 14204Name: Outlet_Size, dtype: int64all_data.Outlet_Size.value_counts()Medium 46552.0 4016Small 3980High 1553Name: Outlet_Size, dtype: int64
這裡缺失值全部補充為Small,不合理
all_data.Outlet_Size=all_data.Outlet_Size.map(size)all_data.Outlet_Size.value_counts()2.0 79961.0 46553.0 1553Name: Outlet_Size, dtype: int64
特徵工程和EDA
1、創建一個表示銷售量的變數
#計算每個商品的銷售量,並將所有小數點大於0的數都向上取整all_data[Item_Sales_Vol]=all_data.Item_Outlet_Sales/all_data.Item_MRP#這裡還沒有取真,分訓練和測試集的時候再取整
- 銷售量將作為模型的預測變數(銷售金額log轉化後作為預測變數更好)
2、調整ItemFatContent錯誤的因子水平
#改變低脂肪類別print (all_data[Item_Fat_Content].value_counts())print ("修改後的類別")all_data[Item_Fat_Content]=all_data[Item_Fat_Content].replace({LF:Low Fat, reg:Regular, low fat:Low Fat})all_data[Item_Fat_Content].value_counts()Low Fat 8485Regular 4824LF 522reg 195low fat 178Name: Item_Fat_Content, dtype: int64修改後的類別Low Fat 9185Regular 5019Name: Item_Fat_Content, dtype: int64
- 將商品的低脂調整為:Low Fat,Regular兩個水平
3、對Item_Type進一步歸類,創建新的商品分類變數
#商品的類別可以進一步歸類all_data.Item_Type.describe()count 14204unique 16top Fruits and Vegetablesfreq 2013Name: Item_Type, dtype: objectall_data.Item_Identifier.head()0 FDA151 DRC012 FDN153 FDX074 NCD19Name: Item_Identifier, dtype: object#通過查看商品ID,可以看到ID開頭似乎是商品的大類,DR飲品,DF食物,NC費消耗品,DR和DF為日常消耗品#ID後的A-Z是商品編碼,似乎看不到品牌信息def Item_Identifier(x): return x[0:2]all_data[Item_Identifier]=all_data.Item_Identifier.map(Item_Identifier)#由於食物才有低脂的情況,因此對於非消耗品則需要不同區分all_data[Item_Identifier].value_counts()FD 10201NC 2686DR 1317Name: Item_Identifier, dtype: int64
4、將Item_Visibility中為0的商品調整為每個商店中的平均值
#採用每店的平均商品展示百分比來補插為0的異常值all_data.Outlet_Identifier.value_counts()OUT027 1559OUT013 1553OUT049 1550OUT046 1550OUT035 1550OUT045 1548OUT018 1546OUT017 1543OUT010 925OUT019 880Name: Outlet_Identifier, dtype: int64#確定產品的平均可見度visibility_avg=all_data.pivot_table(values=Item_Visibility,index=Item_Identifier)visibility_avg
def visibility_avg(x): if x==DR: return 0.064836 elif x==FD: return 0.068098 elif x==NC: return 0.058354 else: return x#使用該產品的平均可見度來賦值0值:miss_bool = all_data[Item_Weight].isnull() all_data.loc[miss_bool,Item_Visibility]=all_data.loc[miss_bool,Item_Identifier].map(visibility_avg)print (修改後的0個值的數量:%d%sum(all_data [Item_Visibility] == 0))修改後的0個值的數量:%d
第5步:確定商店的運營年限
#年份all_data[Outlet_Years]=2013-all_data[Outlet_Establishment_Year]all_data[Outlet_Years].describe()# #年份:# 數據[Outlet_Years] = 2013 - 數據[Outlet_Establishment_Year]# 數據[ Outlet_Years]。描述()count 14204.000000mean 15.169319std 8.371664min 4.00000025% 9.00000050% 14.00000075% 26.000000max 28.000000Name: Outlet_Years, dtype: float64
這顯示運營年限為4-28歲的商店
all_data.Item_Fat_Content.value_counts()Low Fat 9185Regular 5019Name: Item_Fat_Content, dtype: int64
在步驟2中,我們看到有一些非消耗品,並且不應為他們指定脂肪含量。所以我們也可以為這種觀察創建一個單獨的類別
#將非消耗品標記為low_fat中的單獨類別:all_data.loc[all_data[Item_Identifier]==FD,Item_Fat_Content]=non-eat all_data[Item_Fat_Content].value_counts()non-eat 10201Low Fat 3894Regular 109Name: Item_Fat_Content, dtype: int64
可視化探索各個變數
1、商店層面的4個因素:OutletType,OutletLocationType,OutletYears,Outlet_Size
all_data.columnsIndex([Item_Fat_Content, Item_Identifier, Item_MRP, Item_Outlet_Sales, Item_Type, Item_Visibility, Item_Weight, Outlet_Establishment_Year, Outlet_Identifier, Outlet_Location_Type, Outlet_Size, Outlet_Type, Item_Sales_Vol, Outlet_Years], dtype=object)#劃分訓練集和測試集 訓練集有8523個ii=8523train=all_data.loc[:(ii-1),:]test=all_data.loc[ii:,:]train.shape,test.shape((8523, 14), (5681, 14))import matplotlib.pyplot as pltimport seaborn as snssns.boxplot(x="Outlet_Type", y="Item_Sales_Vol", data=train,palette=Set3,hue=Outlet_Type)<matplotlib.axes._subplots.AxesSubplot at 0x112a47b38>
商店類型因素中,Grocery Store銷量最低,type3平均銷量最高
sns.boxplot(x="Outlet_Location_Type", y="Item_Sales_Vol",hue=Outlet_Type, data=train,palette=Set3)<matplotlib.axes._subplots.AxesSubplot at 0x1151946d8>
商店類型1有較完整的數據,但在地理位置的表現上,銷量並無太大的區別
sns.boxplot(x="Outlet_Years", y="Item_Sales_Vol", hue=Outlet_Type,data=train,palette=Set3)<matplotlib.axes._subplots.AxesSubplot at 0x115331860>
商店類型1有較完整的數據,從成立的年數來看,似乎近10年內建立的商店的銷售量表現高一點
sns.boxplot(x="Outlet_Size", y="Item_Sales_Vol",hue=Outlet_Type, data=train,palette=Set3)<matplotlib.axes._subplots.AxesSubplot at 0x115547c18>
商店類型1有較完整的數據,從商店大小的區分來看,並無差異
可視化的結果表明:
- 商店類型因素能夠較明顯區分每個商店的銷量水平
- 商店的地理位置似乎很難看出區別,但其主要原因在於商店數量較少,難以在這個維度區分開來,不能否定該因素的價值
- 商店的年份看,在同個類型的商店,似乎越新的商店有著更好的表現,似乎消費者會更喜歡新開張的商店,而不是因為年代久知名度高的原因,與我們前面的假設有點出入。
- 從開店的趨勢看,似乎管理公司更傾向於商品銷量表現平穩的類型1商店
- 從商店的大小看似乎也很難看出商品銷量的區別,其原因也在於商店數量較少,該變數還是一個比較關鍵的,因為商店大小跟開店成本相關,在其他預測問題上表現應該很不錯,但在此處,可能表現會一般
2、商品層面的因素:ItemVisibility,ItemWeight,ItemMRP,ItemAttribute,ItemFatContent,Item_Type(控制商店類型)
sns.lmplot(x=Item_Visibility,y=Item_Sales_Vol,data=train,hue=Outlet_Type)<seaborn.axisgrid.FacetGrid at 0x112624400>
商品展示的結果與假設接近,展示越大,平均銷量越高
建模和優化
data[Item_Sales_Vol][:30].astype(int)0 141 92 143 44 185 106 57 378 119 2510 3311 1512 1013 1714 1015 2716 1417 1318 2019 1120 1521 2822 823 124 2425 1826 2527 728 329 2Name: Item_Sales_Vol, dtype: int64#需要的特徵data=all_data[[Item_Sales_Vol,Outlet_Type,Item_Visibility,Outlet_Location_Type, Item_MRP,Item_Type,Outlet_Years,Outlet_Size,Item_Outlet_Sales]]#所有定性變數進行One-hot編碼data=pd.get_dummies(data)#分訓練和測試 train=data.loc[:ii-1,:]test=data.loc[ii:,:]train[Item_Sales_Vol]=train[Item_Sales_Vol].astype(int)print (train.shape,test.shape)#特徵x=train.as_matrix()[:,1:]y1=train.as_matrix()[:,0]y2=pd.cut(y1,5,labels=[1,10,20,30,40])y3=np.array(y2)(8523, 29) (5681, 29)all_data.Item_Sales_Vol.describe()count 8523.000000mean 15.425685std 9.192655min 0.95232225% 8.94947950% 14.92689375% 21.092696max 56.844890Name: Item_Sales_Vol, dtype: float64
模型建立
定義一個通用函數,它將演算法和數據作為輸入並製作模型,執行交叉驗證並生成提交。
train[Item_Sales_Vol]=train.Item_Sales_Vol.astype(int)target=[Item_Sales_Vol]IDcol=[Item_Sales_Vol,Item_Identifier,Outlet_Identifier,Item_Outlet_Sales]predictors = [x for x in train.columns if x not in IDcol]predictorsss = [x for x in train.columns if x not in target]train.shape(8523, 29)#劃分數據集from sklearn.cross_validation import train_test_splitx_train, x_test, y_train, y_test = train_test_split(train[predictorsss], train.Item_Sales_Vol, test_size = 0.3)#創建模型評估rmse函數import mathdef rmse(pred,act): return math.sqrt(sum((act-pred)^2)/len(act))#線性回歸模型from sklearn.linear_model import LinearRegression regr = LinearRegression().fit(x_train[predictors], y_train) print(regr.score(x_train[predictors], y_train)) pred=regr.predict(x_test[predictors],)#rmse(pred*x_test.Item_MRP,x_test.Item_Outlet_Sales)math.sqrt(sum((pred*x_test.Item_MRP-x_test.Item_Outlet_Sales)**2)/len(x_test.Item_Outlet_Sales))0.4283816088231072.0115737888589#創建用於上傳評分的測試結果 dtree.csvpred_test=regr.predict(test[predictors])df3=pd.DataFrame({"Item_Identifier":tests.Item_Identifier,"Outlet_Identifier":tests.Outlet_Identifier, "Item_Outlet_Sales":pred_test*tests.Item_MRP})df3.to_csv( sale_pred.csv , index = True )
提交結果1153,排名463 31%
隨機森林回歸
from pyspark.ml.regression import RandomForestRegressor
rf = RandomForestRegressor(featuresCol="indexedFeatures").fit(x_train[predictors]from sklearn.ensemble import RandomForestRegressorrf = RandomForestRegressor()rf.fit(x_train[predictors],y_train)pred=rf.predict(x_test[predictors],)pred_test=rf.predict(test[predictors])df3=pd.DataFrame({"Item_Identifier":tests.Item_Identifier,"Outlet_Identifier":tests.Outlet_Identifier, "Item_Outlet_Sales":pred_test*tests.Item_MRP})df3.to_csv( sssor_pred.csv , index = True )
隨機森林提交結果 1251 沒有線性回歸好
以上是分析和建模過程,有更好的發現,歡迎交流
推薦閱讀:
※年前的一個小計劃
※Python 數據分析(五):數據的處理
※機器學習(周志華)第一、二章
※手把手教你使用ggplot2繪製折線圖
※用戶畫像——搜狗用戶挖掘:文本分類