Pandas: 数据类型

最后更新:2026-08-26

数据类型(dtype)决定了 Pandas 如何存储和运算数据。选对 dtype,你的代码快 10 倍、内存省 80%;选错 dtype,数据精度丢失、运算报错、内存爆炸。本节从 Pandas 的 dtype 体系出发,帮你理解每种类型的特点、转换方法,以及最实用的内存优化技巧。

⚠️ 注意: 以下代码需在本地 Python 环境中运行。

1. 你将学到


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) 类型全景图

100%
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,高基数考虑分块处理。

📖 小节


📝 作业

  1. 基础题(难度⭐):创建一个 DataFrame(3 列:字符串/整数/浮点),用 dtypes 查看类型,用 astype 将整数列转为 int8、浮点列转为 float32,对比 memory_usage 变化。
  2. 进阶题(难度⭐⭐):创建一个含 NaN 的整数 Series,分别用传统方式(自动变 float64)和 Nullable Int64 方式处理,对比 dtype 和运算行为。然后用 category 类型优化一个有 5 个唯一值的字符串列。
  3. 挑战题(难度⭐⭐⭐):创建一个 10K 行的模拟 DataFrame(4 列:低基数字符串/高基数字符串/整数/浮点),执行完整内存优化流程:检查→低基数→category→downcast→Nullable→验证,输出每步的内存变化和最终节省百分比。

← 上一课:Index 索引 · 下一课:数据选取 →

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