第 1.1 节 核心知识点#
预计学习时长: 30 分钟
本节目标:
- 会用字典创建一个 DataFrame;
- 知道怎么看一张表有几行几列;
- 会取出一列或多列,并说清楚它们返回的是什么东西;
- 看懂
dtypes输出的 int64 / float64 / object 是什么意思;- 会用
head()和tail()预览数据。
本课程使用的 Pandas 版本是
'3.1.0'
DataFrame 是什么#
在动手写代码之前,先建立一个直觉,不然每个方法都会显得很突兀。
在 Pandas 中,DataFrame 其实就是一张二维表格,它和 Excel 里的工作表几乎一样:
有行,有列;
每行是一条记录,每列是一个字段;
行列交汇的格子(称之为 Cell)里存一个值。
但和 Excel 不同的是,DataFrame 是一张有户口本的表,它的每一行都有编号(叫索引 index),每一列都有名字(叫列名 columns)。这两样东西你随时可以换、可以查、可以重设,这也是第 2 章将要重点讲的东西。
在 Pandas 里,有两种基本的数据结构:
| 结构 | 是什么 | 长什么样 |
|---|---|---|
| Series | 一列 | 一维的「带标签的数组」,比如下面订单表里的「城市」这一列 |
| DataFrame | 整张表 | 二维的「带行标签和列标签的表格」,由多个 Series 拼起来 |
一句话记住:Series 是一列,DataFrame 是很多列拼起来的表。
现在我们开始把这张表造出来。
本小节示例代码文件: 。
知识点 1:用字典创建 DataFrame#
推荐做法#
Pandas 提供了 pd.DataFrame() 函数。把一个字典传进去,它就变成一张表。
import pandas as pd
data = {
"订单号": [1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008],
"下单日期": ["2024-01-05", "2024-01-08", "2024-01-12", "2024-02-03",
"2024-02-15", "2024-03-02", "2024-03-20", "2024-04-08"],
"城市": ["北京", "上海", "北京", "广州", "上海", "北京", "深圳", "广州"],
"商品": ["手机", "电脑", "耳机", "手机", "键盘", "显示器", "耳机", "手机"],
"销量": [2, 1, 3, 2, 5, 1, 4, 2],
"单价": [3999.0, 7999.0, 599.0, 3999.0, 299.0, 1299.0, 599.0, 3999.0],
"金额": [7998.0, 7999.0, 1797.0, 7998.0, 1495.0, 1299.0, 2396.0, 7998.0],
"客户评分": [4.5, 4.0, 4.8, 3.9, 4.2, 4.6, 4.1, 4.9],
}
df = pd.DataFrame(data)
df输出:
订单号 下单日期 城市 商品 销量 单价 金额 客户评分
0 1001 2024-01-05 北京 手机 2 3999.0 7998.0 4.5
1 1002 2024-01-08 上海 电脑 1 7999.0 7999.0 4.0
2 1003 2024-01-12 北京 耳机 3 599.0 1797.0 4.8
3 1004 2024-02-03 广州 手机 2 3999.0 7998.0 3.9
4 1005 2024-02-15 上海 键盘 5 299.0 1495.0 4.2
5 1006 2024-03-02 北京 显示器 1 1299.0 1299.0 4.6
6 1007 2024-03-20 深圳 耳机 4 599.0 2396.0 4.1
7 1008 2024-04-08 广州 手机 2 3999.0 7998.0 4.9创建 DataFrame 的 3 要素#
第一,字典的每个键就是列名。 上面的
"订单号"、"城市"…… 一共 8 个键,所以表有 8 列,顺序就是你在字典里写的顺序,列也是按这个顺序排的。第二,字典的每个值就是这一列的全部数据,而且必须是一个列表(list)。长度为 8,所以表有 8 行。
第三,所有列的长度必须一样。 这是最容易踩的坑:
# 这行会报错
pd.DataFrame({"城市": ["北京", "上海"], "销量": [1, 2, 3]})报错信息大概长这样:
ValueError: All arrays must be of the same length翻译过来就是「所有数组长度必须一致」。Pandas 不会帮你猜哪列少一个值,它只会直接罢工。
因此,如果你的数据来自 Excel 或 JSON,构造字典这一步最容易出错,务必先数一下每列有几个值。
从列表创建#
有时候数据本来就是由「一堆行」,每行是个列表或字典。此时,用 pd.DataFrame() 直接传列表也行:
# 传列表:每一行是一个列表
df_rows = pd.DataFrame([
[1001, "北京", 2],
[1002, "上海", 1],
], columns=["订单号", "城市", "销量"])传字典的列表也是常见做法,而且可读性更好:
df_rows = pd.DataFrame([
{"订单号": 1001, "城市": "北京", "销量": 2},
{"订单号": 1002, "城市": "上海", "销量": 1},
])注意这两种写法的区别:传字典时列名来自字典的键;传字典的列表时列名来自每个字典的键的并集,某个字典缺哪个键,那个位置就是空值。
实际工作里怎么选? 如果你的数据是「一列一列」整理好的(比如从 Excel 复制来的几列),用字典;如果你的数据是「一行一行」记下来的(比如接口返回的一堆 JSON 对象),用字典的列表。
从 NumPy 数组创建#
如果你已经有一个二维 NumPy 数组,也可以直接转:
import numpy as np
arr = np.array([[1, 2, 3], [4, 5, 6]])
df_arr = pd.DataFrame(arr, columns=["a", "b", "c"])如果不通过 columns 指定列名的话,Pandas 会自动用整数 0, 1, 2 当列名。所以实践上一定要传 columns=。
从现有文件读取#
真实工作中数据基本都来自文件,Pandas 提供了一堆 read_xxx 方法:
df = pd.read_csv("orders.csv") # CSV 文件
df = pd.read_excel("orders.xlsx") # Excel 文件
df = pd.read_json("orders.json") # JSON 文件
df = pd.read_sql("SELECT * FROM orders", conn) # 数据库本课程不展开讲这些(那是另一个话题),但你要有这个印象:上面所有的 read_xxx 返回的也都是 DataFrame,所以本课程讲的所有操作,对读进来的表一样适用。
知识点 2:查看 DataFrame 的形状#
shape 是最该先看的一个属性#
df.shape 告诉你这张表有几行几列,它返回一个元组(tuple):
df.shape输出:
(8, 8)读法是:8 行 8 列。注意顺序是「先行后列」,跟数学里的矩阵一样。
如果你只关心行数或只关心列数:
len(df) # 行数:8
df.shape[0] # 行数:8
df.shape[1] # 列数:8columns 和 index#
df.columns # 列名输出:
Index(['订单号', '下单日期', '城市', '商品', '销量', '单价', '金额', '客户评分'], dtype='object')df.index # 行索引输出:
RangeIndex(start=0, stop=8, step=1)看懂了这两个,你对一张表就基本有数了:有哪些列、每列叫什么、行是怎么编号的。
一个小坑
df.columns打印出来带个Index([...], dtype='object')的壳,看着有点烦。想看干净的列表,用df.columns.tolist(),得到的就是一个普通的 Python 列表:df.columns.tolist() # ['订单号', '下单日期', '城市', '商品', '销量', '单价', '金额', '客户评分']
空表检查#
拿到数据先确认一下「它到底有没有东西」:
pd.DataFrame().shape # (0, 0)——完全空的表
pd.DataFrame({"a": []}).shape # (1, 0)——一列,但一行都没有如果是 (0, 0),说明你的字典是空的或者读文件失败了,值得回头查一下。
知识点 3:选择 DataFrame 的列#
这一节是本课程最容易搞混的地方,所以讲慢一点。
仅取一列#
df["城市"]输出:
0 北京
1 上海
2 北京
3 广州
4 上海
5 北京
6 深圳
7 广州
Name: 城市, dtype: object此时返回的是 Series(一列)。
如果你想把它存起来:
cities = df["城市"]
type(cities) # <class 'pandas.core.series.Series'>同时取多列#
注意这里有两层方括号,外层是「取东西」的语法,内层的中括号是一个 Python 列表,里面装着列名。
subset = df[["城市", "商品", "销量"]]
subset输出:
城市 商品 销量
0 北京 手机 2
1 上海 电脑 1
2 北京 耳机 3
3 广州 手机 2
4 上海 键盘 5
5 北京 显示器 1
6 深圳 耳机 4
7 广州 手机 2返回的是 DataFrame(一张表)。
两者的区别#
用一句话说清:
单层方括号
df["列名"]→ Series(一列,带索引)。双层方括号
df[["列名1", "列名2"]]→ DataFrame(一张表)。
为什么要设计成两种?因为它们在后续操作上真的不一样:
df["销量"].sum() # 对这一列求和 → 一个数字 20
df[["销量"]].sum() # 对这张表(只有一列)求和 → 一张表df["销量"] * 2 # 每一列都乘 2 → Series
df[["销量"]] * 2 # 整张表乘 2 → DataFrame上面两个 * 2 的输出长得几乎一样,但返回的类型不同,后面能接的方法也不同。多花一秒钟记住这个区别,能省掉后面一堆「为什么我的代码报错了」的困惑。
取不存在的列会怎样#
df["性别"] # KeyError: '性别'报错了。因为 Pandas 不知道「性别」这一列该放什么值。
如果你想「有就用,没有就返回空表」,可以用 df.get("性别"):
df.get("性别") # 返回 None,不报错知识点 4:理解数据类型#
看某一列的类型#
df["销量"].dtype输出:
dtype('int64')看所有列的类型#
df.dtypes输出:
订单号 int64
下单日期 str
城市 str
商品 str
销量 int64
单价 float64
金额 float64
客户评分 float64
dtype: object后缀是什么意思#
初学者看到 int64、str 这种名字常一脸茫然。其实它们就是「数据类型 + 内存宽度」:
| 类型 | 全称 / 含义 | 什么时候出现 | 例子 |
|---|---|---|---|
int64 | 64 位整数 | 整数字面量,全都是整数时 | 销量、订单号 |
float64 | 64 位浮点数 | 有小数点,或整数和小数混在一起 | 单价、金额、客户评分 |
str | 字符串对象类型 | 装了字符串时 | 城市、商品、下单日期 |
bool | 布尔值 | 只有 True / False | 某个「是否已付款」的标记列 |
datetime64[ns] | 日期时间 | 专门转成日期类型之后 | df["下单日期"] 转成日期后 |
几点要留意的:
第一,为什么「销量」是 int64 而「金额」是 float64? 因为 Python 里 2 是整数、7998.0 是小数,Pandas 原样保留了。这就是为什么你写数据时要注意:想要整数就写 7998,别写 7998.0。
pd.DataFrame({"a": [1, 2]}).dtypes["a"] # int64
pd.DataFrame({"a": [1.0, 2.0]}).dtypes["a"] # float64第二,把一列里混着数字和文字,会强制变成 object。 这是实际工作里最常见的「数据脏」的来源,第 3 章会专门讲怎么处理它。
info()比 dtypes 更全#
df.info()输出:
<class 'pandas.DataFrame'>
RangeIndex: 8 entries, 0 to 7
Data columns (total 8 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 订单号 8 non-null int64
1 下单日期 8 non-null str
2 城市 8 non-null str
3 商品 8 non-null str
4 销量 8 non-null int64
5 单价 8 non-null float64
6 金额 8 non-null float64
7 客户评分 8 non-null float64
dtypes: float64(3), int64(2), str(3)
memory usage: 644.0 bytes这行信息非常有用,请重点看 Non-Null Count 这一列:
如果显示
8 non-null,说明这列 8 个值一个不缺;如果显示
6 non-null,说明有 2 个缺失值(8 - 6 = 2)。
上面每一列都是 8 non-null,说明我们的数据是干净的。等你面对真实的 Excel 文件时,这一列会立刻帮你定位到哪些列有窟窿。
describe()数值列的统计摘要#
df.describe()输出:
订单号 销量 单价 金额 客户评分
count 8.00000 8.000000 8.000000 8.00000 8.000000
mean 1004.50000 2.500000 2849.000000 4872.50000 4.375000
std 2.44949 1.414214 2659.752942 3356.23969 0.377018
min 1001.00000 1.000000 299.000000 1299.00000 3.900000
25% 1002.75000 1.750000 599.000000 1721.50000 4.075000
50% 1004.50000 2.000000 2649.000000 5197.00000 4.350000
75% 1006.25000 3.250000 3999.000000 7998.00000 4.650000
max 1008.00000 5.000000 7999.000000 7999.00000 4.900000每一行是一个统计量:
count——有多少个非空值;mean——平均值;std——标准差,衡量数据有多分散;min/max——最小值和最大值;25%/50%/75%——四分位数,50%就是中位数。
只统计数值列,字符串列自动被跳过。如果你想让字符串列也参与统计(比如看每个城市出现了几次):
df.describe(include="all")知识点 5:head() 和 tail()#
表可能有几百万行,一屏根本显示不下。head() 和 tail() 让你只看头尾几行。
head() 看开头#
df.head()默认显示前 5 行:
订单号 下单日期 城市 商品 销量 单价 金额 客户评分
0 1001 2024-01-05 北京 手机 2 3999.0 7998.0 4.5
1 1002 2024-01-08 上海 电脑 1 7999.0 7999.0 4.0
2 1003 2024-01-12 北京 耳机 3 599.0 1797.0 4.8
3 1004 2024-02-03 广州 手机 2 3999.0 7998.0 3.9
4 1005 2024-02-15 上海 键盘 5 299.0 1495.0 4.2想看 3 行就传参数:
df.head(3)tail() 看结尾#
df.tail(3)输出:
订单号 下单日期 城市 商品 销量 单价 金额 客户评分
5 1006 2024-03-02 北京 显示器 1 1299.0 1299.0 4.6
6 1007 2024-03-20 深圳 耳机 4 599.0 2396.0 4.1
7 1008 2024-04-08 广州 手机 2 3999.0 7998.0 4.9它们俩有什么区别#
一句话: head() 从上往下看,tail() 从下往上看。
但它们的用途经常被搞混:
| 场景 | 用哪个 | 为什么 |
|---|---|---|
| 想知道数据长什么样、字段什么含义 | head() | 开头几行通常最完整规范 |
| 想知道数据是怎么「结束」的 | tail() | 结尾常暴露格式不一致、导出截断等问题 |
| 排查「最后几行是不是空的」 | tail() | 很多系统导出会在末尾留空行 |
一个实战小技巧:拿到陌生数据,先 head() 看结构,再 tail() 看有没有脏尾巴。两次加起来 10 行,基本就能把这张表的「长相」摸清。
注意:
head()返回的是新的一份数据(前 5 行),改它不会影响df。但如果你想修改原表的前几行,请用df.head()之前先df = df.head()重新赋值,或者直接用df.loc[...]。这是第 2 章的内容。
本节小结#
回顾一下这一节的 5 个知识点:
| 你想做的事 | 用这个 | 得到什么 |
|---|---|---|
| 造一张表 | pd.DataFrame(字典) | DataFrame |
| 看几行几列 | df.shape | (行数, 列数) |
| 看列名 / 行号 | df.columns / df.index | 列名列表 / 行索引 |
| 取一列 | df["城市"] | Series |
| 取多列 | df[["城市", "销量"]] | DataFrame |
| 看数据类型 | df.dtypes / df.info() | 每列的类型和缺失情况 |
| 看数值摘要 | df.describe() | 均值、中位数、标准差等 |
| 预览数据 | df.head() / df.tail() | 头几行 / 尾几行 |
拿到陌生数据的固定三步: ①
df.shape看规模;②df.info()看类型和缺失;③df.head()看长相。三步做完,一张表在你心里就有轮廓了。
下一节我们不学新东西,而是动手把这 5 个操作练熟,顺便把「取一列和多列到底差在哪」这个问题彻底解决。