大家好,歡迎來到數據新手村的第十九天!在數據分析界有句名言:「一個數據分析師 80% 的時間,都花在清理數據上。」
從今天開始,我們將正式動手處理「髒數據」,而所有髒數據中最常見的,就是缺失值 (Missing Values)。缺失值在 Pandas 中通常以 NaN
(Not a Number) 的形式出現,它們會讓我們的統計計算(例如 mean()
)出錯,是分析前必須處理的頭號敵人。
在處理之前,我們得先找到它們。df.info()
是我們的第一道防線,它可以告訴我們每欄的非缺失值數量。如果想更精確地統計每欄到底有多少個缺失值,可以使用 .isna()
搭配 .sum()
。
import pandas as pd
# 假設 orders_df 已經讀取好了
# orders_df = pd.read_csv('../../data/olist_datasets/olist_orders_dataset.csv')
# .isna() 會回傳一個 True/False 的 DataFrame,True 代表該位置是缺失值
# .sum() 會將 True (視為1) 加總,從而計算出每欄的缺失值總數
missing_counts = orders_df.isna().sum()
print("各欄位的缺失值數量:")
print(missing_counts[missing_counts > 0]) # 只顯示有缺失值的欄位
輸出結果:
各欄位的缺失值數量:
order_approved_at 160
order_delivered_carrier_date 1783
order_delivered_customer_date 2965
dtype: int64
從 Olist 訂單表中,我們會發現 order_approved_at, order_delivered_carrier_date, order_delivered_customer_date 這幾個時間欄位有大量的缺失值。這是合理的,因為「已取消」或「處理中」的訂單,自然不會有後續的「送達時間」。
# 為了演示,先複製一份 DataFrame
df_copy = orders_df.copy()
print(f"刪除前,資料筆數: {len(df_copy)}")
# .dropna() 預設會刪除任何包含至少一個缺失值的「列 (row)」
df_dropped = df_copy.dropna()
print(f"刪除後,資料筆數: {len(df_dropped)}")
輸出結果:
刪除前,資料筆數: 99441
刪除後,資料筆數: 96461
何時該用刪除法?
只有當包含缺失值的資料筆數,佔總體比例非常非常小(例如 < 1%),且您確定刪除它們不會對整體數據分佈造成偏差時,才考慮使用。在我們的 Olist 案例中,直接刪除會損失太多寶貴的訂單資訊,顯然不合適。
填補固定值
例如,對於類別型或文字型資料,我們可以用一個特定的字串(如 "Unknown")來填補。
填補統計量 (最常用!)
對於數值型資料,最常見的作法是用該欄位的平均數 (mean) 或中位數 (median) 來填補。
# 假設我們有一個包含缺失值的數值 Series
numeric_data = pd.Series([1.0, 2.0, np.nan, 4.0, 5.0, np.nan])
print(f"原始資料:\n{numeric_data}\n")
# 計算平均值 (忽略 NaN)
mean_value = numeric_data.mean()
print(f"平均值為: {mean_value}\n")
# 使用平均值填補缺失值
filled_data = numeric_data.fillna(mean_value)
print(f"用平均值填補後的資料:\n{filled_data}")
輸出結果:
原始資料:
0 1.0
1 2.0
2 NaN
3 4.0
4 5.0
5 NaN
dtype: float64
平均值為: 3.0
用平均值填補後的資料:
0 1.0
1 2.0
2 3.0
3 4.0
4 5.0
5 3.0
dtype: float64
進階填補 (ffill, bfill)
對於時間序列數據,有時用前一個時間點 (ffill) 或後一個時間點 (bfill) 的值來填補會更合理。
結語
今天我們學會如何偵測並處理數據中最惱人的缺失值。請記住,沒有一種方法是萬能的,我們必須根據數據的特性和分析的目的,來選擇最合適的處理策略。刪除雖然簡單,但填補通常能更好地保留數據的完整性。
我們處理了「空格子」,但如果格子裡有東西,卻是錯誤的「類型」(例如,日期被存成文字)呢?明天,Day 20,我們將繼續資料清洗的旅程,學習如何轉換 Pandas 的資料類型!