pandas 核心数据结构:Series 与 DataFrame

摘要

pandas 是 Python 数据分析中最常用的表格处理工具。理解 Series 和 DataFrame 的结构、索引、列、数据类型和对齐规则,是后续数据读取、清洗、筛选、分组和合并的基础。

本文从一维 Series 和二维 DataFrame 开始,介绍创建、查看、索引、切片、修改、类型转换和数据对齐,并通过订单数据示例建立一套可复用的基本操作习惯。

一、背景与问题

业务数据通常不是单一的数字数组,而是包含多个字段的表格:

order_id customer amount status
A001 Alice 1200 paid
A002 Bob 800 pending

使用嵌套列表处理这类数据时,需要手动维护列名、数据类型和行位置,代码容易出现以下问题:

  • 通过位置访问字段,可读性差。
  • 插入或删除列后,索引关系容易错乱。
  • 缺失值和不同类型混在一起,判断复杂。
  • 多张表关联需要手动编写循环。

pandas 用带标签的数据结构表达表格,使列名、索引和数据类型成为代码的一部分。

二、核心概念

1. Series

Series 是带索引的一维数据。可以把它理解为"带标签的数组":

text 复制代码
index    value
east     1200
south     800
north    1560

Series 的核心属性包括:

属性 说明
index 每个元素对应的标签
values 底层值
dtype 数据类型
name Series 名称
shape 数据形状

2. DataFrame

DataFrame 是由多个 Series 按索引对齐组成的二维表格。每一列是一个 Series,所有列共享行索引。

text 复制代码
DataFrame
  ├─ index
  ├─ Series: order_id
  ├─ Series: customer
  ├─ Series: amount
  └─ Series: status

不同列可以使用不同数据类型,但同一列通常应保持类型稳定。

3. Index

Index 是 pandas 数据结构中的标签系统。它既可以是默认整数索引,也可以是字符串、日期或多级索引。

索引主要用于:

  • 定位数据。
  • 对齐不同 Series。
  • 连接和合并表格。
  • 表示时间序列位置。

索引不是普通的数据列。需要将索引转换为列时,可以使用 reset_index()。

4. 标签对齐

pandas 运算默认按标签对齐,而不是单纯按位置:

python 复制代码
import pandas as pd

left = pd.Series([10, 20], index=["a", "b"])
right = pd.Series([1, 2], index=["b", "c"])

print(left + right)

结果中 a 和 c 因为缺少对应标签会得到缺失值。标签对齐很方便,但也要求在运算前确认索引含义。

5. 数据类型

常见类型包括:

  • 整数和浮点数。
  • 字符串。
  • 布尔值。
  • 日期时间。
  • 分类类型。
  • 可空整数和可空布尔类型。

类型会影响筛选、排序、聚合、内存和输出格式,不能只看表格显示结果判断类型。

三、工作原理

1. DataFrame 的列式结构

DataFrame 以列为核心组织数据。不同列可能拥有不同类型,因此处理单列通常比把所有数据转换成同一种 NumPy 类型更自然。

text 复制代码
读取表格
  → 建立列名和索引
  → 推断或指定 dtype
  → 通过标签选择数据
  → 返回 Series 或 DataFrame

使用链式操作时,要注意每一步返回的是原对象、视图还是新对象,避免隐式修改带来的错误。

2. loc 与 iloc

  • loc 按标签选择。
  • iloc 按整数位置选择。
python 复制代码
df.loc[0, "amount"]
df.iloc[0, 2]

当索引不是连续整数时,loc[0] 和 iloc[0] 的含义可能完全不同。

3. 视图与副本

对 DataFrame 进行切片后再修改,可能产生副本修改警告或无法修改原数据。更明确的方式是使用 .loc 直接赋值,或者在需要独立数据时显式 .copy()。

4. 缺失值

pandas 通常使用 NaN、NaT 或可空类型表示缺失。缺失值具有特殊传播规则:

python 复制代码
import pandas as pd

