3.3.2 Pandas 核心数据结构

- 理解 Pandas 在数据分析中的地位
- 掌握 Series 的创建和基本操作
- 掌握 DataFrame 的创建和基本属性
- 理解索引(Index)机制
先建立一张地图
Section titled “先建立一张地图”第一次学 Pandas,最稳的顺序不是“直接背所有方法”,而是先看清:
flowchart LR A["Series:一列有标签的数据"] --> B["DataFrame:很多列组合成的表"] B --> C["Index:行列的定位系统"] C --> D["后面所有筛选、清洗、聚合都靠它们展开"]所以这节真正想解决的是:
- 为什么
Pandas不只是“Python 版 Excel” - 为什么
Series / DataFrame / Index会成为后面整章的底座
Pandas 是什么?
Section titled “Pandas 是什么?”如果说 NumPy 是 Python 数据科学的引擎,那 Pandas 就是方向盘和仪表盘——它让你能方便地操控和观察数据。
flowchart LR A["原始数据<br/>CSV / Excel / JSON / SQL"] --> B["Pandas<br/>读取 · 清洗 · 分析"] B --> C["分析结果<br/>统计 · 可视化 · 报告"]
style B fill:#ff9800,color:#fff,stroke:#e65100Pandas 的核心能力:
| 能力 | 说明 |
|---|---|
| 数据读写 | 一行代码读取 CSV、Excel、JSON、SQL |
| 数据清洗 | 处理缺失值、重复值、异常值 |
| 数据筛选 | 像 SQL 一样灵活地过滤和查询数据 |
| 分组统计 | groupby 比纯 Python 循环快几十倍 |
| 数据合并 | 像 SQL JOIN 一样合并多张表 |
一个更适合新人的总类比
Section titled “一个更适合新人的总类比”你可以把 Pandas 理解成:
- 一个会记住“行和列标签”的智能表格
普通 list 更像:
- 没有列名的原始数据堆
NumPy 更像:
- 适合高速数值计算的矩阵引擎
Pandas 则更像:
- 你真的开始在处理“字段、行记录、表结构”的地方
还记得第 1 章的预热练习吗?75 行纯 Python 代码做的事情,Pandas 5 行就搞定了。现在让我们正式学习它。
import pandas as pdimport numpy as np
print(pd.__version__) # 如 2.2.0Series:带标签的一维数组
Section titled “Series:带标签的一维数组”Series 是 Pandas 最基本的数据结构——你可以把它理解为一个带标签的 NumPy 数组。
第一次看 Series,最该先抓住什么?
Section titled “第一次看 Series,最该先抓住什么?”最值得先抓住的是这句:
Series = 一列数据 + 一套标签。
只要这句稳住了,后面你看:
- 按标签取值
- 按位置取值
- 对整列做运算
都会顺很多。
创建 Series
Section titled “创建 Series”import pandas as pd
# 从列表创建(自动生成 0, 1, 2... 索引)s1 = pd.Series([85, 92, 78, 95, 88])print(s1)# 0 85# 1 92# 2 78# 3 95# 4 88# dtype: int64
# 指定索引s2 = pd.Series( [85, 92, 78, 95, 88], index=["语文", "数学", "英语", "物理", "化学"])print(s2)# 语文 85# 数学 92# 英语 78# 物理 95# 化学 88# dtype: int64
# 从字典创建(键自动成为索引)scores = {"语文": 85, "数学": 92, "英语": 78, "物理": 95}s3 = pd.Series(scores)print(s3)Series 的结构
Section titled “Series 的结构”索引 (Index) 值 (Values)─────────── ──────────语文 85数学 92英语 78物理 95化学 88每个 Series 都由两部分组成:
- 索引(Index):标签,用来定位数据
- 值(Values):实际数据,底层是 NumPy 数组
一个很适合初学者先记的对照表
Section titled “一个很适合初学者先记的对照表”| 你现在看到的东西 | 可以先把它想成什么 |
|---|---|
Series | 一列带标签的数据 |
Index | 这列数据的“行名” |
Values | 真正存着的数据本体 |
这个表很适合新人,因为它能把抽象名词重新压回成几个更直白的角色。
s = pd.Series([85, 92, 78], index=["语文", "数学", "英语"])
print(s.index) # Index(['语文', '数学', '英语'], dtype='object')print(s.values) # [85 92 78] ← 这是一个 NumPy 数组!print(s.dtype) # int64print(s.shape) # (3,)print(len(s)) # 3Series 的访问
Section titled “Series 的访问”s = pd.Series([85, 92, 78, 95], index=["语文", "数学", "英语", "物理"])
# 用标签索引print(s["数学"]) # 92
# 用位置索引print(s.iloc[1]) # 92
# 切片print(s["语文":"英语"]) # 标签切片(包含末尾!)# 语文 85# 数学 92# 英语 78
# 布尔索引print(s[s >= 90])# 数学 92# 物理 95Series 的运算
Section titled “Series 的运算”s = pd.Series([85, 92, 78, 95], index=["语文", "数学", "英语", "物理"])
# 向量化运算(和 NumPy 一样)print(s + 5) # 每科加 5 分print(s * 1.1) # 每科乘以 1.1print(s.mean()) # 87.5 平均分print(s.max()) # 95 最高分print(s.describe()) # 一键生成描述性统计DataFrame:带标签的二维表格
Section titled “DataFrame:带标签的二维表格”DataFrame 是 Pandas 的核心——你可以把它理解为一张Excel 表格,或者一个由多个 Series 组成的字典。
第一次看 DataFrame,最该先记什么?
Section titled “第一次看 DataFrame,最该先记什么?”最值得先记住的是:
DataFrame = 多列 Series 按同一套行索引拼起来的一张表。
你可以先把它理解成:
- 一张真正有列名和行号的数据表
而不是一堆数组拼在一起。
创建 DataFrame
Section titled “创建 DataFrame”# 方法 1:从字典创建(最常用)data = { "姓名": ["张三", "李四", "王五", "赵六", "钱七"], "年龄": [22, 25, 23, 28, 21], "城市": ["北京", "上海", "广州", "深圳", "杭州"], "薪资": [15000, 22000, 18000, 25000, 16000]}df = pd.DataFrame(data)print(df)# 姓名 年龄 城市 薪资# 0 张三 22 北京 15000# 1 李四 25 上海 22000# 2 王五 23 广州 18000# 3 赵六 28 深圳 25000# 4 钱七 21 杭州 16000# 方法 2:从列表的列表创建data = [ ["张三", 22, "北京"], ["李四", 25, "上海"], ["王五", 23, "广州"]]df = pd.DataFrame(data, columns=["姓名", "年龄", "城市"])
# 方法 3:从 NumPy 数组创建rng = np.random.default_rng(seed=42)arr = rng.integers(60, 100, size=(5, 3))df = pd.DataFrame(arr, columns=["语文", "数学", "英语"])
# 方法 4:从 Series 字典创建df = pd.DataFrame({ "数学": pd.Series([90, 85, 78], index=["张三", "李四", "王五"]), "英语": pd.Series([88, 92, 75], index=["张三", "李四", "王五"])})DataFrame 的结构
Section titled “DataFrame 的结构”| 组成部分 | 示例 |
|---|---|
| 行索引 | 0、1、2、3、4 |
| 列名 | 姓名、年龄、城市、薪资 |
| 一行数据 | 张三、22、北京、15000 |
DataFrame = 行索引(Index) + 列名(Columns) + 数据(Values)
data = { "姓名": ["张三", "李四", "王五", "赵六", "钱七"], "年龄": [22, 25, 23, 28, 21], "城市": ["北京", "上海", "广州", "深圳", "杭州"], "薪资": [15000, 22000, 18000, 25000, 16000]}df = pd.DataFrame(data)
print(df.shape) # (5, 4) → 5 行 4 列print(df.columns) # Index(['姓名', '年龄', '城市', '薪资'], dtype='object')print(df.index) # RangeIndex(start=0, stop=5, step=1)print(df.dtypes)# 姓名 object ← 字符串# 年龄 int64# 城市 object# 薪资 int64print(df.size) # 20 → 5 × 4 = 20 个元素print(len(df)) # 5 → 行数快速查看数据
Section titled “快速查看数据”# 前 3 行print(df.head(3))
# 后 2 行print(df.tail(2))
# 基本信息print(df.info())# <class 'pandas.core.frame.DataFrame'># RangeIndex: 5 entries, 0 to 4# Data columns (total 4 columns):# # Column Non-Null Count Dtype# --- ------ -------------- -----# 0 姓名 5 non-null object# 1 年龄 5 non-null int64# 2 城市 5 non-null object# 3 薪资 5 non-null int64
# 数值列的统计摘要print(df.describe())# 年龄 薪资# count 5.000000 5.000000# mean 23.800000 19200.000000# std 2.774887 4147.288271# min 21.000000 15000.000000# 25% 22.000000 16000.000000# 50% 23.000000 18000.000000# 75% 25.000000 22000.000000# max 28.000000 25000.000000一个新人可直接照抄的“拿到新表先做什么”顺序
Section titled “一个新人可直接照抄的“拿到新表先做什么”顺序”更稳的顺序通常是:
- 先看
df.head() - 再看
df.info() - 再看
df.describe() - 最后再开始筛选和清洗
这样会比一上来就直接写复杂操作更不容易迷路。
# 访问单列 → 返回 Seriesprint(df["姓名"])# 0 张三# 1 李四# ...
# 也可以用点语法(列名不含空格且不与方法冲突时)print(df.年龄)
# 访问多列 → 返回 DataFrameprint(df[["姓名", "薪资"]])# 姓名 薪资# 0 张三 15000# 1 李四 22000# ...为什么“先学会看列”这么重要?
Section titled “为什么“先学会看列”这么重要?”因为后面大多数 Pandas 工作都在做三件事:
- 选列
- 改列
- 基于列做统计和组合
所以第一次学 Pandas,与其急着记很多高阶方法,
不如先把“我怎么找到这列、它是什么类型、我能对它做什么”打稳。
添加和删除列
Section titled “添加和删除列”# 添加新列df["税后薪资"] = df["薪资"] * 0.85print(df[["姓名", "薪资", "税后薪资"]])
# 基于条件添加列df["薪资等级"] = np.where(df["薪资"] >= 20000, "高", "中")print(df[["姓名", "薪资", "薪资等级"]])
# 删除列df = df.drop(columns=["税后薪资"]) # 返回新 DataFrame# 或者# df.drop(columns=["税后薪资"], inplace=True) # 原地修改索引(Index)的重要性
Section titled “索引(Index)的重要性”索引是 Pandas 区别于 NumPy 的关键特性。
df = pd.DataFrame({ "姓名": ["张三", "李四", "王五"], "年龄": [22, 25, 23], "薪资": [15000, 22000, 18000]})
# 把"姓名"列设为索引df_indexed = df.set_index("姓名")print(df_indexed)# 年龄 薪资# 姓名# 张三 22 15000# 李四 25 22000# 王五 23 18000
# 通过索引访问print(df_indexed.loc["李四"])# 年龄 25# 薪资 22000
# 重置索引df_reset = df_indexed.reset_index()print(df_reset) # 和原来一样Pandas 的运算会自动按索引对齐——这是一个非常强大的特性:
s1 = pd.Series({"语文": 85, "数学": 92, "英语": 78})s2 = pd.Series({"数学": 88, "英语": 82, "物理": 90})
# 自动按索引对齐相加result = s1 + s2print(result)# 数学 180.0# 物理 NaN ← s1 没有物理,结果为 NaN# 英语 160.0# 语文 NaN ← s2 没有语文,结果为 NaNSeries vs DataFrame 对比
Section titled “Series vs DataFrame 对比”| 特性 | Series | DataFrame |
|---|---|---|
| 维度 | 一维 | 二维 |
| 类比 | Excel 的一列 | 整张 Excel 表格 |
| 创建 | pd.Series([1,2,3]) | pd.DataFrame({"a":[1,2]}) |
| 访问列 | — | df["列名"] 返回 Series |
| 索引 | 一个 Index | 行索引 + 列索引 |
学完这一页,至少保留这张证据卡:
- 数据框状态
- 列、数据类型、行数、缺失值和样本行
- 操作
- 读/写、select/filter、清洗、转换、groupby、merge,或时间序列步骤
- 输出
- 结果表、保存的文件、聚合、连接结果,或时间索引视图
- 失败检查
- dtype 不匹配、缺失数据、重复键、链式赋值或时间频率错误
- 期望产出
- 前后对比表格样本,以及转换原因
root((Pandas 核心数据结构)) Series 带标签的一维数组 index + values 像增强版的字典 DataFrame 带标签的二维表格 index + columns + values 像 Excel 表格 Index 索引 自动对齐 set_index / reset_index 标签访问 常用方法 head / tail info / describe shape / dtypes / columns练习 1:创建 Series
Section titled “练习 1:创建 Series”# 创建一个 Series 表示一周每天的步数# 索引用 "周一" 到 "周日"# 1. 打印平均步数# 2. 找出步数最多的一天# 3. 找出步数超过 8000 的天数练习 2:创建 DataFrame
Section titled “练习 2:创建 DataFrame”# 创建一个功能进度 DataFrame,包含:# 功能、负责人、计划小时、实际小时、状态 五列,至少 5 条任务# 1. 添加一列"工时差"# 2. 添加一列"是否超时"# 3. 添加一列"风险"(阻塞 -> 高,评审中 -> 中,已完成 -> 低,其他 -> 观察)# 4. 用 describe() 查看数值列的统计信息练习 3:索引操作
Section titled “练习 3:索引操作”# 使用练习 2 的 DataFrame# 1. 把"功能"设为索引# 2. 通过功能名查找某条进度记录# 3. 重置索引参考实现与讲解
- 一周步数 Series 应把星期名设为 index,然后计算均值、最高步数日
idxmax,并用布尔条件筛出高步数日。 - 功能进度 DataFrame 先增加
工时差和是否超时,再根据规则用函数、map或pd.cut创建风险列。describe()是有用证据,但不是完整分析。 - 索引练习要同时展示
set_index和reset_index。好的答案会说明什么时候用.loc的标签查找比.iloc的位置查找更清楚。