数据预览与快速探索
本教程共 54 篇 · 第 17 篇 · 更新于 2026-08-11 · 约 9 分钟阅读
本节目标:拿到一个陌生 DataFrame,能用几个方法快速看清它有多少行多少列、每列什么类型、有没有缺失值、占多少内存。
head 和 tail:看头看尾
数据读进来第一件事,先看几行确认没读错。head() 看前几行,tail() 看后几行,默认都是 5 行:
import pandas as pd
import numpy as np
df = pd.DataFrame({
"姓名": ["张三", "李四", "王五", "赵六", "钱七", "孙八"],
"年龄": [25, 30, 28, 35, 22, 40],
"城市": ["北京", "上海", "广州", "北京", "深圳", "杭州"],
})
print(df.head()) # 前 5 行
print(df.tail(2)) # 最后 2 行
print(df.head(3)) # 前 3 行
输出的末尾会有一行 [6 rows x 3 columns],告诉你这次展示了多少行多少列。拿小数据看更直观,head 的实际输出长这样:
姓名 年龄 城市
0 张三 25 北京
1 李四 30 上海
2 王五 28 广州
3 赵六 35 北京
4 钱七 22 深圳
列数很多时,中间的列会被 ... 省略,这是显示设置,不是数据丢了。想全部展开,用 pd.set_option:
import pandas as pd
import numpy as np
pd.set_option("display.max_columns", None) # 列全部显示
pd.set_option("display.max_rows", 100) # 最多显示 100 行
这些设置只在当前会话生效,是探索阶段的得力工具。
sample:随机抽几行
数据太大不想全看,用 sample 随机抽样,看得更客观:
import pandas as pd
import numpy as np
df = pd.DataFrame({
"姓名": ["张三", "李四", "王五", "赵六", "钱七", "孙八"],
"年龄": [25, 30, 28, 35, 22, 40],
"城市": ["北京", "上海", "广州", "北京", "深圳", "杭州"],
})
print(df.sample(3)) # 随机抽 3 行
print(df.sample(frac=0.5)) # 随机抽一半
sample 返回的还是一个 DataFrame,可以继续接别的操作。默认不会重复抽到同一行(不重复抽样)。加上 random_state 固定随机种子,每次抽的结果一样,方便复现:
import pandas as pd
import numpy as np
df = pd.DataFrame({"年龄": [25, 30, 28, 35, 22, 40]})
print(df.sample(2, random_state=42))
head 看开头、tail 看结尾、sample 看中间,三个搭配着用,能对数据内容有个初步印象。
顺便养成一个习惯:不确定某个操作返回什么类型,用 type() 确认。单括号取一列是 Series,双括号取多列是 DataFrame,这俩经常搞混:
import pandas as pd
import numpy as np
df = pd.DataFrame({"姓名": ["张三"], "年龄": [25], "城市": ["北京"]})
print(type(df["年龄"])) # Series,单列
print(type(df[["年龄"]])) # DataFrame,注意是双括号
shape 和 dtypes:一眼看出规模
shape:返回 (行数, 列数)dtypes:返回每列的类型
import pandas as pd
import numpy as np
df = pd.DataFrame({
"姓名": ["张三", "李四", "王五", "赵六", "钱七", "孙八"],
"年龄": [25, 30, 28, 35, 22, 40],
})
print(df.shape) # (6, 2)
print(df.dtypes)
shape 是个元组,可以拆包成两个变量:
import pandas as pd
import numpy as np
df = pd.DataFrame({"年龄": [25, 30, 28]})
rows, cols = df.shape
print("行数:", rows, "列数:", cols)
shape 还有个妙用:操作前后对比一下,能快速发现操作有没有生效。筛选后行数变少、合并后行数变多,都能用 shape 验证:
import pandas as pd
import numpy as np
df = pd.DataFrame({"年龄": [25, 30, 28, 35, 22]})
print(df.shape) # (5, 1)
df2 = df[df["年龄"] > 25]
print(df2.shape) # (3, 1),行数变少说明筛选生效
Notepandas 3.0 中字符串列默认显示为 str 类型(StringDtype),这是新版的新字符串类型。旧教程里常见的 object 会越来越少(如 §04 所述)。
index 和 columns:看轴标签
import pandas as pd
import numpy as np
df = pd.DataFrame({
"姓名": ["张三", "李四", "王五"],
"年龄": [25, 30, 28],
})
print(df.index) # 行索引
print(df.columns) # 列名
索引是 RangeIndex 说明还是默认的 0、1、2…;列名列表可以快速确认列有没有写错。想拿列名去做别的操作,转成列表:
import pandas as pd
import numpy as np
df = pd.DataFrame({"姓名": ["张三"], "年龄": [25]})
print(df.columns.tolist()) # ['姓名', '年龄']
看看每列有哪些值:unique 和 nunique
探索时经常想知道”这列一共有几种值”。nunique() 返回不重复值的个数,unique() 返回具体的不重复值:
import pandas as pd
import numpy as np
df = pd.DataFrame({"城市": ["北京", "上海", "广州", "北京", "深圳", "杭州"]})
print(df["城市"].nunique()) # 5 种不同的城市
print(df["城市"].unique()) # 具体是哪些值
类型列、城市列这种”取值有限”的列,用这两个方法一眼就能看出数据的分布范围。
想同时看每种值出现多少次,用 value_counts(),下一节会细讲。
info():一行命令的总览
info() 把上面这些信息汇总成一张表,还多了每列的非空计数:
import pandas as pd
import numpy as np
df = pd.DataFrame({
"姓名": ["张三", "李四", "王五", "赵六", "钱七", "孙八"],
"年龄": [25, 30, 28, 35, 22, 40],
"城市": ["北京", "上海", "广州", "北京", "深圳", "杭州"],
})
print(df.info())
输出大概长这样:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 6 entries, 0 to 5
Data columns (total 3 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 姓名 6 non-null str
1 年龄 6 non-null int64
2 城市 6 non-null str
dtypes: int64(1), str(2)
memory usage: 448.0 bytes
逐行解读:
- 第一行:对象的类型,确认是不是 DataFrame
RangeIndex: 6 entries:6 行数据,行索引范围 0 到 5Data columns (total 3 columns):3 列- 表格里:每列的序号、列名、非空数量、类型
- 最后两行:类型汇总和内存占用
Non-Null Count 比行数少,就说明该列有缺失值。试试造一个有缺失的 DataFrame:
import pandas as pd
import numpy as np
df2 = pd.DataFrame({"姓名": ["张三", None, "王五"], "年龄": [25, 30, None]})
print(df2.info()) # 姓名列 2 non-null,年龄列 2 non-null
缺失值怎么处理,后面会专门讲(如 §24 所述)。
快速判断列里有没有缺失
info() 是全局视角,只想快速确认某一列有没有缺失,用 isna() 配合 any():
import pandas as pd
import numpy as np
df = pd.DataFrame({"姓名": ["张三", "李四", None], "年龄": [25, 30, None]})
print(df["年龄"].isna().any()) # True,年龄列有缺失
print(df.isna().any()) # 每列有没有缺失,一次全看
isna() 把每个值变成 True/False,any() 问”有没有任何一个 True”。两个一组合,判断缺失只需要一行。
memory_usage:内存占了多少
担心数据太大内存不够?用 memory_usage 查:
import pandas as pd
import numpy as np
df = pd.DataFrame({
"姓名": ["张三", "李四", "王五", "赵六", "钱七", "孙八"],
"年龄": [25, 30, 28, 35, 22, 40],
})
print(df.memory_usage()) # 每列占用,单位字节
print(df.memory_usage(deep=True)) # 含字符串内容的真实占用
print(df.memory_usage(deep=True).sum()) # 总占用
字符串列默认只算指针大小,deep=True 才会算上字符串实际内容,估算内存时记得带上。info() 里显示的 memory usage 是浅层估算,和 memory_usage() 一致。
对体量有个粗略概念:100 万行、5 列纯数值数据,大约占 40MB(每列 8 字节乘 100 万行)。字符串列就难说了,取决于文本长度,这正是 deep=True 有价值的地方。数据占内存超过可用内存的一半,就要考虑分块读取或换存储格式(如 §16 所述)。
Tip拿到新数据的标准动作:head() 看内容 → info() 看类型和缺失 → shape 看规模。三步下来,数据大概什么样心里就有数了。
转置着看:列多行少时的救星
数据列很多、行很少时,默认打印会挤成一团。转置一下,行列互换,每列变成一行,一眼能看全所有列名和值:
import pandas as pd
import numpy as np
df = pd.DataFrame({
"姓名": ["张三", "李四"],
"年龄": [25, 30],
"城市": ["北京", "上海"],
"得分": [88, 92],
})
print(df.head().T)
组合起来用
import pandas as pd
import numpy as np
df = pd.DataFrame({
"姓名": ["张三", "李四", "王五", "赵六", "钱七", "孙八"],
"年龄": [25, 30, 28, 35, 22, 40],
"得分": [88, 92, 78, 95, 65, 90],
})
# 完整的数据体检流程
print(df.head())
print(df.shape)
print(df.columns.tolist())
print(df.info())
print(df.describe()) # 数值列统计,如 §18 所述
describe() 会在下一节展开,这里先知道它是”数据体检”的一环就行。体检完,数据长什么样、有什么坑,心里基本有底了。