在数据处理流程中,理解并正确转换数据类型是极关键的一环。从 Python 内置类型转换 到 NumPy 类型,再重点展开 Pandas DataFrame 的列类型转换与元素级的值转换。


一、Python 内建数据类型及其转换

1. 基础类型

  • 数字:int、float、complex
  • 布尔:bool(True/False,实际上是 int 的子类)
  • 文本:str
  • 二进制:bytes、bytearray
  • 空值:NoneType(只有 None 一个值)
  • 容器:list、tuple、dict、set、frozenset

2. 类型检查

type(x)           # 返回类型对象
isinstance(x, int) # 推荐,支持继承关系

3. 显式转换(构造函数)

转换目标函数说明
整数int(x)字符串 "10" → 10;浮点数 3.9 → 3(截断)
浮点数float(x)整数 10 → 10.0;字符串 "3.14" → 3.14
布尔值bool(x)绝大多数非空/非零 → True;0、""、[]、None 等 → False
字符串str(x)几乎所有对象都有 __str__
列表/元组list(x)/tuple(x)从可迭代对象转换

常见陷阱:

  • int("abc") 抛出 ValueError
  • int(None) 抛出 TypeError
  • float("3.14.15") 抛出 ValueError
  • bool("False") 返回 True,因为非空字符串为真

4. 隐式转换

  • 整数与浮点数运算:3 + 4.0 → 7.0(int 自动转 float)
  • 字符串与数字拼接:"value: " + 5 报错,需显式 "value: " + str(5)
  • 布尔值参与算术:True + 1 → 2(True 视为 1)

5. 与空值相关的转换

从 Excel/MySQL 导入的数据常混合 None 和 np.nan,转换前务必用 pd.isna() 或 math.isnan() 判空,参考你前一个问题中的空值判断表。


二、NumPy 数据类型(Pandas 的基石)

Pandas 列底层是 numpy.ndarray 或 ExtensionArray,因此了解 NumPy 类型很重要。

类型名描述对应 Python 类型
int6464 位有符号整数int(但不能存 NaN)
float64双精度浮点float(可存 np.nan)
bool_布尔值bool
object任意 Python 对象str、list 等混合类型
datetime64[ns]纳秒精度时间戳pd.Timestamp(类似 datetime.datetime)
timedelta64[ns]时间差pd.Timedelta

转换:np.array([1,2,3]).astype('float64')


三、Pandas DataFrame 的列类型转换

这是数据处理中最常用的操作——将整列的数据类型统一调整。

1. 查看列类型

df.dtypes
# 或单列
df['A'].dtype

2. 主要转换方法

(1) Series.astype() —— 最通用的方法

# 转换为内置或 numpy 类型
df['int_col'] = df['float_col'].astype(int)     # 会截断小数
df['str_col'] = df['num_col'].astype(str)       # 数字 -> 字符串
df['bool_col'] = df['flag'].astype(bool)
 
# 转换为 pandas 可空类型(支持缺失值,不会将 NaN 强转为 float)
df['int_col'] = df['float_col'].astype('Int64') # 注意大写 I
df['str_col'] = df['obj_col'].astype('string')
df['bool_col'] = df['flag'].astype('boolean')
 
# 转换为 category(节省内存)
df['cat_col'] = df['str_col'].astype('category')
 
# 转换为 datetime64
df['date_col'] = pd.to_datetime(df['date_str'])
# 再用 astype 统一格式(如有需要)
df['date_col'] = df['date_col'].astype('datetime64[ns]')

注意:astype() 会忽略缺失值(NaN),但如果列里原本有 pd.NA,转成 int 会报错,此时应转为 Int64。若列里含有无法转换的字符串,astype(int) 会直接报错。

(2) 专门的安全转换函数

  • 转数值:pd.to_numeric()
# errors='coerce' 将无法转换的值变为 NaN
df['price'] = pd.to_numeric(df['price'], errors='coerce')
# downcast 可缩小内存占用
df['price'] = pd.to_numeric(df['price'], downcast='integer')
  • 转日期时间:pd.to_datetime()
df['date'] = pd.to_datetime(df['date_str'], format='%Y-%m-%d', errors='coerce')
# 从 Excel 序列号转换(1899-12-30 为基准)
df['date'] = pd.to_datetime(df['excel_serial'], unit='D', origin='1899-12-30')
  • 转时间差:pd.to_timedelta()
df['duration'] = pd.to_timedelta(df['time_str'])

