
使用 pd.concat 合并列集不同的 DataFrame 时,默认会因填充 NaN 导致整数等类型降级为 float;本文介绍通过 convert_dtypes() 启用可空类型(如 Int64, string)或统一转为 object 类型,安全保留原始语义与类型完整性。
使用 `pd.concat` 合并列集不同的 dataframe 时,默认会因填充 nan 导致整数等类型降级为 float;本文介绍通过 `convert_dtypes()` 启用可空类型(如 `int64`, `string`)或统一转为 `object` 类型,安全保留原始语义与类型完整性。
在构建跨文件汇总表(例如从数十个含千列的源文件各取最后一行)时,一个核心挑战是:各文件列名存在部分差异(如 x, y, z 共有,但 a 仅在文件1出现、b 仅在文件2出现),而直接使用 pd.concat([df1, df2], ignore_index=True) 会导致缺失列被填入 NaN —— 这将强制原本为 int64 的列升级为 float64(因标准 NaN 是浮点型),破坏后续整数校验、导出或类型敏感处理逻辑。
✅ 推荐方案一:启用可空扩展类型(推荐默认选择)
调用 .convert_dtypes() 可将 NaN 映射为 pandas 的可空类型(nullable dtypes),例如:
-
int64→Int64(支持pd.NA) -
string→string(非object,支持pd.NA) -
bool→boolean -
float64→Float64
该方法既保持类型语义(Int64 仍表示“整数”,只是允许缺失),又避免精度丢失与隐式转换:
import pandas as pd
df1 = pd.DataFrame([(2, 3, True, "Test")], columns=["x", "y", "z", "a"])
df2 = pd.DataFrame([(4, 6)], columns=["x", "b"])
df3 = pd.concat([
df1.convert_dtypes(),
df2.convert_dtypes()
], ignore_index=True)
print(df3.dtypes)
# x Int64
# y Int64
# z boolean
# a string
# b Int64
# dtype: object
print(df3)
# x y z a b
# 0 2 3 True Test <na>
# 1 4 <na><na><na> 6</na></na></na></na>
⚠️ 注意事项:
- 若某列在不同 DataFrame 中实际混合了
int和float(如df1["x"] = [2],df2["x"] = [4.5]),convert_dtypes()仍会升为Float64—— 这是合理行为,因数值域已超出整数范围。 - 警告
invalid value encountered in cast通常源于尝试将含pd.NA的Float64列强转int,不应忽略;应确保后续逻辑兼容pd.NA(如用isna()而非== np.nan)。 - 关于弃用警告(
FutureWarning: ... all-NA entries):只要所有参与concat的 DataFrame 非空(即至少有一行),该警告不会触发;若存在空 DataFrame,请先过滤:dfs_nonempty = [df for df in dfs if not df.empty]。
✅ 推荐方案二:统一转为 object 类型(最大兼容性)
当必须绝对保留原始值的 Python 类型对象(如 int(2), str("Test"), True),且不依赖向量化运算性能时,可强制全部转为 object:
df3 = pd.concat([
df1.astype(object),
df2.astype(object)
], ignore_index=True)
print(df3.dtypes)
# x object
# y object
# z object
# a object
# b object
# dtype: object
print(df3.loc[0, "x"], type(df3.loc[0, "x"])) # 2 <class>
print(df3.loc[1, "b"], type(df3.loc[1, "b"])) # 6 <class></class></class>
✅ 优势:零类型妥协,int/str/bool 原样保留,NaN 表现为 np.nan(仍为 float,但仅作占位,不影响其他单元格类型)。
❌ 劣势:丧失数值计算优化、内存占用略高、无法使用 Int64 等专用方法(如 .fillna(0))。
? 不推荐的手动逐列填充方案
原文中提到的“先加空行→再逐单元格赋值”方式不仅低效(O(n²) 列操作)、易出错,还极易因隐式类型推断再次触发降级(如空字符串 "" 赋给新列后,再填 int 会转为 object 或 float)。应彻底避免。
总结建议
| 场景 | 推荐方法 | 说明 |
|---|---|---|
| ✅ 大多数情况(需类型语义 + 缺失支持) | pd.concat(...).convert_dtypes() |
安全、高效、符合 pandas 最佳实践 |
| ✅ 必须保留 Python 原始对象类型 | pd.concat(...).astype(object) |
牺牲性能换确定性,适合小规模汇总或下游为 Python 脚本 |
❌ 需要混合 int/float 但坚持用 int 列 |
不可行 —— 应修正数据源或接受 Float64
|
强制转 Int64 会引发 ValueError
|
最终,类型一致性始于设计:在读取原始文件时即调用 convert_dtypes(),并在整个 pipeline 中统一使用可空类型,可从根本上规避此类问题。