series = pd.Series([10, None, 30])
print(series.isna())
print(series.fillna(0))

缺失值处理不能只做全局填充,应根据字段含义和业务规则决定。

四、实战示例

1. 创建 Series

python 复制代码
import pandas as pd

amounts = pd.Series(
    [1200.0, 800.0, 1560.0],
    index=["A001", "A002", "A003"],
    name="amount",
)

print(amounts)
print(amounts["A002"])
print(amounts.dtype)

2. 创建 DataFrame

python 复制代码
orders = pd.DataFrame(
    {
        "order_id": ["A001", "A002", "A003", "A004"],
        "customer": ["Alice", "Bob", "Carol", "David"],
        "amount": [1200.0, 800.0, 1560.0, 620.0],
        "status": ["paid", "pending", "paid", "cancelled"],
    }
)

print(orders)
print(orders.shape)
print(orders.columns.tolist())
print(orders.dtypes)

3. 查看数据

python 复制代码
print(orders.head(2))
print(orders.tail(2))
print(orders.info())
print(orders.describe(numeric_only=True))
print(orders["status"].value_counts(dropna=False))

info() 适合检查列名、非空数量和类型,value_counts() 适合查看分类字段的分布。

4. 选择列

python 复制代码
amount = orders["amount"]
selected = orders[["order_id", "amount", "status"]]

print(type(amount))
print(type(selected))

选择单列通常返回 Series,选择多列返回 DataFrame。

5. 使用 loc 和 iloc

python 复制代码
first_order = orders.loc[0]
first_amount = orders.loc[0, "amount"]
first_two = orders.iloc[0:2, 0:3]

print(first_order)
print(first_amount)
print(first_two)

使用标签选择时,代码表达的是业务字段;使用位置选择时,代码依赖列和行的位置。

6. 条件筛选

python 复制代码
paid_orders = orders.loc[orders["status"] == "paid"]
large_orders = orders.loc[orders["amount"] >= 1000]
selected_orders = orders.loc[
    (orders["status"] == "paid")
    & (orders["amount"] >= 1000),
    ["order_id", "amount"],
]

print(selected_orders)

多个条件需要使用 &、| 和 ~,并为每个条件加括号。不能直接使用 Python 的 and 和 or 处理 Series。

7. 新增与修改列

python 复制代码
orders = orders.copy()
orders["is_high_value"] = orders["amount"] >= 1000
orders["amount_with_tax"] = orders["amount"] * 1.06

orders.loc[orders["status"] == "pending", "status"] = "待支付"
print(orders)

通过 .loc 修改满足条件的单元格,可以避免链式赋值带来的不确定性。

8. 类型转换

python 复制代码
orders["amount"] = pd.to_numeric(
    orders["amount"],
    errors="coerce",
)
orders["order_date"] = pd.to_datetime(
    ["2026-09-01", "2026-09-02", "2026-09-03", "2026-09-04"],
    errors="coerce",
)
orders["status"] = orders["status"].astype("string")

errors="coerce" 会把无法转换的值变成缺失值,转换后必须继续检查缺失数量,不能把异常静默吞掉。

9. 重置索引

python 复制代码
filtered = orders.loc[orders["amount"] >= 1000].copy()
filtered = filtered.reset_index(drop=True)
print(filtered)

筛选后保留原索引有时很有用,例如追踪原始行;需要生成连续展示序号时再重置索引。

10. 导入和导出

python 复制代码
orders.to_csv(
    "output/orders.csv",
    index=False,
    encoding="utf-8-sig",
)
loaded = pd.read_csv("output/orders.csv")

导出时是否保留索引、使用什么编码、是否包含缺失值,都应成为明确的接口约定。

五、常见问题与实践建议

1. loc 和 iloc 该怎么选?

当代码表达业务字段时优先使用 loc;当处理固定位置、矩阵切片或底层数组时可以使用 iloc。避免用位置索引表达容易变化的业务列。

