Pandas数据分析 -- Pandas进阶语法全攻略:从数据处理到复杂分析实战 - 教程
Pandas进阶语法全攻略:从数据处理到复杂分析实战
在数据分析领域,Pandas 是Python生态中不可或缺的核心工具。基础语法掌握后,进阶功能能大幅提升数据处理效率,覆盖从数据读写、清洗、合并到聚合分析的全流程。本文结合实战代码,系统拆解 Pandas 进阶核心语法,适合有基础的同学查漏补缺、深化应用。
一、文件读取与存储:多源数据无缝对接
Pandas 支持多种数据格式的读写操作,其中 MySQL 数据库和 JSON 文件是实际开发中高频使用的场景,以下是具体实现方案。
1.1 读写 MySQL 数据库
需提前安装依赖包 pymysql(用于数据库连接),核心通过 read_sql() 读取数据,to_sql() 写入数据。
import pandas as pd
import pymysql
from sqlalchemy import create_engine
# 1. 数据库连接配置(SQLAlchemy 引擎,支持多线程)
engine = create_engine('mysql+pymysql://用户名:密码@主机IP:端口/数据库名?charset=utf8mb4')
# 2. 读取 MySQL 表数据
# 方式1:读取整个表
df_read = pd.read_sql('表名', con=engine)
# 方式2:执行SQL查询语句读取
sql = "SELECT id, name, salary FROM employee WHERE department='技术部'"
df_sql = pd.read_sql(sql, con=engine)
# 3. 写入 MySQL 表
# if_exists 参数:replace(覆盖)、append(追加)、fail(存在则报错)
df_write = pd.DataFrame({
'name': ['张三', '李四'],
'department': ['销售部', '技术部'],
'salary': [15000, 20000]
})
df_write.to_sql(
name='employee', # 目标表名
con=engine,
if_exists='append',
index=False # 不写入索引列
)
1.2 读写 JSON 文件
JSON 作为轻量级数据格式,广泛用于接口数据交互,Pandas 提供简洁的读写方法。
import pandas as pd
# 1. 读取 JSON 文件
# 方式1:标准 JSON 格式(每行一个 JSON 对象)
df_json = pd.read_json('data.json', lines=True)
# 方式2:读取嵌套 JSON(需指定 orient 参数)
df_nested = pd.read_json('nested_data.json', orient='records')
# 2. 写入 JSON 文件
# orient 参数:records(每行一个对象,推荐)、index(以索引为键)、columns(以列为键)
df = pd.DataFrame({
'product': ['手机', '电脑', '平板'],
'price': [3999, 5999, 2999],
'stock': [100, 50, 80]
})
# 写入标准 JSON(便于后续读取)
df.to_json('output.json', orient='records', force_ascii=False, indent=2)
# force_ascii=False:保留中文;indent=2:格式化输出,增强可读性
二、DataFrame 数据增删改查:数据操作核心
DataFrame 是 Pandas 核心数据结构,掌握其增删改查操作是数据处理的基础。
2.1 新增数据
import pandas as pd
df = pd.DataFrame({
'name': ['张三', '李四'],
'age': [25, 30],
'salary': [15000, 20000]
})
# 1. 新增列(直接赋值)
df['department'] = ['销售部', '技术部'] # 新增部门列
df['bonus'] = df['salary'] * 0.1 # 基于现有列计算新增
# 2. 新增行(append 已过时,推荐 _append 或 concat)
new_row = pd.DataFrame({'name': ['王五'], 'age': [28], 'salary': [18000], 'department': ['运营部'], 'bonus': 1800})
df = df._append(new_row, ignore_index=True) # ignore_index:重置索引
# 3. 批量新增行(concat 效率更高)
new_rows = pd.DataFrame({
'name': ['赵六', '孙七'],
'age': [32, 27],
'salary': [22000, 16000],
'department': ['技术部', '销售部'],
'bonus': [2200, 1600]
})
df = pd.concat([df, new_rows], ignore_index=True)
2.2 删除数据
# 1. 删除列(drop 方法,axis=1 表示列)
df = df.drop(columns=['bonus'], axis=1) # 按列名删除
df = df.drop(df.columns[2], axis=1) # 按列索引删除(删除第3列)
# 2. 删除行(axis=0 表示行,默认)
df = df.drop(index=[0, 2], axis=0) # 按索引删除
df = df[df['age'] > 28] # 按条件删除(保留年龄>28的行)
2.3 修改数据
# 1. 按条件修改单个值
df.loc[df['name'] == '李四', 'salary'] = 21000 # 李四薪资改为21000
# 2. 批量修改列值
df['age'] = df['age'] + 1 # 所有人年龄+1
# 3. 替换值(replace 方法)
df['department'] = df['department'].replace('销售部', '市场部') # 销售部改为市场部
2.4 查询数据
# 1. 基础查询(按列、按条件)
df['name'] # 查询单个列(返回 Series)
df[['name', 'salary']] # 查询多个列(返回 DataFrame)
df[df['salary'] > 18000] # 条件查询(薪资>18000)
# 2. 高级查询(loc/iloc 精准定位)
df.loc[df['department'] == '技术部', ['name', 'salary']] # 按标签查询(部门=技术部的姓名和薪资)
df.iloc[1:3, [0, 2]] # 按索引查询(第2-3行,第1、3列)
# 3. 多条件查询(& 且、| 或,注意括号)
df[(df['age'] > 28) & (df['salary'] > 20000)] # 年龄>28 且 薪资>20000
三、缺失值处理:数据清洗关键步骤
真实数据常存在缺失值(NaN),处理不当会影响分析结果。Pandas 提供完整的缺失值处理方案,核心分为「检测」「删除」「填充」三类操作。
3.1 检测缺失值
import pandas as pd
import numpy as np
# 创建示例数据
df = pd.DataFrame({
'A': [1, 2, np.nan, 4],
'B': [5, np.nan, np.nan, 8],
'C': [9, 10, 11, 12]
})
# 1. 检查每个值是否为缺失值(返回布尔矩阵)
print(df.isnull())
# 2. 统计每列缺失值数量(常用)
print(df.isnull().sum())
# 3. 统计每行缺失值数量
print(df.isnull().sum(axis=1))
# 4. 统计总缺失值数量
print(df.isnull().sum().sum())
3.2 删除缺失值
# 1. 删除包含任何缺失值的行(默认 axis=0)
df_drop_any = df.dropna()
# 2. 只删除全为缺失值的行
df_drop_all = df.dropna(how='all')
# 3. 删除包含缺失值的列(axis=1)
df_drop_col = df.dropna(axis=1)
# 4. 保留至少有2个非缺失值的行(thresh 阈值)
df_drop_thresh = df.dropna(thresh=2)
# 5. 删除特定列包含缺失值的行(subset 指定列)
df_drop_subset = df.dropna(subset=['A', 'B'])
3.3 填充缺失值
# 1. 固定值填充
df_fill_0 = df.fillna(0) # 用0填充
# 2. 统计值填充(适合数值型数据)
df_fill_mean = df.fillna(df.mean()) # 均值填充
df_fill_median = df.fillna(df.median()) # 中位数填充(抗异常值)
df_fill_mode = df.fillna(df.mode().iloc[0]) # 众数填充(适合分类数据)
# 3. 前后值填充(适合时间序列)
df_fill_ffill = df.fillna(method='ffill') # 前向填充(用前一个值)
df_fill_bfill = df.fillna(method='bfill') # 后向填充(用后一个值)
# 4. 限制填充数量
df_fill_limit = df.fillna(method='ffill', limit=1) # 每行最多填充1个缺失值
# 5. 不同列用不同值填充(灵活配置)
df_fill_dict = df.fillna({'A': 0, 'B': df['B'].mean()})
3.4 高级处理技巧
# 1. 条件填充(根据其他列逻辑填充)
df_conditional = df.copy()
df_conditional.loc[df_conditional['A'].isnull(), 'A'] = df_conditional['C'].mean()
# 2. 分组填充(按分组统计量填充)
df_group_fill = df.copy()
df_group_fill['A'] = df_group_fill.groupby('B')['A'].transform(lambda x: x.fillna(x.mean()))
# 3. KNN填充(基于相似性填充,需安装 sklearn)
from sklearn.impute import KNNImputer
imputer = KNNImputer(n_neighbors=2) # 取2个最近邻的均值
df_knn = pd.DataFrame(imputer.fit_transform(df), columns=df.columns)
# 4. 标记缺失值(保留缺失信息用于分析)
df_indicator = df.copy()
df_indicator['A_missing'] = df_indicator['A'].isnull()
3.5 实战案例(泰坦尼克号数据)
# 加载数据
titanic = pd.read_csv('titanic.csv')
# 查看缺失值情况
print(titanic.isnull().sum())
# 策略性处理
titanic['Age'].fillna(titanic['Age'].median(), inplace=True) # 年龄用中位数填充
titanic['Embarked'].fillna(titanic['Embarked'].mode()[0], inplace=True) # 登船口用众数填充
titanic.drop('Cabin', axis=1, inplace=True) # 客舱列缺失过多,直接删除
# 验证处理结果
print(titanic.isnull().sum())
3.6 关键注意事项
- 填充前需理解数据业务含义,避免无意义填充(如用均值填充分类数据)。
- 数值型数据优先用中位数(抗异常值),分类数据用众数。
- 时间序列数据适合前向/后向填充或插值。
- 谨慎删除数据,避免信息丢失,优先尝试填充。
四、数据合并:Concat 与 Merge 详解
数据合并是多源数据整合的核心操作,Pandas 提供 concat()(拼接)和 merge()(关联)两种核心方法,适用场景不同。
4.1 Concat:数据堆叠(基于索引/列名)
核心用途:沿轴方向(行/列)拼接多个结构相似的 DataFrame,类似「堆叠」操作。
import pandas as pd
# 创建示例数据
df1 = pd.DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6]}, index=['X', 'Y', 'Z'])
df2 = pd.DataFrame({'A': [7, 8, 9], 'B': [10, 11, 12]}, index=['U', 'V', 'W'])
df3 = pd.DataFrame({'C': [13, 14, 15], 'D': [16, 17, 18]}, index=['X', 'Y', 'Z'])
# 1. 纵向拼接(默认 axis=0,行堆叠)
result_v = pd.concat([df1, df2])
# 2. 横向拼接(axis=1,列堆叠)
result_h = pd.concat([df1, df3], axis=1)
# 3. 忽略索引(重置为连续索引)
result_ignore = pd.concat([df1, df2], ignore_index=True)
# 4. 连接方式(outer 外连接默认,inner 内连接)
df4 = pd.DataFrame({'A': [1, 2], 'B': [4, 5]}, index=['X', 'Y'])
df5 = pd.DataFrame({'A': [3, 4], 'B': [6, 7]}, index=['Y', 'Z'])
result_outer = pd.concat([df4, df5], join='outer') # 保留所有索引
result_inner = pd.concat([df4, df5], join='inner') # 只保留共同索引
4.2 Merge:数据库风格关联(基于关键字段)
核心用途:基于一个或多个关键字段(key)关联多个 DataFrame,类似 SQL 的 JOIN 操作,灵活性更高。
import pandas as pd
# 创建示例数据
left = pd.DataFrame({
'key': ['K0', 'K1', 'K2', 'K3'],
'A': ['A0', 'A1', 'A2', 'A3'],
'B': ['B0', 'B1', 'B2', 'B3']
})
right = pd.DataFrame({
'key': ['K0', 'K1', 'K2', 'K4'],
'C': ['C0', 'C1', 'C2', 'C3'],
'D': ['D0', 'D1', 'D2', 'D3']
})
# 1. 内连接(默认 how='inner',只保留共同 key)
result_inner = pd.merge(left, right, on='key')
# 2. 左连接(how='left',保留左表所有数据)
result_left = pd.merge(left, right, on='key', how='left')
# 3. 右连接(how='right',保留右表所有数据)
result_right = pd.merge(left, right, on='key', how='right')
# 4. 外连接(how='outer',保留所有数据)
result_outer = pd.merge(left, right, on='key', how='outer')
# 5. 多键合并(基于多个字段关联)
left2 = pd.DataFrame({
'key1': ['K0', 'K0', 'K1', 'K2'],
'key2': ['K0', 'K1', 'K0', 'K1'],
'A': ['A0', 'A1', 'A2', 'A3'],
'B': ['B0', 'B1', 'B2', 'B3']
})
right2 = pd.DataFrame({
'key1': ['K0', 'K1', 'K1', 'K2'],
'key2': ['K0', 'K0', 'K0', 'K0'],
'C': ['C0', 'C1', 'C2', 'C3'],
'D': ['D0', 'D1', 'D2', 'D3']
})
result_multi = pd.merge(left2, right2, on=['key1', 'key2'], how='inner')
4.3 核心区别对比
| 特性 | concat() | merge() |
|---|---|---|
| 合并方式 | 基于索引或列名 | 基于关键字段 |
| 灵活性 | 较低(仅堆叠) | 较高(支持复杂关联) |
| 内存效率 | 较高 | 较低 |
| 适用场景 | 简单数据堆叠、时间序列拼接 | 关系型数据关联、复杂匹配 |
| 语法复杂度 | 简单 | 相对复杂 |
4.4 最佳实践
- 优先用
concat():数据结构相同、需简单堆叠(如时间序列拼接、特征矩阵合并)。 - 优先用
merge():需基于关键字段匹配(如客户表与订单表关联)。 - 性能优化:大数据集用
concat();merge()可设置sort=False提高速度,或用索引合并(left_index=True, right_index=True)。
五、分组聚合与过滤:数据筛查核心
分组聚合(Group Aggregation)和分组过滤(Group Filtering)是数据分析的核心手段,遵循「split-apply-combine」逻辑:拆分数据→应用操作→合并结果。
5.1 分组聚合(Group Aggregation)
对分组后的数据执行统计计算(如均值、总和),核心语法 groupby() + agg()。
import pandas as pd
# 创建示例数据
df = pd.DataFrame({
"部门": ["销售", "销售", "技术", "技术", "运营", "运营"],
"姓名": ["张三", "李四", "王五", "赵六", "孙七", "周八"],
"月薪": [15000, 18000, 20000, 25000, 12000, 14000],
"出勤天数": [22, 23, 21, 22, 23, 24]
})
# 1. 单分组+单聚合(按部门统计月薪均值)
dept_salary_mean = df.groupby("部门")["月薪"].mean()
# 2. 单分组+多聚合(按部门统计月薪和出勤天数的多个指标)
dept_agg = df.groupby("部门").agg({
"月薪": ["mean", "sum", "max"], # 月薪:均值、总和、最大值
"出勤天数": ["mean", "count"] # 出勤天数:均值、分组人数
})
# 3. 多分组+聚合(按部门+性别分组,统计月薪均值)
df["性别"] = ["男", "女", "男", "男", "女", "女"]
dept_gender_agg = df.groupby(["部门", "性别"])["月薪"].mean()
# 4. 自定义聚合函数(计算月薪中位数与均值的差值)
def median_mean_diff(x):
return x.median() - x.mean()
dept_diff = df.groupby("部门")["月薪"].agg(median_mean_diff)
# 5. 聚合结果重命名(扁平化列名)
dept_agg.columns = ["月薪均值", "月薪总和", "月薪最高", "出勤均值", "出勤人数"]
dept_agg.reset_index(inplace=True) # 分组列从索引转为普通列
5.2 常用聚合函数速查表
| 函数 | 功能 | 适用场景 |
|---|---|---|
| mean() | 平均值 | 数值型数据(薪资、分数) |
| sum() | 总和 | 可累加数据(销售额、时长) |
| count() | 非空值数量 | 统计有效数据量 |
| size() | 分组总数量(含空值) | 统计分组行数 |
| max()/min() | 最大值/最小值 | 数值型、日期型数据 |
| median() | 中位数 | 避免异常值影响的统计 |
| std()/var() | 标准差/方差 | 数据离散程度分析 |
| nunique() | 唯一值数量 | 统计分组类别数(如用户数) |
5.3 分组过滤(Group Filtering)
根据分组的整体属性筛选行,保留满足条件的整个分组,核心语法 groupby() + filter()。
# 1. 按分组大小过滤(保留人数≥2的部门)
df_filter_size = df.groupby("部门").filter(lambda x: len(x) >= 2)
# 2. 按分组统计量过滤(保留平均月薪≥15000的部门)
df_filter_salary = df.groupby("部门").filter(lambda x: x["月薪"].mean() >= 15000)
# 3. 多条件过滤(保留平均月薪≥15000且平均出勤≥22的部门)
df_filter_multi = df.groupby("部门").filter(
lambda x: (x["月薪"].mean() >= 15000) & (x["出勤天数"].mean() >= 22)
)
5.4 分组过滤 vs 普通行过滤
| 操作 | 筛选依据 | 结果保留 | 示例代码 |
|---|---|---|---|
| 普通行过滤 | 单条行的属性 | 满足条件的单条行 | df[df[“月薪”] >= 15000] |
| 分组过滤 | 整个分组的聚合属性 | 满足条件的整个分组 | df.groupby(“部门”).filter(lambda x: x[“月薪”].mean()>=15000) |
六、透视表与交叉表:多维度分析可视化
透视表(pivot_table)和交叉表(crosstab)是分组聚合的「可视化友好版」,适合生成结构化报表,可读性更强。
6.1 透视表(pivot_table):重点掌握
核心用途:多维度数值型数据聚合,支持自定义函数、缺失值填充,灵活性高。
import pandas as pd
# 示例数据(沿用部门数据)
df = pd.DataFrame({
"部门": ["销售", "销售", "技术", "技术", "技术", "运营", "运营"],
"姓名": ["张三", "李四", "王五", "赵六", "钱九", "孙七", "周八"],
"性别": ["男", "女", "男", "男", "女", "女", "女"],
"月薪": [15000, 18000, 20000, 25000, 19000, 12000, 14000],
"出勤天数": [22, 23, 21, 22, 24, 23, 24]
})
# 1. 基础透视表(行:部门,列:性别,值:月薪,聚合:均值)
pivot1 = pd.pivot_table(
df,
values="月薪",
index="部门",
columns="性别",
aggfunc="mean",
fill_value=0 # 缺失值填充为0
)
# 2. 显示总计行/列
pivot2 = pd.pivot_table(
df,
values="月薪",
index="部门",
columns="性别",
aggfunc="mean",
fill_value=0,
margins=True, # 显示总计
margins_name="部门总计"
)
# 3. 多指标聚合(同时统计月薪和出勤天数)
pivot3 = pd.pivot_table(
df,
values=["月薪", "出勤天数"],
index="部门",
columns="性别",
aggfunc={
"月薪": ["mean", "sum"],
"出勤天数": "mean"
},
fill_value=0
)
# 4. 列名扁平化(解决多指标导致的层级列名)
pivot3.columns = ["_".join(col).strip() for col in pivot3.columns.values]
pivot3.reset_index(inplace=True)
# 5. 自定义聚合函数
def my_agg(x):
return x.median()
pivot5 = pd.pivot_table(
df,
values="月薪",
index="部门",
columns="性别",
aggfunc=my_agg,
fill_value=0
)
6.2 交叉表(crosstab):了解即可
核心用途:分类数据的频次统计(默认计数),支持归一化(占比计算),语法简洁。
# 1. 基础交叉表(统计各部门各性别人数)
crosstab1 = pd.crosstab(
index=df["部门"],
columns=df["性别"],
rownames=["部门"],
colnames=["性别"],
margins=True, # 显示总计
margins_name="总计"
)
# 2. 归一化(计算每行占比)
crosstab2 = pd.crosstab(
df["部门"],
df["性别"],
normalize="index", # 按行归一化(行占比和为1)
margins=True
).round(2) * 100 # 转为百分比
# 3. 数值列聚合(类似透视表,灵活性较差)
crosstab3 = pd.crosstab(
df["部门"],
df["性别"],
values=df["月薪"],
aggfunc="mean",
fill_value=0,
margins=True
)
6.3 透视表 vs 交叉表:怎么选?
| 需求场景 | 优先选择 | 原因 |
|---|---|---|
| 分类数据频次/占比统计 | 交叉表(crosstab) | 语法简洁,默认支持归一化 |
| 多指标聚合(均值+总和等) | 透视表(pivot_table) | 支持不同指标自定义函数 |
| 多维度分组(≥2个维度) | 透视表(pivot_table) | 配置更灵活 |
| 复杂自定义聚合逻辑 | 透视表(pivot_table) | 支持任意自定义函数 |
七、总结与实战建议
Pandas 进阶语法围绕「数据读写→清洗→合并→分析」的全流程展开,核心要点如下:
- 数据读写:根据数据源选择
read_sql()/to_sql()(MySQL)或read_json()/to_json()(JSON)。 - 缺失值处理:优先填充(均值/中位数/众数/KNN),谨慎删除,必要时标记缺失值。
- 数据合并:简单堆叠用
concat(),关键字段关联用merge()。 - 分组分析:聚合用
groupby()+agg(),筛选分组用filter()。 - 报表生成:多维度数值分析用透视表,分类频次统计用交叉表。
实战建议:结合真实业务数据(如销售数据、用户行为数据)反复练习,重点关注「语法逻辑」而非死记硬背,遇到问题时查看 Pandas 官方文档或调试代码逐步排查。
浙公网安备 33010602011771号