機器學習實戰:沃爾瑪銷售預測


一、簡介

1.1 比賽描述

建模零售數據的一個挑戰是需要根據有限的歷史做出決策。如果聖誕節一年一次,那么有機會看到戰略決策如何影響到底線。

在此招聘競賽中,為求職者提供位於不同地區的45家沃爾瑪商店的歷史銷售數據。每個商店都包含許多部門,參與者必須為每個商店中的每個部門預測銷售額。要添加挑戰,選定的假日降價事件將包含在數據集中。眾所周知,這些降價會影響銷售,但預測哪些部門受到影響以及影響程度具有挑戰性。

想要在世界上最大的一些數據集的良好環境中工作嗎?這是向沃爾瑪招聘團隊展示您的模特氣概的機會。

這項比賽計入排名和成就。 如果您希望考慮參加沃爾瑪的面試,請在第一次參加時選中“允許主持人與我聯系”復選框。

你必須在招募比賽中作為個人參加比賽。您只能使用提供的數據進行預測。

1.2 比賽評估

本次比賽的加權平均絕對誤差(WMAE)評估:

n是行數
yi是真實銷售額
wi是權重,如果該周是假日周,wi=5,否則為1
提交文件:Id列是通過將Store,Dept和Date與下划線連接而形成的(例如Store_Dept_2012-11-02)

對於測試集中的每一行(商店+部門+日期三元組),您應該預測該部門的每周銷售額。

1.3 數據描述

您將獲得位於不同地區的45家沃爾瑪商店的歷史銷售數據。每個商店都包含許多部門,您的任務是預測每個商店的部門范圍內的銷售額。

此外,沃爾瑪全年舉辦多項促銷降價活動。這些降價活動在突出的假期之前,其中最大的四個是超級碗,勞動節,感恩節和聖誕節。包括這些假期的周數在評估中的加權比非假日周高五倍。本次比賽提出的部分挑戰是在沒有完整/理想的歷史數據的情況下模擬降價對這些假期周的影響。

stores.csv:
此文件包含有關45個商店的匿名信息,指示商店的類型和大小。

train.csv:
這是歷史銷售數據,涵蓋2010-02-05至2012-11-01。在此文件中,您將找到以下字段:
Store - 商店編號
Dept - 部門編號
Date - 一周
Weekly_Sales - 給定商店中給定部門的銷售額(目標值)
sHoliday - 周是否是一個特殊的假日周

test.csv:
此文件與train.csv相同,但我們保留了每周銷售額。您必須預測此文件中每個商店,部門和日期三元組的銷售額。

features.csv:
此文件包含與給定日期的商店,部門和區域活動相關的其他數據。它包含以下字段:
Store - 商店編號
Date - 一周
Temperature - 該地區的平均溫度
Fuel_Price - 該地區的燃料成本
MarkDown1-5 - 與沃爾瑪正在運營的促銷降價相關的匿名數據。MarkDown數據僅在2011年11月之后提供,並非始終適用於所有商店。任何缺失值都標有NA。
CPI - 消費者物價指數
Unemployment - 失業率
IsHoliday - 周是否是一個特殊的假日周

為方便起見,數據集中的四個假期在接下來的幾周內(並非所有假期都在數據中):

超級碗:2月12日至10日,11月2日至11日,10月2日至12日,2月8日至2月13
日勞動節:10月9日至10日,9月9日至9日,9月9日至9月12日-13
感恩節:26-Nov- 10,25 -Nov-11,23-Nov-12,29-Nov-13
聖誕節:31-Dec-10,30-Dec-11,28-Dec-12,27-Dec -13

二、代碼

將數據存在GitHub的壓縮包里,直接運行下載即可,或去kaggle下載數據,鏈接:https://www.kaggle.com/c/walmart-recruiting-store-sales-forecasting

2.1.1 下載數據

import os
import zipfile
from six.moves import urllib

FILE_NAME = "walmart-recruiting-store-sales-forecasting.zip" #文件名
DATA_PATH ="datasets/walmart-recruiting-store-sales-forecasting" #存儲文件的文件夾,取跟文件相同(相近)的名字便於區分
DATA_URL = "https://github.com/824024445/KaggleCases/blob/master/datasets/" + FILE_NAME + "?raw=true"


