Pandas: 拼接追加
最后更新:2026-08-26
merge 是"横向关联"(按键匹配列),concat 是"纵向/横向拼接"(堆叠行或列)。12 个月的销售数据合并成一张年表、3 个分公司的报表拼成总表——都是 concat 的活。本节覆盖 concat 的轴向、索引、对齐和 keys 多层级,以及与 merge 的选择策略。
⚠️ 注意: 以下代码需在本地 Python 环境中运行。
1. 你将学到
- ❶ concat 沿轴拼接
- ❷ axis=0 vs axis=1
- ❸ ignore_index 重建索引
- ❹ keys 多层级标签
- ❺ join 对齐策略
2. Charlie 的 12 月数据拼接
(1) 痛点:12 个 CSV 逐个读
Charlie 有 12 个月的销售 CSV,手动拼太繁琐:
PYTHON
import pandas as pd
# Imagine 12 separate DataFrames
jan = pd.DataFrame({'date': ['2024-01-15'], 'sales': [5000]})
feb = pd.DataFrame({'date': ['2024-02-20'], 'sales': [4500]})
mar = pd.DataFrame({'date': ['2024-03-10'], 'sales': [5200]})
# ... 9 more months
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
(2) 解法:concat 一次拼接
▶ 示例:concat 纵向拼接(难度⭐)
PYTHON
import pandas as pd
jan = pd.DataFrame({'date': ['2024-01-15'], 'sales': [5000]})
feb = pd.DataFrame({'date': ['2024-02-20'], 'sales': [4500]})
mar = pd.DataFrame({'date': ['2024-03-10'], 'sales': [5200]})
# Stack vertically (axis=0 is default)
year = pd.concat([jan, feb, mar], ignore_index=True)
print(year)
# date sales
# 0 2024-01-15 5000
# 1 2024-02-20 4500
# 2 2024-03-10 5200
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
3. concat 基础参数
(1) 纵向 vs 横向
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
:axis=0 vs axis=1(难度⭐)
PYTHON
import pandas as pd
df1 = pd.DataFrame({'A': [1, 2], 'B': [3, 4]})
df2 = pd.DataFrame({'A': [5, 6], 'B': [7, 8]})
# axis=0: stack rows (vertical)
print("axis=0 (vertical):")
print(pd.concat([df1, df2], axis=0))
# A B
# 0 1 3
# 1 2 4
# 0 5 7 ← index repeats!
# 1 6 8
# axis=1: stack columns (horizontal)
print("\naxis=1 (horizontal):")
print(pd.concat([df1, df2], axis=1))
# A B A B
# 0 1 3 5 7
# 1 2 4 6 8
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
(2) ignore_index 重建索引
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
:ignore_index 重建索引(难度⭐)
PYTHON
import pandas as pd
df1 = pd.DataFrame({'A': [1, 2]})
df2 = pd.DataFrame({'A': [3, 4]})
df3 = pd.DataFrame({'A': [5, 6]})
# Without ignore_index — original indices preserved (gaps possible)
result1 = pd.concat([df1, df2, df3])
print(result1.index.tolist()) # [0, 1, 0, 1, 0, 1] — duplicate!
# With ignore_index — clean sequential index
result2 = pd.concat([df1, df2, df3], ignore_index=True)
print(result2.index.tolist()) # [0, 1, 2, 3, 4, 5] — clean!
print(result2)
# A
# 0 1
# 1 2
# 2 3
# 3 4
# 4 5
# 5 6
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
4. keys 多层级标签
(1) 标记数据来源
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
:keys 标记来源(难度⭐⭐)
PYTHON
import pandas as pd
q1 = pd.DataFrame({'sales': [100, 200, 300]})
q2 = pd.DataFrame({'sales': [150, 250, 350]})
q3 = pd.DataFrame({'sales': [180, 280, 380]})
# keys creates MultiIndex — trace which DataFrame each row came from
result = pd.concat([q1, q2, q3], keys=['Q1', 'Q2', 'Q3'])
print(result)
# sales
# Q1 0 100
# 1 200
# 2 300
# Q2 0 150
# 1 250
# 2 350
# Q3 0 180
# 1 280
# 2 380
# Select by key
print(result.loc['Q2'])
# sales
# 0 150
# 1 250
# 2 350
# Add source column instead of MultiIndex
result2 = pd.concat([q1, q2, q3], keys=['Q1', 'Q2', 'Q3'], names=['quarter'])
result2 = result2.reset_index(level=0)
print(result2)
# quarter sales
# 0 Q1 100
# 1 Q1 200
# ...
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
5. join 对齐策略
(1) 列名不完全一致时
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
:join='outer' vs 'inner'(难度⭐⭐)
PYTHON
import pandas as pd
df1 = pd.DataFrame({'A': [1, 2], 'B': [3, 4], 'C': [5, 6]})
df2 = pd.DataFrame({'B': [7, 8], 'C': [9, 10], 'D': [11, 12]})
# join='outer' (default) — keep all columns, fill NaN
print("outer:")
print(pd.concat([df1, df2], axis=0, join='outer'))
# A B C D
# 0 1.0 3 5 NaN
# 1 2.0 4 6 NaN
# 0 NaN 7 9 11.0
# 1 NaN 8 10 12.0
# join='inner' — only shared columns
print("\ninner:")
print(pd.concat([df1, df2], axis=0, join='inner'))
# B C
# 0 3 5
# 1 4 6
# 0 7 9
# 1 8 10
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
6. concat vs merge
| 特性 | concat | merge |
|---|---|---|
| 操作 | 堆叠(纵向/横向) | 关联(按键匹配) |
| 键 | 不需要 | 需要 on/left_on |
| 行数 | 源行数之和(axis=0) | ≤源行数之和 |
| 列数 | 源列数之和(axis=1) | 源列并集 |
| 适用 | 相同结构的数据堆叠 | 不同结构的数据关联 |
| 典型 | 12月拼成年表 | 用户+订单关联 |
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
:何时用 concat vs merge(难度⭐⭐)
PYTHON
import pandas as pd
# Use CONCAT: same structure, stack vertically
jan_sales = pd.DataFrame({'product': ['A', 'B'], 'sales': [100, 200]})
feb_sales = pd.DataFrame({'product': ['A', 'B'], 'sales': [150, 250]})
annual = pd.concat([jan_sales, feb_sales], keys=['Jan', 'Feb'])
# Use MERGE: different structures, link by key
products = pd.DataFrame({'product': ['A', 'B'], 'price': [10, 20]})
categories = pd.DataFrame({'product': ['A', 'B'], 'category': ['Electronics', 'Home']})
enriched = pd.merge(products, categories, on='product')
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
7. 完整示例:多源数据拼接分析
(5) ▶ concat 拼接类型
graph TB
A[多个 DataFrame] --> B{拼接方向?}
B -->|纵向 axis=0| C[上下堆叠行]
B -->|横向 axis=1| D[左右并排列]
C --> E{列名一致?}
E -->|是| F[完美拼接]
E -->|否| G[join='outer' 补 NaN / join='inner' 取交集]
D --> H{行索引一致?}
H -->|是| I[对齐拼接]
H -->|否| J[交叉填充 NaN]
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
:多源拼接全流程(难度⭐⭐⭐)
PYTHON
import pandas as pd
import numpy as np
# ============================================
# Comprehensive example: Multi-source concat
# 3 regional reports → annual analysis
# ============================================
# 1. Regional monthly reports
np.random.seed(42)
regions = {
'North': pd.DataFrame({
'month': pd.date_range('2024-01', periods=6, freq='MS'),
'sales': np.random.randint(3000, 8000, 6),
'orders': np.random.randint(50, 150, 6)
}),
'South': pd.DataFrame({
'month': pd.date_range('2024-01', periods=6, freq='MS'),
'sales': np.random.randint(2000, 6000, 6),
'orders': np.random.randint(30, 120, 6)
}),
'East': pd.DataFrame({
'month': pd.date_range('2024-01', periods=6, freq='MS'),
'sales': np.random.randint(1500, 5000, 6),
'orders': np.random.randint(20, 100, 6)
})
}
# 2. Concat with keys to mark region
all_data = pd.concat(regions, names=['region', 'idx'])
all_data = all_data.reset_index(level=0).reset_index(drop=True)
print(f"Total rows: {len(all_data)}")
# 3. Add derived columns
all_data['avg_order_value'] = (all_data['sales'] / all_data['orders']).round(2)
# 4. Analyze by region
region_summary = all_data.groupby('region').agg(
total_sales=('sales', 'sum'),
total_orders=('orders', 'sum'),
avg_monthly_sales=('sales', 'mean')
).round(0)
print("\n=== Region Summary ===")
print(region_summary)
# 5. Monthly trend across all regions
monthly = all_data.groupby('month')['sales'].sum()
print(f"\nPeak month: {monthly.idxmax().strftime('%Y-%m')}")
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
❓ 常见问题
Q concat 和 merge 区别?
A concat 是堆叠——纵向拼行或横向拼列,不需要匹配键。merge 是关联——按键匹配两表行,类似 SQL JOIN。结构相同的多个表拼成一张→concat。不同结构的表按某列关联→merge。口诀:"相同堆叠用 concat,不同关联用 merge"。
Q axis=0 和 axis=1 效果?
A axis=0(默认)纵向拼行——上下堆叠,行数增加。axis=1 横向拼列——左右并排,列数增加。纵向拼接列自动对齐(缺的填 NaN),横向拼接行自动对齐(缺的填 NaN)。
Q ignore_index 什么时候用?
A 纵向拼接时几乎总是用 ignore_index=True——重建连续索引,避免原索引重复([0,1,0,1,0,1])。除非原索引有意义(如日期索引),才保留。横向拼接不需要 ignore_index(行对齐靠索引)。
Q append 为什么废弃?
A df.append() 在 Pandas 1.4 标记为 deprecated,2.0 完全移除。原因:append 每次创建新对象,循环调用 O(n²) 性能;而 pd.concat 一次性拼接 O(n)。替代:
pd.concat([df1, df2])。循环追加:先收集到列表,最后一次 concat。Q 列名不同怎么办?
A 纵向拼接时,列名不同会产生 NaN——只有同名列的数据会合并。解决方案:① 拼接前 rename 列统一名称;② 用 join='inner' 只保留共有列;③ 拼接后手动处理 NaN 列。建议:先统一列名再 concat。
Q concat 性能优化?
A 避免循环中反复 concat——每次 concat 都创建新 DataFrame。正确做法:先收集到列表,最后一次 concat。
frames = [df1, df2, ..., df100]; result = pd.concat(frames)。这与循环 append 有 O(n) vs O(n²) 的性能差距。Q verify_integrity 有什么用?
A verify_integrity=True 会检查拼接后的索引是否有重复,有重复则抛出 ValueError。用于确保数据完整性——比如唯一 ID 不应该出现两次。但检查有性能开销,大数据时不用。日常场景用 ignore_index=True 更简单。
📖 小节
- concat 用于堆叠数据(纵向拼行/横向拼列),不需要匹配键
- axis=0 纵向拼行(默认),axis=1 横向拼列
- ignore_index=True 重建连续索引,纵向拼接几乎总是需要
- keys 标记数据来源,产生 MultiIndex,可追溯每行来自哪个源
- join='outer' 保留所有列(填 NaN),join='inner' 只保留共有列
- concat vs merge:相同结构堆叠用 concat,不同结构关联用 merge
- 避免循环 concat,先收集到列表再一次 concat
📝 作业
- 基础题(难度⭐):创建 3 个结构相同的 DataFrame(Q1/Q2/Q3 销售数据),用 concat 纵向拼接(ignore_index=True),统计总销售额。
- 进阶题(难度⭐⭐):创建 2 个列名不完全相同的 DataFrame,分别用 join='outer' 和 join='inner' 拼接,对比列差异。
- 挑战题(难度⭐⭐⭐):模拟 4 个地区各 6 个月的数据,用 concat(keys=地区) 拼接→reset_index 整理→按地区统计总量→按月份看趋势。