RSS
菜单
全部文章快讯开发科技深度热点

第 44 章 常见报错与排查大全(二):类型与运算类报错

内容摘要

本篇是"常见报错与排查大全"的第二篇,专攻两类最折磨人的报错——**类型类**(TypeError / ValueError / dtype 不匹配)和**运算与比较类**(NaN 比较失效、浮点精度、布尔位运算混用、inplace 误用、GroupBy.apply 形状异常、concat 后列退化为 object)。前置知识:[第 7 章](07-缺失值与重复值处理.md)的缺失值机制、[第 8 章](08-数据类型与转换.md)的 dtype 转换、[第 13 章](13-分组聚合:groupby.md)的 groupby 用法。学完后你看到任何 TypeError / ValueError 都能在 30 秒内定位到是"类型不对"还是"运算不对",并立刻给出修复代码。

📖 本篇简介:本篇是"常见报错与排查大全"的第二篇,专攻两类最折磨人的报错——类型类(TypeError / ValueError / dtype 不匹配)和运算与比较类(NaN 比较失效、浮点精度、布尔位运算混用、inplace 误用、GroupBy.apply 形状异常、concat 后列退化为 object)。前置知识:第 7 章的缺失值机制、第 8 章的 dtype 转换、第 13 章的 groupby 用法。学完后你看到任何 TypeError / ValueError 都能在 30 秒内定位到是"类型不对"还是"运算不对",并立刻给出修复代码。
上一篇 第 43 章解决的是"读不进来、取不出来"的问题;本篇解决的是"读进来了、算着算着就崩了"的问题——这类报错不崩在开头,专挑你 pipeline 跑了一半的时候崩,最难排查。本篇 12 个报错全部在 pandas 3.0.6 本地实跑触发,报错原文真实可复现。

目录

1. 报错速查总表

2. 本篇运行环境与版本约定

3. 类型类报错:dtype 不匹配家族

4. 运算与比较类报错:逻辑精度聚合家族

5. 本章小结与自测清单

6. 练习题


1. 报错速查总表

先把这张表存进脑子。遇到报错时,先用报错关键字(TypeError / ValueError / 哪一行)对照下表,直接跳到对应小节:

| 报错名 | 一句话原因 | 本篇小节 |

|--------|-----------|---------|

| TypeError: operation 'add' not supported for dtype 'str' | 字符串列和数值做了 + | 报错 1 |

| TypeError: can only concatenate str (not "int") to str | object 列里混着字符串和数字,却直接 .mean()/.sum() | 报错 2 |

| TypeError: Invalid comparison between dtype=str and int | 字符串列和数字比大小(>、<) | 报错 3 |

| ValueError: invalid literal for int() with base 10 | astype(int) 时列里有非数字字符串 | 报错 4 |

| TypeError: unsupported operand type(s) for -: 'Timestamp' and 'str' | 单个 Timestamp 和字符串直接做减法 | 报错 5 |

| TypeError: unsupported operand type(s) for +: 'Categorical' and 'int' | category 列直接做算术 | 报错 6 |

| (不报错,但结果全错)== np.nan 永远 False | NaN 用 == 比较永远不等 | 报错 7 |

| (不报错,但结果错)0.1 + 0.2 != 0.3 | 二进制浮点精度 | 报错 8 |

| ValueError: The truth value of a Series is ambiguous | 过滤条件用了 and/or 而不是 &/| | 报错 9 |

| ChainedAssignmentError | 链式索引上用 inplace=True(CoW 下直接失败) | 报错 10 |

| TypeError: cannot concatenate object of type 'numpy.int64' | groupby.apply 每个组返回的类型不一致 | 报错 11 |

| TypeError: unsupported operand type(s) for +: 'int' and 'str' | concat 后同名列变成 object,再做聚合 | 报错 12 |

⚠️ 读报错的正确姿势:报错信息最长的部分是 traceback(调用栈),真正有用的只有最后一行。不要从下往上读半天,直接翻到最后一行看异常类型和消息,再回到代码找触发点。本篇每个报错都遵循"报错原文 → 触发场景 → 原因 → 修复 → 预防"五步模板。

2. 本篇运行环境与版本约定

本篇所有代码在以下环境实测通过:

Windows / Python 3.14.7 / pandas 3.0.6 / numpy 2.5.3

与网上你能搜到的旧教程相比,pandas 3.0 有三个会直接影响"报错长什么样"的变化,先记住:

1. 字符串列默认 dtype 是 `str`(PyArrow 后端),不再是 `object`。旧教程里 print(s.dtype) 输出 object,在本篇环境下输出 str。

2. Copy-on-Write(CoW)默认开启。链式赋值/视图副本的行为变了:旧版静默失败或只给 SettingWithCopyWarning,3.0 下链式 inplace 会直接抛 ChainedAssignmentError(见报错 10)。

3. 方法签名正在清理 `inplace` 参数。注意:实测 pandas 3.0.6 中 rename / sort_values / fillna / reset_index / drop 等方法的 inplace=True 仍然被接受并生效,并未全部移除;但官方已不推荐使用,新代码应一律写"赋值回去"的形式。

下面正式开始。


3. 类型类报错:dtype 不匹配家族

这一类报错的共同根源只有一句话:pandas 是强类型的,每一列都有 dtype,dtype 不匹配时它不会偷偷帮你转换,而是直接抛 TypeError / ValueError。Python 裸列表里 "1" + 1 会报错,pandas 列里同样会,只是报错信息更长。

报错 1:TypeError —— 字符串列与数值直接相加

① 报错原文(本地实跑截取):

TypeError: operation 'add' not supported for dtype 'str' with object of type <class 'int'>

② 触发场景:

import pandas as pd

# 一列姓名,pandas 3.0 默认 str dtype
s = pd.Series(['张三', '李四', '王五'])

# 想给每个人编号,直觉写 + 1 —— 直接崩
print(s + 1)

③ 原因分析:

str dtype 的列,+ 号在 pandas 里是"字符串拼接"的意思,不是"数学加法"。你拿一个字符串列去加一个整数标量,pandas 不知道你是想拼字符串还是想做加法,就抛 TypeError。

⚠️ 常见坑:旧版 pandas(字符串列是 object dtype)下这段代码的报错信息略有不同(TypeError: can only concatenate str (not "int") to str),3.0 换成 PyArrow str 后端后变成上面这行。报错文案会变,但问题的本质永远是"类型不匹配"。

④ 解决方案:

import pandas as pd

s = pd.Series(['张三', '李四', '王五'])

# 方案 A:确实想拼接字符串 —— 把数字先转成字符串,再用 + 或 str.cat
print(s + pd.Series(['1', '2', '3']))
# 输出:
# 0    张三1
# 1    李四2
# 2    王五3
# dtype: str

print(s.str.cat(pd.Series(['甲', '乙', '丙']), sep='_'))
# 输出:张三_甲、李四_乙、王五_丙

# 方案 B:其实是想给每行加序号 —— 用 range 生成数字列再转字符串
print('用户' + (s.index + 1).astype(str))

⑤ 预防建议:做任何运算前先 print(s.dtype) 看一眼。凡是 str / object 列,先问自己:这列到底该不该参与数学运算?不该就先 pd.to_numeric() 转类型(见报错 4)。


报错 2:TypeError —— object 混合类型列直接聚合

① 报错原文(本地实跑截取):

TypeError: can only concatenate str (not "int") to str

② 触发场景:

import pandas as pd

# 读进来一列,看起来都是数字,但混进了字符串
s = pd.Series(['1', 2, '3'])

# 想求平均 —— 崩在 numpy 的求和底层
print(s.mean())

③ 原因分析:

这一列的 dtype 是 object,意思是"这一列每个格子里装的是 Python 对象的引用,不保证类型一致"。.mean() 内部要先把整列加起来再除以个数:遇到 '1' + 2 时,Python 试图把字符串和 int 相加,于是报 "can only concatenate str to str"。

④ 解决方案:

import pandas as pd

s = pd.Series(['1', 2, '3'])

# 先把整列强制转成数值:转不动的变成 NaN,再求均值
print(pd.to_numeric(s, errors='coerce').mean())   # 输出 2.0

# 验证转换结果:
print(pd.to_numeric(s, errors='coerce'))
# 0    1.0
# 1    2.0
# 2    3.0
# dtype: float64

⑤ 预防建议:从 Excel / CSV 读进来的列,不要相信它"长得像数字"。 pipeline 第一步就把所有"应该是数字"的列过一遍 pd.to_numeric(errors='coerce'),越早转类型,后面越不会崩。


报错 3:TypeError —— 字符串列与数值比较

