Pandas: 拼接追加

最后更新:2026-08-26

merge 是"横向关联"(按键匹配列),concat 是"纵向/横向拼接"(堆叠行或列)。12 个月的销售数据合并成一张年表、3 个分公司的报表拼成总表——都是 concat 的活。本节覆盖 concat 的轴向、索引、对齐和 keys 多层级,以及与 merge 的选择策略。

⚠️ 注意: 以下代码需在本地 Python 环境中运行。

1. 你将学到


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 拼接类型

100%
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 更简单。

📖 小节


📝 作业

  1. 基础题(难度⭐):创建 3 个结构相同的 DataFrame(Q1/Q2/Q3 销售数据),用 concat 纵向拼接(ignore_index=True),统计总销售额。
  2. 进阶题(难度⭐⭐):创建 2 个列名不完全相同的 DataFrame,分别用 join='outer' 和 join='inner' 拼接,对比列差异。
  3. 挑战题(难度⭐⭐⭐):模拟 4 个地区各 6 个月的数据,用 concat(keys=地区) 拼接→reset_index 整理→按地区统计总量→按月份看趋势。

← 上一课:合并连接 · 下一课:重塑与透视 →

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