(3) 将 object 列中的多种空值占位符统一处理后再转类型

结合你之前的空值判断,典型流程:

# 先将各种空值占位符替换为 NaN
na_vals = ['', 'NA', 'NULL', 'N/A', '-']
df.replace(na_vals, np.nan, inplace=True)
# 再安全转为数值
df['col'] = pd.to_numeric(df['col'], errors='coerce')
# 最后转为可空整型(避免 float)
df['col'] = df['col'].astype('Int64')

(4) 多列同时转换

df = df.astype({
    'id': 'Int64',
    'name': 'string',
    'score': 'float',
    'active': 'boolean',
    'created_at': 'datetime64[ns]'
})

四、Pandas DataFrame 值级别的类型转换

有时不是整列转类型,而是对每个单元格进行自定义转换。

1. Series 上的逐元素操作

  • Series.apply(func):对每个元素应用函数
df['new'] = df['old'].apply(lambda x: x * 2 if isinstance(x, (int,float)) else x)
  • Series.map(func/dict):可用于查找替换或函数变换
# 映射转换
df['code'] = df['status'].map({'pass':1, 'fail':0})

2. DataFrame 上的逐元素操作

  • DataFrame.applymap(func)(新版本推荐用 DataFrame.map(func))
# 将所有数字四舍五入到两位小数
df = df.applymap(lambda x: round(x, 2) if isinstance(x, float) else x)
  • 向量化条件转换:直接用布尔索引或 np.where
df['new'] = np.where(df['score'] > 60, 'pass', 'fail')

3. 分箱转换(连续值 → 类别)

df['age_group'] = pd.cut(df['age'], bins=[0,18,35,60,100], labels=['child','youth','adult','senior'])
# 结果自动为 category 类型

4. 处理混合类型列的坑

从 Excel 读取时可能一列中混合数字和文本,其 dtype 为 object。直接 .apply() 通常安全,但性能低;若想转为数值,务必先用 pd.to_numeric(errors='coerce') 将非数字转 NaN,否则会报错。


五、实战:Excel / MySQL 数据导入后的类型修正示例

结合你之前询问的 Excel 和 MySQL 类型对应,这里给出几个典型修正场景:

从 Excel 读取后

import pandas as pd
df = pd.read_excel('data.xlsx')
 
# 1. 日期序列号转日期(如列中为数字)
df['date'] = pd.to_datetime(df['date'], unit='D', origin='1899-12-30', errors='coerce')
 
# 2. 带有千位分隔符的数字字符串 (如 "1,234") 转为数值
df['amount'] = df['amount'].str.replace(',', '').astype(float)
 
# 3. 百分比字符串 (如 "85%") 转为浮点数
df['pct'] = df['pct'].str.rstrip('%').astype(float) / 100.0
 
# 4. 空值占位符清理后转为 Int64
df.replace(['', 'NULL', 'N/A'], pd.NA, inplace=True)
df['id'] = df['id'].astype('Int64')

从 MySQL 读取后

from sqlalchemy import create_engine
engine = create_engine('mysql+pymysql://...')
df = pd.read_sql("SELECT * FROM products", engine)
 
# 1. Decimal 类型列可能被读为 object(字符串或 Decimal 对象)
# 转为 float,注意精度,或保留 Decimal 用于后续精确计算
from decimal import Decimal
df['price'] = df['price'].apply(lambda x: float(x) if x is not None else None)
 
# 2. TINYINT(1) 布尔标志:pymysql 返回 0/1,转为 bool
df['active'] = df['active'].astype(bool)
 
# 3. JSON 字符串解析为字典
import json
df['props'] = df['props'].apply(lambda x: json.loads(x) if pd.notna(x) else {})

六、总结与最佳实践

  1. 先诊断,后转换:用 df.dtypes、df.head()、df['col'].unique() 了解数据实际类型和异常值。
  2. 优先使用列级转换:pd.to_numeric、pd.to_datetime 配合 errors='coerce',比循环 apply 快得多。
  3. 缺失值友好:想让整数列带 NaN,用 pandas 的可空类型 Int64、boolean、string,不要用 numpy 的 int64。
  4. 精确度控制:金融数据避免 float → Decimal,或读取 MySQL 时直接保留 Decimal 对象。
  5. 链式操作:结合 astype、replace、fillna 形成清洗流水线,最终得到干净、类型正确的 DataFrame。

掌握了这些,无论数据来自 Excel、MySQL 还是其他源,你都能游刃有余地完成类型整理和后续分析了。