① 报错原文(本地实跑截取):

TypeError: Invalid comparison between dtype=str and int

② 触发场景:

import pandas as pd

s = pd.Series(['apple', 'banana', 'cherry'])

# 想筛选"名字长度大于 3"的,手滑写成和数字比大小
print(s > 3)

③ 原因分析:

< > >= <= 这些比较符要求两边 dtype 兼容。str 列和 int 标量之间没有定义大小关系,pandas 3.0 的 PyArrow str 后端对此直接拒绝(旧版 object 列下有时不会报错、只是返回全 False,更隐蔽)。

④ 解决方案:

import pandas as pd

s = pd.Series(['apple', 'banana', 'cherry'])

# 想按"长度"筛选:先取长度,再和数字比
print(s.str.len() > 3)
# 0    True
# 1    True
# 2    True
# dtype: bool

# 如果本意就是想和字符串比(如筛选订单号大于某个前缀),两边都用字符串:
print((s > 'b').tolist())   # [False, False, True]

⑤ 预防建议:写过滤条件前,把条件里"左边那列"和"右边那个值"的类型对一下。一边是 str 另一边是数字,先转一边再比。


报错 4:ValueError —— astype 强转失败

① 报错原文(本地实跑截取):

ValueError: invalid literal for int() with base 10: 'x'

② 触发场景:

import pandas as pd

s = pd.Series(['1', '2', 'x'])

# 想把字符串列转成整数列
print(s.astype(int))

③ 原因分析:

.astype(int) 是"强转"——它假设每个值都能被解析成数字,只要有一个值(这里是 'x')转不动,整列转换失败并报错。这是它和 pd.to_numeric() 的本质区别:astype 不允许"漏网之鱼"。

④ 解决方案:

import pandas as pd

s = pd.Series(['1', '2', 'x', '3.5'])

# 方案 A:转不动的变成 NaN,脏数据被"隔离",不阻塞后续流程
print(pd.to_numeric(s, errors='coerce'))
# 0    1.0
# 1    2.0
# 2    NaN      <- 'x' 被变成缺失值
# 3    3.5
# dtype: float64

# 方案 B:先定位到底是哪些值转不动,人工清洗
bad = s[pd.to_numeric(s, errors='coerce').isna()]
print("转不动的值:", bad.tolist())   # 转不动的值: ['x']

# 方案 C:确认数据干净、想一步到位转 int —— 先 coerce 再 dropna 再 astype
clean = pd.to_numeric(s, errors='coerce').dropna().astype(int)

⑤ 预防建议:记住口诀——"想强转就 astype,想容错就 to_numeric"。生产数据 99% 的情况应该用 pd.to_numeric(errors='coerce'),然后单独检查 NaN 来源。


报错 5:TypeError —— 时间戳与字符串直接做运算

① 报错原文(本地实跑截取):

TypeError: unsupported operand type(s) for -: 'Timestamp' and 'str'

② 触发场景:

import pandas as pd

ts = pd.Timestamp('2024-05-01')

# 想算今天距离年初多少天
print(ts - '2024-01-01')

③ 原因分析:

这里有一个非常容易踩的不对称:

  • Series 和字符串比较时(pd.Series(时间) > '2024-03-01'),pandas 会自动把字符串解析成时间,不报错;
  • 单个 Timestamp 和字符串做算术(-、+、/ 天)时,pandas 不会自动帮你转,直接抛 TypeError。

很多人被前一种"宽容"惯坏了,以为后一种也能省一步。

④ 解决方案:

import pandas as pd

ts = pd.Timestamp('2024-05-01')

# 差值:两边都先变成 Timestamp
delta = ts - pd.Timestamp('2024-01-01')
print(delta)                     # 121 days 00:00:00
print(delta.days)                # 121

# 日期加减天数:用 pd.Timedelta,不要用字符串
print(ts + pd.Timedelta(days=30))   # 2024-05-31 00:00:00

# 而 Series 的比较确实可以偷懒(实测通过,自动解析字符串):
s = pd.Series(pd.to_datetime(['2024-01-01', '2024-06-01']))
print((s > '2024-03-01').tolist())   # [False, True]

⑤ 预防建议:凡是单个 Timestamp / datetime64 标量参与运算,先 pd.Timestamp(...) 包一下;加减时间用 pd.Timedelta(days=...)。不要用字符串当时间用。


