认识 DataFrame
完整讲解 · 8 段教学·配 3 道练习题·预计 40 分钟
本页是本章的通读版,可直接读完全部讲解。想动手写代码、跑判分,去 闯关模式。
前言
各位童鞋还记得吗?上一章,只用三行 pandas 代码,就回答了「钱都花到哪儿去了」。那三行里,最要紧的其实是第一步:pd.read_csv() 读出来的那张表,也就是 DataFrame。为什么说它最要紧呢?因为后面所有的筛选、统计、分组、画图,都是在这张表上做的。
所以这一章先不急着算,而是先把这张表认清楚:它由哪几部分组成?有多少行、多少列?每一列装的是什么类型的数据?里面有没有奇怪的值?这一步,就是六步里的「探索」。
有的童鞋可能会想:看个表有什么好学的,直接算不就行了?其实,拿到一份新数据,先花几分钟把它看清楚,比一上来就埋头计算要省事得多。很多分析出了错,追查到最后,都是因为一开始没发现:某一列存的是文字,而不是数字;或者某一列有一大半是空的。
学这一章之前,需要先学完 数据分析课第 1 章 。
目录
这一章会学到:
- 一、读进一张表——
read_csv()、head()和tail() - 二、看清表的样子——行数、列名、数据类型和
info() - 三、一列就是一个 Series——取出一列,算一算
- 四、
describe()——一眼看全局 - 五、常见坑
- 六、自己查一次文档——让
describe()也看看文字列 - 七、小结与回看
学完这一章,拿到任何一份 csv,你都能在几分钟内说清楚:它有多少行、有哪些列、每一列是什么样的。
一、读进一张表
要认识一张表,最直接的办法就是把它打印出来看看。先把记账本读进来:
import pandas as pd
ledger = pd.read_csv('ledger_2026.csv')
print(ledger)输出的结果:
date item category cost payment
0 2026-01-01 房租 居住 2500.0 银行卡
1 2026-01-01 水电燃气 居住 156.0 支付宝
2 2026-01-01 早饭 餐饮 13.0 支付宝
3 2026-01-01 午饭 餐饮 24.5 微信
4 2026-01-01 晚饭 餐饮 30.0 微信
... ... ... ... ... ...
1349 2026-12-31 早饭 餐饮 6.5 支付宝
1350 2026-12-31 午饭 餐饮 27.5 微信
1351 2026-12-31 晚饭 餐饮 58.5 现金
1352 2026-12-31 地铁 交通 4.0 微信
1353 2026-12-31 零食 餐饮 9.5 微信
[1354 rows x 5 columns]可以看到,一张 DataFrame 由三部分组成:
- 列名:最上面那一行,
date、item、category、cost、payment。它们来自 csv 文件的第一行。 - 行号:最左边那一列,从 0 开始数,pandas 管它叫索引(index)。要注意,它不是文件里的数据,而是 pandas 自己加上去的。
- 数据:除了列名和行号,中间的部分就是数据。
表中间那一行 ... 又是什么意思呢?因为这张表太长了,pandas 只显示开头 5 行和结尾 5 行,中间的部分就用 ... 省略掉了。最后一行还告诉你,这张表一共有 1354 行、5 列。
其实很多时候,咱们并不需要看整张表,只想看看开头或者结尾。这时候就用 head() 和 tail() :括号里写要看几行,不写的话,默认就是 5 行。
import pandas as pd
ledger = pd.read_csv('ledger_2026.csv')
print(ledger.head(3))
print(ledger.tail(2))输出的结果:
date item category cost payment
0 2026-01-01 房租 居住 2500.0 银行卡
1 2026-01-01 水电燃气 居住 156.0 支付宝
2 2026-01-01 早饭 餐饮 13.0 支付宝
date item category cost payment
1352 2026-12-31 地铁 交通 4.0 微信
1353 2026-12-31 零食 餐饮 9.5 微信head(3) 打印出开头 3 行,tail(2) 打印出结尾 2 行。所以拿到一份数据,第一件事就是 head() 一下,看看每一列长什么样。
最后还有一个小地方。细心的童鞋可能注意到了,表里的中文能对得这么整齐,其实是因为网页里已经替你打开了 pandas 的一个显示选项。要是在自己电脑上运行,就在 import pandas as pd 下面加上这一行,效果是一样的:
pd.set_option('display.unicode.east_asian_width', True)不加也没关系,数据还是一样的,只是汉字比较多的列会有点歪。
二、看清表的样子
上一节是用眼睛看表。可是一千多行的表,光靠眼睛是看不全的。那么,这张表到底有多少行、有哪些列、每一列又是什么类型?这些基本情况,几行代码就能问清楚:
import pandas as pd
ledger = pd.read_csv('ledger_2026.csv')
print(ledger.shape)
print(len(ledger))
print(list(ledger.columns))
print(ledger.dtypes)输出的结果:
(1354, 5)
1354
['date', 'item', 'category', 'cost', 'payment']
date object
item object
category object
cost float64
payment object
dtype: object这四个 print() ,各问了一件事:
shape是一个元组,写的是(行数,列数)。len(ledger)得到的是行数。columns是所有的列名。用list()把它变成普通的列表,打印出来好看一些。dtypes是每一列的数据类型。
这四样里面,数据类型最要紧。为什么呢?因为一列是什么类型,决定了它能拿来做什么。pandas 读文件的时候,会看看每一列的内容,自己判断它是什么类型。常见的有下面这几种:
| 类型 | 意思 | 记账本里的 |
|---|---|---|
float64 | 小数 | cost 金额 |
int64 | 整数 | 这里没有 |
object | 一般就是文字 | 日期、名称、类别、付款方式 |
对照着输出来看:cost 是 float64 ,说明 pandas 认出了它是数字,可以直接拿来加减。日期是 object ,说明 pandas 现在只把它当成文字。要把它当成日期来用,还得转换一下,这个第 5 章会讲。
一项一项地问,要写好几行。有没有办法把这些信息一次看全呢?有,用 info() :
import pandas as pd
ledger = pd.read_csv('ledger_2026.csv')
ledger.info()输出的结果:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1354 entries, 0 to 1353
Data columns (total 5 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 date 1354 non-null object
1 item 1354 non-null object
2 category 1354 non-null object
3 cost 1354 non-null float64
4 payment 1354 non-null object
dtypes: float64(1), object(4)
memory usage: 31.8+ KB先说一个要留意的地方:info() 自己就会打印,不用再在外面套 print() 。
再来看输出里的 Non-Null Count ,它说的是每一列非空的值有几个。这里每一列都是 1354,和总行数一样,说明这份账本没有缺失的数据。要是哪一列只有一千出头,那就说明这一列有空着的地方。第 5 章清洗数据时,第一件事就是看这个数。
整张表的样子看清楚了。下一节,咱们把目光放到其中的一列上。在那之前,先做一道练习:用同样的办法,看看两点书店的书目有多大。
books.csv 是两点书店的书目,爬虫课第 9 章就是把它抓下来的。读进这份文件,先打印它的行数和列数,再把列名变成列表打印出来。
运行后的输出应该是:
(120, 10)
['id', 'title', 'author', 'category', 'price', 'rating', 'reviews', 'stock', 'pages', 'year']💡 提示(3 条)
行数和列数在 shape 里,列名在 columns 里
列名用 list(books.columns) 变成列表
完整代码:
import pandas as pd books = pd.read_csv('books.csv') print(books.shape) print(list(books.columns))
三、一列就是一个 Series
整张表看清楚了,可真到了算数的时候,咱们往往只关心其中的一列。比如想知道一年一共花了多少钱,只用得着金额这一列。那怎么把一列单独取出来呢?很简单,在方括号里写上列名就行:
import pandas as pd
ledger = pd.read_csv('ledger_2026.csv')
cost = ledger['cost']
print(type(cost))
print(cost.head(3))输出的结果:
<class 'pandas.core.series.Series'>
0 2500.0
1 156.0
2 13.0
Name: cost, dtype: float64第一行告诉我们,取出来的这一列,类型叫 Series。那 Series 是什么呢?可以把它看成一串值:每个值都带着自己的行号,最下面还写着列名和数据类型。反过来想,一张 DataFrame,其实就是好几个 Series 并排放在一起。
把一列取出来,有什么用呢?因为 Series 自带了很多统计方法,想算什么,直接调用就行:
import pandas as pd
ledger = pd.read_csv('ledger_2026.csv')
cost = ledger['cost']
print(cost.sum())
print(cost.max(), cost.min())
print(round(cost.mean(), 2))
print(cost.count())输出的结果:
69195.5
2500.0 3.0
51.1
1354从上往下,依次是总和、最大和最小、平均数,还有一共有几个值。一个方法就得到一个数,不用再像上一章那样写 for 循环,一个一个地加。
数字列能算,那文字列呢?文字虽然没法加减,不过也能看出不少东西:
import pandas as pd
ledger = pd.read_csv('ledger_2026.csv')
print(ledger['category'].unique())
print(ledger['category'].nunique())输出的结果:
['居住' '餐饮' '交通' '娱乐' '购物' '学习' '医疗']
7unique() 会列出这一列里出现过的所有不同的值,nunique() 则告诉你一共有几种。一共七个类别,一目了然。
上面取的都只是一列。要是想同时取好几列呢?那就在方括号里放一个列表。这回得到的就不是 Series 了,还是一张 DataFrame:
import pandas as pd
ledger = pd.read_csv('ledger_2026.csv')
print(ledger[['item', 'cost']].head(3))输出的结果:
item cost
0 房租 2500.0
1 水电燃气 156.0
2 早饭 13.0这里要注意,写的是两层方括号:外面一层表示「取列」,里面一层是列名组成的列表。
取出一列,一个方法算一个数,已经很方便了。先做一道练习,拿书店的价格这一列试一试。做完了,下一节再看一个更省事的办法:一次就把好几个数都算出来。
读进 books.csv ,取出价格这一列(price),分三行打印:最贵的价格、最便宜的价格、平均价格。平均价格用 round() 保留两位小数。
运行后的输出应该是:
117.2
20.0
58.18💡 提示(3 条)
先 price = books['price'] ,再用 max()、min()、mean()
平均值写成 round(price.mean(), 2)
完整代码:
import pandas as pd books = pd.read_csv('books.csv') price = books['price'] print(price.max()) print(price.min()) print(round(price.mean(), 2))
四、describe() 一眼看全局
上一节算总和、平均、最大、最小,是一个方法算一个数。要是想把这些数放在一起看,一个一个地调用就有点麻烦了。这时候可以用 describe() ,它一次就能算出好几个统计量:
import pandas as pd
ledger = pd.read_csv('ledger_2026.csv')
print(ledger['cost'].describe())输出的结果:
count 1354.000000
mean 51.104505
std 234.946263
min 3.000000
25% 9.625000
50% 21.000000
75% 32.000000
max 2500.000000
Name: cost, dtype: float64输出里,每一行都是一个统计量。它们分别是什么意思呢?对照这份账本,一行一行地看:
| 统计量 | 意思 | 这份账本里 |
|---|---|---|
count | 有几个值 | 1354 笔 |
mean | 平均数 | 平均每笔约 51 元 |
std | 标准差,数值有多分散 | 235,很分散 |
min / max | 最小、最大 | 最少 3 元,最多 2500 元 |
25% / 50% / 75% | 四分位数:排好序以后,排在四分之一、正中间、四分之三处的那个值 | 一半的花销不超过 21 元 |
看完这张表,有没有发现什么奇怪的地方?平均每笔 51 元,可排在正中间的那一笔只有 21 元;而且四分之三的花销,都不超过 32 元。也就是说,平均数比大多数花销都要大得多。
这是为什么呢?因为账本里有几笔 2500 元的房租,把平均数拉高了。这就是第 1 章说过的:平均数很容易被少数几个特别大的值带偏。这时候,排在正中间的那个数(50% ,也叫中位数)更能代表「一笔普通的花销」。这一点,第 4 章还会细讲。
所以说,describe() 的用处,并不是让你把这些数抄下来,而是帮你发现问题:最大值是不是大得离谱?最小值是不是出现了负数?平均数和中位数是不是差得很远?哪里看着不对劲,就去原始数据里看个究竟。
五、常见坑
看表的这几样工具,用起来都不难。不过刚上手的时候,还是有几个坑很容易踩到,有的还不会报错,踩了都不知道。咱们一个一个来看。
1、列名写错了
先看一段代码,各位童鞋能看出哪里写错了吗?
import pandas as pd
ledger = pd.read_csv('ledger_2026.csv')
print(ledger['Cost'].sum())报错信息的最后一行是:
KeyError: 'Cost'为什么会找不到呢?因为列名是区分大小写的:明明是 cost ,这里写成了 Cost ,pandas 就找不到这一列了。所以拿不准列名的时候,先 print(list(ledger.columns)) 看一眼。
2、数字列被认成了文字
列名写错,好歹会报错。下面这个坑,代码不会报错,所以更不容易发现。
ledger_2026_raw.csv 是一份还没整理过的账单,第 5 章会用它来练习清洗。先来看看它的数据类型:
import pandas as pd
raw = pd.read_csv('ledger_2026_raw.csv')
print(raw.dtypes['cost'])
print(raw['cost'].head(4).tolist())输出的结果:
object
['2500', '229.5', '8', '22.5元']金额这一列的类型变成了 object ,取出来的也都是字符串。这是怎么回事呢?因为有的金额后面写了「元」,比如第 4 个值 '22.5元' 。pandas 一看这一列里有不是数字的东西,就把整列都当成了文字。这时候要是对它求和,得到的不是总金额,而是一长串拼接起来的文字。
所以读进数据以后,先看看 dtypes :本该是数字的列,是不是真的是数字?
3、只看了打印出来的那几行
print(ledger) 只会显示开头和结尾各 5 行,中间一千多行都被省略掉了。可问题往往就藏在没显示出来的地方。所以别光靠眼睛看,要用 info() 、describe() 、unique() 这些方法,让 pandas 替你把整列都检查一遍。
4、info() 外面又套了 print()
print(ledger.info())这样写会怎么样呢?info() 自己就会打印,它的返回值是 None 。外面再套一个 print() ,就会把这个 None 也打印出来,所以表格下面会多出一行 None 。实战 4 讲的「只打印,没有返回」,说的就是这类函数。
有的书还没有人打分,rating 这一列就空着。读进 books.csv ,先打印 rating 列有几个值,再打印有几本书没有评分。
count() 只数非空的值,用总行数减去它,就是空着的个数。
运行后的输出应该是:
109
11💡 提示(3 条)
非空值的个数:books['rating'].count()
总行数是 len(books),两个数相减
完整代码:
import pandas as pd books = pd.read_csv('books.csv') rated = books['rating'].count() print(rated) print(len(books) - rated)
六、自己查一次文档
第四节用 describe() 看的是金额这一列。不过,describe() 默认只统计数字列,像日期、名称、类别这些文字列,它就不管了。那么,文字列能不能也让它看一看呢?这种问题,最靠得住的办法就是去查文档:DataFrame.describe 。
打开说明,参数里有一个 include ,就是管「统计哪些列」的。说明里提到了它的三种用法:
- 不写的话,默认只统计数字列。
- 设成
'all',所有的列都统计。 - 也可以传一个类型的列表,只统计这些类型的列。
文档下面的例子里,正好有一条是 df.describe(include=[object]) ,专门用来看 object 类型的列。
查到了,就动手试一试:
import pandas as pd
ledger = pd.read_csv('ledger_2026.csv')
print(ledger.describe(include=[object]))输出的结果:
date item category payment
count 1354 1354 1354 1354
unique 365 22 7 4
top 2026-12-18 午饭 餐饮 支付宝
freq 8 326 984 582可以看到,文字列的统计量和数字列不一样。除了 count 还是有几个值,其余几项都换了:
unique:有几种不同的值。日期有 365 种,说明一年里每天都记了账;名称有 22 种;类别有 7 种;付款方式有 4 种。top:出现次数最多的那个值。freq:这个值一共出现了几次。
这样一来,一眼就能看出不少东西:记得最多的是「午饭」,有 326 笔;类别里「餐饮」最多,有 984 笔,占了一大半;最常用的付款方式是「支付宝」。日期那一列,2026-12-18 记了 8 笔,是一年里记账最多的一天。
不过,top 有一点要留意,文档的说明里也写了:要是有好几个值出现的次数一样多,top 会从中随便挑一个给你。这份账本里,记了 8 笔的只有这一天,所以没问题;可要是有两天都记了 8 笔,这里也只会显示其中一天。所以读统计结果的时候,要多问一句:「它是怎么算出来的?」
七、小结与回看
这一章没有急着算,而是先把记账本这张表,从整张表到每一列,都看了个清楚。回头梳理一下,学到了这几件事:
- DataFrame 就是一张表,由列名、索引(行号)和数据三部分组成。想看开头和结尾,就用
head()和tail()。 shape是行数和列数,columns是列名,dtypes是每一列的数据类型:float64、int64是数字,object一般是文字。info()能把这些一次看全:每一列的类型,还有非空值的个数。非空值比总行数少,就说明有缺失。ledger['列名']能取出一列,它是一个 Series,自带sum()、mean()、max()、count()、unique()、nunique()这些方法。要取好几列,就用两层方括号。describe()能一眼看全局,用来发现问题,比如平均数和中位数差得很远、最大值大得离谱、出现了负数……- 常见的坑有这几个:列名写错,会报
KeyError;数字列混进了文字,会变成object;只看打印出来的那几行,会漏掉中间的问题。 describe(include=[object])能统计文字列:有几种值,最多的是哪个,它出现了几次。
哪里卡住了,就回去看一看:
- 元组和列表:第 3 章
- 「只打印、不返回」:实战 4 的「六、常见坑」
这一章看的,都是整张表或者一整列。可分析的时候,咱们常常只关心其中的一部分。所以下一章,就来学怎么从这张表里挑出想要的部分:只要某几列、只要某几行,或者只要满足条件的那些行。
读完了?动手练一遍才算真会。
去闯关模式练习 →