Python數(shù)據(jù)清洗之缺失值處理,重復(fù)項去重與異常值檢測詳解
本節(jié)學(xué)習(xí)目標
- 理解缺失值的類型與成因
- 掌握缺失值的檢測、填充與刪除方法
- 學(xué)會識別和刪除重復(fù)數(shù)據(jù)
- 掌握 IQR(四分位距)法檢測異常值
- 建立數(shù)據(jù)清洗的標準化流程思維
為什么學(xué)這個?
在真實的數(shù)據(jù)分析工作中,有一句著名的格言:“Garbage in, garbage out”(垃圾進,垃圾出)。
無論你的模型多高級、分析多精巧,如果輸入的數(shù)據(jù)質(zhì)量有問題,得出的結(jié)論就一定是錯的。
舉個真實的例子:某電商公司的數(shù)據(jù)分析師在分析年度銷售額時,忘記處理缺失值,結(jié)果系統(tǒng)把缺失值(NaN)當成了 0 參與計算,最終報告顯示年度銷售額比實際少了 30%。這個錯誤直接導(dǎo)致公司在年末促銷預(yù)算上做出了錯誤的決策。
根據(jù)行業(yè)統(tǒng)計,數(shù)據(jù)科學(xué)家 80% 的時間都花在數(shù)據(jù)清洗上。掌握數(shù)據(jù)清洗技能,是每一個數(shù)據(jù)分析師的"基本功"。
數(shù)據(jù)清洗主要解決三大問題:
- 缺失值 —— 數(shù)據(jù)"缺了一塊"
- 重復(fù)值 —— 數(shù)據(jù)"多了一份"
- 異常值 —— 數(shù)據(jù)"出了圈"
核心知識點講解
缺失值(Missing Values)
1. 缺失值是什么?
在 Pandas 中,缺失值用 NaN(Not a Number)表示,它是 NumPy 定義的一個特殊浮點值。
常見的缺失值來源:
- 用戶填寫表單時漏填了某些字段
- 數(shù)據(jù)采集設(shè)備故障導(dǎo)致數(shù)據(jù)丟失
- 數(shù)據(jù)庫遷移過程中數(shù)據(jù)不完整
- 某些測量根本無法獲取(如"離職員工的離職后績效")
import pandas as pd
import numpy as np
# 創(chuàng)建包含缺失值的數(shù)據(jù)
df = pd.DataFrame({
'姓名': ['張三', '李四', '王五', '趙六', '孫七'],
'年齡': [25, np.nan, 28, 35, np.nan], # np.nan 表示缺失值
'工資': [15000.0, 20000.0, np.nan, 25000.0, 12000.0],
'城市': ['北京', '上海', None, '深圳', '杭州'], # None 也會被轉(zhuǎn)為 NaN
'部門': ['技術(shù)', '技術(shù)', '銷售', '管理', '技術(shù)']
})
print(df)
# 輸出:
# 姓名 年齡 工資 城市 部門
# 0 張三 25.0 15000.0 北京 技術(shù)
# 1 李四 NaN 20000.0 上海 技術(shù)
# 2 王五 28.0 NaN NaN 銷售
# 3 趙六 35.0 25000.0 深圳 管理
# 4 孫七 NaN 12000.0 杭州 技術(shù)
2. 檢測缺失值
# ===== isnull() 和 notnull() =====
# 檢測每個單元格是否為缺失值(返回布爾 DataFrame)
print(df.isnull())
# 輸出(True 表示缺失):
# 姓名 年齡 工資 城市 部門
# 0 False False False False False
# 1 False True False False False
# 2 False False True True False
# 3 False False False False False
# 4 False True False False False
# 檢測非缺失值
print(df.notnull()) # 與 isnull() 相反
# ===== 統(tǒng)計每列的缺失值數(shù)量(最常用的檢查命令)=====
print(df.isnull().sum())
# 輸出:
# 姓名 0
# 年齡 2
# 工資 1
# 城市 1
# 部門 0
# dtype: int64
# ===== 統(tǒng)計缺失值占比 =====
missing_pct = df.isnull().mean() * 100
print("缺失值占比:")
print(missing_pct)
# 輸出:
# 姓名 0.0
# 年齡 40.0
# 工資 20.0
# 城市 20.0
# 部門 0.0
# dtype: float64
# ===== 查看哪些行有缺失值 =====
rows_with_na = df[df.isnull().any(axis=1)]
print("有缺失值的行:")
print(rows_with_na)
# ===== 查看完整數(shù)據(jù)(無任何缺失的行)=====
complete_rows = df.dropna()
print("完整數(shù)據(jù)行:")
print(complete_rows)
3. 缺失值處理策略
處理缺失值有兩大類策略:刪除法和填充法。
策略一:刪除法(dropna)
# 創(chuàng)建示例數(shù)據(jù)
df = pd.DataFrame({
'A': [1, 2, np.nan, 4, 5],
'B': [np.nan, 2, 3, np.nan, 5],
'C': [1, 2, 3, 4, 5]
})
# 默認:刪除包含任何缺失值的行
print(df.dropna())
# 只保留第 1 行和第 4 行(索引 1 和 4)
# how='all':只刪除全部為缺失值的行
df2 = pd.DataFrame({
'A': [1, np.nan, np.nan],
'B': [2, np.nan, 3]
})
print(df2.dropna(how='all')) # 不會刪除任何行
# thresh=N:保留至少有 N 個非缺失值的行
print(df.dropna(thresh=2)) # 保留至少有 2 個非缺失值的行
# subset=:只檢查指定列的缺失值
df3 = pd.DataFrame({
'姓名': ['張三', '李四', '王五'],
'年齡': [25, np.nan, 28],
'備注': [np.nan, np.nan, '好學(xué)生']
})
print(df3.dropna(subset=['年齡'])) # 只看"年齡"列,刪除李四
# axis=1:刪除包含缺失值的列(縱向刪除)
print(df.dropna(axis=1)) # 刪除 B 列(因為它有缺失值)
策略二:填充法(fillna)
df = pd.DataFrame({
'姓名': ['張三', '李四', '王五', '趙六', '孫七'],
'年齡': [25, np.nan, 28, np.nan, 22],
'工資': [15000.0, 20000.0, np.nan, 25000.0, np.nan]
})
# ===== 用固定值填充 =====
print(df.fillna(0)) # 所有缺失值填 0
print(df.fillna('未知')) # 所有缺失值填"未知"
# ===== 用統(tǒng)計值填充 =====
# 用均值填充(數(shù)值列)
df_filled_mean = df.fillna(df.mean(numeric_only=True))
print("均值填充:")
print(df_filled_mean)
# 用中位數(shù)填充(更穩(wěn)健,不受極端值影響)
df_filled_median = df.fillna(df.median(numeric_only=True))
print("\n中位數(shù)填充:")
print(df_filled_median)
# 用眾數(shù)填充(適合分類數(shù)據(jù))
df_filled_mode = df.fillna(df.mode().iloc[0])
print("\n眾數(shù)填充:")
print(df_filled_mode)
# ===== 前向填充和后向填充 =====
df_time = pd.DataFrame({
'日期': pd.date_range('2024-01-01', periods=5),
'溫度': [20.0, np.nan, np.nan, 25.0, np.nan]
})
# ffill(forward fill):用前一個值填充
print(df_time['溫度'].ffill())
# 輸出:[20.0, 20.0, 20.0, 25.0, 25.0]
# bfill(backward fill):用后一個值填充
print(df_time['溫度'].bfill())
# 輸出:[20.0, 25.0, 25.0, 25.0, NaN]
# ===== 按列用不同方式填充 =====
fill_values = {'年齡': df['年齡'].median(), '工資': df['工資'].mean()}
df_filled = df.fillna(fill_values)
print("按列不同方式填充:")
print(df_filled)
# ===== inplace=True:原地修改 =====
# df.fillna(0, inplace=True) # 直接修改原 DataFrame
4. 缺失值處理的選擇指南
| 缺失比例 | 建議策略 | 說明 |
|---|---|---|
| < 5% | 直接刪除 | 影響微乎其微 |
| 5% ~ 30% | 填充(均值/中位數(shù)/眾數(shù)/插值) | 保留樣本量 |
| > 30% | 刪除該列或深入研究 | 數(shù)據(jù)可能不可靠 |
重復(fù)值處理
1. 檢測重復(fù)值
# 創(chuàng)建包含重復(fù)值的數(shù)據(jù)
df = pd.DataFrame({
'訂單號': ['ORD001', 'ORD002', 'ORD003', 'ORD002', 'ORD004'],
'客戶': ['張三', '李四', '王五', '李四', '趙六'],
'金額': [500, 300, 800, 300, 200]
})
print("原始數(shù)據(jù):")
print(df)
# 輸出:
# 訂單號 客戶 金額
# 0 ORD001 張三 500
# 1 ORD002 李四 300
# 2 ORD003 王五 800
# 3 ORD002 李四 300 ← 與第1行完全相同
# 4 ORD004 趙六 200
# ===== duplicated() —— 檢測重復(fù)行 =====
print(df.duplicated())
# 輸出:[False, False, False, True, False]
# 第 4 行(索引3)被標記為 True,因為它是重復(fù)的
# 檢測哪些列組合是重復(fù)的
print(df.duplicated(subset=['訂單號']))
# 只看訂單號列,ORD002 出現(xiàn)兩次,第二次標記為 True
# ===== 統(tǒng)計重復(fù)行數(shù)量 =====
print(f"重復(fù)行數(shù):{df.duplicated().sum()}") # 1
# ===== 查看所有重復(fù)行 =====
print(df[df.duplicated(keep=False)])
# keep=False 會標記所有重復(fù)的行(而不僅是后面的)
2. 刪除重復(fù)值
# ===== drop_duplicates() —— 刪除重復(fù)行 =====
# 默認:刪除完全相同的行,保留第一條
df_clean = df.drop_duplicates()
print("刪除完全重復(fù)的行:")
print(df_clean)
# keep='last':保留最后一條
df_clean2 = df.drop_duplicates(keep='last')
print("\n保留最后一條:")
print(df_clean2)
# keep=False:刪除所有重復(fù)行(一條都不保留)
df_clean3 = df.drop_duplicates(keep=False)
print("\n全部刪除:")
print(df_clean3)
# subset=:只根據(jù)指定列判斷重復(fù)
df2 = pd.DataFrame({
'訂單號': ['ORD001', 'ORD002', 'ORD003', 'ORD004'],
'客戶': ['張三', '李四', '張三', '趙六'],
'金額': [500, 300, 600, 200] # 金額不同
})
# 只看"客戶"列去重
df_unique = df2.drop_duplicates(subset=['客戶'])
print("按客戶去重:")
print(df_unique) # 張三只保留第一條
# inplace=True 原地修改
# df.drop_duplicates(inplace=True)
異常值(Outliers)檢測與處理
1. 什么是異常值?
異常值是指顯著偏離其他觀測值的數(shù)據(jù)點。
舉例:一個班級的學(xué)生年齡大多是 18~22 歲,但有一條記錄是 85 歲。這可能是數(shù)據(jù)錄入錯誤(實際應(yīng)該是 18 歲),也可能是真實情況(確實有一位 85 歲的學(xué)生)。
異常值不一定是"錯誤數(shù)據(jù)",但它值得你特別關(guān)注。
2. IQR(四分位距)法檢測異常值
IQR 法是最常用的異常值檢測方法,它基于四分位數(shù)。
原理:
- Q1(第一四分位數(shù)):25% 的數(shù)據(jù)小于此值
- Q3(第三四分位數(shù)):75% 的數(shù)據(jù)小于此值
- IQR = Q3 - Q1
- 下界 = Q1 - 1.5 × IQR
- 上界 = Q3 + 1.5 × IQR
- 超出上下界的數(shù)據(jù)即為異常值
# 創(chuàng)建示例數(shù)據(jù)
np.random.seed(42)
salaries = np.concatenate([
np.random.normal(15000, 3000, 100), # 正常工資分布
[50000, 60000, -5000] # 異常值
])
df = pd.DataFrame({'工資': salaries})
# ===== IQR 法 =====
Q1 = df['工資'].quantile(0.25)
Q3 = df['工資'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
print(f"Q1 = {Q1:.2f}")
print(f"Q3 = {Q3:.2f}")
print(f"IQR = {IQR:.2f}")
print(f"下界 = {lower_bound:.2f}")
print(f"上界 = {upper_bound:.2f}")
# 找出異常值
outliers = df[(df['工資'] < lower_bound) | (df['工資'] > upper_bound)]
print(f"\n異常值數(shù)量:{len(outliers)}")
print("異常值:")
print(outliers)
# ===== 處理異常值 =====
# 方法一:刪除異常值
df_clean = df[(df['工資'] >= lower_bound) & (df['工資'] <= upper_bound)]
print(f"刪除后數(shù)據(jù)量:{len(df_clean)}")
# 方法二:用邊界值替換(Winsorization/蓋帽法)
df_capped = df.copy()
df_capped.loc[df_capped['工資'] > upper_bound, '工資'] = upper_bound
df_capped.loc[df_capped['工資'] < lower_bound, '工資'] = lower_bound
print(f"蓋帽后最大值:{df_capped['工資'].max():.2f}")
print(f"蓋帽后最小值:{df_capped['工資'].min():.2f}")
# 方法三:用中位數(shù)替換
df_median = df.copy()
df_median.loc[
(df_median['工資'] < lower_bound) | (df_median['工資'] > upper_bound),
'工資'
] = df_median['工資'].median()
3. Z-Score 法(標準差法)
Z-Score 法適用于近似正態(tài)分布的數(shù)據(jù)。
# Z-Score = (x - 均值) / 標準差
# |Z| > 3 的數(shù)據(jù)通常被視為異常值
from scipy import stats
# 計算 Z-Score
z_scores = np.abs(stats.zscore(df['工資']))
# 找出異常值
outlier_mask = z_scores > 3
outliers_z = df[outlier_mask]
print(f"Z-Score 法檢測到的異常值:{len(outliers_z)} 個")
print(outliers_z)
4. 異常值可視化(箱線圖)
import matplotlib.pyplot as plt
# 箱線圖是觀察異常值最直觀的方式
plt.figure(figsize=(8, 6))
df['工資'].plot(kind='box')
plt.title('工資分布箱線圖')
plt.ylabel('工資')
plt.grid(True, alpha=0.3)
plt.show()
# 箱線圖中,超出"須線"的點就是潛在的異常值
數(shù)據(jù)清洗的標準化流程
在實際工作中,建議按照以下流程進行數(shù)據(jù)清洗:
def data_cleaning_pipeline(df):
"""
標準數(shù)據(jù)清洗流程
"""
print("=" * 50)
print("數(shù)據(jù)清洗報告")
print("=" * 50)
# 第1步:查看數(shù)據(jù)概況
print(f"\n1. 數(shù)據(jù)形狀:{df.shape}")
print(f" 列名:{list(df.columns)}")
# 第2步:檢查缺失值
missing = df.isnull().sum()
missing_pct = (missing / len(df)) * 100
missing_summary = pd.DataFrame({
'缺失數(shù)量': missing,
'缺失比例(%)': missing_pct.round(2)
})
print("\n2. 缺失值統(tǒng)計:")
print(missing_summary[missing_summary['缺失數(shù)量'] > 0])
# 第3步:檢查重復(fù)值
dup_count = df.duplicated().sum()
print(f"\n3. 重復(fù)行數(shù):{dup_count}")
# 第4步:數(shù)值列基本統(tǒng)計
print("\n4. 數(shù)值列統(tǒng)計摘要:")
print(df.describe())
# 第5步:數(shù)據(jù)類型
print("\n5. 數(shù)據(jù)類型:")
print(df.dtypes)
print("\n" + "=" * 50)
print("檢查完成,請根據(jù)報告決定清洗策略")
print("=" * 50)
return df
# 使用示例
df = pd.DataFrame({
'姓名': ['張三', '李四', '王五', '趙六', '孫七', '張三'], # 張三重復(fù)
'年齡': [25, np.nan, 28, 35, np.nan, 25],
'工資': [15000.0, 20000.0, np.nan, 25000.0, 12000.0, 15000.0],
'部門': ['技術(shù)', '技術(shù)', '銷售', '管理', '技術(shù)', '技術(shù)']
})
data_cleaning_pipeline(df)
實戰(zhàn)練習(xí)
練習(xí) 1:缺失值處理實戰(zhàn)
題目:處理以下包含缺失值的學(xué)生成績數(shù)據(jù)。
# 參考答案
import pandas as pd
import numpy as np
df = pd.DataFrame({
'學(xué)號': [1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 1010],
'姓名': ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'],
'語文': [85, np.nan, 78, 92, np.nan, 88, 76, np.nan, 91, 84],
'數(shù)學(xué)': [90, 85, np.nan, 88, 92, np.nan, 78, 95, np.nan, 86],
'英語': [78, 92, 85, np.nan, 88, 76, np.nan, 82, 95, np.nan]
})
# 1. 檢查缺失值
print("缺失值統(tǒng)計:")
print(df.isnull().sum())
# 2. 各科用中位數(shù)填充
for col in ['語文', '數(shù)學(xué)', '英語']:
df[col] = df[col].fillna(df[col].median())
print("\n填充后數(shù)據(jù):")
print(df)
# 3. 驗證填充結(jié)果
print("\n驗證:還有缺失值嗎?", df.isnull().sum().sum() == 0)
練習(xí) 2:去重與異常值檢測
題目:以下是一份電商交易數(shù)據(jù),包含重復(fù)記錄和異常價格。
# 參考答案
import pandas as pd
import numpy as np
df = pd.DataFrame({
'訂單號': ['A001', 'A002', 'A003', 'A002', 'A004', 'A005', 'A005', 'A006'],
'商品': ['手機', '電腦', '平板', '電腦', '耳機', '手表', '手表', '充電器'],
'價格': [3999, 5999, 2999, 5999, 299, 15000, 15000, -50],
'數(shù)量': [1, 1, 2, 1, 3, 1, 1, 5]
})
# 1. 去除完全重復(fù)的行
print("原始行數(shù):", len(df))
df_clean = df.drop_duplicates()
print("去重后行數(shù):", len(df_clean))
# 2. 檢測異常價格
Q1 = df_clean['價格'].quantile(0.25)
Q3 = df_clean['價格'].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
print(f"\n價格正常范圍:[{lower:.0f}, {upper:.0f}]")
abnormal = df_clean[(df_clean['價格'] < lower) | (df_clean['價格'] > upper)]
print("異常價格記錄:")
print(abnormal)
# 3. 修正異常值:負價格改為絕對值,過高價格用中位數(shù)替換
df_clean = df_clean.copy()
df_clean.loc[df_clean['價格'] < 0, '價格'] = df_clean.loc[df_clean['價格'] < 0, '價格'].abs()
median_price = df_clean['價格'].median()
df_clean.loc[
(df_clean['價格'] < lower) | (df_clean['價格'] > upper),
'價格'
] = median_price
print("\n修正后數(shù)據(jù):")
print(df_clean)
練習(xí) 3:完整清洗流程
題目:對以下"臟"數(shù)據(jù)進行完整清洗。
# 參考答案
import pandas as pd
import numpy as np
# 模擬臟數(shù)據(jù)
df = pd.DataFrame({
'ID': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 2], # ID=2 重復(fù)
'年齡': [25, 30, 28, 150, 22, np.nan, 35, 27, -5, 29, 30], # 150和-5是異常值
'收入': [5000, 8000, np.nan, 12000, 6000, 7000, np.nan, 9000, 5500, 7500, 8000],
'性別': ['男', '女', '男', '女', 'M', '男', '女', np.nan, '男', '女', '女'],
'城市': ['北京', '上海', '北京', '深圳', '上海', np.nan, '上海', '北京', '深圳', '上海', '上海']
})
print("=== 清洗前 ===")
print(f"數(shù)據(jù)量:{df.shape}")
print(df.describe())
# 第1步:去重
df = df.drop_duplicates()
# 第2步:處理年齡異常值
df = df[(df['年齡'] > 0) & (df['年齡'] < 100)]
# 第3步:收入填充中位數(shù)
df['收入'] = df['收入'].fillna(df['收入'].median())
# 第4步:年齡填充中位數(shù)
df['年齡'] = df['年齡'].fillna(df['年齡'].median())
# 第5步:性別標準化
df['性別'] = df['性別'].replace({'M': '男'})
df['性別'] = df['性別'].fillna('未知')
# 第6步:城市填充眾數(shù)
df['城市'] = df['城市'].fillna(df['城市'].mode()[0])
print("\n=== 清洗后 ===")
print(f"數(shù)據(jù)量:{df.shape}")
print(df)
print("\n缺失值:", df.isnull().sum().sum())
本節(jié)總結(jié)
本節(jié)我們學(xué)習(xí)了數(shù)據(jù)清洗的三大核心任務(wù):
缺失值處理:
- 檢測方法:
isnull()、isnull().sum() - 刪除:
dropna()(注意how、thresh、subset參數(shù)) - 填充:
fillna()(均值/中位數(shù)/眾數(shù)/前向/后向填充)
重復(fù)值處理:
- 檢測:
duplicated() - 刪除:
drop_duplicates()(注意keep、subset參數(shù))
異常值檢測:
- IQR 法:基于四分位距,適用范圍廣
- Z-Score 法:基于標準差,適合正態(tài)分布
- 處理方式:刪除、蓋帽法(Winsorization)、中位數(shù)替換
到此這篇關(guān)于Python數(shù)據(jù)清洗之缺失值處理,重復(fù)項去重與異常值檢測詳解的文章就介紹到這了,更多相關(guān)Python數(shù)據(jù)清洗內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Python數(shù)據(jù)自動化處理之數(shù)據(jù)清洗和報表生成詳解
- Python數(shù)據(jù)清洗的四大核心操作(去重/標準化/歸一化/編碼)實戰(zhàn)指南
- Python批量清洗Excel數(shù)據(jù)的操作指南(去重+補缺失值+可視化)
- 使用Python實現(xiàn)處理和清洗CSV格式的數(shù)據(jù)文件
- Python數(shù)據(jù)分析必備的12種數(shù)據(jù)清洗技術(shù)分享
- Python中數(shù)據(jù)清洗與預(yù)處理技巧分享
- 使用Python處理Excel文件的全面指南(從讀取到數(shù)據(jù)清洗)
- Python數(shù)據(jù)分析與可視化的全面指南(從數(shù)據(jù)清洗到圖表呈現(xiàn))
相關(guān)文章
python標準算法實現(xiàn)數(shù)組全排列的方法
這篇文章主要介紹了python標準算法實現(xiàn)數(shù)組全排列的方法,實例分析了全排列的原理與Python實現(xiàn)技巧,需要的朋友可以參考下2015-03-03