报错 6:TypeError —— category 列做算术运算

① 报错原文(本地实跑截取):

TypeError: unsupported operand type(s) for +: 'Categorical' and 'int'

② 触发场景:

import pandas as pd

# 把一列名次存成 category(节省内存)
s = pd.Series([1, 2, 3], dtype='category')

# 想给所有人名次 +1
print(s + 1)

③ 原因分析:

category dtype 的语义是"这是一组有限类别",pandas 默认不允许你对类别值做算术——因为很多类别(比如 ['男','女'])根本没有加法。即使底层存的是数字,pandas 也不认。

④ 解决方案:

import pandas as pd

s = pd.Series([1, 2, 3], dtype='category')

# 先转回普通数值列再运算,运算完如需省内存再转回去
print((s.astype(int) + 1).tolist())   # [2, 3, 4]

# 如果 category 是字符串类别(如 ['低','中','高'])想做"编码映射",用 cat.categories + 字典映射,不要直接加减

⑤ 预防建议:category 只用于"分类变量"(性别、等级、地区),并且只在 groupby / 排序 / 省内存时用;一旦要参与数学运算,先 .astype() 回数值。


4. 运算与比较类报错:逻辑精度聚合家族

这一类更阴险:有一半根本不抛异常,而是悄悄给你错误结果(报错 7、8),等你发现时报表已经发出去了。另一半抛异常(报错 9~12),但报错信息和真正的 bug 常常隔了一层窗户纸。

报错 7:NaN 比较"静默失效"(最隐蔽的坑)

① 报错原文:这个坑没有报错,这才是它可怕的地方。

>>> import numpy as np
>>> np.nan == np.nan
False

② 触发场景:

import pandas as pd
import numpy as np

s = pd.Series([1.0, np.nan, 3.0])

# 想筛出缺失值那一行,直觉写 == np.nan
print(s[s == np.nan])      # 输出一个空 Series!一行都没选出来

③ 原因分析:

这是 IEEE 754 浮点数标准规定的行为:NaN 和任何东西(包括它自己)比较都返回 False。所以 s == np.nan 永远全是 False,筛选结果恒为空。更隐蔽的是它在 groupby 里的表现:pandas groupby 默认 dropna=True,NaN 组会被直接丢掉,你可能根本不知道有一组数据没参与聚合。

④ 解决方案:

import pandas as pd
import numpy as np

s = pd.Series([1.0, np.nan, 3.0])

# 正确:判断缺失值用专门的 .isna() / .notna()
print(s[s.isna()])                    # 正确选出索引 1
print("NaN 所在行号:", s[s.isna()].index.tolist())   # [1]

# groupby 里要保留 NaN 这一组,显式传 dropna=False
df = pd.DataFrame({'g': ['a', 'a', None, 'b'], 'v': [1, 2, 3, 4]})
print(df.groupby('g', dropna=False)['v'].sum())
# a      3
# b      4
# NaN    3     <- 显式保留了缺失组
# dtype: int64

# 对照:默认 dropna=True 时,NaN 组被静默丢掉:
print(df.groupby('g')['v'].sum())
# a    3
# b    4     <- 注意没有 NaN 那一行,3 这个数凭空消失了

⑤ 预防建议:全宇宙记住一条铁律——判断缺失值永远用 `.isna()` / `.notna()`,永远不要用 `==`。做完 groupby 聚合后,先打印一下结果行数,和"你预期应该有的组数"对一下,防止 NaN 组被静默丢弃。


报错 8:浮点精度陷阱

① 报错原文:同样不抛异常,结果悄悄错。

>>> 0.1 + 0.2
0.30000000000000004
>>> 0.1 + 0.2 == 0.3
False

② 触发场景:

import pandas as pd

# 财务算金额:三笔 0.1 元、0.2 元、0.3 元的订单,求和后和目标值比
s = pd.Series([0.1, 0.2])
print(s.sum())                 # 0.30000000000000004
print(s.sum() == 0.3)          # False —— 你以为相等,其实不相等

# round 也救不了你(银行家舍入 + 精度问题叠加):
print(round(2.675, 2))         # 2.67,而不是你以为的 2.68

③ 原因分析:

计算机用二进制存浮点数,0.1 和 0.2 在二进制里都是无限循环小数,相加后尾部多出一个 0.00000000000000004。这不是 pandas 的 bug,是 IEEE 754 的天性。round() 在这种边界值上也会因为"先精度损失再舍入"而给出反直觉结果。

