Pandas-数据清洗与缺失值处理
数据清洗与缺失值处理
目录
- 检测缺失值
- 处理缺失值
- 删除缺失值
- 填充缺失值
- 处理重复值
- 数据类型转换
- 数据规范化
- 处理异常值
- 实际应用示例
检测缺失值
基本检测方法
import pandas as pd
import numpy as np# 创建包含缺失值的数据
data = {'姓名': ['张三', '李四', '王五', '赵六', '孙七'],'年龄': [25, 30, np.nan, 28, np.nan],'工资': [8000, np.nan, 10000, 11000, 9500],'城市': ['北京', '上海', '广州', np.nan, '杭州']
}
df = pd.DataFrame(data)# 检查缺失值(返回布尔 DataFrame)
print(df.isna())
# 输出:
# 姓名 年龄 城市 工资
# 0 False False False False
# 1 False False False True
# 2 False True False False
# 3 False False True False
# 4 False True False False# 检查非缺失值
print(df.notna())# 统计每列的缺失值数量
print(df.isna().sum())
# 输出:
# 姓名 0
# 年龄 2
# 城市 1
# 工资 1
# dtype: int64# 统计缺失值百分比
print(df.isna().sum() / len(df) * 100)# 检查是否有缺失值
print(df.isna().any().any()) # True# 检查每行是否有缺失值
print(df.isna().any(axis=1))# 统计每行缺失值数量
print(df.isna().sum(axis=1))
缺失值类型
import pandas as pd
import numpy as np# Pandas 中的缺失值表示
# - NaN (Not a Number): 用于浮点数
# - None: 用于对象类型
# - NaT (Not a Time): 用于时间戳df = pd.DataFrame({'A': [1, 2, np.nan, 4],'B': ['a', 'b', None, 'd'],'C': pd.date_range('2024-01-01', periods=4),
})
df.loc[2, 'C'] = pd.NaTprint(df)
print(df.isna())
处理缺失值
基本统计
import pandas as pd
import numpy as npdata = {'年龄': [25, 30, np.nan, 28, np.nan],'工资': [8000, np.nan, 10000, 11000, 9500]
}
df = pd.DataFrame(data)# 跳过缺失值进行统计
print(f"平均年龄: {df['年龄'].mean()}") # 自动跳过 NaN
print(f"年龄总和: {df['年龄'].sum()}") # 自动跳过 NaN
print(f"年龄最大值: {df['年龄'].max()}") # 自动跳过 NaN# 统计非缺失值数量
print(f"有效年龄数量: {df['年龄'].count()}")# 使用 skipna 参数
print(f"包含 NaN 的平均值: {df['年龄'].mean(skipna=False)}") # 返回 NaN
删除缺失值
dropna 方法
import pandas as pd
import numpy as npdata = {'姓名': ['张三', '李四', '王五', '赵六', '孙七'],'年龄': [25, 30, np.nan, 28, np.nan],'工资': [8000, np.nan, 10000, 11000, 9500],'城市': ['北京', '上海', '广州', np.nan, '杭州']
}
df = pd.DataFrame(data)# 删除包含任何缺失值的行(默认)
df_dropped = df.dropna()
print(df_dropped)
# 输出:
# 姓名 年龄 工资 城市
# 0 张三 25.0 8000.0 北京# 删除所有值都为缺失值的行
df_dropped = df.dropna(how='all')
print(df_dropped)# 删除至少包含 n 个缺失值的行
df_dropped = df.dropna(thresh=3) # 至少 3 个非缺失值
print(df_dropped)# 删除包含缺失值的列
df_dropped = df.dropna(axis=1)
print(df_dropped)# 删除特定列包含缺失值的行
df_dropped = df.dropna(subset=['年龄'])
print(df_dropped)# 修改原 DataFrame
df.dropna(inplace=True)
print(df)
填充缺失值
fillna 方法
import pandas as pd
import numpy as npdata = {'年龄': [25, 30, np.nan, 28, np.nan],'工资': [8000, np.nan, 10000, 11000, 9500],'城市': ['北京', '上海', np.nan, '深圳', '杭州']
}
df = pd.DataFrame(data)# 用固定值填充
df_filled = df.fillna(0)
print(df_filled)# 用不同值填充不同列
df_filled = df.fillna({'年龄': 0, '工资': df['工资'].mean(), '城市': '未知'})
print(df_filled)# 前向填充(用前一个值填充)
df_filled = df.fillna(method='ffill') # 或 method='pad'
print(df_filled)# 后向填充(用后一个值填充)
df_filled = df.fillna(method='bfill') # 或 method='backfill'
print(df_filled)# 限制填充的行数
df_filled = df.fillna(method='ffill', limit=1)
print(df_filled)
使用统计值填充
import pandas as pd
import numpy as npdata = {'年龄': [25, 30, np.nan, 28, np.nan, 32],'工资': [8000, np.nan, 10000, 11000, 9500, np.nan]
}
df = pd.DataFrame(data)# 用均值填充
df['年龄'].fillna(df['年龄'].mean(), inplace=True)
print(df)# 用中位数填充
df['工资'].fillna(df['工资'].median(), inplace=True)
print(df)# 用众数填充
df['年龄'].fillna(df['年龄'].mode()[0], inplace=True)
print(df)# 用分组均值填充
df_grouped = df.groupby('部门')['工资'].transform(lambda x: x.fillna(x.mean()))
插值填充
import pandas as pd
import numpy as np# 线性插值(适用于数值型数据)
data = {'日期': pd.date_range('2024-01-01', periods=10, freq='D'),'价格': [100, 102, np.nan, np.nan, 108, 110, np.nan, 115, 117, 120]
}
df = pd.DataFrame(data)# 线性插值
df['价格'] = df['价格'].interpolate(method='linear')
print(df)# 时间索引的插值
df_indexed = df.set_index('日期')
df_indexed['价格'] = df_indexed['价格'].interpolate(method='time')
print(df_indexed)# 其他插值方法
# method='polynomial': 多项式插值
# method='spline': 样条插值
# method='quadratic': 二次插值
处理重复值
检测重复值
import pandas as pddata = {'姓名': ['张三', '李四', '王五', '张三', '赵六'],'年龄': [25, 30, 35, 25, 28],'城市': ['北京', '上海', '广州', '北京', '深圳']
}
df = pd.DataFrame(data)# 检查是否有重复行
print(df.duplicated())
# 输出:
# 0 False
# 1 False
# 2 False
# 3 True
# 4 False
# dtype: bool# 检查特定列的重复值
print(df.duplicated(subset=['姓名']))# 检查所有列都重复的行(keep='first' 保留第一个,keep='last' 保留最后一个)
print(df.duplicated(keep='first'))
print(df.duplicated(keep='last'))
print(df.duplicated(keep=False)) # 标记所有重复# 统计重复值数量
print(df.duplicated().sum())
删除重复值
import pandas as pddata = {'姓名': ['张三', '李四', '王五', '张三', '赵六'],'年龄': [25, 30, 35, 25, 28],'城市': ['北京', '上海', '广州', '北京', '深圳']
}
df = pd.DataFrame(data)# 删除重复行(保留第一个)
df_dedup = df.drop_duplicates()
print(df_dedup)# 删除重复行(保留最后一个)
df_dedup = df.drop_duplicates(keep='last')
print(df_dedup)# 删除所有重复的行
df_dedup = df.drop_duplicates(keep=False)
print(df_dedup)# 基于特定列删除重复
df_dedup = df.drop_duplicates(subset=['姓名'])
print(df_dedup)# 基于多列删除重复
df_dedup = df.drop_duplicates(subset=['姓名', '年龄'])
print(df_dedup)# 修改原 DataFrame
df.drop_duplicates(inplace=True)
数据类型转换
import pandas as pddata = {'年龄': ['25', '30', '35', '28'],'工资': [8000.5, 12000.3, 10000.7, 11000.2],'日期': ['2024-01-01', '2024-01-02', '2024-01-03', '2024-01-04']
}
df = pd.DataFrame(data)print(df.dtypes)# 转换单列类型
df['年龄'] = df['年龄'].astype(int)
print(df.dtypes)# 转换多列类型
df = df.astype({'年龄': int, '工资': int})
print(df.dtypes)# 转换为字符串
df['年龄'] = df['年龄'].astype(str)# 转换为日期
df['日期'] = pd.to_datetime(df['日期'])
print(df.dtypes)# 转换为类别类型(节省内存)
df['类别'] = ['A', 'B', 'A', 'B']
df['类别'] = df['类别'].astype('category')
print(df.dtypes)# 处理转换错误
df['年龄'] = pd.to_numeric(df['年龄'], errors='coerce') # 错误值转为 NaN
df['年龄'] = pd.to_numeric(df['年龄'], errors='ignore') # 忽略错误
数据规范化
标准化
import pandas as pd
import numpy as npdata = {'身高': [170, 175, 165, 180, 172],'体重': [65, 70, 60, 80, 68]
}
df = pd.DataFrame(data)# Z-score 标准化
df['身高_zscore'] = (df['身高'] - df['身高'].mean()) / df['身高'].std()
df['体重_zscore'] = (df['体重'] - df['体重'].mean()) / df['体重'].std()print(df)# Min-Max 标准化(缩放到 0-1)
df['身高_minmax'] = (df['身高'] - df['身高'].min()) / (df['身高'].max() - df['身高'].min())
print(df)
归一化
import pandas as pd# 字符串归一化
data = {'姓名': [' 张三 ', '李四', ' 王五 ', '赵六']
}
df = pd.DataFrame(data)# 去除空格
df['姓名'] = df['姓名'].str.strip()
print(df)# 统一大小写
df['姓名'] = df['姓名'].str.lower()
print(df)# 统一格式
df['姓名'] = df['姓名'].str.title()
print(df)
处理异常值
检测异常值
import pandas as pd
import numpy as npnp.random.seed(42)
data = {'年龄': [25, 30, 35, 28, 32, 150, 27, 29], # 150 是异常值'工资': [8000, 12000, 10000, 11000, 9500, 120000, 9000, 10500] # 120000 是异常值
}
df = pd.DataFrame(data)# 使用 IQR 方法检测异常值
def detect_outliers_iqr(df, column):Q1 = df[column].quantile(0.25)Q3 = df[column].quantile(0.75)IQR = Q3 - Q1lower_bound = Q1 - 1.5 * IQRupper_bound = Q3 + 1.5 * IQRoutliers = df[(df[column] < lower_bound) | (df[column] > upper_bound)]return outliersoutliers_age = detect_outliers_iqr(df, '年龄')
print("年龄异常值:")
print(outliers_age)outliers_salary = detect_outliers_iqr(df, '工资')
print("\n工资异常值:")
print(outliers_salary)# 使用 Z-score 方法检测异常值(通常阈值为 3)
from scipy import stats
z_scores = np.abs(stats.zscore(df['年龄']))
outliers = df[z_scores > 3]
print("\nZ-score 异常值:")
print(outliers)
处理异常值
import pandas as pd
import numpy as npnp.random.seed(42)
data = {'年龄': [25, 30, 35, 28, 32, 150, 27, 29],'工资': [8000, 12000, 10000, 11000, 9500, 120000, 9000, 10500]
}
df = pd.DataFrame(data)# 方法1:删除异常值
def remove_outliers_iqr(df, column):Q1 = df[column].quantile(0.25)Q3 = df[column].quantile(0.75)IQR = Q3 - Q1lower_bound = Q1 - 1.5 * IQRupper_bound = Q3 + 1.5 * IQRreturn df[(df[column] >= lower_bound) & (df[column] <= upper_bound)]df_cleaned = remove_outliers_iqr(df.copy(), '年龄')
print("删除年龄异常值后:")
print(df_cleaned)# 方法2:用边界值替换异常值(Winsorization)
def winsorize(df, column):Q1 = df[column].quantile(0.25)Q3 = df[column].quantile(0.75)IQR = Q3 - Q1lower_bound = Q1 - 1.5 * IQRupper_bound = Q3 + 1.5 * IQRdf[column] = df[column].clip(lower=lower_bound, upper=upper_bound)return dfdf_winsorized = winsorize(df.copy(), '工资')
print("\n工资异常值截断后:")
print(df_winsorized)# 方法3:用中位数或均值替换
def replace_outliers_with_median(df, column):Q1 = df[column].quantile(0.25)Q3 = df[column].quantile(0.75)IQR = Q3 - Q1lower_bound = Q1 - 1.5 * IQRupper_bound = Q3 + 1.5 * IQRmedian = df[column].median()df.loc[(df[column] < lower_bound) | (df[column] > upper_bound), column] = medianreturn dfdf_replaced = replace_outliers_with_median(df.copy(), '年龄')
print("\n年龄异常值替换为 median 后:")
print(df_replaced)
实际应用示例
示例 1:完整的数据清洗流程
import pandas as pd
import numpy as np# 创建包含各种问题的数据
np.random.seed(42)
data = {'ID': [1, 2, 3, 4, 5, 6, 7, 8],'姓名': ['张三', '李四', '王五', '赵六', ' 孙七 ', '周八', '张三', '吴九'],'年龄': [25, 30, np.nan, 28, 32, 150, 27, np.nan], # 有缺失值和异常值'工资': [8000, np.nan, 10000, 11000, 9500, 120000, 9000, 10500], # 有缺失值和异常值'部门': ['销售', '技术', '销售', '技术', '销售', '技术', '销售', '技术'],'入职日期': ['2020-01-01', '2019-06-15', '2020-01-01', 'invalid', '2021-09-10', '2020-01-01', '2020-01-01', '2022-03-01']
}
df = pd.DataFrame(data)print("原始数据:")
print(df)
print(f"\n缺失值统计:\n{df.isna().sum()}")# 步骤1:处理字符串列的空格
df['姓名'] = df['姓名'].str.strip()# 步骤2:删除重复值
df = df.drop_duplicates(subset=['姓名'], keep='first')# 步骤3:处理异常值(年龄)
Q1_age = df['年龄'].quantile(0.25)
Q3_age = df['年龄'].quantile(0.75)
IQR_age = Q3_age - Q1_age
df = df[(df['年龄'] >= Q1_age - 1.5*IQR_age) & (df['年龄'] <= Q3_age + 1.5*IQR_age) | df['年龄'].isna()]# 步骤4:处理工资异常值(用边界值替换)
Q1_salary = df['工资'].quantile(0.25)
Q3_salary = df['工资'].quantile(0.75)
IQR_salary = Q3_salary - Q1_salary
upper_bound = Q3_salary + 1.5 * IQR_salary
df['工资'] = df['工资'].clip(upper=upper_bound)# 步骤5:填充缺失值
df['年龄'].fillna(df['年龄'].median(), inplace=True)
df['工资'].fillna(df.groupby('部门')['工资'].transform('median'), inplace=True)# 步骤6:处理日期列
df['入职日期'] = pd.to_datetime(df['入职日期'], errors='coerce')
df['入职日期'].fillna(df['入职日期'].median(), inplace=True)# 步骤7:数据类型转换
df['年龄'] = df['年龄'].astype(int)print("\n清洗后的数据:")
print(df)
print(f"\n缺失值统计:\n{df.isna().sum()}")
示例 2:处理真实场景数据
import pandas as pd
import numpy as np# 模拟真实数据场景
np.random.seed(42)
n = 1000
df = pd.DataFrame({'用户ID': range(1, n+1),'年龄': np.random.randint(18, 80, n),'收入': np.random.normal(5000, 2000, n),'城市': np.random.choice(['北京', '上海', '广州', '深圳', '杭州'], n),'注册日期': pd.date_range('2020-01-01', periods=n, freq='D')
})# 随机添加缺失值
missing_indices = np.random.choice(df.index, size=int(n*0.1), replace=False)
df.loc[missing_indices, '年龄'] = np.nanmissing_indices = np.random.choice(df.index, size=int(n*0.05), replace=False)
df.loc[missing_indices, '收入'] = np.nan# 添加异常值
outlier_indices = np.random.choice(df.index, size=10, replace=False)
df.loc[outlier_indices, '年龄'] = np.random.randint(150, 200, 10)
df.loc[outlier_indices, '收入'] = np.random.randint(100000, 200000, 10)# 数据清洗
print("清洗前统计:")
print(df.describe())
print(f"缺失值:\n{df.isna().sum()}")# 删除年龄异常值
df = df[(df['年龄'] >= 18) & (df['年龄'] <= 100)]# 处理收入异常值
Q1 = df['收入'].quantile(0.25)
Q3 = df['收入'].quantile(0.75)
IQR = Q3 - Q1
df['收入'] = df['收入'].clip(lower=Q1-1.5*IQR, upper=Q3+1.5*IQR)# 填充缺失值
df['年龄'].fillna(df.groupby('城市')['年龄'].transform('median'), inplace=True)
df['收入'].fillna(df.groupby(['城市', df['年龄']//10*10])['收入'].transform('median'), inplace=True)print("\n清洗后统计:")
print(df.describe())
print(f"缺失值:\n{df.isna().sum()}")
总结
数据清洗是数据分析的重要步骤:
- 缺失值处理:检测、删除或填充
- 重复值处理:检测和删除
- 数据类型转换:确保数据类型正确
- 异常值处理:检测、删除或替换
- 数据规范化:标准化和归一化
