iT邦幫忙

2025 iThome 鐵人賽

DAY 24
0
自我挑戰組

數據新手村:統計系畢業生 30 天打怪升級之旅系列 第 24

Day 24 - Pandas 的時間序列資料處理

  • 分享至 

  • xImage
  •  

大家好,歡迎來到數據新手村的第二十四天!到目前為止,我們已經學會了如何載入、清理、篩選並合併我們的 Olist 數據集。我們的 DataFrame 已經越來越有模有樣。

但我們還忽略了一個數據中極其寶貴的資訊:時間

如果老闆問你:

  • 「我們的銷售額在哪個月份最高?」
  • 「客戶通常在星期幾下單?」
  • 「一天中的哪個時段是下單高峰期?」

如果我們的日期欄位只是普通的「文字」,這些問題將極難回答。這正是我們在 Day 20 學習 pd.to_datetime() 的原因。今天,我們就要來解鎖 datetime 型態的全部潛力,學習如何從時間戳中提取有價值的商業洞見。


1. 準備工作:確保時間欄位已轉換

在開始之前,我們需要一份包含訂單時間和支付金額的 DataFrame,並確保所有的時間欄位都已經是 datetime 型態。

import pandas as pd

# 讀取需要的資料表
orders_df = pd.read_csv('../../data/olist_datasets/olist_orders_dataset.csv')
payments_df = pd.read_csv('../../data/olist_datasets/olist_order_payments_dataset.csv')

# 合併訂單與支付資訊
df = pd.merge(orders_df, payments_df, on='order_id', how='left')

# (本篇核心) 將所有時間相關的欄位,一次性轉換為 datetime 型態
time_columns = ['order_purchase_timestamp', 
                'order_approved_at', 
                'order_delivered_carrier_date', 
                'order_delivered_customer_date', 
                'order_estimated_delivery_date']

for col in time_columns:
    df[col] = pd.to_datetime(df[col])

# 用 .info() 再次確認轉換是否成功
df.info()

結果輸出:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 103887 entries, 0 to 103886
Data columns (total 12 columns):

Column Non-Null Count Dtype


0 order_id 103887 non-null object
1 customer_id 103887 non-null object
2 order_status 103887 non-null object
3 order_purchase_timestamp 103887 non-null datetime64[ns]
4 order_approved_at 103712 non-null datetime64[ns]
5 order_delivered_carrier_date 101999 non-null datetime64[ns]
6 order_delivered_customer_date 100755 non-null datetime64[ns]
7 order_estimated_delivery_date 103887 non-null datetime64[ns]
8 payment_sequential 103886 non-null float64
9 payment_type 103886 non-null object
10 payment_installments 103886 non-null float64
11 payment_value 103886 non-null float64
dtypes: datetime64ns, float64(3), object(4)
memory usage: 9.5+ MB

看到 datetime64[ns] 了嗎?這代表我們的時間欄位已經準備就緒,可以開始施展魔法了!

  1. .dt Accessor:時間序列的魔法棒
    一旦一個欄位是 datetime 型態,我們就可以使用 .dt accessor (存取器),來輕易地提取出時間的各種組成部分,例如年、月、日、星期、小時等。

讓我們來為 order_purchase_timestamp (客戶下單時間) 這個欄位,衍生出一些新的特徵欄位:

# 提取年份
df['purchase_year'] = df['order_purchase_timestamp'].dt.year

# 提取月份
df['purchase_month'] = df['order_purchase_timestamp'].dt.month

# 提取星期幾 (0=週一, 1=週二, ..., 6=週日)
df['purchase_dayofweek'] = df['order_purchase_timestamp'].dt.dayofweek

# 提取當天的小時
df['purchase_hour'] = df['order_purchase_timestamp'].dt.hour

# 檢視新增的四個欄位
df[['order_purchase_timestamp', 'purchase_year', 'purchase_month', 'purchase_dayofweek', 'purchase_hour']].head()

結果輸出:

order_purchase_timestamp purchase_year purchase_month purchase_dayofweek purchase_hour
0 2017-10-02 10:56:33 2017 10 0 10
1 2017-10-02 10:56:33 2017 10 0 10
2 2017-10-02 10:56:33 2017 10 0 10
3 2018-07-24 20:41:37 2018 7 1 20
4 2018-08-08 08:38:49 2018 8 2 8
  1. 解答我們的商業問題
    有了這些新的時間特徵欄位,我們現在可以結合 Day 22 學到的 groupby(),來回答開頭提出的問題了!

問題一:哪個月份的銷售額最高?


# 按月份分組,計算每個月份的支付金額總和
monthly_sales = df.groupby('purchase_month')['payment_value'].sum()

print("各月份總銷售額:")
print(monthly_sales)

結果輸出:
各月份總銷售額:
purchase_month
1 1253492.22
2 1284371.35
3 1609515.72
4 1578573.51
5 1746900.97
6 1535156.88
7 1658923.67
8 1696821.64
9 732454.23
10 839358.03
11 1194882.80
12 878421.10
Name: payment_value, dtype: float64

問題二:星期幾是下單熱點?

# 按星期幾分組,計算每個星期的訂單數量
weekday_orders = df.groupby('purchase_dayofweek')['order_id'].nunique()

# 為了方便閱讀,我們可以將 0-6 映射到實際的星期名稱
weekday_map = {0: '週一', 1: '週二', 2: '週三', 3: '週四', 4: '週五', 5: '週六', 6: '週日'}
weekday_orders.index = weekday_orders.index.map(weekday_map)

print("\n各星期訂單總數:")
print(weekday_orders)

結果輸出:
各星期訂單總數:
purchase_dayofweek
週一 16196
週二 15963
週三 15552
週四 14761
週五 14122
週六 10887
週日 11960
Name: order_id, dtype: int64

  1. 時間差計算
    datetime 型態的另一個強大之處,就是可以直接進行數學運算來計算「時間差」。

問題:平均每筆訂單需要多少天才能送達客戶手中?

# 兩個 datetime 欄位相減,會得到一個 Timedelta 物件
# 我們再用 .dt.days 將其轉換為「天數」
df['delivery_days'] = (df['order_delivered_customer_date'] - df['order_purchase_timestamp']).dt.days

# 計算平均運送天數 (忽略缺失值)
avg_delivery_time = df['delivery_days'].mean()

print(f"平均運送天數: {avg_delivery_time:.2f} 天")

結果輸出:
平均運送天數: 12.11 天


結語
今天,我們解鎖了 Pandas 強大的時間序列分析能力。透過將欄位轉換為 datetime 型態,並活用 .dt accessor,我們學會了如何從時間戳中提取有價值的特徵(年月日時),並結合 groupby 回答了複雜的商業問題。

我們已經透過 groupby 算出了很多驚人的統計數字,但純粹的數字表格往往不夠直觀。要如何讓我們的分析結果一目了然、更具說服力呢?明天,Day 25,我們將正式進入「數據可視化」的世界,學習使用 Matplotlib 將我們的數據變成美麗的圖表!


上一篇
Day 23 - Pandas 的 Merge 與 Join 多表合併
系列文
數據新手村:統計系畢業生 30 天打怪升級之旅24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言