首页 / Pandas 入门教程 / 数据预览与快速探索

Pandas 入门教程

数据预览与快速探索

本教程共 54 篇 · 第 17 篇 · 更新于 2026-08-11 · 约 9 分钟阅读

PandasPandas 入门教程headinfo数据探索内存

本节目标:拿到一个陌生 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),行数变少说明筛选生效
Note

pandas 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 到 5
  • Data 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() 会在下一节展开,这里先知道它是”数据体检”的一环就行。体检完,数据长什么样、有什么坑,心里基本有底了。