④ 解决方案:

import pandas as pd
import numpy as np

# 方案 A:比较浮点数不要用 ==,用 np.isclose(允许极小误差)
print(np.isclose(0.1 + 0.2, 0.3))   # True

# 方案 B(财务首选):金额一律用"分"做单位存成整数,彻底绕开浮点
amounts_cents = pd.Series([10, 20, 30])          # 单位:分
print(amounts_cents.sum(), "分 =", amounts_cents.sum() / 100, "元")
# 60 分 = 0.6 元,整数加法永远精确

# 方案 C:确需高精度小数,用 decimal.Decimal(大数据量下较慢,按需使用)
from decimal import Decimal
print(Decimal('0.1') + Decimal('0.2'))            # 0.3,精确

⑤ 预防建议:金额、库存数量等业务敏感数字,入库就用整数(最小货币单位);工程上比较两个浮点数,永远写 np.isclose(a, b),不要写 a == b。


报错 9:ValueError —— and/or 与 &/| 混用

① 报错原文(本地实跑截取):

ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().

② 触发场景:

import pandas as pd

df = pd.DataFrame({'a': [1, 2, 3], 'b': [4, 5, 6]})

# 想同时满足两个条件,用了 Python 原生 and
print(df[(df.a > 1) and (df.b > 5)])

③ 原因分析:

df.a > 1 的结果是一个布尔 Series(长度和表一样),不是单个 True/False。Python 的 and 要求左右两边都是"单个布尔值",看到一个 Series 它懵了:"你到底想让我判断这个序列整体是真还是假?"于是抛出 "truth value is ambiguous"。

④ 解决方案:

import pandas as pd

df = pd.DataFrame({'a': [1, 2, 3], 'b': [4, 5, 6]})

# 正确写法:& 表示"逐元素与",| 表示"逐元素或"
# 关键:每个条件必须用圆括号包起来!(& 的优先级比 > 高)
print(df[(df.a > 1) & (df.b > 5)])
#    a  b
# 2  3  6

# 多个条件同理:
print(df[(df.a > 1) | (df.b < 5)])

# 条件多到括号眼花时,用 query() 更清爽:
print(df.query("a > 1 and b > 5"))

⑤ 预防建议:背下来——逐元素逻辑用 `&` `|` `~`,且每个条件套括号;and/or/not 只在"结果是单个布尔"时用(比如 if df.empty:)。


报错 10:inplace 误用与 CoW 下的 ChainedAssignmentError

① 报错原文(本地实跑截取,pandas 3.0.6):

ChainedAssignmentError: A value is being set on a copy of a DataFrame or Series
through chained assignment using an inplace method.
Such inplace method never works to update the original DataFrame or Series,
because the intermediate object on which we are setting values always behaves as
a copy (due to Copy-on-Write).

② 触发场景:

import pandas as pd
import numpy as np

df = pd.DataFrame({'a': [1.0, np.nan, 3.0]})

# 想在原表上把缺失值填成 0,顺手链式 + inplace
df['a'].fillna(0, inplace=True)
print(df)      # 你以为 a 列已经变了 —— 其实没变

③ 原因分析:

df['a'] 先返回一个中间 Series,你在它身上 inplace=True。但在 pandas 3.0 默认开启的 Copy-on-Write(CoW) 下,这个中间对象永远是个"副本",你的修改根本回不到原表。旧版 pandas(<2.0)这种写法是静默失败(不报错、数据也没改),2.x 给 SettingWithCopyWarning,3.0 直接抛 `ChainedAssignmentError`。

📌 版本差异说明(务必注意):网上很多文章说"pandas 3.0 已经移除了 inplace 参数"。实测本机 pandas 3.0.6:df.rename(..., inplace=True)、df.sort_values(..., inplace=True) 这类顶层直接在 DataFrame 上调用的写法仍然被接受且生效。真正会爆炸的是"先选列、再 inplace"的链式写法。无论哪种,官方推荐的写法都是下面这种"赋值回去"的形式。

④ 解决方案:

import pandas as pd
import numpy as np

df = pd.DataFrame({'a': [1.0, np.nan, 3.0]})

# 正确写法:把方法的返回值整体赋值回列,不要用 inplace
df['a'] = df['a'].fillna(0)
print(df['a'].tolist())     # [1.0, 0.0, 3.0]

