數據準備與探索性分析
現在我們已經概述了我們的方法,讓我們來看看我們的數據以及我們正在處理的特徵。
從上面的資料中,我們看到我們的數據包含約197,000筆送貨記錄,並且有各種數值和非數值的特徵。沒有任何特徵缺少大量的值(最低的非空計數約為181,000),所以我們可能不需要擔心完全刪除任何特徵。
讓我們檢查一下數據中是否有重複的送貨記錄,以及是否有任何觀察值我們無法計算送貨時間。
print(f”重複的數量: {df.duplicated().sum()} \n”)
print(pd.DataFrame({‘缺失計數’: df[[‘created_at’, ‘actual_delivery_time’]].isna().sum()}))
我們看到所有的送貨都是獨特的。然而,有7筆送貨缺少實際送貨時間的值,這意味著我們無法計算這些訂單的送貨持續時間。由於這些數量不多,我們將從數據中刪除這些觀察值。
現在,讓我們創建我們的預測目標。我們想要預測送貨持續時間(以秒為單位),這是客戶下訂單的時間(‘created_at’)與他們收到訂單的時間(‘actual_delivery_time’)之間的經過時間。
# 將列轉換為日期時間 df[‘created_at’] = pd.to_datetime(df[‘created_at’], utc=True)df[‘actual_delivery_time’] = pd.to_datetime(df[‘actual_delivery_time’], utc=True)
# 創建預測目標df[‘seconds_to_delivery’] = (df[‘actual_delivery_time’] – df[‘created_at’]).dt.total_seconds()
在將數據分為訓練和測試之前,我們最後要做的事情是檢查缺失值。我們已經查看了每個特徵的非空計數,但讓我們查看比例以獲得更好的了解。
我們看到市場特徵(‘onshift_dashers’,‘busy_dashers’,‘outstanding_orders’)的缺失值百分比最高(約8%缺失)。缺失數據率第二高的特徵是‘store_primary_category’(約2%)。所有其他特徵的缺失率都低於1%。
由於沒有特徵的缺失計數很高,我們不會刪除任何特徵。稍後,我們將查看特徵的分佈,以幫助我們決定如何適當處理每個特徵的缺失觀察值。
但首先,讓我們將數據分為訓練和測試。我們將進行80/20的分割,並將這個測試數據寫入一個單獨的文件,直到評估我們的最終模型之前,我們不會接觸這個文件。
from sklearn.model_selection import train_test_splitimport os
# 隨機打亂df = df.sample(frac=1, random_state=42)df = df.reset_index(drop=True)
# 分割train_df, test_df = train_test_split(df, test_size=0.2, random_state=42)
# 將測試數據寫入單獨的文件directory = ‘datasets’file_name = ‘test_data.csv’file_path = os.path.join(directory, file_name)os.makedirs(directory, exist_ok=True)test_df.to_csv(file_path, index=False)
現在,讓我們深入了解我們的訓練數據。我們將確定我們的數值和類別特徵,以便在後面的探索性步驟中明確引用哪些列。
categorical_feats = [‘market_id’,’store_id’,’store_primary_category’,’order_protocol’]
numeric_feats = [‘total_items’,’subtotal’,’num_distinct_items’,’min_item_price’,’max_item_price’,’total_onshift_dashers’,’total_busy_dashers’,’total_outstanding_orders’, ‘estimated_order_place_duration’,’estimated_store_to_consumer_driving_duration’]
讓我們重新檢查缺失值的類別特徵(‘market_id’,‘store_primary_category’,‘order_protocol’)。由於這些特徵的缺失數據很少(<3%),我們將簡單地用“未知”類別填補這些缺失值。
這樣,我們就不需要從其他特徵中刪除數據。也許特徵值的缺失對送貨持續時間有一些預測能力,即這些特徵的缺失不是隨機的。此外,我們將在建模過程中將這個填補步驟添加到我們的預處理管道中,這樣我們就不必在測試集上手動重複這項工作。missing_cols_categorical = [‘market_id’, ‘store_primary_category’, ‘order_protocol’]
train_df[missing_cols_categorical] = train_df[missing_cols_categorical].fillna(“unknown”)
讓我們來看看我們的類別特徵。
pd.DataFrame({‘基數’: train_df[categorical_feats].nunique()}).rename_axis(‘特徵’)
由於‘market_id’和‘order_protocol’的基數較低,我們可以輕鬆地可視化它們的分佈。另一方面,‘store_id’和‘store_primary_category’是高基數特徵。我們稍後將更深入地查看這些。
import seaborn as snsimport matplotlib.pyplot as plt
categorical_feats_subset = [‘market_id’,’order_protocol’]
# 設置網格fig, axes = plt.subplots(1, len(categorical_feats_subset), figsize=(13, 5), sharey=True)
# 為每個變量創建條形圖for i, col in enumerate(categorical_feats_subset):sns.countplot(x=col, data=train_df, ax=axes[i])axes[i].set_title(f”頻率: {col}”)
# 調整佈局plt.tight_layout()plt.show()
一些關鍵的觀察:
約70%的訂單的‘market_id’為1,2,4,少於1%的訂單的‘order_protocol’為6或7
不幸的是,我們沒有關於這些變數的其他信息,例如哪些‘market_id’值與哪些城市/地點相關,以及每個‘order_protocol’號碼代表什麼。此時,要求有關這些信息的額外數據可能是一個好主意,因為這可能有助於調查送貨持續時間在更廣泛的地區/地點類別中的趨勢。
讓我們看看我們的高基數類別特徵。也許每個‘store_primary_category’都有一個相關的‘store_id’範圍?如果是這樣,我們可能不需要‘store_id’,因為‘store_primary_category’已經包含了很多有關訂購商店的信息。
store_info = train_df[[‘store_id’, ‘store_primary_category’]]
store_info.groupby(‘store_primary_category’)[‘store_id’].agg([‘min’, ‘max’])
顯然不是這樣:我們看到‘store_id’範圍在‘store_primary_category’的不同層級之間重疊。
快速查看‘store_id’和‘store_primary_category’的不同值及其相關頻率顯示這些特徵具有高基數且分佈稀疏。一般來說,高基數的類別特徵在回歸任務中可能會有問題,特別是對於僅需要數值數據的回歸算法。當這些高基數特徵被編碼時,它們可能會大幅擴大特徵空間,使可用數據變得稀疏,降低模型在該特徵空間中對新觀察的泛化能力。想了解更多這種現象的專業解釋,可以在這裡閱讀。
讓我們了解這些特徵的稀疏分佈情況。
store_id_values = train_df[‘store_id’].value_counts()
# 繪製直方圖plt.figure(figsize=(8, 5))plt.bar(store_id_values.index, store_id_values.values, color=’skyblue’)
# 添加標題和標籤plt.title(‘數值計數: store_id’, fontsize=14)plt.xlabel(‘store_id’, fontsize=12)plt.ylabel(‘頻率’, fontsize=12)plt.xticks(rotation=45) # 旋轉x軸標籤以提高可讀性plt.tight_layout()plt.show()
我們看到有一些商店的訂單數量達到幾百,但大多數商店的訂單數量少於100。
為了處理‘store_id’的高基數,我們將創建另一個特徵‘store_id_freq’,根據頻率對‘store_id’值進行分組。
我們將‘store_id’值分為五個不同的百分位區間,如下所示。‘store_id_freq’的基數將比‘store_id’低得多,但將保留有關訂購商店受歡迎程度的相關信息。想了解這一邏輯的更多靈感,請查看這個討論串。def encode_frequency(freq, percentiles) -> str:if freq < percentiles[0]:return ‘[0-50)’elif freq < percentiles[1]:return ‘[50-75)’elif freq < percentiles[2]:return ‘[75-90)’elif freq < percentiles[3]:return ‘[90-99)’else:return ’99+’
value_counts = train_df[‘store_id’].value_counts()percentiles = np.percentile(value_counts, [50, 75, 90, 99])
# 根據每個store_id的訂單數量應用encode_frequencytrain_df[‘store_id_freq’] = train_df[‘store_id’].apply(lambda x: encode_frequency(value_counts[x], percentiles))
pd.DataFrame({‘計數’:train_df[‘store_id_freq’].value_counts()}).rename_axis(‘頻率區間’)
我們的編碼顯示,約60,000筆送貨來自於在受歡迎程度上位於90–99百分位的商店,而約12,000筆送貨來自於在受歡迎程度上位於0–50百分位的商店。
現在我們已經(嘗試)在較低維度中捕捉到相關的‘store_id’信息,讓我們試著對‘store_primary_category’做類似的事情。
讓我們看看最受歡迎的‘store_primary_category’層級。
快速查看顯示,許多這些‘store_primary_category’層級並不互相排斥(例如:‘american’和‘burger’)。進一步調查顯示還有許多這種重疊的例子。
所以,讓我們試著將這些不同的商店類別映射到幾個基本的、包羅萬象的組別中。
store_category_map = {‘american’: [‘american’, ‘burger’, ‘sandwich’, ‘barbeque’],’asian’: [‘asian’, ‘chinese’, ‘japanese’, ‘indian’, ‘thai’, ‘vietnamese’, ‘dim-sum’, ‘korean’, ‘sushi’, ‘bubble-tea’, ‘malaysian’, ‘singaporean’, ‘indonesian’, ‘russian’],’mexican’: [‘mexican’],’italian’: [‘italian’, ‘pizza’],}
def map_to_category_type(category: str) -> str:for category_type, categories in store_category_map.items():if category in categories:return category_typereturn “other”
train_df[‘store_category_type’] = train_df[‘store_primary_category’].apply(lambda x: map_to_category_type(x))
value_counts = train_df[‘store_category_type’].value_counts()
# 繪製圓餅圖plt.figure(figsize=(6, 6))value_counts.plot.pie(autopct=’%1.1f%%’, startangle=90, cmap=’viridis’, labels=value_counts.index)plt.title(‘類別分佈’)plt.ylabel(”) # 隱藏y軸標籤以提高美觀plt.show()
這種分組可能過於簡單,可能還有更好的方法來分組這些商店類別。為了簡單起見,我們暫時這樣做。
我們已經對我們的類別特徵進行了大量調查。讓我們來看看我們的數值特徵的分佈。
# 為箱形圖創建網格fig, axes = plt.subplots(nrows=5, ncols=2, figsize=(12, 15)) # 調整圖形大小axes = axes.flatten() # 將5×2的axes展平為一維數組以便於迭代
# 為每個數值特徵生成箱形圖for i, column in enumerate(numeric_feats):sns.boxplot(y=train_df[column], ax=axes[i])axes[i].set_title(f”{column}的箱形圖”)axes[i].set_ylabel(column)
# 刪除任何未使用的子圖(如果有)for i in range(len(numeric_feats), len(axes)):fig.delaxes(axes[i])
# 調整佈局以改善間距plt.tight_layout()plt.show()
許多分佈似乎因為存在異常值而偏向右側。
特別是,似乎有一個訂單有400多個項目。這看起來很奇怪,因為下一個最大的訂單少於100個項目。
讓我們更深入地看看那個400多項目的訂單。
train_df[train_df[‘total_items’]==train_df[‘total_items’].max()]
本文由 AI 台灣 運用 AI 技術編撰,內容僅供參考,請自行核實相關資訊。
歡迎加入我們的 AI TAIWAN 台灣人工智慧中心 FB 社團,
隨時掌握最新 AI 動態與實用資訊!