def fetch_data(data_url=DATA_URL, data_path=DATA_PATH, file_name=FILE_NAME):
    if not os.path.isdir(data_path): #查看當前文件夾下是否存在"datasets/titanic",沒有的話創建
        os.makedirs(data_path)
    zip_path = os.path.join(data_path, file_name) #下載到本地的文件的路徑及名稱
    # urlretrieve()方法直接將遠程數據下載到本地
    urllib.request.urlretrieve(data_url, zip_path) #第二個參數zip_path是保存到的本地路徑
    data_zip = zipfile.ZipFile(zip_path)
    data_zip.extractall(path=data_path) #什么參數都不輸入就是默認解壓到當前文件,為了保持統一,是泰坦尼克的數據就全部存到titanic文件夾下
    data_zip.close()
fetch_data()

2.1.2 讀取數據

import pandas as pd
import numpy as np

train_df = pd.read_csv("datasets/walmart-recruiting-store-sales-forecasting/train.csv")
test_df = pd.read_csv("datasets/walmart-recruiting-store-sales-forecasting/test.csv")
features = pd.read_csv("datasets/walmart-recruiting-store-sales-forecasting/features.csv")
stores = pd.read_csv("datasets/walmart-recruiting-store-sales-forecasting/stores.csv")

train_df = train_df.merge(features, on=["Store", "Date"], how="left").merge(stores, on="Store", how="left")
test_df = test_df.merge(features, on=["Store", "Date"], how="left").merge(stores, on="Store", how="left")
combine = [train_df, test_df]
train_df.head()

train_df.info()

test_df.info()

train_df.describe()
train_df.describe(include="O")

#各變量與Weekly_Sales的關系
corr_matrix = train_df.corr()
corr_matrix.Weekly_Sales.sort_values(ascending=False)

corr_matrix[["MarkDown1","MarkDown2","MarkDown3","MarkDown4","MarkDown5"]].sort_values(by="MarkDown5", ascending=False)

  • markdown1和4的關聯度比較大,只需要要一個就行,刪除markdown4

2.3 數據清洗

2.3.1 缺失值處理

## Markdown 對於訓練集markdown的缺失,這里先不處理,等會分成兩個數據集,一個含缺失markdown然后填充,一個去掉這些數據
test_df[['MarkDown1','MarkDown2','MarkDown3','MarkDown5']] = test_df[['MarkDown1','MarkDown2','MarkDown3','MarkDown5']].fillna(0)
test_df[["CPI","Unemployment"]] = test_df[["CPI","Unemployment"]].fillna(method="ffill")  

2.3.2 創建新特征

將type列轉變成onehot編碼

train_df = pd.get_dummies(train_df, columns=["Type"])
test_df = pd.get_dummies(test_df, columns=["Type"])
train_df.head()

 

把日期換成月份

train_df['Month'] = pd.to_datetime(train_df['Date']).dt.month
test_df["Month"] = pd.to_datetime(test_df['Date']).dt.month
#等下記得刪除Date,test的暫時先不刪,后面要用

溫度
想來,人們在極端天氣的時候不太會出門。所以把數據分成兩組:小於22.01,大於91.03(根據溫度分布划分的,畫柱狀圖可得,我已經刪掉了)

train_df.loc[(train_df["Temperature"]<22.01)|(train_df["Temperature"]>91.03), "Is_temp_extr"]=1
train_df.loc[(train_df["Temperature"]>=22.01)& (train_df["Temperature"]<=91.03), "Is_temp_extr"]=0

test_df.loc[(test_df["Temperature"]<22.01)|(test_df["Temperature"]>91.03), "Is_temp_extr"]=1
test_df.loc[(test_df["Temperature"]>=22.01)& (test_df["Temperature"]<=91.03), "Is_temp_extr"]=0

train_df.corr().Weekly_Sales.sort_values(ascending=False)[["Temperature", "Is_temp_extr"]]
#提取新特征后相關性提升了十多倍 等下記得把這個特征刪除。

燃油價格
人們會因為燃油費太貴不出門嗎?