# 顶层方法同理,推荐写法是接收返回值:
df = df.rename(columns={'a': '数值'})     # 而不是 df.rename(..., inplace=True)
df = df.sort_values('数值')                # 而不是 inplace=True

⑤ 预防建议:新代码里彻底忘掉 `inplace=True` 这个参数,一律写成 df = df.方法() 或 df['列'] = df['列'].方法()。多写一个变量赋值,换来的是行为可预测、未来版本升级不踩雷。


报错 11:TypeError —— GroupBy.apply 返回类型不一致

① 报错原文(本地实跑截取):

TypeError: cannot concatenate object of type '<class 'numpy.int64'>'; only Series and DataFrame objs are valid

② 触发场景:

import pandas as pd

df = pd.DataFrame({'g': ['a', 'a', 'b'], 'v': [1, 2, 3]})

# 按组处理:组均值大于 1.5 就返回求和(标量),否则返回整列乘 2(Series)
result = df.groupby('g')['v'].apply(
    lambda x: x.sum() if x.mean() > 1.5 else x * 2
)
print(result)

③ 原因分析:

groupby.apply 会把每个组的返回结果拼接(concat)成最终结果。它要求每个组返回的东西类型一致:要么每组都返回 Series/DataFrame,要么每组都返回标量。上面这个 lambda 里,g='a'(均值 1.5,不大于 1.5)走 x*2 返回 Series,g='b'(均值 3.0,大于 1.5)走 x.sum() 返回一个 numpy 标量——Series 和标量没法 concat,于是报错。

④ 解决方案:

import pandas as pd

df = pd.DataFrame({'g': ['a', 'a', 'b'], 'v': [1, 2, 3]})

# 方案 A:只是想给每组算几个标量指标 —— 用 agg,不要用 apply
print(df.groupby('g')['v'].agg(['mean', 'sum']))
#    mean  sum
# g
# a   1.5    3
# b   3.0    3

# 方案 B:确实需要逐组自定义变换、且输出和原表一样长 —— 用 transform
print(df.groupby('g')['v'].transform('mean'))   # 每组均值广播回每行

# 方案 C:apply 一定要用,就保证每个组返回相同类型(都返回 Series):
def myfunc(x):
    if x.mean() > 1.5:
        return pd.Series(x.sum(), index=['total'])   # 包装成 Series
    return x * 2
print(df.groupby('g')['v'].apply(myfunc))

⑤ 预防建议:记住三者分工——`agg` 算聚合标量、`transform` 按行对齐变换、`apply` 兜底复杂逻辑。能用 agg/transform 就别用 apply;真用 apply 就保证所有组返回值类型一致。


报错 12:TypeError —— concat 后列变 object

① 报错原文(本地实跑截取):

TypeError: unsupported operand type(s) for +: 'int' and 'str'

② 触发场景:

import pandas as pd

# 两份订单:第一份金额是整数,第二份金额是字符串(人工补录时带了单位)
d1 = pd.DataFrame({'x': [1, 2]})
d2 = pd.DataFrame({'x': ['a', 'b']})

# 拼起来
r = pd.concat([d1, d2], ignore_index=True)
print(r.dtypes)     # x    object  <- 注意这一列已经变成 object 了

# 想算总金额 —— 崩
print(r['x'].sum())

③ 原因分析:

pd.concat 遇到同名列两边 dtype 不一致时,不会报错,而是悄悄把这一列升级成 `object`(什么都能装)。这一步是静默的,你完全感觉不到。直到后面做 .sum()、.mean() 时,object 列里混着 int 和 str,加法做不动才炸。

④ 解决方案:

import pandas as pd

d1 = pd.DataFrame({'x': [1, 2]})
d2 = pd.DataFrame({'x': ['3', '4']})

r = pd.concat([d1, d2], ignore_index=True)

# 合并后立刻统一转类型,脏值变 NaN,再聚合
r['x'] = pd.to_numeric(r['x'], errors='coerce')
print(r['x'].dtype)     # int64
print(r['x'].sum())     # 10

# 更稳的做法:合并之前就把每份数据的列类型对齐,不要等 concat 后再擦屁股

⑤ 预防建议:每次 `pd.concat` 之后,第一件事就是 `print(df.dtypes)`,盯着看有没有列莫名其妙变成 object。多表拼接的 pipeline 里,合并后统一做一次 pd.to_numeric / pd.to_datetime 清洗是标准动作。


