| Day 23 | Pandas:Series 与 DataFrame 表格分析 |

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.085.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'没安装 Pandaspip install pandas(确认在正确的虚拟环境里)
UnicodeDecodeError 或中文乱码读取 CSV 时编码不对pd.read_csv("文件", encoding="utf-8")
ParserError: Error tokenizing dataCSV 里列数不一致(某行少个逗号)用文本编辑器检查 CSV 文件,补齐逗号
KeyError: '工资'列名写错了print(df.columns) 查看实际列名
ValueError: The truth value of a Series is ambiguous条件筛选里用了 and / or换成 & / |,每个条件加括号
KeyError: 1pandas 3.0 起 s[整数] 只按标签取值,标签不是整数时不再自动回退位置按位置取用 s.iloc[1],按标签取用 s["b"]
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