train_df.loc[train_df["Fuel_Price"]>3.47, "Is_fuel_expen"]=1
train_df.loc[train_df["Fuel_Price"]<=3.47, "Is_fuel_expen"]=0
#無論怎么改,這個相關性都很低,所以這個特征等下去除
train_df.corr().Weekly_Sales.sort_values(ascending=False)[["Fuel_Price", "Is_fuel_expen"]]

IsHoliday

(在最開始df.merge時)
由於前面合並表格的時候的問題,出現了兩個isholidy,刪掉一個即可。
另外,把bool值換成0和5(后面權重),新建一列IsHoliday,一會吧IsHoliday_x,IsHoliday_y刪掉

train_df["IsHoliday"] = train_df["IsHoliday_x"].replace(True, 5).replace(False,0)
test_df["IsHoliday"] = test_df["IsHoliday_x"].replace(True, 5).replace(False,0)

train_df.corr().Weekly_Sales.sort_values(ascending=False)[["IsHoliday_x", "IsHoliday"]]

把剛剛處理過的多余的特征值刪掉

train_df = train_df.drop(["IsHoliday_x", "IsHoliday_y",'MarkDown4',"Date", "Temperature", "Fuel_Price","Is_fuel_expen"], axis=1) #這是后面提交表格需要用到的變量,用到了測試集的date特征,先在這里給id變量賦值,然后就可以吧date特征刪除了
id = test_df["Store"].astype(str)+"_"+test_df["Dept"].astype(str)+"_"+test_df["Date"].astype(str) test_df = test_df.drop(["IsHoliday_x", "IsHoliday_y", "MarkDown4", "Date","Temperature", "Fuel_Price"], axis=1) 

最終檢查

將數據集用到模型前,一定要確保沒有空值,所以最后再檢查一下

先把訓練集做成兩份:一份含缺失的markdown,一個去除掉這些數據

train_df_one = train_df.copy()
train_df_two = train_df.copy()
train_df_one[['MarkDown1','MarkDown2','MarkDown3','MarkDown5']] = train_df_one[['MarkDown1','MarkDown2','MarkDown3','MarkDown5']].fillna(0)
train_df_two.dropna(inplace=True)

train_df_one.info()

train_df_two.info()

test_df.info()

模型和預測

 為了快速測試,寫了一個類。我寫的案例大部分都回用到這個類。不過每次因為性能評測的指標不同,所以需要微改。

import time
import os
from sklearn.metrics import mean_absolute_error
from sklearn.base import clone

class Tester():
    def __init__(self, target):
        self.target = target
        self.datasets = {}
        self.models = {}
        self.scores = {}
        self.cache = {} # 我們添加了一個簡單的緩存來加快速度

    def addDataset(self, name, df):
        self.datasets[name] = df.copy()

    def addModel(self, name, model):
        self.models[name] = model
        
    def clearModels(self):
        self.models = {}

    def clearCache(self):
        self.cache = {}
    
    def testModelWithDataset(self, m_name, df_name, sample_len, cv):
        if (m_name, df_name, sample_len, cv) in self.cache:
            return self.cache[(m_name, df_name, sample_len, cv)]

        clf = clone(self.models[m_name])
        
        if not sample_len: 
            sample = self.datasets[df_name]
        else: sample = self.datasets[df_name].sample(sample_len)
            
        X = sample.drop([self.target], axis=1)
        Y = sample[self.target]

        #評分標准不一樣的話,修改這里
        weights = X["IsHoliday"]
        clf.fit(X, Y)
        Y_pred = clf.predict(X)
        s = mean_absolute_error(Y, Y_pred, sample_weight=weights)
        self.cache[(m_name, df_name, sample_len, cv)] = s

        return s

    def runTests(self, sample_len=97056, cv=3):
        # 在所有添加的數據集上測試添加的模型
        for m_name in self.models:
            for df_name in self.datasets:
                # print('Testing %s' % str((m_name, df_name)), end='')
                start = time.time()

                score = self.testModelWithDataset(m_name, df_name, sample_len, cv)
                self.scores[(m_name, df_name)] = score
                
                end = time.time()
                
                # print(' -- %0.2fs ' % (end - start))

        print('--- Top 10 Results ---')
        # 評分標准改了之后這里也得改
        for score in sorted(self.scores.items(), key=lambda x: x[1])[:10]:
            # score = int(score[1])
            print(score)

    def obtian_result(self, X_test):
        clf = self.models[sorted(self.scores.items(), key=lambda x: x[1])[0][0]]
        Y_pred = clf.predict(X_test)
        return Y_pred
