Pandas: 数据类型
最后更新:2026-08-26
数据类型(dtype)决定了 Pandas 如何存储和运算数据。选对 dtype,你的代码快 10 倍、内存省 80%;选错 dtype,数据精度丢失、运算报错、内存爆炸。本节从 Pandas 的 dtype 体系出发,帮你理解每种类型的特点、转换方法,以及最实用的内存优化技巧。
⚠️ 注意: 以下代码需在本地 Python 环境中运行。
1. 你将学到
- ❶ Pandas dtype 体系与 NumPy dtype 的区别
- ❷ object vs StringDtype vs category 的选择
- ❸ astype / convert_dtypes / infer_objects 类型转换
- ❹ Nullable 类型(Int64 / Float64 / boolean)
- ❺ 内存优化策略(category / downcast)
2. Bob 的内存爆炸危机
(1) 痛点:500 MB 的客户表
Bob 加载了一份客户数据到 DataFrame,一看内存占用——500 MB!100 thousand 行数据不应该这么庞大:
PYTHON
import pandas as pd
# Load customer data (simulated)
df = pd.DataFrame({
'customer_id': range(100000),
'name': ['Customer_' + str(i) for i in range(100000)],
'city': ['New York', 'London', 'Tokyo', 'Paris', 'Sydney'] * 20000,
'age': [25 + i % 50 for i in range(100000)],
'loyalty_points': [100 + i * 10 for i in range(100000)]
})
print(f"Memory before optimization: {df.memory_usage(deep=True).sum() / 1024 / 1024:.1f} MB")
# Typical output: ~18 MB (this example is small)
# Real 100K-row dataset with many string columns can reach 500+ MB
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
罪魁祸首:object 类型。Pandas 默认把字符串列存为 object(Python 对象指针),每个元素都是完整的 Python 字符串对象,内存开销巨大。
(2) 解法:category 类型 10 倍压缩
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
:category 内存优化(难度⭐⭐)
PYTHON
import pandas as pd
df = pd.DataFrame({
'customer_id': range(100000),
'name': ['Customer_' + str(i) for i in range(100000)],
'city': ['New York', 'London', 'Tokyo', 'Paris', 'Sydney'] * 20000,
'age': [25 + i % 50 for i in range(100000)],
'loyalty_points': [100 + i * 10 for i in range(100000)]
})
# Check memory before optimization
mem_before = df.memory_usage(deep=True).sum() / 1024 / 1024
# Optimize: city has only 5 unique values → category
df['city'] = df['city'].astype('category')
# Optimize: age range 25-74 → int8 is enough (max 127)
df['age'] = df['age'].astype('int8')
# Optimize: loyalty_points range → int32 is enough
df['loyalty_points'] = df['loyalty_points'].astype('int32')
mem_after = df.memory_usage(deep=True).sum() / 1024 / 1024
print(f"Before: {mem_before:.1f} MB")
print(f"After: {mem_after:.1f} MB")
print(f"Saved: {(1 - mem_after/mem_before)*100:.0f}%")
# Before: ~18 MB
# After: ~5 MB
# Saved: ~72%
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
(3) 收益:3 大优化手段
| 手段 | 效果 | 适用场景 |
|---|---|---|
| object→category | 内存减少 90%+ | 低基数字符串(唯一值<50%行数) |
| int64→int8/int16/int32 | 内存减少 75%~ | 数值范围较小 |
| float64→float32 | 内存减少 50% | 精度要求不高 |
3. Pandas dtype 体系
(1) 类型全景图
graph TB
DT["Pandas dtype"] --> NUM["Numeric"]
DT --> STR["String-like"]
DT --> DT_TYPE["Datetime"]
DT --> BOOL["Boolean"]
DT --> CAT["Categorical"]
DT --> NULL["Nullable"]
NUM --> I["int8/16/32/64"]
NUM --> UI["uint8/16/32/64"]
NUM --> F["float32/float64"]
STR --> O["object (legacy)"]
STR --> SD["StringDtype (new)"]
DT_TYPE --> DT64["datetime64[ns]"]
DT_TYPE --> TD64["timedelta64[ns]"]
BOOL --> PB["bool (NumPy)"]
BOOL --> NB["boolean (Nullable)"]
NULL --> NI["Int8/16/32/64"]
NULL --> NF["Float32/64"]
NULL --> NBO["boolean"]
NULL --> NS["string"]
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
(2) Pandas dtype vs NumPy dtype
| 特性 | NumPy dtype | Pandas dtype |
|---|---|---|
| 数值类型 | int8~64 / float32~64 | 同 NumPy + Nullable (Int64/Float64) |
| 字符串 | object | object / StringDtype / string |
| 布尔 | bool | bool / boolean (Nullable) |
| 缺失值 | np.nan (仅浮点) | NaN / NaT / pd.NA (全覆盖) |
| 分类 | 无 | category |
| 时间 | datetime64 | datetime64[ns] / timedelta64[ns] |
| 扩展 | 无 | ExtensionDtype (自定义) |
📌 重点: Pandas 的 Nullable 类型(大写开头:Int64 / Float64 / boolean / string)是 1.0 引入的新特性,用
pd.NA 统一表示缺失值,解决了"整数列不能有 NaN"的历史问题。
4. object vs StringDtype vs category
(1) 三者对比
| 特性 | object | StringDtype | category |
|---|---|---|---|
| 底层 | Python 对象指针 | 专用字符串存储 | 整数编码 + 查找表 |
| 缺失值 | None / np.nan | pd.NA | pd.NA |
| 内存 | 最高 | 中等 | 最低(低基数时) |
| 字符串方法 | .str 访问器 | .str 访问器 | .str 访问器 |
| 比较 | 可能不一致 | 一致 | 一致 |
| 排序 | 字典序 | 字典序 | 可自定义顺序 |
| 适用 | 兼容旧代码 | 新项目推荐 | 低基数列 |
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
:三者内存对比(难度⭐⭐)
PYTHON
import pandas as pd
import numpy as np
# 1000 rows, 5 unique cities
cities = ['New York', 'London', 'Tokyo', 'Paris', 'Sydney']
data = [cities[i % 5] for i in range(1000)]
s_object = pd.Series(data, dtype='object')
s_string = pd.Series(data, dtype='string')
s_category = pd.Series(data, dtype='category')
print(f"object: {s_object.memory_usage(deep=True) / 1024:.1f} KB")
print(f"string: {s_string.memory_usage(deep=True) / 1024:.1f} KB")
print(f"category: {s_category.memory_usage(deep=True) / 1024:.1f} KB")
# object: ~62 KB
# string: ~55 KB
# category: ~5 KB ← 12x smaller!
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
🔥 易错: 当唯一值数量接近行数时(高基数),category 反而比 object 更耗内存——因为它需要维护一个完整的映射表。经验法则:唯一值 < 50% 行数时用 category 有收益。
5. 类型转换方法
(1) astype:强制转换
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
:astype 类型转换(难度⭐)
PYTHON
import pandas as pd
df = pd.DataFrame({
'price_str': ['9.99', '19.99', '29.99'],
'quantity_int': [1, 3, 5],
'flag_str': ['True', 'False', 'True']
})
# String to float
df['price'] = df['price_str'].astype(float)
# Int to string (for ID columns)
df['qty_str'] = df['quantity_int'].astype(str)
# String to boolean (caution: 'True'/'False' as strings → bool)
df['flag'] = df['flag_str'].map({'True': True, 'False': False})
print(df.dtypes)
# price_str object
# quantity_int int64
# flag_str object
# price float64
# qty_str object
# flag bool
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
(2) convert_dtypes:自动推断最佳类型
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
:convert_dtypes 自动转换(难度⭐⭐)
PYTHON
import pandas as pd
df = pd.DataFrame({
'name': ['Alice', 'Bob', 'Charlie'],
'age': [28, 34, 25],
'score': [85.5, 92.0, 78.3],
'active': [True, True, False]
})
# Convert to best nullable dtypes
df_converted = df.convert_dtypes()
print(df_converted.dtypes)
# name string ← object → StringDtype
# age Int64 ← int64 → nullable Int64
# score Float64 ← float64 → nullable Float64
# active boolean ← bool → nullable boolean
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
(3) infer_objects:推断 object 列的最佳类型
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
:infer_objects 推断(难度⭐)
PYTHON
import pandas as pd
df = pd.DataFrame({
'a': [1, 2, 3], # stored as object
'b': ['x', 'y', 'z'] # stays as object
}, dtype='object')
print(df.dtypes)
# a object
# b object
df_inferred = df.infer_objects()
print(df_inferred.dtypes)
# a int64 ← inferred from content
# b object ← still object (strings stay)
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
(4) 转换方法对比
| 方法 | 作用 | 自动/手动 | Nullable |
|---|---|---|---|
| astype() | 强制转指定类型 | 手动 | 不自动 |
| convert_dtypes() | 推断最佳 Nullable 类型 | 自动 | 是 |
| infer_objects() | 推断 object 列类型 | 自动 | 不自动 |
6. Nullable 类型:解决整数缺失值问题
(1) 为什么需要 Nullable 类型
NumPy 的 int64 不能存储 NaN——如果整数列出现缺失值,Pandas 被迫把整列升级为 float64,导致类型丢失。
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
:Nullable vs 传统类型(难度⭐⭐)
PYTHON
import pandas as pd
import numpy as np
# Traditional: int column with NaN → forced to float
s_trad = pd.Series([1, 2, np.nan, 4])
print(s_trad) # 1.0, 2.0, NaN, 4.0 — floats!
print(s_trad.dtype) # float64
# Nullable: Int64 keeps integer type with <NA>
s_null = pd.Series([1, 2, pd.NA, 4], dtype='Int64')
print(s_null) # 1, 2, <NA>, 4 — integers!
print(s_null.dtype) # Int64
# Nullable boolean
b_null = pd.Series([True, False, pd.NA], dtype='boolean')
print(b_null)
# Nullable string
str_null = pd.Series(['Alice', pd.NA, 'Charlie'], dtype='string')
print(str_null)
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
(2) Nullable 类型一览
| Nullable 类型 | 对应传统类型 | 缺失值标记 | 适用场景 |
|---|---|---|---|
| Int8/16/32/64 | int8/16/32/64 | pd.NA | 整数列有缺失值 |
| Float32/64 | float32/64 | pd.NA | 浮点列有缺失值 |
| boolean | bool | pd.NA | 布尔列有缺失值 |
| string | object | pd.NA | 字符串列(替代 object) |
💡 提示: Nullable 类型用
pd.NA 统一表示缺失值,替代了传统的 np.nan / None / NaT 混乱局面。运算时 pd.NA 遵循"传播规则"——任何涉及 pd.NA 的运算结果都是 pd.NA。
7. 内存优化实战
(1) 检查内存占用
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
:memory_usage 分析(难度⭐)
PYTHON
import pandas as pd
df = pd.DataFrame({
'id': range(10000),
'city': ['New York', 'London', 'Tokyo', 'Paris', 'Sydney'] * 2000,
'score': [85.5 + i * 0.1 for i in range(10000)],
'status': ['Active', 'Inactive', 'Pending'] * 3333 + ['Active']
})
# Per-column memory usage (deep=True for object columns)
print(df.memory_usage(deep=True))
# Index 80
# id 80000 ← int64 for 10K rows
# city 630000 ← object (huge!)
# score 80000 ← float64
# status 460000 ← object (huge!)
# Total
total = df.memory_usage(deep=True).sum() / 1024 / 1024
print(f"Total: {total:.1f} MB")
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
(2) 优化策略
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
:全链路内存优化(难度⭐⭐⭐)
PYTHON
import pandas as pd
df = pd.DataFrame({
'id': range(10000),
'city': ['New York', 'London', 'Tokyo', 'Paris', 'Sydney'] * 2000,
'score': [85.5 + i * 0.1 for i in range(10000)],
'status': ['Active', 'Inactive', 'Pending'] * 3333 + ['Active']
})
mem_before = df.memory_usage(deep=True).sum() / 1024 / 1024
# 1. String columns with low cardinality → category
df['city'] = df['city'].astype('category')
df['status'] = df['status'].astype('category')
# 2. Numeric downcast
df['id'] = pd.to_numeric(df['id'], downcast='integer') # int64 → int16
df['score'] = pd.to_numeric(df['score'], downcast='float') # float64 → float32
mem_after = df.memory_usage(deep=True).sum() / 1024 / 1024
print(f"Before: {mem_before:.1f} MB")
print(f"After: {mem_after:.1f} MB")
print(f"Saved: {(1 - mem_after/mem_before)*100:.0f}%")
print(f"\nOptimized dtypes:")
print(df.dtypes)
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
(3) Downcast 对照表
| 原类型 | downcast='integer' | downcast='float' | 节省 |
|---|---|---|---|
| int64 | int8/int16/int32 (自动选最小) | — | 50%~75% |
| float64 | — | float32 | 50% |
| uint64 | uint8/uint16/uint32 | — | 50%~75% |
8. 完整示例:大型客户表优化
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
:客户表全链路优化(难度⭐⭐⭐)
PYTHON
import pandas as pd
import numpy as np
# ============================================
# Comprehensive example: Customer table
# full optimization pipeline
# ============================================
# 1. Create realistic customer data
n = 50000
df = pd.DataFrame({
'customer_id': range(n),
'name': [f'Customer_{i:05d}' for i in range(n)],
'city': np.random.choice(['New York', 'London', 'Tokyo', 'Paris',
'Sydney', 'Berlin', 'Toronto', 'Seoul'], n),
'age': np.random.randint(18, 80, n),
'membership': np.random.choice(['Basic', 'Silver', 'Gold', 'Platinum'], n),
'points': np.random.randint(0, 100000, n),
'satisfaction': np.random.choice([1, 2, 3, 4, 5, np.nan], n, p=[0.05,0.1,0.2,0.3,0.3,0.05])
})
# 2. Analyze before optimization
print("=== BEFORE Optimization ===")
print(df.dtypes)
mem_before = df.memory_usage(deep=True).sum() / 1024 / 1024
print(f"Total memory: {mem_before:.1f} MB")
# 3. Optimize
# Low-cardinality strings → category
for col in ['city', 'membership']:
df[col] = df[col].astype('category')
# Integer downcast
for col in ['customer_id', 'age', 'points']:
df[col] = pd.to_numeric(df[col], downcast='integer')
# Float with NaN → nullable Int64
df['satisfaction'] = df['satisfaction'].astype('Int8')
# 4. Analyze after optimization
print("\n=== AFTER Optimization ===")
print(df.dtypes)
mem_after = df.memory_usage(deep=True).sum() / 1024 / 1024
print(f"Total memory: {mem_after:.1f} MB")
print(f"Saved: {(1 - mem_after/mem_before)*100:.0f}%")
# 5. Verify data integrity
print(f"\n=== Integrity Check ===")
print(f"Rows: {len(df)}")
print(f"Satisfaction with NaN: {df['satisfaction'].isna().sum()}")
print(f"Unique cities: {df['city'].nunique()}")
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
❓ 常见问题
Q object 和 string 区别?
A object 是 Python 对象指针数组,每个元素是独立的 Python 字符串对象,内存开销大。string(StringDtype)是 Pandas 1.0+ 引入的专用字符串类型,用 pd.NA 表示缺失值,运算语义更一致。新项目推荐用 string 替代 object。
Q Nullable 类型有什么好处?
A 解决"整数列有缺失值就被迫变 float"的历史问题。Int64 可以同时存整数和 pd.NA,保持整数语义。例如客户年龄列有缺失值时,Int8 仍然是整数类型而非 float64。
Q category 何时用?
A 当列的唯一值数量远少于行数时(经验法则:唯一值 < 50% 行数)。典型场景:城市、性别、状态、等级等有限类别列。category 用整数编码+查找表替代重复字符串,内存可减少 90%+。高基数列(如用户ID)不适合。
Q convert_dtypes 和 astype 区别?
A astype 需要手动指定目标类型(如
astype('int64')),convert_dtypes 自动推断最佳 Nullable 类型(object→string, int64→Int64, float64→Float64)。astype 更精确,convert_dtypes 更便捷。Q 如何查看内存占用?
A
df.memory_usage(deep=True) 返回每列字节数。deep=True 对 object 列计算实际字符串内存(而非仅指针大小)。df.info(memory_usage='deep') 也显示总内存。Q downcast 安全吗?
A 安全——Pandas 的
pd.to_numeric(downcast=...) 自动选择能容纳数据范围的最小类型。例如 0-100 的整数会选 uint8(0-255),不会溢出。但如果你后续添加超出范围的数据,会被静默截断或升级类型。建议在数据清洗完成后最后做 downcast。Q 为什么 string 列的 memory_usage 这么大?
A object/string 列的每个元素都是独立的 Python 对象,有 50+ 字节的对象头开销。1 万行字符串可能占 600 KB,而同等行数的 int64 只占 80 KB。解决办法:低基数用 category,高基数考虑分块处理。
📖 小节
- Pandas dtype 体系比 NumPy 更丰富:新增 Nullable 类型、category、StringDtype
- object 是内存杀手,低基数字符串列应转换为 category(内存减少 90%+)
- Nullable 类型(Int64/Float64/boolean/string)用 pd.NA 统一缺失值,解决整数列不能有 NaN 的问题
- astype 手动转类型,convert_dtypes 自动推断 Nullable 类型,infer_objects 推断 object 列
- 内存优化三步:低基数字符串→category → 数值 downcast → float64→float32
- memory_usage(deep=True) 查看真实内存占用,object 列的实际内存远大于指针大小
- downcast 在数据清洗完成后做,避免后续数据超出类型范围
📝 作业
- 基础题(难度⭐):创建一个 DataFrame(3 列:字符串/整数/浮点),用 dtypes 查看类型,用 astype 将整数列转为 int8、浮点列转为 float32,对比 memory_usage 变化。
- 进阶题(难度⭐⭐):创建一个含 NaN 的整数 Series,分别用传统方式(自动变 float64)和 Nullable Int64 方式处理,对比 dtype 和运算行为。然后用 category 类型优化一个有 5 个唯一值的字符串列。
- 挑战题(难度⭐⭐⭐):创建一个 10K 行的模拟 DataFrame(4 列:低基数字符串/高基数字符串/整数/浮点),执行完整内存优化流程:检查→低基数→category→downcast→Nullable→验证,输出每步的内存变化和最终节省百分比。