在数据分析领域,Pandas 是 Python 生态系统中不可或缺的核心工具。掌握其基础语法后,进一步学习进阶功能可以显著提升数据处理效率,涵盖数据读取、清洗、合并以及聚合分析等全流程操作。本文通过实际代码示例,系统梳理 Pandas 的高级用法,适合已有基础的读者查漏补缺、深化实践能力。
DataFrame 作为 Pandas 的核心数据结构,熟练掌握其增删改查操作是高效数据处理的前提。
以下展示如何向 DataFrame 中添加新的列和行:
import pandas as pd
df = pd.DataFrame({
'name': ['张三', '李四'],
'age': [25, 30],
'salary': [15000, 20000]
})
1. **新增列(直接赋值)**
# 新增静态列
df['department'] = ['销售部', '技术部']
# 基于薪资计算奖金(10%)
df['bonus'] = df['salary'] * 0.1
2. **新增行(推荐使用 _append 或 concat)**
注意:`append` 方法已弃用,建议使用 `_append` 或 `pd.concat` 实现。
new_row = pd.DataFrame({
'name': ['王五'],
'age': [28],
'salary': [18000],
'department': ['运营部'],
'bonus': 1800
})
df = df._append(new_row, ignore_index=True) # 自动重置索引
Pandas 支持多种格式的数据输入输出,尤其在对接数据库和接口数据时表现强大。MySQL 和 JSON 是开发中常见的两种数据源,下面分别介绍其实现方式。
需先安装必要的依赖包:
pymysql
连接数据库主要借助 SQLAlchemy 提供的引擎机制,实现稳定高效的读写操作。
read_sql()
读取使用 pd.read_sql,写入则调用 to_sql 方法。
to_sql()
import pandas as pd
import pymysql
from sqlalchemy import create_engine
# 创建数据库连接引擎(支持多线程)
engine = create_engine('mysql+pymysql://用户名:密码@主机IP:端口/数据库名?charset=utf8mb4')
# 方式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)
# 写入数据至 MySQL 表
df_write = pd.DataFrame({
'name': ['张三', '李四'],
'department': ['销售部', '技术部'],
'salary': [15000, 20000]
})
df_write.to_sql(
name='employee',
con=engine,
if_exists='append', # 可选:replace, append, fail
index=False # 避免写入索引列
)
JSON 格式因其轻量与通用性,广泛应用于 API 接口和配置文件中。Pandas 提供了灵活的读写支持。
import pandas as pd
# 读取 JSON 文件
# 方式1:每行为一个独立 JSON 对象(lines=True)
df_json = pd.read_json('data.json', lines=True)
# 方式2:读取嵌套结构 JSON,需指定 orient 参数
df_nested = pd.read_json('nested_data.json', orient='records')
写入时可通过 orient 控制输出结构:
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:增加缩进,提升可读性。在真实场景中,数据常常包含缺失值(NaN),若不妥善处理,将直接影响分析结果的准确性。Pandas 提供了完整的缺失值处理机制,主要分为三大类操作:「检测」、「删除」和「填充」。
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]
})
对缺失值进行识别是数据清洗的第一步,常用方法如下:
print(df.isnull())
print(df.isnull().sum())
print(df.isnull().sum(axis=1))
print(df.isnull().sum().sum())
根据业务需求,可以选择性地移除含有缺失值的行或列:
df_drop_any = df.dropna()
df_drop_all = df.dropna(how='all')
df_drop_col = df.dropna(axis=1)
df_drop_thresh = df.dropna(thresh=2)
df_drop_subset = df.dropna(subset=['A', 'B'])
相较于直接删除,填充是一种更温和且常用的方法,尤其适用于缺失较少但信息重要的情况。
df_fill_0 = df.fillna(0) # 用0填充
df_fill_mean = df.fillna(df.mean()) # 使用各列均值填补
df_fill_median = df.fillna(df.median()) # 使用中位数填补,对异常值更稳健
# 按列名称删除
df = df.drop(columns=['bonus'], axis=1)
# 按列索引删除(如删除第3列)
df = df.drop(df.columns[2], axis=1)
# 删除指定索引的行(如第0行和第2行)
df = df.drop(index=[0, 2], axis=0)
# 条件删除:保留满足条件的行(如年龄大于28)
df = df[df['age'] > 28]
df.loc[df['name'] == '李四', 'salary'] = 21000 # 将李四的薪资改为21000
df['age'] = df['age'] + 1 # 所有人年龄增加1岁
df['department'] = df['department'].replace('销售部', '市场部') # 销售部更名为市场部
df['name'] # 获取姓名列(返回 Series)
df[['name', 'salary']] # 获取多个列(返回 DataFrame)
df[df['salary'] > 18000] # 筛选薪资高于18000的员工
df.loc[df['department'] == '技术部', ['name', 'salary']] # 标签查询:技术部员工的姓名与薪资
df.iloc[1:3, [0, 2]] # 索引查询:第2到第3行,第1和第3列
df[(df['age'] > 28) & (df['salary'] > 20000)] # 年龄大于28且薪资超过20000
当需要一次性添加多条新记录时,推荐使用 pd.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)
# 缺失值处理策略
# 1. 众数填充(适用于分类变量)
df_fill_mode = df.fillna(df.mode().iloc[0])
# 2. 前向与后向填充(适用于时间序列数据)
df_fill_ffill = df.fillna(method='ffill') # 使用前一个有效值填充
df_fill_bfill = df.fillna(method='bfill') # 使用后一个有效值填充
# 3. 限制连续填充数量
df_fill_limit = df.fillna(method='ffill', limit=1) # 每列最多向前填充1个缺失值
# 4. 多列差异化填充(灵活配置填充规则)
df_fill_dict = df.fillna({'A': 0, 'B': df['B'].mean()})
concat()
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) # 利用最近的两个样本进行填补
df_knn = pd.DataFrame(imputer.fit_transform(df), columns=df.columns)
# 4. 缺失标记法(保留缺失信息用于后续建模分析)
df_indicator = df.copy()
df_indicator['A_missing'] = df_indicator['A'].isnull().astype(int)
merge()
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)
在 Pandas 中,使用 concat 函数进行数据拼接时,可通过设置 join 参数来控制索引的处理方式。默认为 'outer'(外连接),即保留所有出现过的索引;若设为 'inner'(内连接),则仅保留两个 DataFrame 共有的索引。
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') # 仅保留共同索引 Y
该方法类似于 SQL 中的 JOIN 操作,适用于根据一个或多个关键列对多个 DataFrame 进行精确匹配和合并,具有更高的灵活性。
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']
})
常见的连接类型包括:
result_inner = pd.merge(left, right, on='key')
result_left = pd.merge(left, right, on='key', how='left')
result_right = pd.merge(left, right, on='key', how='right')
result_outer = pd.merge(left, right, on='key', how='outer')
此外,支持多键合并——基于多个字段同时匹配:
# 创建含双键的数据集
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']
})
# 内连接,基于 key1 和 key2 联合匹配
result_multi = pd.merge(left2, right2, on=['key1', 'key2'], how='inner')
| 特性 | concat() | merge() |
|---|---|---|
| 合并依据 | 基于索引或列名进行轴向堆叠 | 基于指定的关键字段进行匹配 |
| 灵活性 | 较低,主要用于垂直或水平拼接 | 较高,支持多种连接模式及复杂条件 |
| 内存效率 | 相对更高,操作更直接 | 略低,因需建立映射关系 |
| 适用场景 | 结构一致的数据堆叠、时间序列拼接等 | 关系型数据整合,如客户-订单关联分析 |
| 语法复杂度 | 简单直观 | 较复杂,但功能强大 |
在实际应用中,应根据需求选择合适的合并方式:
concat()merge()对于大规模数据处理:
concat()merge()sort=Falseleft_index=True, right_index=True分组聚合(Group Aggregation)与分组过滤(Group Filtering)是数据分析中的核心操作,遵循“拆分-应用-合并”(split-apply-combine)范式:先按某维度拆分数据,再对每组执行计算或筛选,最后汇总结果。
用于对各分组执行统计函数,如求均值、总和、计数等,基本语法结构如下:
groupby() + agg()
import pandas as pd
# 示例数据构建
df = pd.DataFrame({
"部门": ["销售", "销售", "技术", "技术", "运营", "运营"],
"姓名": ["张三", "李四", "王五", "赵六", "孙七", "周八"],
"月薪": [15000, 18000, 20000, 25000, 12000, 14000],
"出勤天数": [22, 23, 21, 22, 23, 24]
})
通过部门字段进行数据分组,并对“月薪”列执行均值聚合,获取每个部门的平均薪资水平:
dept_salary_mean = df.groupby("部门")["月薪"].mean()
在按部门分组的基础上,同时对“月薪”和“出勤天数”两个字段进行多种统计运算。其中,“月薪”计算均值、总和与最大值;“出勤天数”则计算均值及有效记录数量(即该部门人数):
dept_agg = df.groupby("部门").agg({
"月薪": ["mean", "sum", "max"],
"出勤天数": ["mean", "count"]
})
首先为数据添加“性别”字段,随后以“部门”和“性别”作为联合分组依据,计算各组合下的平均月薪:
df["性别"] = ["男", "女", "男", "男", "女", "女"]
dept_gender_agg = df.groupby(["部门", "性别"])["月薪"].mean()
定义一个函数用于计算一组数据中中位数与均值之间的差值,再应用于按部门分组后的月薪数据,揭示分布偏移情况:
def median_mean_diff(x):
return x.median() - x.mean()
dept_diff = df.groupby("部门")["月薪"].agg(median_mean_diff)
将多层列名扁平化并赋予更具可读性的名称,同时将索引中的分组字段还原为普通列,便于后续使用:
dept_agg.columns = ["月薪均值", "月薪总和", "月薪最高", "出勤均值", "出勤人数"]
dept_agg.reset_index(inplace=True)
| 函数 | 功能 | 适用场景 |
|---|---|---|
| mean() | 平均值 | 数值型数据(如薪资、分数等) |
| sum() | 总和 | 可用于累加的数据类型(如销售额、工作时长) |
| count() | 非空值的数量 | 衡量有效数据条目数 |
| size() | 分组内总行数(含缺失值) | 统计每组包含的记录总数 |
| max()/min() | 最大值 / 最小值 | 适用于数值或日期类字段 |
| median() | 中位数 | 减少异常值影响的集中趋势度量 |
| std()/var() | 标准差 / 方差 | 评估数据波动性或离散程度 |
| nunique() | 唯一值个数 | 统计分组中不同类别的数量,例如用户数、产品种类等 |
分组过滤是基于整个分组的聚合特征来决定是否保留该组所有成员的一种筛选方式。其核心在于判断条件作用于整体而非单条记录。
groupby() + filter()
df_filter_size = df.groupby("部门").filter(lambda x: len(x) >= 2)
df_filter_salary = df.groupby("部门").filter(lambda x: x["月薪"].mean() >= 15000)
df_filter_multi = df.groupby("部门").filter(
lambda x: (x["月薪"].mean() >= 15000) & (x["出勤天数"].mean() >= 22)
)
| 操作 | 筛选依据 | 结果保留 | 示例代码 |
|---|---|---|---|
| 普通行过滤 | 单条记录自身的属性 | 符合条件的独立行 | df[df[“月薪”] >= 15000] |
| 分组过滤 | 整个分组的聚合特性 | 满足条件的完整分组(包括组内所有行) | df.groupby(“部门”).filter(lambda x: x[“月薪”].mean()>=15000) |
透视表(pivot_table)和交叉表(crosstab)是对分组聚合操作的增强版表达形式,更适合生成结构清晰、易于阅读的报表,广泛应用于数据汇总与可视化前处理。
主要用途:支持多维度数值聚合,允许指定聚合函数、处理缺失值填充,灵活性强,适合复杂报表构建。
import pandas as pd
# 示例数据(沿用已有部门信息)
df = pd.DataFrame({
"部门": ["销售", "销售", "技术", "技术", "技术", "运营", "运营"],
"姓名": ["张三", "李四", "王五", "赵六", "钱九", "孙七", "周八"],
"性别": ["男", "女", "男", "男", "女", "女", "女"],
"月薪": [15000, 18000, 20000, 25000, 19000, 12000, 14000],
"出勤天数": [22, 23, 21, 22, 24, 23, 24]
})
pivot1 = pd.pivot_table(
df,
values="月薪",
index="部门",
columns="性别",
aggfunc="mean",
fill_value=0
)
pivot2 = pd.pivot_table(
df,
values="月薪",
主要用途:用于分类数据的频次统计,默认执行计数操作,支持归一化处理(即计算占比),语法结构简洁明了。
1. 基础交叉表(统计各部门中不同性别的员工人数)
crosstab1 = pd.crosstab(
index=df["部门"],
columns=df["性别"],
rownames=["部门"],
colnames=["性别"],
margins=True, # 显示行和列的总计
margins_name="总计"
)
2. 归一化处理(按行计算比例,每行总和为1)
crosstab2 = pd.crosstab(
df["部门"],
df["性别"],
normalize="index", # 按行进行归一化
margins=True
).round(2) * 100 # 结果保留两位小数并转换为百分比形式
3. 对数值列进行聚合(功能类似透视表,但灵活性较低)
crosstab3 = pd.crosstab(
df["部门"],
df["性别"],
values=df["月薪"],
aggfunc="mean",
fill_value=0,
margins=True
)
1. 基本聚合操作(以月薪均值为例)
pivot1 = pd.pivot_table(
df,
values="月薪",
index="部门",
columns="性别",
aggfunc="mean",
fill_value=0,
margins=True,
margins_name="部门总计"
)
2. 多指标聚合分析(同时统计月薪与出勤天数)
pivot3 = pd.pivot_table(
df,
values=["月薪", "出勤天数"],
index="部门",
columns="性别",
aggfunc={
"月薪": ["mean", "sum"],
"出勤天数": "mean"
},
fill_value=0
)
3. 列名扁平化处理(解决多层列索引问题)
pivot3.columns = ["_".join(col).strip() for col in pivot3.columns.values] pivot3.reset_index(inplace=True)
4. 使用自定义聚合函数(例如取中位数)
def my_agg(x):
return x.median()
pivot5 = pd.pivot_table(
df,
values="月薪",
index="部门",
columns="性别",
aggfunc=my_agg,
fill_value=0
)
| 需求场景 | 优先选择 | 原因 |
|---|---|---|
| 分类数据的频次或占比统计 | 交叉表(crosstab) | 语法更简洁,原生支持归一化计算 |
| 多个指标聚合(如均值、总和等组合) | 透视表(pivot_table) | 支持对不同字段设置不同的聚合函数 |
| 涉及两个及以上维度的分组分析 | 透视表(pivot_table) | 配置方式更加灵活,扩展性强 |
| 需要实现复杂或自定义的聚合逻辑 | 透视表(pivot_table) | 允许传入任意自定义函数进行计算 |
Pandas 的进阶用法贯穿于「数据读写 → 数据清洗 → 数据合并 → 数据分析」的完整流程,关键要点归纳如下:
read_sql() / to_sql() 连接 MySQL 数据库,或通过 read_json() / to_json() 加载 JSON 格式文件。concat();若基于关键字段进行关联匹配,则选用 merge()。groupby()+agg(),若需筛选特定分组结果,则使用 filter()。实战建议:结合真实业务场景中的数据(如销售记录、用户行为日志等)反复练习,重点理解语法背后的逻辑结构,避免机械记忆。遇到问题时,应查阅 Pandas 官方文档或逐步调试代码排查错误。
扫码加好友,拉您进群



收藏