from sklearn.ensemble import ExtraTreesRegressor, RandomForestRegressor, GradientBoostingRegressor
from sklearn.neighbors import KNeighborsRegressor
from sklearn.svm import SVR
from sklearn.feature_selection import RFE
from sklearn.neural_network import MLPRegressor

# 我們將在所有模型中使用測試對象
tester = Tester('Weekly_Sales')

# 添加數據集
tester.addDataset('all_markdown', train_df_one)
tester.addDataset('wipe_markdown', train_df_two)

# 添加模型
knn_reg = KNeighborsRegressor(n_neighbors=10)
tree_reg = ExtraTreesRegressor(n_estimators=100,max_features='auto', verbose=1, n_jobs=1)
rf_reg = RandomForestRegressor(n_estimators=100,max_features='log2', verbose=1)
svr_reg = SVR(kernel='rbf', gamma='auto')
mlp_reg = MLPRegressor(hidden_layer_sizes=(10,),  activation='relu', verbose=3)
gbrt_reg = GradientBoostingRegressor(max_depth=8, warm_start=True)
tester.addModel('KNeighborsRegressor', knn_reg)
tester.addModel('ExtraTreesRegressor', tree_reg)
tester.addModel('RandomForestRegressor', rf_reg)
tester.addModel('SVR', svr_reg)
tester.addModel('MLPRegressor', mlp_reg)
tester.addModel('GradientBoostingRegressor', gbrt_reg)

# 測試
tester.runTests()

X = train_df_one.drop(["Weekly_Sales"], axis=1)
Y = train_df_one["Weekly_Sales"]

gbrt_reg.fit(X, Y)
Y_pred = gbrt_reg.predict(test_df)
submission = pd.DataFrame({
        "Id": id,
        "Weekly_Sales": pd.DataFrame(Y_pred)[0]
    })
id

submission.to_csv('submission.csv', index=False)

完整代碼

 

