大家好,歡迎來到數據新手村的第二十一天!在過去幾天,我們學會了載入資料、篩選資料、處理缺失值和轉換資料類型。我們的數據集已經變得越來越乾淨。
但數據分析的魅力,不僅在於「清理」現有的資料,更在於從中「創造」出新的、更有價值的資訊。這個過程,我們稱之為**「特徵工程 (Feature Engineering)」**。
今天,我們就要來學習特徵工程最基礎、也最重要的一步:如何在 Pandas DataFrame 中新增與刪除欄位 (Column)。
這是最簡單、最直觀的新增欄位方式。我們可以直接對一個新的欄位名稱賦值,Pandas 就會自動幫我們創建它。
讓我們讀取 order_items
資料表,它包含了商品價格 (price
) 和運費 (freight_value
)。我們可以很輕易地計算出每個品項的「總價」。
import pandas as pd
# 讀取訂單商品資料表
order_items_df = pd.read_csv('../../data/olist_datasets/olist_order_items_dataset.csv')
# 新增 'total_value' 欄位,其值等於 price + freight_value
order_items_df['total_value'] = order_items_df['price'] + order_items_df['freight_value']
# 檢視結果,注意看最右邊多出來的新欄位
print(order_items_df.head())
輸出結果:
order_id order_item_id
0 00010242fe8c5a6d1ba2dd792cb16214 1
1 00018f77f2f0320c557190d7a144bdd3 1
2 000229ec398224ef6ca0657da4fc703e 1
3 00024acbcdf0a6daa1e931b038114c75 1
4 00042b26cf59d7ce69dfabb4e55b4fd9 1
product_id seller_id \
0 4244733e06e7ecb4970a6e2683c13e61 48436dade18ac8b2bce089ec2a041202
1 e5f2d52b802189ee658865ca93d83a8f dd7ddc04e1b6c2c614352b383efe2d36
2 c777355d18b72b67abbeef9df44fd0fd 5b51032eddd242adc84c38acab88f23d
3 7634da152a4610f1595efa32f14722fc 9d7a1d34a5052409006425275ba1c2b4
4 ac6c3623068f30de03045865e4e10089 df560393f3a51e74553ab94004ba5c87
shipping_limit_date price freight_value total_value
0 2017-09-19 09:45:35 58.90 13.29 72.19
1 2017-05-03 11:05:13 239.90 19.93 259.83
2 2018-01-18 14:48:30 199.00 17.87 216.87
3 2018-08-15 10:10:18 12.99 12.79 25.78
4 2017-02-13 13:57:51 199.90 18.14 218.04
範例二:發揮 datetime 型態的威力 (本篇重點)
還記得我們昨天學的 pd.to_datetime() 嗎?現在就是它發揮價值的時刻!一旦欄位是 datetime 型態,我們就可以輕易地進行日期運算。
讓我們來計算 Olist 訂單從「客戶下單」到「商品送達」總共花了幾天。
# 讀取訂單主表
orders_df = pd.read_csv('../../data/olist_datasets/olist_orders_dataset.csv')
# 將兩個時間欄位都轉換成 datetime 型態
orders_df['order_purchase_timestamp'] = pd.to_datetime(orders_df['order_purchase_timestamp'])
orders_df['order_delivered_customer_date'] = pd.to_datetime(orders_df['order_delivered_customer_date'])
# 兩個 datetime 欄位相減,會得到一個 Timedelta 物件
# 我們再用 .dt.days 將其轉換為「天數」
orders_df['delivery_days'] = (orders_df['order_delivered_customer_date'] - orders_df['order_purchase_timestamp']).dt.days
# 檢視我們新建立的 'delivery_days' 欄位
# 為了方便觀察,我們只選取相關的欄位來看
orders_df[['order_purchase_timestamp', 'order_delivered_customer_date', 'delivery_days']].head()
輸出結果:
order_purchase_timestamp | order_delivered_customer_date | delivery_days | |
---|---|---|---|
0 | 2017-10-02 10:56:33 | 2017-10-10 21:25:13 | 8.0 |
1 | 2018-07-24 20:41:37 | 2018-08-07 15:27:45 | 13.0 |
2 | 2018-08-08 08:38:49 | 2018-08-17 18:06:29 | 9.0 |
3 | 2017-11-18 19:28:06 | 2017-12-02 00:28:42 | 13.0 |
4 | 2018-02-13 21:18:39 | 2018-02-16 18:17:02 | 2.0 |
看到 delivery_days 欄位了嗎?這就是一個全新的、由我們自己創造出來的有價值的「特徵」!
如果新增欄位的邏輯比較複雜,無法用簡單的加減乘除完成呢?例如,我們想根據價格,將商品分為「高價」、「中價」、「低價」三類。這時,.apply() 方法就派上用場了。
.apply() 可以讓我們將一個自訂的函式,應用到欄位的每一個元素上。
# 為了演示,我們只取 order_items_df 的前五筆
sample_df = order_items_df.head().copy() # .copy() 避免 SettingWithCopyWarning
# 步驟 1: 定義一個分類邏輯的函式
def categorize_price(price):
if price > 500:
return '高價'
elif price > 100:
return '中價'
else:
return '低價'
# 步驟 2: 將這個函式 apply 到 'price' 欄位上,並將結果存入新欄位
sample_df['price_category'] = sample_df['price'].apply(categorize_price)
print(sample_df[['price', 'price_category']])
輸出結果:
price price_category
0 58.90 低價
1 239.90 中價
2 199.00 中價
3 12.99 低價
4 199.90 中價
有增就有刪。當我們發現某些欄位不再需要時,可以使用 .drop() 方法來移除它們。
# 假設我們想刪除 'total_value' 和 'price_category' 這兩個我們剛建立的欄位
# 使用 columns 參數來指定要刪除的欄位列表
df_after_drop = sample_df.drop(columns=['total_value', 'price_category'])
# .drop() 預設會回傳一個新的 DataFrame,原始的 sample_df 維持不變
print("刪除後的 DataFrame:")
df_after_drop.head()
Pro Tip: .drop() 方法有一個 inplace=True 的參數。如果設定了它,drop 會直接在原始的 DataFrame 上進行修改,而不會回傳新的。但一般更推薦 df = df.drop(...) 這種重新賦值的方式,程式碼的流向會更清晰。
輸出結果:
刪除後的 DataFrame:
order_id | order_item_id | product_id | seller_id | shipping_limit_date | price | freight_value | |
---|---|---|---|---|---|---|---|
0 | 00010242fe8c5a6d1ba2dd792cb16214 | 1 | 4244733e06e7ecb4970a6e2683c13e61 | 48436dade18ac8b2bce089ec2a041202 | 2017-09-19 09:45:35 | 58.90 | 13.29 |
1 | 00018f77f2f0320c557190d7a144bdd3 | 1 | e5f2d52b802189ee658865ca93d83a8f | dd7ddc04e1b6c2c614352b383efe2d36 | 2017-05-03 11:05:13 | 239.90 | 19.93 |
2 | 000229ec398224ef6ca0657da4fc703e | 1 | c777355d18b72b67abbeef9df44fd0fd | 5b51032eddd242adc84c38acab88f23d | 2018-01-18 14:48:30 | 199.00 | 17.87 |
3 | 00024acbcdf0a6daa1e931b038114c75 | 1 | 7634da152a4610f1595efa32f14722fc | 9d7a1d34a5052409006425275ba1c2b4 | 2018-08-15 10:10:18 | 12.99 | 12.79 |
4 | 00042b26cf59d7ce69dfabb4e55b4fd9 | 1 | ac6c3623068f30de03045865e4e10089 | df560393f3a51e74553ab94004ba5c87 | 2017-02-13 13:57:51 | 199.90 | 18.14 |
今天我們學會了如何「改造」我們的 DataFrame。新增欄位是數據分析中極具創造性的一環,我們不再只是被動地清理數據,而是開始主動地從數據中創造新的知識與洞見。
我們已經學會了如何準備單一的表格。但數據分析的威力,體現在從「多個表格」的關聯中找出答案。明天,Day 22,我們將迎來 Pandas 中威力最強大、也是數據分析師每天必用的功能——Group By 分組聚合。我們將學會如何從數萬筆訂單中,計算出「每個城市的平均銷售額」這類高價值的商業洞見!