1 Series —— 一列「带名牌」的数据
是什么?为什么学?
Series(序列)是 Pandas 的一维数据结构:一串值 + 一排行标签(index),像「一列 Excel 数据」。它是 DataFrame(表格)的基本组成单位——每列都是一个 Series。理解 Series 的「标签」和「位置」两个概念,后面所有 Pandas 操作都建立在这上面。昨天学的 NumPy 解决了「数值计算」,但真实数据通常是表格:有列名、有不同类型的数据。Pandas 就是专门处理表格的库。
底层原理
Series 由两部分组成:值数组(底层就是 NumPy ndarray)和索引(index)——一组标签。默认索引是 0, 1, 2...(叫 RangeIndex),也可以自己指定(如 ["a", "b", "c"])。取值有两种方式:按标签(s["b"],走索引查找)和按位置(s.iloc[1],数第几个)。注意:pandas 3.0 里用 s[整数] 只按标签查找——如果标签不是整数(如 "a"),s[1] 会报 KeyError,老版本「自动回退位置取值」的行为已被移除。**标签可以不是数字,所以「第几个」必须显式用 iloc**。
生活类比
Series 像点名册上的一列分数:每个分数旁边都贴着一个姓名标签。你要查「张三的分数」是按标签找(看名字);要「第三个同学是谁」是按位置找(数顺序)。点名册的顺序(位置)和名字(标签)是两套系统,混着用就会找错人。
注意事项总结
1 创建 Series + 按标签取 + 按位置取:
import pandas as pd
s = pd.Series([10, 20, 30], index=["a", "b", "c"])
print(s)
print("取 b:", s["b"]) # 按标签取值
print("第 2 个:", s.iloc[1]) # 按位置取值(iloc = integer location)
print("平均值:", s.mean())
运行结果:
a 10
b 20
c 30
dtype: int64
取 b: 20
第 2 个: 20
平均值: 20.0
pd.Series([10, 20, 30], index=["a", "b", "c"]) 创建「值 + 标签」;打印时左边是标签、右边是值;s["b"] 按标签取 20;s.iloc[1] 按位置取第 2 个;mean() 算平均值。
2 标签也可以是人名,支持布尔筛选:
scores = pd.Series({"张三": 90, "李四": 85, "王五": 60})
print(scores[scores >= 85]) # 布尔筛选:只看 >=85 的
运行结果:
张三 90
李四 85
dtype: int64
用字典创建 Series 时,键成为标签、值成为数据;scores >= 85 逐元素比较生成布尔序列,放进 scores[...] 只保留满足条件的——和 NumPy 布尔索引是同一个思路,但结果还带着名字标签。
易错点
- 错误写法:标签是
"a","b","c"却写s[1]→ 问题:pandas 3.0 起整数下标只按标签查找,报KeyError: 1→ 正确写法:按位置取用s.iloc[1],按标签取用s["b"]。 - 错误写法:
pd.Series([1, 2, 3], index=["a", "b", "c", "d"])→ 问题:值 3 个、标签 4 个,长度不一致报ValueError→ 正确写法:保证值个数 = 标签个数。 - 错误写法:想给 Series 起「列名」却用
s["列名"]创建 → 问题:字典键是标签不是列名,取不到会KeyError→ 正确写法:明确概念:Series 只有 index(标签),没有列名。
记忆口诀:Series 一列带名牌,标签位置两套牌;按名用方括号,按位用 iloc。
2 DataFrame —— 一张「会计算的 Excel 表」
是什么?为什么学?
DataFrame(数据框)是 Pandas 的二维表格结构:多列数据,每列一个 Series,共用一套行标签。它是数据分析的主战场——读文件、筛选、分组、统计全在 DataFrame 上进行。理解「行是记录、列是字段」,就理解了数据分析的通用视角。
底层原理
DataFrame 是「Series 的字典」:列名 → Series。用字典创建时,键是列名、值是整列数据,所有列自动按行对齐。df["列名"] 取一列(得到 Series);df[["列1", "列2"]] 取多列(双中括号,得到 DataFrame);df.loc[行标签] 按行标签取行、df.iloc[行号] 按位置取行、df.loc[行, 列] 取单个格子。df.columns 返回列名列表、df.index 返回行标签。行和列是两套坐标,loc 按名字、iloc 按编号,混用会取错。
生活类比
DataFrame 像班级成绩单:每一行是一个学生(记录),每一列是一门课(字段),表头是列名、最左列是学号(行标签)。「查第 3 个学生的数学分」要两套坐标:第几行(位置)+ 哪一列(名字)。
注意事项总结
1 字典创建 DataFrame + 取一列/多列:
import pandas as pd
df = pd.DataFrame({
"姓名": ["张三", "李四", "王五"],
"分数": [90, 85, 95],
"城市": ["北京", "上海", "广州"],
})
print(df)
print("---")
print(df["姓名"]) # 一列 → Series
print(df[["姓名", "分数"]]) # 多列 → DataFrame(双中括号)
运行结果:
姓名 分数 城市
0 张三 90 北京
1 李四 85 上海
2 王五 95 广州
---
0 张三
1 李四
2 王五
Name: 姓名, dtype: str
姓名 分数
0 张三 90
1 李四 85
2 王五 95
字典的键按顺序成为列名,值按顺序成为每列数据;df["姓名"] 返回 Series;df[["姓名", "分数"]] 必须双中括号才表示「取多列」。
2 loc 按标签取行、iloc 按位置取行、loc 取单个格子:
df = pd.DataFrame({"姓名": ["张三", "李四", "王五"], "分数": [90, 85, 95]})
print(df.loc[1]) # 按行标签取第 2 行
print(df.iloc[0]) # 按位置取第 1 行
print(df.loc[0, "分数"]) # 第 1 行、分数列 → 单个值
运行结果:
姓名 李四
分数 85
Name: 1, dtype: object
姓名 张三
分数 90
Name: 0, dtype: object
90
df.loc[1] 按行标签取行,返回一个「竖着的 Series」;df.iloc[0] 按位置取第 1 行;df.loc[0, "分数"] 逗号分隔「行, 列」直接取一个格子——**行标签和位置在默认索引下恰好一致,但一旦行标签变成日期/人名,必须用 loc**。
易错点
- 错误写法:
df["姓名", "分数"]想取两列 → 问题:逗号是「行, 列」语法,报KeyError→ 正确写法:取多列必须双中括号:df[["姓名", "分数"]]。 - 错误写法:行标签是日期却用
df.iloc["2026-08-01"]→ 问题:iloc只认位置编号,传标签报错 → 正确写法:按标签用df.loc["2026-08-01"]。 - 错误写法:
df["不存在的列"]→ 问题:列名拼错,报KeyError→ 正确写法:先print(df.columns)看真实列名。
记忆口诀:DataFrame 是成绩单:行是记录列是字段;取一列单括号,取多列双括号;loc 按名 iloc 按号。
3 read_csv —— 一行把文件变成表格
是什么?为什么学?
CSV(Comma-Separated Values,逗号分隔值)是数据交换最常见的格式:纯文本、一行一条记录、逗号分列,Excel 导出的数据、网上公开数据集基本都是它。pd.read_csv("文件") 一行读成 DataFrame——这是「真实数据分析」的入口:数据几乎都从文件来。
底层原理
read_csv 会自动完成三件事:第一行识别为列名(header)、按逗号切分每一行、自动推断每列类型(数字列 → int64 / float64,文本列 → str)。注意 pandas 3.0 中文本列 dtype 显示为 str(老版本叫 object),含义一样:这列是文本。中文 CSV 必须注意编码:写文件用 utf-8,读文件也要指定 encoding="utf-8",否则可能乱码或报 UnicodeDecodeError。
生活类比
CSV 像超市进货单的纯文本版:一行一个商品,逗号是「分栏」的分隔线。read_csv 像扫码枪:对着整张单子扫一遍,自动认出「第一行是表头、后面是数据、数字归数字、文字归文字」,装进一个带格子的表格。
注意事项总结
1 写一个 CSV 再读进来(离线演示):
import pandas as pd
with open("city.csv", "w", encoding="utf-8") as f:
f.write("城市,人口(万),GDP(亿)\n")
f.write("北京,2189,40000\n")
f.write("上海,2487,45000\n")
f.write("广州,1874,28000\n")
df = pd.read_csv("city.csv", encoding="utf-8")
print(df)
print("列名:", list(df.columns))
print("各列类型:")
print(df.dtypes)
运行结果:
城市 人口(万) GDP(亿)
0 北京 2189 40000
1 上海 2487 45000
2 广州 1874 28000
列名: ['城市', '人口(万)', 'GDP(亿)']
各列类型:
城市 str
人口(万) int64
GDP(亿) int64
dtype: object
pd.read_csv("city.csv", encoding="utf-8") 读入后第一行自动成为列名,中文列名也正常;df.columns 列出列名、df.dtypes 看每列类型——数字列自动识别成 int64,文本列是 str。
易错点
- 错误写法:
pd.read_csv("文件.csv")读中文文件乱码 → 问题:系统默认编码不是 utf-8,中文解析失败 → 正确写法:pd.read_csv("文件.csv", encoding="utf-8")。 - 错误写法:写文件用
encoding="gbk"、读文件用encoding="utf-8"→ 问题:读写编码不一致,读出来是乱码 → 正确写法:写和读都用encoding="utf-8",保持统一。 - 错误写法:文件第一行不是表头却直接读 → 问题:数据行被当成列名,
df["列1"]全错 → 正确写法:没有表头时pd.read_csv("文件", header=None),再手动df.columns = [...]。
记忆口诀:CSV 逗号分栏,read_csv 自动分;第一行是表头,中文记得 utf-8。
4 head / info / describe —— 新数据的「三分钟体检」
是什么?为什么学?
拿到任何陌生表格,第一步不是写分析代码,而是先体检:数据长什么样、有多少行、每列是什么类型、有没有缺值、数值分布如何。head(看前几行)、info(结构体检)、describe(数值统计)三件套就是体检仪——不体检就分析,等于不看说明书就开车。
底层原理
head(n):取前 n 行(默认 5),tail(n)取后 n 行——先看「长什么样」info():打印 RangeIndex(行数范围)、每列非空数量、dtype 类型——知道「每列是什么、缺不缺值」describe():只统计数值列,给出count(数量)、mean(平均)、std(标准差)、min/max(最大最小)、25%/50%/75%(四分位数)。pandas 3.0 的打印优化:某列所有统计值能精确表示时用最短格式(如85.0),含无限小数时用 6 位小数(如84.333333)——这是新版显示行为,不是精度问题
生活类比
体检三件套像入职检查:head 是「看照片认人」(正面照、侧面照),info 是「身高体重血压」(基础指标),describe 是「各项化验单」(详细数值范围)。三样看完,你对这位「新员工」(数据)就有数了。
注意事项总结
1 head / tail 看头尾:
df = pd.read_csv("scores3.csv")
print(df.tail(2)) # 看后 2 行
运行结果:
姓名 语文 数学 英语
2 王五 90 88 92
3 赵六 95 98 96
tail(2) 取最后 2 行——配合 head() 一前一后,快速确认「数据有没有倒序、尾部有没有异常行」。
2 info 看结构 + describe 看统计:
print("=== 基本信息 ===")
df.info()
print("=== 数值统计 ===")
print(df.describe())
运行结果:
=== 基本信息 ===
<class 'pandas.DataFrame'>
RangeIndex: 4 entries, 0 to 3
Data columns (total 4 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 姓名 4 non-null str
1 语文 4 non-null int64
2 数学 4 non-null int64
3 英语 4 non-null int64
dtypes: int64(3), str(1)
memory usage: 260.0 bytes
=== 数值统计 ===
语文 数学 英语
count 4.000000 4.000000 4.000000
mean 87.000000 89.500000 87.750000
std 8.524475 7.549834 7.932003
min 75.000000 80.000000 78.000000
25% 84.750000 86.000000 83.250000
50% 89.000000 90.000000 88.500000
75% 91.250000 93.500000 93.000000
max 95.000000 98.000000 96.000000
info() 显示总行数(4 entries)、每列非空数量(都是 4 non-null,说明没缺值)和类型;describe() 只统计数值列:语文 25% 分位 84.75 表示「25% 的人低于 84.75」,50% 是中位数。注意这三列都显示成 6 位小数——因为标准差是无限小数,只要某列有任意一个统计值不能精确表示,该列所有值就统一用 6 位小数显示。这是 pandas 3.0 的显示规则,不是数据精度问题。
易错点
- 错误写法:
df.head()后直接分析全部数据 → 问题:只看了前 5 行,后面的脏数据没看到 → 正确写法:配合tail()、info()、describe()全面体检。 - 错误写法:认为
describe会统计文本列 → 问题:describe()默认只统计数值列,文本列被忽略 → 正确写法:想统计文本列用df["列"].value_counts()。 - 错误写法:看到
85.0和85.000000以为数据不一致 → 问题:pandas 3.0 的最短精确显示优化 → 正确写法:这是显示格式差异,数据精度一致。
记忆口诀:新数据先体检:head 看头 tail 看尾,info 查结构,describe 看分布。
5 groupby —— 一键「分组汇总」
是什么?为什么学?
groupby(分组)是数据分析最高频的操作:按部门算平均工资、按商品算总销量、按城市统计人数。df.groupby("列").统计() 一行完成「按某列分组 → 对每组做统计」,是「分类汇总」的自动化。
底层原理
groupby("部门") 先把数据按「部门」列的值分成若干组(内部是「键 → 子表」的映射),然后对每组执行后续操作:
df.groupby("部门")["工资"].mean():先取每组的「工资」列,再算平均——结果是一个 Series,分组键变成索引(index).agg(["mean", "sum", "max"]):一次算多个统计量,结果带多层索引reset_index():把分组键从索引变回普通列,得到「干净的表格」
生活类比
groupby 像教务处按班级分卷子:先按班级把一摞卷子分成几沓(分组),再对每沓算平均分(统计)——不用你手动「一张张挑出来再算」。reset_index 像把「每沓卷子上贴的班级标签」抄到成绩表里,变成一列普通数据。
注意事项总结
1 分组求平均 + 求和 + 计数:
import pandas as pd
df = pd.DataFrame({
"部门": ["销售", "技术", "销售", "技术", "行政"],
"姓名": ["张三", "李四", "王五", "赵六", "孙七"],
"工资": [8000, 12000, 9000, 15000, 6000],
})
print(df.groupby("部门")["工资"].mean())
print(df.groupby("部门")["工资"].sum())
print(df.groupby("部门")["姓名"].count())
运行结果:
部门
技术 13500.0
行政 6000.0
销售 8500.0
Name: 工资, dtype: float64
---
部门
技术 27000
行政 6000
销售 17000
Name: 工资, dtype: int64
---
部门
技术 2
行政 1
销售 2
Name: 姓名, dtype: int64
df.groupby("部门") 按部门分 3 组,["工资"] 选工资列,.mean() 每组平均——结果里「部门」成了索引,自动按拼音/字母顺序排;sum() 求和、count() 数每组人数。
2 agg 一次算多个统计 + reset_index 变回普通列:
df = pd.DataFrame({
"部门": ["销售", "技术", "销售"],
"工资": [8000, 12000, 9000],
})
g = df.groupby("部门")["工资"].agg(["mean", "sum", "max"])
print(g)
print("---")
print(g.reset_index())
运行结果:
mean sum max
部门
技术 12000.0 12000 12000
销售 8500.0 17000 9000
---
部门 mean sum max
0 技术 12000.0 12000 12000
1 销售 8500.0 17000 9000
.agg(["mean", "sum", "max"]) 一次算三个统计量,列名就是统计名;结果里「部门」是索引(打印时左边空了一格);reset_index() 把部门变成普通列,索引恢复成 0, 1——想继续用 df["部门"] 筛选或合并时,先 reset_index() 更顺手。
易错点
- 错误写法:
df.groupby("部门").mean()想统计所有列 → 问题:文本列(姓名)会被跳过,只统计数值列 → 正确写法:明确想统计哪列:df.groupby("部门")["工资"].mean()。 - 错误写法:groupby 结果里直接
df["部门"]→ 问题:分组键现在是索引不是列,报KeyError→ 正确写法:先reset_index()再当普通列用。 - 错误写法:
df.groupby("部门")["工资"].mean()结果当 DataFrame 用df[列名]→ 问题:结果是 Series,没有列选择语法 → 正确写法:想要表格形式就.agg()或.reset_index()。
记忆口诀:groupby 分卷子,每组算统计;分组键变索引,reset 回列;agg 一次算多样。
6 动手实践:读取并分析 CSV 数据
做一份「销售数据分析」:先用代码生成一份商品销售 CSV,再读进来完成前几行查看、基本信息、统计摘要、列筛选、条件筛选、分组统计。
# analyze_sales.py —— 读取并分析 CSV 数据
import pandas as pd
# 1. 准备一份销售数据 CSV(模拟文件)
csv_text = """日期,商品,类别,销量,单价
2026-08-01,苹果,水果,100,8.5
2026-08-01,香蕉,水果,80,4.0
2026-08-02,牛奶,饮品,60,12.0
2026-08-02,苹果,水果,120,8.5
2026-08-03,面包,食品,45,6.5
2026-08-03,牛奶,饮品,75,12.0
"""
with open("sales.csv", "w", encoding="utf-8") as f:
f.write(csv_text)
# 2. 读取并快速了解
df = pd.read_csv("sales.csv")
print("=== 前 3 行 ===")
print(df.head(3))
print("\n=== 基本信息 ===")
df.info()
print("\n=== 统计摘要 ===")
print(df.describe())
# 3. 列筛选:只看商品和销量
print("\n=== 商品与销量两列 ===")
print(df[["商品", "销量"]])
# 4. 条件筛选:销量大于 80 的记录
print("\n=== 销量大于 80 的记录 ===")
print(df[df["销量"] > 80])
# 5. 新增销售额列,按类别汇总
df["销售额"] = df["销量"] * df["单价"]
print("\n=== 各类别销售额汇总 ===")
print(df.groupby("类别")["销售额"].sum())
运行结果:
=== 前 3 行 ===
日期 商品 类别 销量 单价
0 2026-08-01 苹果 水果 100 8.5
1 2026-08-01 香蕉 水果 80 4.0
2 2026-08-02 牛奶 饮品 60 12.0
=== 基本信息 ===
<class 'pandas.DataFrame'>
RangeIndex: 6 entries, 0 to 5
Data columns (total 5 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 日期 6 non-null str
1 商品 6 non-null str
2 类别 6 non-null str
3 销量 6 non-null int64
4 单价 6 non-null float64
dtypes: float64(1), int64(1), str(3)
memory usage: 372.0 bytes
=== 统计摘要 ===
销量 单价
count 6.000000 6.000000
mean 80.000000 8.583333
std 27.018512 3.121164
min 45.000000 4.000000
25% 63.750000 7.000000
50% 77.500000 8.500000
75% 95.000000 11.125000
max 120.000000 12.000000
=== 商品与销量两列 ===
商品 销量
0 苹果 100
1 香蕉 80
2 牛奶 60
3 苹果 120
4 面包 45
5 牛奶 75
=== 销量大于 80 的记录 ===
日期 商品 类别 销量 单价
0 2026-08-01 苹果 水果 100 8.5
3 2026-08-02 苹果 水果 120 8.5
=== 各类别销售额汇总 ===
类别
水果 2190.0
食品 292.5
饮品 1620.0
Name: 销售额, dtype: float64
这个练习模拟了真实工作里的完整流程:拿到 CSV → 了解结构 → 初步统计 → 筛选关注的数据 → 按类别汇总得出结论。从汇总看,水果类销售额最高(2190),饮品次之,食品最低。
升级挑战:用 df.groupby("商品")["销量"].sum() 找出销量最高的商品;用 df["日期"] 统计哪天卖得最多。
今日总结
能说清 Series(一列带标签)和 DataFrame(多列表格)的区别与关系:DataFrame 是「Series 的字典」
会按标签(s["b"] / loc)和按位置(iloc)取值,知道 pandas 3.0 里 s[整数] 只按标签查
会用 read_csv 读文件(记得 encoding="utf-8"),文本列类型显示为 str
会用 head / tail / info / describe 快速了解数据,理解 describe 短格式显示
会用 df[["列"]] 和 df[条件] 做列筛选与条件筛选,多条件用 & / | 加括号
会用 groupby + 统计方法 + agg 做分组统计,reset_index 把分组键变回列
销售数据分析脚本运行正常,各类别销售额汇总正确
| 报错 | 原因 | 修复 |
|---|---|---|
ModuleNotFoundError: No module named 'pandas' | 没安装 Pandas | pip install pandas(确认在正确的虚拟环境里) |
UnicodeDecodeError 或中文乱码 | 读取 CSV 时编码不对 | pd.read_csv("文件", encoding="utf-8") |
ParserError: Error tokenizing data | CSV 里列数不一致(某行少个逗号) | 用文本编辑器检查 CSV 文件,补齐逗号 |
KeyError: '工资' | 列名写错了 | print(df.columns) 查看实际列名 |
ValueError: The truth value of a Series is ambiguous | 条件筛选里用了 and / or | 换成 & / |,每个条件加括号 |
KeyError: 1 | pandas 3.0 起 s[整数] 只按标签取值,标签不是整数时不再自动回退位置 | 按位置取用 s.iloc[1],按标签取用 s["b"] |