#下載數據
import
os import zipfile from six.moves import urllib FILE_NAME = "walmart-recruiting-store-sales-forecasting.zip" #文件名 DATA_PATH ="datasets/walmart-recruiting-store-sales-forecasting" #存儲文件的文件夾,取跟文件相同(相近)的名字便於區分 DATA_URL = "https://github.com/824024445/KaggleCases/blob/master/datasets/" + FILE_NAME + "?raw=true" def fetch_data(data_url=DATA_URL, data_path=DATA_PATH, file_name=FILE_NAME): if not os.path.isdir(data_path): #查看當前文件夾下是否存在"datasets/titanic",沒有的話創建 os.makedirs(data_path) zip_path = os.path.join(data_path, file_name) #下載到本地的文件的路徑及名稱 # urlretrieve()方法直接將遠程數據下載到本地 urllib.request.urlretrieve(data_url, zip_path) #第二個參數zip_path是保存到的本地路徑 data_zip = zipfile.ZipFile(zip_path) data_zip.extractall(path=data_path) #什么參數都不輸入就是默認解壓到當前文件,為了保持統一,是泰坦尼克的數據就全部存到titanic文件夾下 data_zip.close() fetch_data() #數據處理 import pandas as pd import numpy as np train_df = pd.read_csv("datasets/walmart-recruiting-store-sales-forecasting/train.csv") test_df = pd.read_csv("datasets/walmart-recruiting-store-sales-forecasting/test.csv") features = pd.read_csv("datasets/walmart-recruiting-store-sales-forecasting/features.csv") stores = pd.read_csv("datasets/walmart-recruiting-store-sales-forecasting/stores.csv") train_df = train_df.merge(features, on=["Store", "Date"], how="left").merge(stores, on="Store", how="left") test_df = test_df.merge(features, on=["Store", "Date"], how="left").merge(stores, on="Store", how="left") combine = [train_df, test_df] train_df.head() train_df.info() test_df.info() train_df.describe() train_df.describe(include="O") #各變量與Weekly_Sales的關系 corr_matrix = train_df.corr() corr_matrix.Weekly_Sales.sort_values(ascending=False) corr_matrix[["MarkDown1","MarkDown2","MarkDown3","MarkDown4","MarkDown5"]].sort_values(by="MarkDown5", ascending=False) #數據清洗 ## Markdown 對於訓練集markdown的缺失,這里先不處理,等會分成兩個數據集,一個含缺失markdown然后填充,一個去掉這些數據 #缺失值處理 test_df[['MarkDown1','MarkDown2','MarkDown3','MarkDown5']] = test_df[['MarkDown1','MarkDown2','MarkDown3','MarkDown5']].fillna(0) test_df[["CPI","Unemployment"]] = test_df[["CPI","Unemployment"]].fillna(method="ffill") #創建新特征 #type轉變成onehot編碼 train_df = pd.get_dummies(train_df, columns=["Type"]) test_df = pd.get_dummies(test_df, columns=["Type"]) train_df.head() #把日期換成月份,便於分析 train_df['Month'] = pd.to_datetime(train_df['Date']).dt.month test_df["Month"] = pd.to_datetime(test_df['Date']).dt.month #等下記得刪除Date,test的暫時先不刪,后面要用 #溫度 #想來,人們在極端天氣的時候不太會出門。所以把數據分成兩組:小於22.01,大於91.03(根據溫度分布划分的,畫柱狀圖可得,我已經刪掉了) train_df.loc[(train_df["Temperature"]<22.01)|(train_df["Temperature"]>91.03), "Is_temp_extr"]=1 train_df.loc[(train_df["Temperature"]>=22.01)& (train_df["Temperature"]<=91.03), "Is_temp_extr"]=0 test_df.loc[(test_df["Temperature"]<22.01)|(test_df["Temperature"]>91.03), "Is_temp_extr"]=1 test_df.loc[(test_df["Temperature"]>=22.01)& (test_df["Temperature"]<=91.03), "Is_temp_extr"]=0 train_df.corr().Weekly_Sales.sort_values(ascending=False)[["Temperature", "Is_temp_extr"]] #提取新特征后相關性提升了十多倍 等下記得把這個特征刪除。 #燃油價格 #人們會因為燃油費太貴不出門嗎? train_df.loc[train_df["Fuel_Price"]>3.47, "Is_fuel_expen"]=1 train_df.loc[train_df["Fuel_Price"]<=3.47, "Is_fuel_expen"]=0 #無論怎么改,這個相關性都很低,所以這個特征等下刪除 train_df.corr().Weekly_Sales.sort_values(ascending=False)[["Fuel_Price", "Is_fuel_expen"]] #IsHoliday #(在最開始df.merge時) #由於前面合並表格的時候的問題,出現了兩個isholidy,刪掉一個即可。 #另外,把bool值換成0和5(后面權重) train_df["IsHoliday"] = train_df["IsHoliday_x"].replace(True, 5).replace(False,0) test_df["IsHoliday"] = test_df["IsHoliday_x"].replace(True, 5).replace(False,0) #train_df.corr().Weekly_Sales.sort_values(ascending=False)[["IsHoliday_x", "IsHoliday"]] #把剛剛處理過的多余的特征值刪掉 train_df = train_df.drop(["IsHoliday_x", "IsHoliday_y",'MarkDown4',"Date", "Temperature", "Fuel_Price","Is_fuel_expen"], axis=1) #這是后面提交表格需要用到的變量,用到了測試集的date特征,先在這里給id變量賦值,然后就可以吧date特征刪除了 id = test_df["Store"].astype(str)+"_"+test_df["Dept"].astype(str)+"_"+test_df["Date"].astype(str) test_df = test_df.drop(["IsHoliday_x", "IsHoliday_y", "MarkDown4", "Date","Temperature", "Fuel_Price"], axis=1) #最終檢查 #將數據集用到模型前,一定要確保沒有空值,所以最后再檢查一下 #先把訓練集做成兩份:一份含缺失的markdown,一個去除掉這些數據 train_df_one = train_df.copy() train_df_two = train_df.copy() train_df_one[['MarkDown1','MarkDown2','MarkDown3','MarkDown5']] = train_df_one[['MarkDown1','MarkDown2','MarkDown3','MarkDown5']].fillna(0) train_df_two.dropna(inplace=True) train_df_one.info() train_df_two.info() test_df.info() #訓練模型 import time import os from sklearn.metrics import mean_absolute_error from sklearn.base import clone class Tester(): def __init__(self, target): self.target = target self.datasets = {} self.models = {} self.scores = {} self.cache = {} # 我們添加了一個簡單的緩存來加快速度 def addDataset(self, name, df): self.datasets[name] = df.copy() def addModel(self, name, model): self.models[name] = model def clearModels(self): self.models = {} def clearCache(self): self.cache = {} def testModelWithDataset(self, m_name, df_name, sample_len, cv): if (m_name, df_name, sample_len, cv) in self.cache: return self.cache[(m_name, df_name, sample_len, cv)] clf = clone(self.models[m_name]) if not sample_len: sample = self.datasets[df_name] else: sample = self.datasets[df_name].sample(sample_len) X = sample.drop([self.target], axis=1) Y = sample[self.target] #評分標准不一樣的話,修改這里 weights = X["IsHoliday"] clf.fit(X, Y) Y_pred = clf.predict(X) s = mean_absolute_error(Y, Y_pred, sample_weight=weights) self.cache[(m_name, df_name, sample_len, cv)] = s return s def runTests(self, sample_len=97056, cv=3): # 在所有添加的數據集上測試添加的模型 for m_name in self.models: for df_name in self.datasets: # print('Testing %s' % str((m_name, df_name)), end='') start = time.time() score = self.testModelWithDataset(m_name, df_name, sample_len, cv) self.scores[(m_name, df_name)] = score end = time.time() # print(' -- %0.2fs ' % (end - start)) print('--- Top 10 Results ---') # 評分標准改了之后這里也得改 for score in sorted(self.scores.items(), key=lambda x: x[1])[:10]: # score = int(score[1]) print(score) def obtian_result(self, X_test): clf = self.models[sorted(self.scores.items(), key=lambda x: x[1])[0][0]] Y_pred = clf.predict(X_test) return Y_pred from sklearn.ensemble import ExtraTreesRegressor, RandomForestRegressor, GradientBoostingRegressor from sklearn.neighbors import KNeighborsRegressor from sklearn.svm import SVR from sklearn.feature_selection import RFE from sklearn.neural_network import MLPRegressor # 我們將在所有模型中使用測試對象 tester = Tester('Weekly_Sales') # 添加數據集 tester.addDataset('all_markdown', train_df_one) tester.addDataset('wipe_markdown', train_df_two) # 添加模型 knn_reg = KNeighborsRegressor(n_neighbors=10) tree_reg = ExtraTreesRegressor(n_estimators=100,max_features='auto', verbose=1, n_jobs=1) rf_reg = RandomForestRegressor(n_estimators=100,max_features='log2', verbose=1) svr_reg = SVR(kernel='rbf', gamma='auto') mlp_reg = MLPRegressor(hidden_layer_sizes=(10,), activation='relu', verbose=3) gbrt_reg = GradientBoostingRegressor(max_depth=8, warm_start=True) tester.addModel('KNeighborsRegressor', knn_reg) tester.addModel('ExtraTreesRegressor', tree_reg) tester.addModel('RandomForestRegressor', rf_reg) tester.addModel('SVR', svr_reg) tester.addModel('MLPRegressor', mlp_reg) tester.addModel('GradientBoostingRegressor', gbrt_reg) # 測試 tester.runTests() X = train_df_one.drop(["Weekly_Sales"], axis=1) Y = train_df_one["Weekly_Sales"] gbrt_reg.fit(X, Y) Y_pred = gbrt_reg.predict(test_df) submission = pd.DataFrame({ "Id": id, "Weekly_Sales": pd.DataFrame(Y_pred)[0] }) id submission.to_csv('submission.csv', index=False)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM