WeHelp
Pandas 是 Python 用來做資料分析和處理的熱門工具,也是資料分析師的必備技能。
  1. 簡介、安裝、快速開始
  2. Series 單維度資料
  3. Series 實務案例
  4. DataFrame 雙維度資料
  5. DataFrame 實務案例
  6. 過濾資料的範例說明
  7. 缺失填補的範例說明
  8. 排序資料的範例說明
  9. CSV、Excel 檔案存取
  10. JSON 格式檔案存取
缺失填補的範例說明
# Pandas 填補缺失資料範例與技巧 在 Pandas 中,缺失資料通常會以以下形式出現: - `NaN` - `None` - `NaT`:日期時間欄位的缺失值 - `pd.NA` 常用函式包括: ```python isna() notna() fillna() dropna() interpolate() ffill() bfill() ``` --- ## 1. 建立範例資料 ```python import pandas as pd import numpy as np df = pd.DataFrame({ "姓名": ["小明", "小華", "小美", "小強"], "年齡": [20, np.nan, 22, None], "城市": ["台北", "台中", None, "台南"], "分數": [80, 90, np.nan, 70] }) print(df) ``` 輸出: ```text 姓名 年齡 城市 分數 0 小明 20.0 台北 80.0 1 小華 NaN 台中 90.0 2 小美 22.0 None NaN 3 小強 NaN 台南 70.0 ``` --- # 2. 查看缺失資料 ## 判斷每個儲存格是否缺失 ```python df.isna() ``` 結果會是布林值: ```text 姓名 年齡 城市 分數 0 False False False False 1 False True False False 2 False False True True 3 False True False False ``` `isnull()` 和 `isna()` 功能相同: ```python df.isnull() ``` --- ## 計算每個欄位的缺失數量 ```python df.isna().sum() ``` 輸出: ```text 姓名 0 年齡 2 城市 1 分數 1 dtype: int64 ``` --- ## 計算每個欄位的缺失比例 ```python missing_ratio = df.isna().mean() print(missing_ratio) ``` 例如: ```text 姓名 0.00 年齡 0.50 城市 0.25 分數 0.25 dtype: float64 ``` 若要以百分比呈現: ```python print(df.isna().mean() * 100) ``` --- # 3. 使用固定值填補 ## 整個 DataFrame 使用同一個值 ```python df_filled = df.fillna(0) ``` 所有缺失值都會被替換為 `0`。 但這種方式不一定適合所有欄位,例如: - 年齡填 `0` 可能合理或不合理 - 城市填 `0` 通常不合理 - 分數填 `0` 可能代表真的考零分,也可能代表資料缺失 因此通常會針對不同欄位指定不同值。 --- ## 各欄位使用不同填充值 ```python df_filled = df.fillna({ "年齡": df["年齡"].median(), "城市": "未知", "分數": df["分數"].mean() }) print(df_filled) ``` 此範例的做法: - `年齡`:使用中位數填補 - `城市`:使用文字 `"未知"` - `分數`:使用平均數填補 --- # 4. 使用平均數、中位數或眾數填補 ## 平均數 Mean ```python df["分數"] = df["分數"].fillna(df["分數"].mean()) ``` 平均數適合: - 數值資料 - 分布沒有太多極端值的情況 例如分數: ```python mean_score = df["分數"].mean() df["分數"] = df["分數"].fillna(mean_score) ``` ### 注意 如果資料中有極端值,平均數可能會被拉高或拉低。 ```python [50, 60, 70, 1000] ``` 此時平均數通常不太能代表一般資料。 --- ## 中位數 Median ```python df["年齡"] = df["年齡"].fillna(df["年齡"].median()) ``` 中位數對極端值比較不敏感,通常適合: - 收入 - 房價 - 年齡 - 交易金額 - 有明顯偏態分布的數值欄位 例如: ```python [30000, 35000, 40000, 1000000] ``` 此時使用中位數通常比平均數穩定。 --- ## 眾數 Mode 文字欄位常使用眾數填補: ```python most_common_city = df["城市"].mode()[0] df["城市"] = df["城市"].fillna(most_common_city) ``` 也可以直接寫: ```python df["城市"] = df["城市"].fillna(df["城市"].mode()[0]) ``` 眾數代表出現次數最多的值,常用於: - 城市 - 類別 - 性別 - 商品種類 - 是否通過等欄位 --- # 5. 前一筆與後一筆資料填補 ## 使用前一筆資料:`ffill` ```python df["分數"] = df["分數"].ffill() ``` 若資料如下: ```text 80 90 NaN 70 ``` 使用 `ffill()` 後: ```text 80 90 90 70 ``` 也可以寫成: ```python df["分數"].fillna(method="ffill") ``` 不過新版 Pandas 建議直接使用: ```python df["分數"].ffill() ``` --- ## 使用下一筆資料:`bfill` ```python df["分數"] = df["分數"].bfill() ``` 結果: ```text 80 90 70 70 ``` `bfill()` 適合使用下一筆已知資料填補前面的缺失值。 --- ## 限制連續填補筆數 假設: ```python s = pd.Series([10, np.nan, np.nan, np.nan, 20]) ``` 只填補最多一筆: ```python s.ffill(limit=1) ``` 結果: ```text 0 10.0 1 10.0 2 NaN 3 NaN 4 20.0 dtype: float64 ``` 這可以避免長時間缺失資料被不合理地全部延用。 --- # 6. 線性插值 `interpolate()` 對具有順序性的數值資料,可以使用插值法。 ```python s = pd.Series([10, np.nan, np.nan, 40]) print(s.interpolate()) ``` 結果: ```text 0 10.0 1 20.0 2 30.0 3 40.0 dtype: float64 ``` Pandas 會在 `10` 和 `40` 之間平均推估: - 第一個缺失值:20 - 第二個缺失值:30 --- ## 應用於 DataFrame ```python df["分數"] = df["分數"].interpolate() ``` 適合的情境: - 溫度紀錄 - 感測器資料 - 股價 - 時間序列 - 連續測量值 不適合用於: - 城市 - 姓名 - 類別 - 性別 - 商品類型 --- ## 使用時間索引進行插值 ```python dates = pd.date_range("2024-01-01", periods=5) temperature = pd.Series( [20, np.nan, 24, np.nan, 28], index=dates ) temperature_filled = temperature.interpolate(method="time") print(temperature_filled) ``` `method="time"` 會根據時間間隔進行插值,適合日期間隔不完全相同的資料。 --- # 7. 依群組填補缺失值 假設每個城市的資料應該使用該城市自己的平均數填補: ```python df = pd.DataFrame({ "城市": ["台北", "台北", "台中", "台中"], "分數": [80, np.nan, 70, np.nan] }) df["分數"] = df["分數"].fillna( df.groupby("城市")["分數"].transform("mean") ) print(df) ``` 結果: ```text 城市 分數 0 台北 80.0 1 台北 80.0 2 台中 70.0 3 台中 70.0 ``` 這比直接使用全體平均數更合理,因為不同群組可能有不同的分布。 --- ## 依群組使用中位數 ```python df["分數"] = df["分數"].fillna( df.groupby("城市")["分數"].transform("median") ) ``` 使用 `transform()` 的好處是,結果會保留原本 DataFrame 的索引與列數,能直接指定回欄位。 --- # 8. 時間序列的缺失值填補 先建立時間序列: ```python df = pd.DataFrame({ "日期": pd.to_datetime([ "2024-01-01", "2024-01-02", "2024-01-03", "2024-01-04" ]), "銷售額": [100, np.nan, 140, 160] }) df = df.set_index("日期") ``` ## 使用前後值插值 ```python df["銷售額"] = df["銷售額"].interpolate() ``` ## 使用前一日資料 ```python df["銷售額"] = df["銷售額"].ffill() ``` ## 使用後一日資料 ```python df["銷售額"] = df["銷售額"].bfill() ``` 一般而言: - 感測器連續數值:常用 `interpolate()` - 狀態或標籤:可考慮 `ffill()` - 預測資料:需小心避免使用未來資訊 --- # 9. 依列或依欄填補 `fillna()` 預設是針對每個欄位處理,也可以指定方向。 ## 依欄位填補 ```python df.fillna(0, axis="columns") ``` ## 依列填補 ```python df.fillna(0, axis="rows") ``` 不過實務上最常見的做法是直接對指定欄位操作,例如: ```python df["年齡"] = df["年齡"].fillna(df["年齡"].median()) ``` 這樣比較清楚,也比較不容易誤填其他欄位。 --- # 10. 直接刪除缺失資料 有時候不適合填補,可以選擇刪除。 ## 刪除包含任何缺失值的列 ```python df_clean = df.dropna() ``` ## 只有整列全部都是缺失值才刪除 ```python df_clean = df.dropna(how="all") ``` ## 指定欄位缺失時才刪除 ```python df_clean = df.dropna(subset=["年齡", "分數"]) ``` ## 保留至少有兩個非缺失值的列 ```python df_clean = df.dropna(thresh=2) ``` `thresh=2` 表示該列至少要有兩個非缺失值。 --- # 11. 缺失值過多的欄位 如果某個欄位缺失比例很高,填補可能沒有意義。 例如刪除缺失比例超過 50% 的欄位: ```python threshold = 0.5 df = df.loc[:, df.isna().mean() <= threshold] ``` 也可以計算缺失比例後自行判斷: ```python missing_ratio = df.isna().mean() print(missing_ratio[missing_ratio > 0.5]) ``` 通常需要考慮: - 該欄位是否重要 - 缺失是否具有特殊意義 - 是否有替代欄位 - 填補後是否會造成偏誤 --- # 12. 將特殊文字轉換成缺失值 有些資料不會使用 `NaN`,而是使用: - `""` - `"N/A"` - `"NA"` - `"未知"` - `"-"` - `"null"` 讀取 CSV 時可以指定: ```python df = pd.read_csv( "data.csv", na_values=["", "N/A", "NA", "-", "null"] ) ``` 如果資料已經讀入,也可以替換: ```python df = df.replace(["N/A", "NA", "-", "null"], np.nan) ``` 對空字串處理: ```python df = df.replace(r"^\s*$", np.nan, regex=True) ``` 這可將空字串或只包含空白的內容轉為 `NaN`。 --- # 13. 使用 `pd.NA` 與資料型別 Pandas 中的缺失值可能影響資料型別: ```python s = pd.Series([1, 2, None]) print(s.dtype) ``` 通常會轉成: ```text float64 ``` 因為傳統 NumPy 的整數型別無法容納 `NaN`。 可以使用 Pandas 的可空整數型別: ```python s = pd.Series([1, 2, None], dtype="Int64") print(s) ``` 結果: ```text 0 1 1 2 2 <NA> dtype: Int64 ``` 其他可空型別包括: ```python "Int64" "boolean" "string" ``` 例如: ```python df["年齡"] = df["年齡"].astype("Int64") ``` --- # 14. 避免資料洩漏:訓練集與測試集分開處理 在機器學習中,不應該先把訓練集和測試集合併後再計算平均數或中位數,否則會造成資料洩漏。 錯誤概念: ```python all_data = pd.concat([train, test]) median = all_data["年齡"].median() ``` 正確做法: ```python median_age = train["年齡"].median() train["年齡"] = train["年齡"].fillna(median_age) test["年齡"] = test["年齡"].fillna(median_age) ``` 填補值應該只從訓練資料計算,再套用到測試資料。 使用 Scikit-learn 時,可以使用: ```python from sklearn.impute import SimpleImputer imputer = SimpleImputer(strategy="median") train[["年齡"]] = imputer.fit_transform(train[["年齡"]]) test[["年齡"]] = imputer.transform(test[["年齡"]]) ``` 重點是: - 訓練集使用 `fit_transform()` - 測試集只使用 `transform()` --- # 15. 常見實務範例 ## 數值欄位使用中位數,類別欄位使用眾數 ```python numeric_cols = ["年齡", "分數"] category_cols = ["城市"] for col in numeric_cols: df[col] = df[col].fillna(df[col].median()) for col in category_cols: df[col] = df[col].fillna(df[col].mode()[0]) ``` --- ## 自訂缺失值標籤 ```python df["城市"] = df["城市"].fillna("未提供") ``` 如果缺失本身具有意義,使用明確文字通常比填入眾數更好。 例如: - 沒有填寫城市:`"未提供"` - 沒有購買紀錄:`"無購買紀錄"` - 尚未評分:`"尚未評分"` --- ## 填補完成後檢查 ```python print(df.isna().sum()) ``` 若想確認整個 DataFrame 是否仍有缺失值: ```python print(df.isna().any().any()) ``` 結果: - `True`:仍存在缺失值 - `False`:沒有缺失值 也可以查看缺失位置: ```python print(df[df.isna().any(axis=1)]) ``` --- # 16. 如何選擇填補方法? | 資料類型或情境 | 建議方法 | |---|---| | 一般數值欄位,分布穩定 | 平均數 | | 有極端值的數值欄位 | 中位數 | | 類別欄位 | 眾數或自訂類別 | | 缺失具有特殊意義 | `"未知"`、`"未提供"` 等標籤 | | 連續時間序列 | `interpolate()` | | 狀態延續的時間資料 | `ffill()` | | 要使用後續觀測值 | `bfill()` | | 缺失比例非常高 | 考慮刪除欄位 | | 關鍵欄位缺失且無法合理推估 | `dropna()` | | 不同群組有不同分布 | `groupby()` 後分組填補 | --- # 17. 一個完整的小範例 ```python import pandas as pd import numpy as np df = pd.DataFrame({ "部門": ["A", "A", "B", "B", "B"], "年齡": [25, np.nan, 30, 35, np.nan], "薪資": [40000, 42000, np.nan, 50000, np.nan], "職位": ["工程師", None, "主管", "工程師", None] }) # 數值欄位:使用各部門中位數填補 df["年齡"] = df["年齡"].fillna( df.groupby("部門")["年齡"].transform("median") ) df["薪資"] = df["薪資"].fillna( df.groupby("部門")["薪資"].transform("median") ) # 類別欄位:使用眾數填補 df["職位"] = df["職位"].fillna(df["職位"].mode()[0]) print(df) print("\n缺失值統計:") print(df.isna().sum()) ``` --- # 18. 使用 `inplace=True` 的注意事項 以下寫法可以直接修改原始資料: ```python df["年齡"].fillna(df["年齡"].median(), inplace=True) ``` 但在部分 Pandas 版本中,對欄位切片使用 `inplace=True` 可能產生警告或不如預期。較推薦明確重新指定: ```python df["年齡"] = df["年齡"].fillna(df["年齡"].median()) ``` 這種寫法: - 可讀性較高 - 較不容易遇到 chained assignment 問題 - 在不同 Pandas 版本中通常更穩定 --- ## 總結 Pandas 填補缺失資料時,不應只是一律使用 `0` 或平均數,而應根據資料特性選擇方法: 1. 先用 `isna().sum()` 了解缺失狀況。 2. 數值資料可考慮平均數、中位數或插值。 3. 類別資料可使用眾數或明確的缺失標籤。 4. 時間序列可使用 `ffill()`、`bfill()` 或 `interpolate()`。 5. 不同群組的資料,最好分組後分別填補。 6. 缺失比例過高時,應評估刪除欄位。 7. 機器學習情境中,填補統計值應只從訓練集計算。
相關學習地圖、教學課程
Python 資料工程
從 0 開始,成為資料工程師的學習路徑。