🐍 Python Day74:Pandas 数据处理 — 缺失值、分组与聚合
🕐 预计用时:2-3 小时 | 🎯 目标:掌握缺失值处理、数据筛选、排序、分组聚合和数据清洗
📖 今日目录
1. 缺失值处理
import pandas as pd
import numpy as np
df = pd.DataFrame({
'姓名': ['小明', '小红', '小刚', '小美', '小强'],
'年龄': [20, np.nan, 19, 22, np.nan],
'城市': ['北京', '上海', None, '深圳', '杭州'],
'薪资': [8000, 12000, 7500, np.nan, 9000]
})
# === 检测缺失值 ===
df.isnull() # 返回布尔 DataFrame
df.isnull().sum() # 每列缺失值数量
df.isnull().sum().sum() # 总缺失值数量
df.isnull().mean() # 每列缺失比例
# === 删除缺失值 ===
df.dropna() # 删除任何含缺失的行
df.dropna(subset=['年龄']) # 只看'年龄'列,有缺失就删
df.dropna(how='all') # 整行全为缺失才删
df.dropna(thresh=3) # 至少有 3 个非缺失值才保留
# === 填充缺失值 ===
df.fillna(0) # 全部填 0
df['年龄'].fillna(df['年龄'].mean()) # 用均值填充
df['年龄'].fillna(df['年龄'].median()) # 用中位数填充
df['城市'].fillna('未知') # 用固定值
df.ffill() # 用前一个非缺失值填充(前向填充)
df.bfill() # 用后一个非缺失值填充(后向填充)
# 注:pandas 2.1+ 弃用了 fillna(method='ffill'),请用 df.ffill() / df.bfill()
# === 按列指定填充值 ===
df.fillna({'年龄': df['年龄'].mean(), '城市': '未知', '薪资': 0})
💡 缺失值处理策略:
• 数值列(年龄、薪资)→ 用均值或中位数填充
• 分类列(城市、性别)→ 用众数或"未知"填充
• 时间列(日期)→ 用前向/后向填充
• 缺失太多(>50%)→ 考虑删除该列
2. 数据筛选
import pandas as pd
df = pd.DataFrame({
'姓名': ['小明', '小红', '小刚', '小美', '小强'],
'年龄': [20, 21, 19, 22, 20],
'城市': ['北京', '上海', '广州', '深圳', '杭州'],
'薪资': [8000, 12000, 7500, 15000, 9000]
})
# === 单条件筛选 ===
df[df['年龄'] > 20] # 年龄大于 20
df[df['城市'] == '北京'] # 北京的人
df[df['薪资'] >= 10000] # 高薪
# === 多条件筛选(& 与 / | 或)===
df[(df['年龄'] > 20) & (df['薪资'] > 10000)] # 且
df[(df['城市'] == '北京') | (df['城市'] == '上海')] # 或
# === isin 包含 ===
df[df['城市'].isin(['北京', '上海', '广州'])]
# === 字符串筛选 ===
df[df['姓名'].str.contains('小')] # 包含"小"
df[df['姓名'].str.startswith('小')] # 以"小"开头
# === between 范围 ===
df[df['年龄'].between(20, 22)] # 年龄在 20-22 之间
# === 取反 ===
df[~df['城市'].isin(['北京', '上海'])] # 不在北京和上海
df[df['年龄'] != 20] # 不等于 20
# === query 方法(字符串表达式,更易读)===
df.query('年龄 > 20')
df.query('年龄 > 20 and 薪资 > 10000')
df.query('城市 in ["北京", "上海"]')
# === 筛选并赋值 ===
df.loc[df['薪资'] < 8000, '薪资'] = 8000 # 低于 8000 的补到 8000
3. 排序
# 单列排序
df.sort_values('薪资') # 升序
df.sort_values('薪资', ascending=False) # 降序
# 多列排序
df.sort_values(['年龄', '薪资'], ascending=[True, False])
# 先按年龄升序,年龄相同按薪资降序
# 索引排序
df.sort_index() # 按索引排序
# 排名
df['薪资排名'] = df['薪资'].rank(ascending=False) # 排名(1=最高)
df['薪资排名'] = df['薪资'].rank(method='dense') # 密集排名(无间隔)
df['薪资排名'] = df['薪资'].rank(method='min') # 最小排名
# Top N
df.nlargest(3, '薪资') # 薪资最高的 3 个
df.nsmallest(3, '薪资') # 薪资最低的 3 个
4. 分组聚合(GroupBy)
GroupBy 是 Pandas 最强大的功能——类似 Excel 的数据透视表。
import pandas as pd
df = pd.DataFrame({
'姓名': ['小明', '小红', '小刚', '小美', '小强', '小丽'],
'部门': ['技术', '市场', '技术', '市场', '技术', '市场'],
'城市': ['北京', '上海', '北京', '上海', '广州', '广州'],
'薪资': [15000, 12000, 18000, 15000, 16000, 13000]
})
# === 基本分组 ===
df.groupby('部门')['薪资'].mean()
# 部门
# 市场 13333.33
# 技术 16333.33
# === 多列分组 ===
df.groupby(['部门', '城市'])['薪资'].mean()
# === 多个聚合函数 ===
df.groupby('部门')['薪资'].agg(['mean', 'max', 'min', 'count', 'sum'])
# mean max min count sum
# 部门
# 市场 13333 15000 12000 3 40000
# 技术 16333 18000 15000 3 49000
# === 不同列不同聚合 ===
df.groupby('部门').agg({
'薪资': ['mean', 'max'],
'姓名': 'count'
})
# === 对分组结果遍历 ===
for name, group in df.groupby('部门'):
print(f'\n{name}部门:')
print(group)
# === 分组后筛选 ===
# 找平均薪资最高的部门
df.groupby('部门')['薪资'].mean().idxmax() # '技术'
# === transform(保持原 DataFrame 形状)===
# 每个人的薪资与部门均值的差
df['薪资偏差'] = df['薪资'] - df.groupby('部门')['薪资'].transform('mean')
# === 每组排序 ===
df.groupby('部门').apply(lambda x: x.nlargest(2, '薪资'))
💡 GroupBy 的核心公式:
df.groupby('分组列')['值列'].聚合函数()
常用聚合函数:mean()sum()count()max()min()std()agg([...])
5. 数据清洗
import pandas as pd
# === 去重 ===
df.duplicated() # 检查重复行(布尔)
df.duplicated(subset=['姓名', '城市']) # 按指定列检查
df.drop_duplicates() # 删除重复行
df.drop_duplicates(subset=['姓名']) # 按姓名去重,保留第一条
df.drop_duplicates(keep='last') # 保留最后一条
# === 替换 ===
df['城市'].replace('北京', 'BJ')
df['城市'].replace({'北京': 'BJ', '上海': 'SH'})
df.replace({-999: np.nan, -1: np.nan}) # 多个异常值替换为 NaN
# === 重命名列 ===
df.rename(columns={'姓名': 'name', '年龄': 'age'})
df.columns = ['name', 'age', 'city', 'salary'] # 全部重命名
# === 删除列/行 ===
df.drop(columns=['年龄']) # 删除列
df.drop(columns=['年龄', '城市']) # 删除多列
df.drop(index=[0, 1]) # 删除行
# === 删除异常值 ===
# IQR 方法(四分位距)
Q1 = df['薪资'].quantile(0.25)
Q3 = df['薪资'].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
df_clean = df[(df['薪资'] >= lower) & (df['薪资'] <= upper)]
# === 字符串清洗 ===
df['姓名'] = df['姓名'].str.strip() # 去首尾空格
df['姓名'] = df['姓名'].str.replace(' ', '', regex=False) # 去所有空格
df['电话'] = df['电话'].str.replace('-', '', regex=False) # 去横线
6. 数据变换
import pandas as pd
import numpy as np
# === 新增列 ===
df['年薪'] = df['薪资'] * 12
df['税后'] = df['薪资'] * 0.8
# === apply 自定义函数 ===
def level(salary):
if salary >= 15000:
return '高薪'
elif salary >= 10000:
return '中等'
else:
return '低薪'
df['薪资等级'] = df['薪资'].apply(level)
# apply + lambda(更简洁)
df['薪资等级'] = df['薪资'].apply(lambda x: '高薪' if x >= 15000 else '中等' if x >= 10000 else '低薪')
# 对多列 apply
df['描述'] = df.apply(lambda row: f"{row['姓名']}在{row['城市']}", axis=1)
# === map 映射 ===
city_map = {'北京': '华北', '上海': '华东', '广州': '华南'}
df['区域'] = df['城市'].map(city_map)
# === cut 分箱 ===
df['年龄段'] = pd.cut(df['年龄'],
bins=[0, 18, 25, 35, 100],
labels=['少年', '青年', '中年', '老年'])
# === qcut 等频分箱 ===
df['薪资等级'] = pd.qcut(df['薪资'], q=3, labels=['低', '中', '高'])
# === 透视表(pivot_table)===
pivot = df.pivot_table(
values='薪资',
index='部门',
columns='城市',
aggfunc='mean',
fill_value=0
)
7. 今日练习
🏋️ 练习 1:销售数据清洗
# 给定一份脏数据(含缺失值、重复行、异常值):
# 1. 统计缺失值比例
# 2. 填充/删除缺失值
# 3. 去重
# 4. 删除异常值(IQR 方法)
# 5. 输出清洗后的数据
🏋️ 练习 2:分组分析
# 对电商订单数据:
# 1. 按品类分组,计算总销售额、平均单价、订单数
# 2. 找出每个品类销量 Top3 的商品
# 3. 按月统计销售趋势
🏋️ 练习 3:员工数据处理
# 创建员工 DataFrame:
# 1. 用 apply 计算工龄等级(<3年/3-10年/>10年)
# 2. 用 pivot_table 生成部门×城市的平均薪资透视表
# 3. 按部门分组,找出每组薪资最高的人
8. 今日小结
| |
|---|
| isnull / dropna / fillna(均值/中位数/前后填充) |
| 条件筛选 / isin / between / str.contains / query |
| sort_values / sort_index / nlargest / rank |
| groupby + mean/sum/count/agg/transform |
| |
| apply / map / cut / pivot_table |
🚀 明日预告:Day 75 — Pandas 数据合并
merge(类似 SQL JOIN)、concat(拼接)、透视表——多表关联是数据分析的日常操作。明天搞定它!