2. 为什么筛选后修改会出现警告?

常见原因是对一个可能由切片产生的对象继续赋值。可以使用:

python 复制代码
result = orders.loc[orders["amount"] > 1000].copy()
result.loc[:, "level"] = "high"

3. 为什么字符串列有时是 object?

旧版本或混合内容可能让 pandas 使用 object 表示字符串列。需要稳定处理时,可以显式转换为 string,并检查其中是否混入数字、列表或其他对象。

4. DataFrame 复制会不会很慢?

大表复制确实会增加内存和时间。先明确是否需要独立副本,避免无意义地反复 copy();但在需要安全修改筛选结果时,显式复制往往比隐式共享更可靠。

5. 如何处理重复索引?

先判断重复索引是否具有业务意义:

python 复制代码
print(orders.index.is_unique)
print(orders.index[orders.index.duplicated()])

如果索引应该唯一,应在读取或转换后立即校验;如果允许重复,则避免把索引唯一性当作前提。

六、进阶思考

1. 数据类型与内存

大数据表可以从以下方面优化:

  • 整数列使用合适的位宽。
  • 重复字符串转换为 category。
  • 日期列转换为 datetime64。
  • 不要让数字列长期保持字符串。

优化前后应比较内存和计算结果,不能只追求占用更小。

2. 链式方法与可读性

pandas 支持链式处理:

python 复制代码
summary = (
    orders.loc[orders["status"] == "paid"]
    .assign(amount_with_tax=lambda frame: frame["amount"] * 1.06)
    .loc[:, ["order_id", "amount_with_tax"]]
)

链式代码可以减少中间变量,但过长时应拆成具名函数,优先保证可读性和可调试性。

3. DataFrame 与业务模型

DataFrame 适合数据处理,不一定适合直接作为整个业务系统的数据模型。进入数据库、API 或领域服务前,应明确字段类型、空值语义和主键规则。

4. 从单表操作进入分析流程

掌握 Series 与 DataFrame 后,下一步可以学习:

text 复制代码
读取数据
  → 清洗类型
  → 条件筛选
  → 分组聚合
  → 多表合并
  → 透视与可视化

每一步都应保留数据行数、列名和关键指标的检查。

结论

Series 是带标签的一维数据,DataFrame 是由多个 Series 按索引组织起来的二维表格。loc、iloc、索引对齐、缺失值、类型转换和 .copy() 是日常 pandas 开发最重要的基础。

后续数据读取、清洗和统计分析都建立在这些结构之上。先把数据结构和索引关系理解清楚,再扩展到更复杂的业务处理。

参考资料

  1. pandas 官方文档:https://pandas.pydata.org/docs/
  2. pandas 用户指南:https://pandas.pydata.org/docs/user_guide/
  3. NumPy 官方文档:https://numpy.org/doc/
相关推荐
All for pursuit.2 小时前
【回溯-5】78.子集
数据结构·c++·算法·leetcode
_wxd6662 小时前
C++STL map与set
数据结构·c++
mmmmath_33 小时前
LeetCode.150.逆波兰表达式求值
数据结构
漂流瓶jz3 小时前
UVA-1610 聚会游戏 题解答案代码 算法竞赛入门经典第二版
数据结构·c++·算法·题解·aoapc·算法竞赛入门经典·uva
imDwAaY3 小时前
Redis Set 如何节省内存?从整数集合到哈希表的设计取舍
数据结构·redis·散列表
无名猿3 小时前
map / set 完全指南:红黑树与有序容器
数据结构·c++·性能优化·stl·标准库
All for pursuit.3 小时前
【回溯-3】39.组合总和
数据结构·c++·算法·leetcode
不会就选b4 小时前
算法日常・每日刷题--<动态规划>6
数据结构·算法·leetcode
漂流瓶jz4 小时前
UVA-12545 比特变换器 题解答案代码 算法竞赛入门经典第二版
数据结构·算法·题解·aoapc·算法竞赛入门经典·uva·12545