5. 本章小结与自测清单

本篇 12 个报错可以归纳成三条主线:

1. 类型不匹配是万恶之源:字符串列和数字运算(报错 1/3)、object 列混类型聚合(报错 2)、astype 强转遇脏值(报错 4)、Timestamp 与字符串混用(报错 5)、category 做算术(报错 6)——统一解法都是"先 pd.to_numeric / pd.to_datetime / .astype 把类型对齐,再做运算"。

2. 不报错的错最致命:== np.nan 永远 False(报错 7)、浮点求和不精确(报错 8)——这两个不抛异常,但结果是错的,靠的是"用 .isna()、用 np.isclose、金额用整数分"这类好习惯去防。

3. pandas 3.0 行为变了:CoW 让链式 inplace 直接抛 ChainedAssignmentError(报错 10),字符串默认 str dtype,写新代码时按本篇的"赋值回去"风格来,未来升级不返工。

自测清单(读完后逐条打勾):

  • [ ] 看到 TypeError: ... not supported for dtype 'str',能立刻判断是"字符串列做了数学运算",并用 pd.to_numeric 或 str.cat 修复。
  • [ ] 知道 astype(int) 遇到脏值会报 invalid literal,且会主动改用 pd.to_numeric(errors='coerce')。
  • [ ] 筛选缺失值时写的是 df[df.col.isna()],而不是 df[df.col == np.nan]。
  • [ ] 比较浮点数时用 np.isclose,金额用整数分或 Decimal 存储。
  • [ ] 多条件过滤写的是 df[(df.a>1) & (df.b<5)](带括号),从不用 and/or。
  • [ ] 知道 pandas 3.0 CoW 下 df['col'].方法(..., inplace=True) 会抛 ChainedAssignmentError,新代码一律写 df['col'] = df['col'].方法(...)。
  • [ ] groupby 算标量指标用 agg,按行变换用 transform,复杂逻辑才用 apply,且 apply 保证各组返回同类型。
  • [ ] pd.concat 后会立刻检查 df.dtypes,防止列悄悄退化成 object。

6. 练习题

第 1 题:下面这段代码报 ValueError: invalid literal for int() with base 10: 'N/A':

import pandas as pd
df = pd.DataFrame({'age': ['23', '45', 'N/A', '30']})
df['age'] = df['age'].astype(int)

请写出修复代码:既要把 age 转成数值,又要让 'N/A' 不阻塞流程,并指出转换后该列的 dtype 是什么。

第 2 题:下面这行代码报 ValueError: The truth value of a Series is ambiguous,请指出错在哪、并改成正确写法:

import pandas as pd
df = pd.DataFrame({'age': [18, 35, 50], 'city': ['北京', '上海', '北京']})
print(df[df.age > 30 and df.city == '北京'])

第 3 题:为什么 df[df.col == np.nan] 永远选不出缺失值?请写出两种正确的筛选缺失行的写法,并说明 groupby('col').sum() 默认会不会把缺失值那一组算进去。

第 4 题:某财务表 amount 列求和时报 TypeError: unsupported operand type(s) for +: 'int' and 'str'。请描述:(1) 你会先用哪两条命令定位问题;(2) 写出修复后求和的完整代码。

第 5 题:解释为什么 0.1 + 0.2 == 0.3 在 Python 里是 False。写出两种让浮点数比较"可靠"的做法,以及财务金额最推荐的存储方式。

第 6 题:df.groupby('g')['v'].apply(lambda x: x.sum() if x.mean() > 2 else x * 2) 这种写法有什么风险?如果你的真实目的只是"按组求均值和总和",应该用哪个方法?如果目的是"把每组的均值广播回每一行",又该用哪个方法?


下一篇预告:第 45 章 常见报错与排查大全(三):性能与内存问题与调试方法论 —— 前两篇解决"报错崩掉"的问题,本篇解决"没报错但慢得要死、内存爆掉、结果对不上"的问题,以及一套可复用的系统化调试方法论。
— 全文完 —回到顶部 ↑
下载推广海报

文章推广海报

《第 44 章 常见报错与排查大全(二):类型与运算类报错》完整推广海报
DISCUSSION

文章回复

0 条公开回复
未登录回复需要审核后公开
还没有回复,欢迎参与讨论。