ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

pandas astype(int)报错IntCastingNaNError:NaN与Inf的排查与解决

pandas astype(int)报错IntCastingNaNError:NaN与Inf的排查与解决 直接在数据流水线里跑astype(int)突然抛出一个IntCastingNaNError: Cannot convert non-finite values (NA or inf) to integer我相信写过 pandas 的人不会陌生。这个报错的核心内容一句话就能概括你的数据里混进了NaN或者正负无穷而 Python 自带的整数类型和 NumPy 的整数类型都没有办法表达“不是数”和“无限大”这两个概念。今天就把这个错误的来龙去脉、排查路径和最终解决办法完整拆开讲。这个报错通常出现在三件套组合拳之后读取外部文件、做数据清洗、然后急急忙忙把 float 列转成 int。新手最容易直接问我“怎么让 astype 不报错”但正确的问题应该是“我的数据为什么会出现 non-finite 值”。只要搞清楚后者前者根本没有一劳永逸的捷径——因为缺失值和无穷值必须由你自己决定怎么处理而你的业务场景会决定哪种处理方式是正确的。下面按我自己排查这个错误的固定流程来展开从错误本质到实操再到我踩过的坑尽量让你看完就能直接用到项目里。1. 先看清错误为什么 non-finite 值不能转成 integer1.1 整数类型在内存层面就没有 NaN 和 Inf 的“坑位”很多人第一次看到这个报错时会觉得“不就是一个类型转换吗怎么会失败”。其实这是底层表示的问题。float类型在内存里是 IEEE 754 标准定义的它专门留了几种特殊位模式来表示NaNNot a Number和Infinity。比如0.0 / 0.0会得到NaN1.0 / 0.0会得到Inf这些值在 float 世界里都是合法的。而整数类型无论是 Python 的int还是 NumPy 的int32、int64用补码表示所有二进制组合都会被解释成有限整数没有任何一个位模式留给了“缺失”或“无穷大”这种概念。所以当你对包含NaN的 float 数组执行astype(int)时NumPy 根本找不到一个合理整数来对应NaN于是直接抛出IntCastingNaNError。这里有一个容易混淆的细节过去某些版本里float(nan).astype(int)会返回一个极端整数比如 -9223372036854775808而不会报错。后来 NumPy 和 pandas 意识到这种行为会静默污染数据才在较新版本里改成显式报错。也就是说你现在看到的报错实际上是库的作者为了保护你防止产生错误结果而故意为之。1.2 pandas 的 astype(int) 与 NumPy 的 asarray 在转换时的差异pandas 的Series.astype(int)底层调用的是 NumPy 的转换机制但又有一些细节差异。当你写成df[col].astype(int)时pandas 会先检查当前 Series 的 dtype。如果字段本身是float64它会尝试把每个元素转成 C 语言的long或long long。在转换过程中如果遇到NaNpandas 会在包装层发现这是一个 non-finite 值然后抛出带IntCastingNaNError名字的异常。而如果你用np.asarray(df[col], dtypeint)直接转 NumPy 数组行为可能略微不同有的环境会直接得到垃圾整数有的会警告但归根结底都是同一个问题你丢失了 NaN 和 Inf 的语义信息。所以无论走哪条路径第一步都是确认数据里到底哪些位置出现了非有限值。1.3 为什么 Inf 也属于 non-finite 值报错信息里写着NA or inf很多人会忽略后半截inf。实际数据处理中inf的出现频率一点都不比NaN低。最常见的来源是除法比如df[ratio] df[a] / df[b]分母有 0 或接近 0 的极小数时结果就会变成inf甚至-inf。Inf在 float 里同样是合法的但它同样没有整数表示。数学上无限大根本不属于整数集合所以转换必然失败。很多同学处理完NaN后仍然报错就是因为只查了isna()没查isinf()结果被 Inf 卡住了。这一点我在后面的排查步骤里会专门演示。2. 排查路径三分钟定位全部 non-finite 值2.1 先看整体情况不要一上来就转换遇到报错我推荐的第一件事永远是把出问题的列单独拿出来做诊断。比如报错发生在df[score].astype(int)那就先执行print(df[score].dtype) print(df[score].isna().sum()) print(np.isinf(df[score]).sum())如果isna().sum()大于 0说明有缺失值如果np.isinf(df[score]).sum()大于 0说明有正负无穷。两个都要检查。这里要特别提醒一个新手最容易犯的错df.isna()只检查NaN和None它不会把inf当成缺失值处理。np.isinf()则刚好相反只检查正负无穷。所以两个函数必须配合使用non_finite_mask df[score].isna() | np.isinf(df[score])得到这个布尔掩码后可以立刻看看这些非有限值到底长什么样bad_rows df[non_finite_mask] print(bad_rows.head(20))这一步的作用不是马上修复而是搞清楚数据是在哪个环节被污染的。比如你发现只有特定日期的数据是NaN那可能是源文件空隙如果某列全是Inf那大概率是某个除法步骤出了问题。2.2 使用分步布尔索引定位具体行和列如果 DataFrame 很大你不希望一次性把整个表print出来可以用df[non_finite_mask].index拿到索引列表也可以进一步分析非有限值在不同列里的分布for col in df.columns: if pd.api.types.is_numeric_dtype(df[col]): n_nan df[col].isna().sum() n_inf np.isinf(df[col]).sum() if n_nan n_inf 0: print(f{col}: NaN{n_nan}, Inf{n_inf})这段代码输出每列的非有限值数量方便快速锁定问题范围。实测下来超过一半的场景里问题只出现在一两列并不需要处理整个表。把范围缩小以后修复决策才会清晰。为什么我强调用pd.api.types.is_numeric_dtype过滤因为np.isinf()在字符串列上会直接报错先过滤掉非数值列可以避免次要错误干扰你找主因。这也是一个经验之谈排查时不要用一行大而全的代码把所有列都处理了分段诊断更容易找到源头。2.3 检查 dtype 和版本差异报错可能来自 pandas 版本升级同样的代码在 pandas 0.25 和 pandas 1.5 里行为可能完全不同。早期版本里astype(int)遇到 NaN 可能只是警告甚至静默转出垃圾值后来 pandas 对这类行为收紧了策略变成了显式异常。所以如果你升级了 pandas 版本之后才发现这个报错先不要怀疑业务逻辑变了很可能只是库更加严格了。检查一下自己环境里的 pandas 版本import pandas as pd print(pd.__version__)如果你的业务逻辑依赖旧版那种“自动填 0”或者“自动取整”的行为那升级之后必须显式处理缺失值否则报错不可避免。这一点很容易被忽略很多人以为代码没改就没问题但依赖升级同样会引入这种隐性破坏。3. 修复方案根据业务语义决定填充、删除还是保留缺失3.1 能填的填缺失值填充的几种标准姿势当你确认了非有限值的位置后接下来要回答“这些值在业务上代表什么”。如果NaN代表“没有数据”而你希望把它当成 0 参加后续计算那就直接填充df[score] df[score].fillna(0)如果数据是时间序列NaN常常需要按照前后趋势补齐ffill前向填充最常用df[score] df[score].ffill()注意ffill默认只在缺失值处使用上一个非缺失值填充如果整列开头就是NaN那开头部分依然会是NaN所以有时需要配合bfill双向处理df[score] df[score].ffill().bfill()如果NaN代表异常样本用中位数或均值填充也是一种选择df[score] df[score].fillna(df[score].median())但这里我要泼冷水填充方案看起来简单实际上最容易引入数据偏差。比如一个年龄字段里缺失值非常多直接用整体均值填充会让分布扭曲更多时候应该根据分组后的众数或中位数填充。所以在动手前建议先看一眼缺失值占比missing_ratio df[score].isna().mean()如果缺失比例超过 20%填充法就要格外谨慎了。这时候可以考虑把该列直接保留为 float或者使用可空整数类型下面会专门讲。3.2 能删的删确认这些行是否已经不参与核心分析如果非有限值只占整份数据的极小比例比如 1% 不到而且这组数据本身质量很差删除可能是最省事且不过分影响结果的选择。mask ~(df[score].isna() | np.isinf(df[score])) df_clean df[mask].copy()注意这里的copy()很重要。如果不加copy()后续对df_clean的修改可能会触发SettingWithCopyWarning虽然不致命但很容易让人误以为代码出了严重问题。我在实际项目里被这个警告骗过好多次所以习惯在筛选后主动copy()一次。删除前还有一件事值得做把被删掉的行单独存一份日志。尤其是金融、风控等敏感场景删掉的数据以后可能被审计追问临时保存有问题的行能让你后期解释“为什么最终结果少了几条记录”。df_bad df[df[score].isna() | np.isinf(df[score])].copy() df_bad.to_csv(bad_rows.csv, indexFalse)这种做法对团队协作很有帮助别人在 review 代码时一眼就能知道你做了什么样的取舍。3.3 替换无穷值不要直接填 0先搞清楚正负号和业务含义Inf的修复方案和NaN不太一样。最简单的办法是把Inf统一替换成NaN这样后续都能用缺失值的统一方案df[score].replace([np.inf, -np.inf], np.nan, inplaceTrue)但如果你直接把Inf当成NaN填充成 0可能会得到完全错误的结果。比如某个指标是“金额除以时长”当时长趋近于 0 时结果可能是Inf它表示“单位时长对应的金额极大”这在某些场景下是有效信息填 0 就等于抹平了这种极端情况。更稳妥的做法是先分清楚正无穷和负无穷分别代表什么pos_inf_mask df[score] np.inf neg_inf_mask df[score] -np.inf然后按照业务逻辑分别处理有时需要截断成一个上限值比如风控评分超过 999 分就按 999 算有时需要换成NaN再走填充逻辑。千万不要把所有Inf一律填 0这个“省事”操作往往是最危险的。3.4 使用 pandas 可空整数类型 Int64允许转换后保留缺失如果你不想丢弃NaN又必须把列类型转为整数pandas 从 1.0 版本开始提供了可空整数类型Int64注意首字母大写字符串写法是Int64不是int64。它的底层用 mask 记录缺失值因此可以同时表达整数和缺失值。df[score] df[score].astype(Int64)这个操作不会报IntCastingNaNError转换后NaN依然保留但 dtype 显示为Int64。这个类型的优势是既保留了缺失语义又允许你后续做整数运算。它和 NumPy 的int64不完全是同一个类型有些第三方库可能不认但 pandas 内部操作基本都能兼容。如果你希望整数列里有缺失值同时保持缺失值为pd.NA还可以直接构造df[score] pd.array(df[score].dropna().astype(int), dtypeInt64) # 不推荐繁琐实际上更简洁的写法就是上面那种astype(Int64)它会自动把浮点整数转换成可空整数。不过需要提醒如果列里有99.7这种带小数部分的数值astype(Int64)同样会报错因为它也要求元素必须是整数值。所以Int64只解决了NaN/Inf的表示问题没有解决“非整数小数”的转换问题。真要处理小数要么先取整要么直接保留 float。我自己的经验是如果一列同时有缺失值和整数语义绝大多数场景直接使用Int64比 fillna 更干净。因为它保留了数据的真实性不发生人为捏造。等到后续建模或者导出时再根据需求决定是否填充。4. 源头治理数据清洗阶段就堵住 NaN 和 Inf4.1 读取文件时就用 dtype 参数做前置声明很多NaN其实在文件读取阶段就已经产生。比如 CSV 里有空单元格pandas 读入后自动变成NaN又比如原文件里某列混入了 N/A、NULL 等字符串也会被解析成NaN。当你之后执行转换时这些历史遗留问题才集中爆发。更好的做法是在pd.read_csv()阶段就把该列声明为字符串或特定类型避免后续二次推断。例如df pd.read_csv(data.csv, dtype{score: string})这样原始缺失值会以pd.NA形式保留而不会先被推断为float64从而避免后面astype的尴尬。当然如果该列本来就是纯数值也可以用dtype{score: Int64}直接声明可空整数读取时就让缺失值合法地停留在整数列里。不过要注意一点dtype声明会影响 pandas 解析空值时的方式。设置dtypestring后空单元格会变成pd.NA而不是NaN后续数值运算时你需要把它转回去。建议根据实际业务选择不要盲目照搬。4.2 以 pd.to_numeric 处理脏数据时errorscoerce 才是 NaN 大户另一个常见的NaN来源是pd.to_numeric()。当你把一个包含字符串的 Series 转数值时如果写成pd.to_numeric(s, errorscoerce)所有无法解析的值都会被替换成NaN。这个功能很有用但它同时制造了大量NaN等你再astype(int)时自然会触发错误。这里推荐一个两阶段方案。第一阶段定位原始无效值s_clean pd.to_numeric(s, errorscoerce) nan_mask_before s_clean.isna()第二阶段针对这些NaN再单独处理比如打印原始值看看是哪些字符串导致的for idx in s_clean.index[nan_mask_before]: print(idx, repr(s[idx]))这样你能立即知道数据里到底有哪些“脏文本”。很多场景下你会发现脏文本种类其实很少比如“-”和“None”两种这时候直接用replace先清洗再转换比事后盲目填充精准得多。4.3 运算过程中的 inf对除零场景建立拦截机制前面提到除法会产生inf这里提供一个实用的防御性写法。与其等结果出来后处理不如在计算前先检查分母中是否有 0denominator df[b] if (denominator 0).any() or np.isinf(denominator).any(): # 至少有一个分母为 0 或非法 df[ratio] np.where(denominator ! 0, df[a] / denominator, np.nan) else: df[ratio] df[a] / denominator这段代码把除零产生的inf提前替换成NaN你再对ratio列执行astype(int)时只会面临NaN如何填充的问题不会再突然冒出inf的意外。这个方法虽然多写几行但能让数据管线在源头就保持干净。如果你的数值计算涉及对数、根号等数学函数np.log(0)会得到-infnp.sqrt(负数)会得到NaN这些都是同样的原理。我建议凡是用到了可能产生非有限值的数学函数跟着写一个np.isfinite()的校验result np.log(df[x]) bad_mask ~np.isfinite(result) print(f非有限值个数: {bad_mask.sum()})这一步放在计算之后、入库之前效果相当于给管线装了个质检器。5. 转换前最后一关用 isfinite 做统一质检5.1 把 isna 和 isinf 合并成一条防线既然NaN和Inf都会导致转换失败最稳妥的质检方式不是分别检查而是直接用np.isfinite()。它会一次性把这两类值都识别出来all_finite np.isfinite(df[score])np.isfinite返回布尔数组只有在元素是有限数值时才为True。如果你在一个 Series 上使用效果等同于~(isna() | isinf())但写法更直观。检测完成后我一般会写一个断言assert np.isfinite(df[score]).all(), 数据中存在非有限值禁止转换如果断言触发就回到前面的步骤逐步排查如果断言通过再放心执行df[score] df[score].astype(int)这种“先断言后转换”的写法特别适合放在自动化流程或定时任务里。它把异常提前暴露在明确的位置而不是让IntCastingNaNError在两小时后某个你完全想不到的角落炸出来。5.2 列合并时注意索引对齐产生的 NaN还有一个隐蔽的坑当你用df[c] df[a] df[b]合并两个 Series 时如果它们的索引对不上结果里会出现NaN。这在从不同来源拼接数据的脚本里非常常见。举个例子s1 pd.Series([1, 2], index[0, 1]) s2 pd.Series([3, 4], index[0, 2]) s_sum s1 s2此时s_sum的索引是[0, 1, 2]其中索引 1 和 2 分别缺失对方的值所以结果里出现了NaN。一旦你用这个s_sum做astype(int)就会触发报错。排查这种问题最简单的方法是查看合并后结果的索引和元素数量print(s_sum.index.tolist()) print(s_sum.isna().sum())如果发现这种情况需要明确你想要的合并语义是要外连接保留缺失还是内连接只保留共有索引如果是内连接用s1.add(s2, fill_value0)可以避免产生NaN。这一步虽然和报错不直接挂钩但属于“平时不注意出问题找半天”的典型场景。5.3 大 DataFrame 性能提示先局部验证再全量转换当你的 DataFrame 有几百万行时直接对整个列做astype又报错又慢。推荐先取一个小样本进行验证确认无误后再全量处理。具体做法是sample df[score].head(1000) try: converted sample.astype(int) except IntCastingNaNError: print(样本中已经存在非有限值) else: print(小样本转换成功继续全量处理)哪怕全量处理仍然可能因为尾部数据有问题而失败至少你能更快发现大部分问题。如果直接用全量数据跑你还要花时间等它读完几百 MB 的 CSV效率差别很大。另一个提升性能的技巧是如果该列本来就应该是整数但在读入时被推断成了 float可以早一点用convert_dtypes()自动转换成可空整数df df.convert_dtypes()这个函数会尽量把float64中有整数值的列转换为Int64并且保留NaN。它不会自动把小数变成整数但能大大减少后续astype(int)的报错概率。实测下来在导入多个来源的表格时很省心。6. 避坑锦囊那些文档里不写的细节6.1 别用 isna() 去判断 inf也别用 isinf() 去判断 NaN一句话isna()和isinf()是完全不同的两套逻辑。isna()检查的是缺失值包括NaN、None、pd.NAisinf()检查的是正无穷和负无穷。如果你用isna()去筛 inf会漏掉目标用isinf()去筛 NaN也会漏掉目标。最安全的做法是记住一个公式non_finite ~np.isfinite(data)只要数据类型是数值型np.isfinite一定能把 NaN 和 Inf 都找出来。如果你喜欢用 pandas 风格也可以组合non_finite data.isna() | np.isinf(data)这两条公式等价看个人口味。但一定要避免只查其中一个然后就开始填充的偷懒行为。6.2 astype(int) 和 astype(Int64) 的坑大小写敏感且语义不同astype(int)是 NumPy 的int64它不允许NaN遇到Inf直接报错。astype(Int64)是 pandas 的可空整数类型它允许NaN存在但不允许Inf存在。如果你在Int64列里硬塞一个inf它同样会报错。所以数据只有NaN没有Inf转换为Int64可以成功数据里有Inf无论int还是Int64都会失败必须先替换Inf。另外字符串的Int64和int64只差一个大小写字母但类型完全不同。int64就是普通的 NumPy 整数类型Int64才是 pandas 可空类型。我见过有人把两者混写导致代码明明用了Int64却照样报错最后发现是字符串拼成了int64。这看起来是个低级错误但在紧急处理线上问题时很容易发生。6.3 检查你的数据里有没有字符串 nan很多时候报错并不是float里的NaN而是字符串nan。看起来长相一样实际类型完全不同。当你对某个对象列执行astype(int)时pandas 会尝试把字符串解析成整数这时也会报别的错如ValueError: invalid literal for int()。如果你的错误信息里带着IntCastingNaNError那说明列里确实存在真正的非有限 float 值如果带着invalid literal那才是字符串问题。两类错误的处理方案完全不同。判断办法很简单print(df[score].dtype)float64和object是两种命运。如果你看到object先考虑是否要把字符串的空值文本变成真正的缺失值df[score] df[score].replace([nan, NaN, None, ], np.nan)然后再走上面的方向。很多初级脚本都是倒在类型判断这一步因为打印出来显示NaN就以为一定是 float忽略了 dtype 是 object 的可能性。6.4 终极方案使用条件转换函数代替 astype如果你对列值的构成有更复杂的要求比如某些特殊标记值也要换掉用apply自定义转换函数是最灵活的兜底方案def safe_int_convert(x): if pd.isna(x) or np.isinf(x): return None # 或者你想要的值 return int(x) df[score] df[score].apply(safe_int_convert)这种写法没有astype那么简单直接但胜在完全可控。你可以在转换函数里加入业务规则比如“超过 10000 的数值截断为 9999”或者“等于 -1 的数值改成缺失值”。想怎么处理就怎么处理不容易被库的默认行为迷惑。不过要注意apply的性能在大数据集中会比较差一般只建议用在转换逻辑复杂且数据量不超过几十万行的场景。如果数据量很大还是尽量用向量化的fillna加astype性能差距非常明显。我自己的习惯是先用向量化方法覆盖 95% 的常规情况个别无法覆盖的再用apply兜底。7. 后续扩展把这个报错当成数据质量的提示器处理完IntCastingNaNError后我建议不要把代码就丢在那里不管而是趁这个机会给项目增加一个数据质量检测模块。比如写一个函数专门检查所有数值列的非有限值比例如果有异常就提前报警def check_finite_ratio(df, threshold0.01): report {} for col in df.select_dtypes(includenumber).columns: bad (~np.isfinite(df[col])).mean() if bad threshold: report[col] float(bad) return report这个函数返回“非有限值占比超标的列”你可以在数据导入后执行一次让异常在源头暴露。实际项目里你在这一步拦截住的问题往往比下游astype报错时发现的问题更早、更容易处理。这也是我处理这类报错后的固定动作不只在错误发生时救火而是建立起防火墙。回到最初的问题——看到IntCastingNaNError时别急着让 exception 消失。先坐下查一遍数据问自己三个问题哪些位置有NaN哪些位置有Inf业务上应该怎么处理它们三个问题有了答案astype只是最后一分钟的敲键盘而已。数据本身有问题转换永远只是纸上解决方案真正重要的是你选择用哪种方式保留数据里的真实含义。
返回列表