Pandas數(shù)據(jù)清洗的8大核心技巧完整指南
數(shù)據(jù)清洗是數(shù)據(jù)分析中耗時(shí)最長(zhǎng)的環(huán)節(jié),據(jù)統(tǒng)計(jì)占整個(gè)數(shù)據(jù)分析工作量的60%-80%。本文系統(tǒng)整理了pandas數(shù)據(jù)清洗的8大核心技巧,每個(gè)知識(shí)點(diǎn)都配有完整的代碼示例和詳細(xì)注釋,適合初中級(jí)數(shù)據(jù)分析師收藏學(xué)習(xí)。
一、環(huán)境準(zhǔn)備
# ============================================================
# pandas數(shù)據(jù)清洗完整指南 - 環(huán)境準(zhǔn)備
# 公主號(hào):船長(zhǎng)Talk(更多數(shù)據(jù)分析干貨,關(guān)注公主號(hào))
# ============================================================
import pandas as pd
import numpy as np
# 設(shè)置pandas顯示選項(xiàng),方便查看數(shù)據(jù)
pd.set_option('display.max_columns', None) # 顯示所有列
pd.set_option('display.max_rows', 50) # 最多顯示50行
pd.set_option('display.float_format', lambda x: '%.2f' % x) # 浮點(diǎn)數(shù)保留2位小數(shù)
print("pandas版本:", pd.__version__)
print("numpy版本:", np.__version__)二、構(gòu)造測(cè)試數(shù)據(jù)集
我們先構(gòu)造一個(gè)包含各種"臟數(shù)據(jù)"的真實(shí)數(shù)據(jù)集,模擬電商用戶行為數(shù)據(jù):
# ============================================================
# 構(gòu)造臟數(shù)據(jù)集 —— 模擬電商用戶行為數(shù)據(jù)
# 特意制造各種數(shù)據(jù)質(zhì)量問(wèn)題,便于演示清洗方法
# ============================================================
data = {
'user_id': [1001, 1002, 1003, 1004, 1005, 1002, 1007, 1008, 1009, 1010],
'username': ['張三', '李四', ' 王五 ', 'ZHAO6', '田七', '李四', None, '周九', '吳十', ''],
'age': [25, 200, 28, -1, 32, 200, 29, None, 31, 26],
'gender': ['男', '女', '男', '未知', '女', '女', '男', '男', '未知', '女'],
'city': ['北京', '上海', '廣州', '深圳', '杭州', '上海', '成都', None, '武漢', '南京'],
'purchase_amt': [288.5, 1500.0, 66.0, 999.0, 0, 1500.0, 350.0, 88.0, None, 122.5],
'order_date': ['2024-01-15', '2024-01-16', '2024/01/17', '20240118', '2024-01-19',
'2024-01-16', '2024-01-21', '2024-01-22', '2024-01-23', 'invalid_date'],
'category': ['電子', '服裝', '食品', '電子', '美妝', '服裝', '電子', '食品', '服裝', '家居'],
'score': [4.5, 3.8, 5.0, 2.1, 4.2, 3.8, None, 4.0, 3.5, 4.8],
}
df = pd.DataFrame(data)
print("原始數(shù)據(jù)形狀:", df.shape)
print("\n原始數(shù)據(jù):")
print(df)
print("\n數(shù)據(jù)類型:")
print(df.dtypes)輸出結(jié)果(數(shù)據(jù)概覽):
原始數(shù)據(jù)形狀: (10, 9)
user_id username age gender city purchase_amt order_date category score
0 1001 張三 25 男 北京 288.50 2024-01-15 電子 4.5
1 1002 李四 200 女 上海 1500.00 2024-01-16 服裝 3.8
2 1003 王五 28 男 廣州 66.00 2024/01/17 食品 5.0
3 1004 ZHAO6 -1 未知 深圳 999.00 20240118 電子 2.1
...
三、核心技巧1:缺失值處理
缺失值是最常見(jiàn)的數(shù)據(jù)質(zhì)量問(wèn)題,處理方式根據(jù)業(yè)務(wù)場(chǎng)景不同而異。
# ============================================================
# 技巧1:缺失值檢測(cè)與處理
# 公主號(hào):船長(zhǎng)Talk
# ============================================================
# --- 1.1 檢測(cè)缺失值 ---
# 查看每列缺失數(shù)量和比例
missing_info = pd.DataFrame({
'缺失數(shù)量': df.isnull().sum(),
'缺失比例': (df.isnull().sum() / len(df) * 100).round(2),
'非空數(shù)量': df.notnull().sum()
})
print("缺失值統(tǒng)計(jì):")
print(missing_info[missing_info['缺失數(shù)量'] > 0]) # 只顯示有缺失的列
# 快速查看:哪些行有缺失值
rows_with_na = df[df.isnull().any(axis=1)]
print(f"\n含缺失值的行數(shù):{len(rows_with_na)}")
# --- 1.2 刪除缺失值 ---
# 刪除缺失值比例超過(guò)50%的行(可調(diào)整閾值)
df_clean = df.dropna(thresh=int(len(df.columns) * 0.5))
print(f"\n刪除高缺失行后剩余:{len(df_clean)} 行")
# 只刪除關(guān)鍵字段為空的行(user_id不能為空)
df_clean = df.dropna(subset=['user_id', 'username'])
print(f"刪除user_id/username為空后剩余:{len(df_clean)} 行")
# --- 1.3 填充缺失值 ---
df_filled = df.copy()
# 數(shù)值型:用中位數(shù)填充(比均值更穩(wěn)健,不受極端值影響)
df_filled['age'].fillna(df_filled['age'].median(), inplace=True)
df_filled['score'].fillna(df_filled['score'].median(), inplace=True)
df_filled['purchase_amt'].fillna(df_filled['purchase_amt'].median(), inplace=True)
# 分類型:用眾數(shù)填充
df_filled['gender'].fillna(df_filled['gender'].mode()[0], inplace=True)
# 字符串型:用固定值填充
df_filled['city'].fillna('未知城市', inplace=True)
df_filled['username'].fillna('匿名用戶', inplace=True)
# 用前一行的值填充(適合時(shí)間序列)
# df_filled['purchase_amt'].fillna(method='ffill', inplace=True)
print("\n填充后缺失值數(shù)量:")
print(df_filled.isnull().sum())四、核心技巧2:重復(fù)值處理
# ============================================================
# 技巧2:重復(fù)值檢測(cè)與去重
# 注意:去重要根據(jù)業(yè)務(wù)邏輯判斷"什么是真正的重復(fù)"
#
# ============================================================
# --- 2.1 檢測(cè)重復(fù)行 ---
# 完全重復(fù)(所有列都相同)
full_dup = df.duplicated()
print(f"完全重復(fù)行數(shù):{full_dup.sum()}")
# 關(guān)鍵字段重復(fù)(業(yè)務(wù)上同一用戶的重復(fù)訂單)
key_dup = df.duplicated(subset=['user_id', 'order_date'])
print(f"user_id + order_date 重復(fù)行數(shù):{key_dup.sum()}")
# 查看重復(fù)的具體內(nèi)容
print("\n重復(fù)數(shù)據(jù)明細(xì):")
print(df[df.duplicated(subset=['user_id'], keep=False)]) # keep=False 顯示所有重復(fù)行
# --- 2.2 去重 ---
# 保留第一次出現(xiàn)的記錄
df_dedup = df.drop_duplicates(subset=['user_id'], keep='first')
print(f"\n按user_id去重后:{len(df_dedup)} 行(原始:{len(df)} 行)")
# 保留最新的記錄(先排序再取最后一條)
df_sorted = df.sort_values('order_date', ascending=True)
df_dedup_latest = df_sorted.drop_duplicates(subset=['user_id'], keep='last')
print(f"保留最新記錄去重后:{len(df_dedup_latest)} 行")
# 重置索引(去重后索引可能不連續(xù))
df_dedup = df_dedup.reset_index(drop=True)
print("\n去重并重置索引后:")
print(df_dedup[['user_id', 'username', 'order_date']].head())五、核心技巧3:異常值處理
# ============================================================
# 技巧3:異常值檢測(cè)與處理
# 兩種主流方法:IQR箱線圖法 + 3σ規(guī)則
# 公主號(hào):船長(zhǎng)Talk
# ============================================================
# --- 3.1 業(yè)務(wù)規(guī)則檢查(最直接的方法)---
print("=== 業(yè)務(wù)規(guī)則異常檢測(cè) ===")
# 年齡異常:人類年齡合理范圍 0-120歲
age_anomaly = df[(df['age'] 120)]
print(f"年齡異常(120)的行數(shù):{len(age_anomaly)}")
print(age_anomaly[['user_id', 'username', 'age']])
# 消費(fèi)金額異常:不能為負(fù)數(shù)
amt_anomaly = df[df['purchase_amt'] upper)]
print(f" Q1={Q1:.2f}, Q3={Q3:.2f}, IQR={IQR:.2f}")
print(f" 正常范圍:[{lower:.2f}, {upper:.2f}]")
print(f" 異常值數(shù)量:{len(outliers)},索引:{outliers.index.tolist()}")
return lower, upper
# 對(duì)年齡列做IQR檢測(cè)
print("年齡列IQR檢測(cè):")
age_valid = df[df['age'] > 0]['age'] # 先過(guò)濾負(fù)值
lower_age, upper_age = detect_outliers_iqr(age_valid)
# --- 3.3 異常值處理策略 ---
df_clean2 = df.copy()
# 策略1:直接刪除異常行
df_clean2 = df_clean2[(df_clean2['age'] >= 0) & (df_clean2['age'] 120, 'age'] = median_age
print(f"\n處理異常值后數(shù)據(jù)量:{len(df_clean2)} 行")
六、核心技巧4:數(shù)據(jù)類型轉(zhuǎn)換
# ============================================================
# 技巧4:數(shù)據(jù)類型檢查與轉(zhuǎn)換
# 數(shù)據(jù)類型不對(duì)是很多報(bào)錯(cuò)的根源
# 公主號(hào):船長(zhǎng)Talk
# ============================================================
df_typed = df_filled.copy()
print("轉(zhuǎn)換前數(shù)據(jù)類型:")
print(df_typed.dtypes)
# --- 4.1 日期類型轉(zhuǎn)換 ---
# pd.to_datetime 支持多種格式,errors='coerce' 把無(wú)法解析的轉(zhuǎn)為 NaT
df_typed['order_date'] = pd.to_datetime(df_typed['order_date'], errors='coerce')
print(f"\n日期轉(zhuǎn)換后,NaT數(shù)量:{df_typed['order_date'].isnull().sum()}")
# 從日期中提取更多特征
df_typed['order_year'] = df_typed['order_date'].dt.year
df_typed['order_month'] = df_typed['order_date'].dt.month
df_typed['order_day'] = df_typed['order_date'].dt.day
df_typed['order_weekday'] = df_typed['order_date'].dt.dayofweek # 0=周一, 6=周日
# --- 4.2 數(shù)值類型轉(zhuǎn)換 ---
# 字符串轉(zhuǎn)數(shù)值,無(wú)法轉(zhuǎn)換的設(shè)為 NaN
df_typed['age'] = pd.to_numeric(df_typed['age'], errors='coerce')
df_typed['purchase_amt'] = pd.to_numeric(df_typed['purchase_amt'], errors='coerce')
# --- 4.3 分類類型轉(zhuǎn)換(節(jié)省內(nèi)存)---
# 低基數(shù)列(取值有限)轉(zhuǎn)為 category 類型,大幅節(jié)省內(nèi)存
cat_cols = ['gender', 'city', 'category']
for col in cat_cols:
df_typed[col] = df_typed[col].astype('category')
print("\n轉(zhuǎn)換后數(shù)據(jù)類型:")
print(df_typed.dtypes)
# 內(nèi)存對(duì)比
print(f"\n原始內(nèi)存:{df.memory_usage(deep=True).sum() / 1024:.1f} KB")
print(f"轉(zhuǎn)換后內(nèi)存:{df_typed.memory_usage(deep=True).sum() / 1024:.1f} KB")
七、核心技巧5:字符串清洗
# ============================================================
# 技巧5:字符串清洗(str accessor 系列方法)
# 處理姓名/地址等文本字段的常見(jiàn)問(wèn)題
# 公主號(hào):船長(zhǎng)Talk
# ============================================================
df_str = df_filled.copy()
# --- 5.1 去除空格 ---
# strip() 去首尾空格,lstrip() 去左側(cè),rstrip() 去右側(cè)
df_str['username'] = df_str['username'].str.strip()
# 去除中間多余空格(正則替換)
df_str['username'] = df_str['username'].str.replace(r'\s+', '', regex=True)
# --- 5.2 大小寫統(tǒng)一 ---
# 統(tǒng)一轉(zhuǎn)為大寫(適合身份證、訂單號(hào)等編碼)
df_str['username'] = df_str['username'].str.upper()
# 或首字母大寫
# df_str['username'] = df_str['username'].str.title()
# --- 5.3 字符串過(guò)濾與篩選 ---
# 篩選包含特定字符的行
china_users = df_str[df_str['city'].str.contains('京|滬|穗', na=False)]
print(f"一線城市用戶數(shù):{len(china_users)}")
# 過(guò)濾掉空字符串(空字符串不是NaN,要單獨(dú)處理)
df_str = df_str[df_str['username'].str.strip() != '']
df_str = df_str[df_str['username'].notna()] # 再過(guò)濾NaN
# --- 5.4 字符串提取 ---
# 從字符串中提取數(shù)字
sample = pd.Series(['訂單001', '訂單002abc', '無(wú)編號(hào)', '訂單100'])
order_nums = sample.str.extract(r'(\d+)', expand=False) # 提取數(shù)字部分
print("\n訂單編號(hào)提?。?)
print(order_nums)
# --- 5.5 字符串替換 ---
# 替換特定字符
df_str['gender'] = df_str['gender'].str.replace('未知', '保密')
print("\n字符串清洗完成,username示例:")
print(df_str['username'].head(8))
八、核心技巧6:數(shù)據(jù)標(biāo)準(zhǔn)化與歸一化
# ============================================================
# 技巧6:數(shù)值標(biāo)準(zhǔn)化(機(jī)器學(xué)習(xí)前必做的預(yù)處理步驟)
# Min-Max歸一化 vs Z-score標(biāo)準(zhǔn)化
# 公主號(hào):船長(zhǎng)Talk
# ============================================================
df_norm = df_filled.copy()
# 只處理數(shù)值列
numeric_cols = ['age', 'purchase_amt', 'score']
# --- 6.1 Min-Max 歸一化(將數(shù)值縮放到[0,1]區(qū)間)---
# 適用場(chǎng)景:神經(jīng)網(wǎng)絡(luò)、KNN、聚類等對(duì)量綱敏感的算法
def minmax_normalize(series):
"""Min-Max歸一化公式:(x - min) / (max - min)"""
return (series - series.min()) / (series.max() - series.min())
for col in numeric_cols:
df_norm[f'{col}_minmax'] = minmax_normalize(df_norm[col])
# --- 6.2 Z-score 標(biāo)準(zhǔn)化(均值為0,標(biāo)準(zhǔn)差為1)---
# 適用場(chǎng)景:線性回歸、SVM、PCA等假設(shè)正態(tài)分布的算法
def zscore_normalize(series):
"""Z-score公式:(x - mean) / std"""
return (series - series.mean()) / series.std()
for col in numeric_cols:
df_norm[f'{col}_zscore'] = zscore_normalize(df_norm[col])
print("歸一化和標(biāo)準(zhǔn)化結(jié)果對(duì)比(前5行):")
compare_cols = ['purchase_amt', 'purchase_amt_minmax', 'purchase_amt_zscore']
print(df_norm[compare_cols].head())
print("\n歸一化后統(tǒng)計(jì):")
print(df_norm[['purchase_amt_minmax', 'purchase_amt_zscore']].describe().round(3))
九、核心技巧7:分組聚合發(fā)現(xiàn)數(shù)據(jù)問(wèn)題
# ============================================================
# 技巧7:用分組聚合快速發(fā)現(xiàn)數(shù)據(jù)質(zhì)量問(wèn)題
# 這是數(shù)據(jù)分析師常用的"數(shù)據(jù)探查"手段
# 公主號(hào):船長(zhǎng)Talk
# ============================================================
df_check = df_filled.copy()
# --- 7.1 按城市分組,查看各城市數(shù)據(jù)分布 ---
city_stats = df_check.groupby('city').agg(
用戶數(shù)=('user_id', 'count'),
平均年齡=('age', 'mean'),
平均消費(fèi)=('purchase_amt', 'mean'),
最高消費(fèi)=('purchase_amt', 'max'),
最低消費(fèi)=('purchase_amt', 'min'),
).round(2)
print("各城市數(shù)據(jù)統(tǒng)計(jì):")
print(city_stats)
# --- 7.2 交叉分析:性別 × 品類 的消費(fèi)習(xí)慣 ---
cross_table = df_check.pivot_table(
values='purchase_amt',
index='gender',
columns='category',
aggfunc='mean',
fill_value=0 # 缺失組合填0
).round(2)
print("\n性別×品類消費(fèi)交叉表:")
print(cross_table)
# --- 7.3 用 value_counts 檢查類別字段 ---
print("\n性別分布(包含異常值):")
print(df['gender'].value_counts(dropna=False)) # dropna=False 顯示空值統(tǒng)計(jì)
print("\n城市分布 Top5:")
print(df['city'].value_counts().head())
十、核心技巧8:構(gòu)建數(shù)據(jù)清洗Pipeline
# ============================================================
# 技巧8:將所有清洗步驟封裝成Pipeline(生產(chǎn)環(huán)境推薦)
# 好處:可復(fù)用、可測(cè)試、流程透明
# 公主號(hào):船長(zhǎng)Talk
# ============================================================
def clean_user_data(df_raw):
"""
用戶數(shù)據(jù)清洗完整Pipeline
Parameters:
df_raw: 原始DataFrame
Returns:
df_clean: 清洗后的DataFrame
report: 清洗報(bào)告字典
"""
df = df_raw.copy()
report = {'原始行數(shù)': len(df)}
# Step1: 刪除完全重復(fù)行
before = len(df)
df = df.drop_duplicates(subset=['user_id'], keep='first')
report['去重刪除行數(shù)'] = before - len(df)
# Step2: 處理異常值(業(yè)務(wù)規(guī)則)
df = df[(df['age'].isna()) | (df['age'].between(0, 120))]
df = df[(df['purchase_amt'].isna()) | (df['purchase_amt'] >= 0)]
report['異常值刪除行數(shù)'] = len(df_raw.drop_duplicates(subset=['user_id'])) - len(df)
# Step3: 字符串清洗
if 'username' in df.columns:
df['username'] = df['username'].str.strip().replace('', np.nan)
# Step4: 類型轉(zhuǎn)換
df['order_date'] = pd.to_datetime(df['order_date'], errors='coerce')
# Step5: 缺失值填充
df['age'].fillna(df['age'].median(), inplace=True)
df['purchase_amt'].fillna(0, inplace=True)
df['score'].fillna(df['score'].median(), inplace=True)
df['city'].fillna('未知', inplace=True)
df['gender'].fillna('保密', inplace=True)
# Step6: 重置索引
df = df.reset_index(drop=True)
report['清洗后行數(shù)'] = len(df)
report['清洗率'] = f"{(1 - len(df)/report['原始行數(shù)'])*100:.1f}%"
return df, report
# 執(zhí)行Pipeline
df_final, clean_report = clean_user_data(df)
print("="*40)
print("數(shù)據(jù)清洗報(bào)告:")
print("="*40)
for key, value in clean_report.items():
print(f" {key}: {value}")
print(f"\n最終數(shù)據(jù)預(yù)覽:")
print(df_final.head())
print(f"\n最終數(shù)據(jù)形狀:{df_final.shape}")
print(f"\n最終缺失值統(tǒng)計(jì):")
print(df_final.isnull().sum())
十一、數(shù)據(jù)清洗最佳實(shí)踐總結(jié)
| 場(chǎng)景 | 推薦方法 | 注意事項(xiàng) |
|---|---|---|
| 缺失值 - 數(shù)值型 | 中位數(shù)填充 | 比均值更穩(wěn)健,不受極端值影響 |
| 缺失值 - 分類型 | 眾數(shù)填充 | 或填"未知",保留信息 |
| 重復(fù)值 | 按業(yè)務(wù)鍵去重 | 先排序再去重,保留最新/最優(yōu)記錄 |
| 異常值 | IQR法 + 業(yè)務(wù)規(guī)則 | 異常≠錯(cuò)誤,先分析再處理 |
| 日期格式 | pd.to_datetime errors='coerce' | 統(tǒng)一為datetime64類型 |
| 字符串 | str.strip() + 正則 | 空字符串≠NaN,要單獨(dú)處理 |
| 數(shù)值標(biāo)準(zhǔn)化 | 根據(jù)算法選Min-Max或Z-score | 訓(xùn)練集fit,測(cè)試集transform |
| 批量處理 | 封裝Pipeline函數(shù) | 記錄每步處理量,方便排查 |
結(jié)語(yǔ)
數(shù)據(jù)清洗沒(méi)有"萬(wàn)能公式",需要結(jié)合具體的業(yè)務(wù)場(chǎng)景和數(shù)據(jù)特點(diǎn)來(lái)選擇最合適的方法。建議每次清洗都輸出一份清洗報(bào)告,記錄每步處理了多少數(shù)據(jù),方便后續(xù)復(fù)盤和維護(hù)。
以上就是Pandas數(shù)據(jù)清洗的8大核心技巧完整指南的詳細(xì)內(nèi)容,更多關(guān)于Pandas數(shù)據(jù)清洗技巧的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
解決安裝python3.7.4報(bào)錯(cuò)Can''''t connect to HTTPS URL because the S
這篇文章主要介紹了解決安裝python3.7.4報(bào)錯(cuò)Can't connect to HTTPS URL because the SSL module is not available,本文給大家簡(jiǎn)單分析了錯(cuò)誤原因,給出了解決方法,需要的朋友可以參考下2019-07-07
Python循環(huán)語(yǔ)句中else的用法總結(jié)
這篇文章給大家整理了關(guān)于Python中循環(huán)語(yǔ)句中else的用法,包括常規(guī)的 if else 用法、if else 快捷用法、與 for 關(guān)鍵字一起用、與 while 關(guān)鍵字一起用以及與 try except 一起用的用法總結(jié),有需要的朋友們可以參考借鑒。2016-09-09
Python實(shí)現(xiàn)批量提取BLF文件時(shí)間戳
BLF(Binary Logging Format)作為 Vector 公司推出的 CAN 總線數(shù)據(jù)記錄格式,被廣泛用于存儲(chǔ)車輛通信數(shù)據(jù),本文將使用Python輕松提取關(guān)鍵時(shí)間戳信息,希望對(duì)大家有所幫助2025-07-07
Python Numpy實(shí)現(xiàn)計(jì)算矩陣的均值和標(biāo)準(zhǔn)差詳解
NumPy(Numerical Python)是Python的一種開源的數(shù)值計(jì)算擴(kuò)展。這種工具可用來(lái)存儲(chǔ)和處理大型矩陣,比Python自身的嵌套列表結(jié)構(gòu)要高效的多。本文主要介紹用NumPy實(shí)現(xiàn)計(jì)算矩陣的均值和標(biāo)準(zhǔn)差,感興趣的小伙伴可以了解一下2021-11-11
Python整數(shù)對(duì)象實(shí)現(xiàn)原理詳解
這篇文章主要介紹了Python整數(shù)對(duì)象實(shí)現(xiàn)原理詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-07-07
python中g(shù)et_dummies()用法示例詳解
get_dummies函數(shù)是Python中的一個(gè)函數(shù),用于將分類變量轉(zhuǎn)換為啞變量,下面這篇文章主要介紹了python中g(shù)et_dummies()用法的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-09-09
python+selenium 腳本實(shí)現(xiàn)每天自動(dòng)登記的思路詳解
這篇文章主要介紹了python+selenium 腳本實(shí)現(xiàn)每天自動(dòng)登記,本文你給大家分享基本的思路,通過(guò)實(shí)例代碼截圖的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-03-03

