news 2026/11/3 8:35:02

pandas分组聚合报错:mean与sum差异及object类型根治

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
pandas分组聚合报错:mean与sum差异及object类型根治

Pandasgroupby.mean()抛出No numeric types to aggregate,而同一份数据换成.sum()却能正常返回结果——这个组合我从入行到现在被问过十几次,自己也确实在深夜盯着屏幕反复确认过。它的迷惑性在于:代码没改、数据没变、分组键也没错,只是聚合函数换了一个,程序的行为就从“报错”变成了“正常返回”。很多人第一反应是 pandas 有 bug,第二反应是把mean换成sum再手动除以计数,结果算出来的数字死活对不上。这篇内容就想把这件事完整说清楚:No numeric types to aggregate究竟在什么条件下被触发,sum为什么能绕过它,以及从排查到根治的一套完整做法。无论你是刚接手一份脏 CSV 的分析新人,还是已经在写数据流水线的工程师,只要碰过 pandas 的分组聚合,这里面的坑大概率都会踩到,区别只是踩得早还是踩得晚。

1. 先把现场还原:这个报错到底长什么样

1.1 一段最小复现代码

要让这个报错稳定出现,只需要一个条件:参与聚合的数据里,没有 pandas 认可的数值列。最常见的形态是从 CSV 或者 Excel 里读进来的一列“金额”,看着全是数字,实际 dtype 是object。构造一份最小数据:

import pandas as pd df = pd.DataFrame({ "city": ["A", "A", "B", "B"], "amount": ["1,200", "980", "2,300", "1,750"], }) print(df.dtypes) # city object # amount object df.groupby("city")["amount"].sum() # city # A 1,200980 # B 2,3001,750 # Name: amount, dtype: object df.groupby("city")["amount"].mean() # TypeError: Could not convert string to float: '1,200'

如果是在 pandas 1.x 上跑不带列选择的版本,报错信息就是标题里那句:

df.groupby("city").mean() # pandas.errors.DataError: No numeric types to aggregate

注意sum那一行的结果:1,200980。这不是一千二百加九百八十,这是两个字符串首尾相接。也就是说sum所谓“有效”,只是没抛异常,结果本身是错的。这是整件事里最危险的部分——报错至少还拦住了你,静默返回错误结果才是真正的事故源头。

amount列之所以是object,原因可能有很多:千分位逗号、货币符号前缀、单位后缀、全角数字、首尾空格、混进来的"-"或"暂无"占位符、来源系统导出时加了强制引号。从 pandas 的视角看,它们通通只有一个身份:不是数字。

1.2 两种“看起来不一样”的结果

把这两次调用放在一起对比,能看出一条很关键的线索:报错发生在“挑列”阶段,而不是“计算”阶段。mean()在真正开始算之前,先做了一件事——从 DataFrame 里筛出数值列。筛完发现是空的,于是直接抛异常,一次加法都没做过。sum()则压根没做这个筛选动作,它把所有列都拿过来硬算,字符串列的加法在 Python 里天然合法(字符串拼接),所以它“跑通了”。

这解释了一个经常让人困惑的现象:数据里明明有数值列,mean()也报这个错。原因通常是分组之后剩下可见的列里没有数值列。比如df.groupby("k")[["a"]].mean()里a恰好是字符串;又比如列是 MultiIndex,你按名字取的时候取到了分组键那一层。这些情况在第 3 章会逐个拆。

还有一种反向情况值得提前说:数据里既有数值列又有字符串列时,pandas 1.x 的df.groupby("k").mean()会静默地只对数值列求均值,把字符串列扔掉,不给你任何提示。你可能以为算的是全表,实际只算了一半的列。到了 pandas 2.x,同一句代码会直接抛TypeError,因为默认不再帮你挑列了。同一个项目,换个环境跑出来的行为完全不同,这也是“在我机器上明明是好的”这类问题的常见来源。

2. 根因拆解:mean 和 sum 走的根本不是同一条路

2.1 mean 的列筛选:先挑数值列,挑不出来就掀桌

DataFrameGroupBy.mean()内部的核心逻辑是“先筛后算”。它会调用_get_numeric_data()这个被重写过的方法,用意是拿到一个只包含数值列的子 DataFrame,再交给底层的 cython 聚合例程做批量计算。这份筛选依赖select_dtypes(include=np.number)的判断标准,而np.number这个类型集合比大多数人想象的要窄。

具体哪些列会被排除在外,值得单独列清楚:

列的实际类型是否被判定为数值mean 的行为
int64/float64是正常参与计算
object(哪怕内容全是数字)否被排除
datetime64[ns]否被排除
timedelta64[ns]否被排除
bool否被排除
category(哪怕类别是数字)否被排除
Decimal(存在 object 里)否被排除
Int64/Float64(可空类型)是正常参与计算

排除完之后,如果剩下的列数是零,就走到那句raise DataError("No numeric types to aggregate")。整条路径的关键在于:它判断的是列的元数据类型声明,不是你肉眼看到的内容。一列"1,200"和 一列"abc"在 pandas 眼里没有任何区别,都是object。

这个设计其实是有道理的。mean对时间戳做平均是没有定义的,对布尔值做平均在不同版本里行为不一致,对字符串做平均更是无从谈起。与其算出一个没人能解释的结果,不如早点报错。只是这个“早点”报得有点突然,错误信息也只说了“没有数值类型”,没告诉你到底哪一列有问题,所以排查成本被转嫁给了使用者。

2.2 sum 的降级路径:不挑列,硬算,算出字符串

sum的行为逻辑完全是另一套。它的numeric_only默认值是False,不主动筛选列,而是尝试把每一列都送进聚合流程。送进去之后,cython 那层对object类型做不了快速运算,就会降级到 Python 层的逐元素循环,调用内置的sum去处理每一组的值。

内置sum的起始值是0,遇到字符串时会变成0 + "1,200"——这行代码在 Python 里会直接抛TypeError。但 pandas 在这里做了特殊处理,字符串列走的是拼接语义,于是结果变成"1,200980"。同理,如果一列里混了Decimal,sum能算出正确的数值;如果混了datetime和timedelta,sum也可能给出一个时间戳;如果混了数值和字符串,那就是最糟糕的情况,你会得到一个拼接后的怪字符串,而且看不出任何异常。

所以“sum有效”这个说法,严格来讲不成立。它只是“没有报错”。判断它到底对不对,只需要多看一眼dtype:

result = df.groupby("city")["amount"].sum() print(result.dtype) # object print(result.iloc[0]) # '1,200980'

返回object的聚合结果,九成以上是需要重新检查的。这个检查习惯能省掉大量后续的对账麻烦。

2.3 版本差异:同一句代码在 1.x 和 2.x 报的错都不一样

这一点必须单独拎出来说,因为它直接影响你在网上搜到的答案能不能用。

pandas 1.x 里,DataFrameGroupBy.mean()的numeric_only参数默认是True,也就是默认帮你筛数值列。筛不到就抛DataError: No numeric types to aggregate。同时,有数值列时它会静默丢弃非数值列,你能拿到结果,但那个结果可能只覆盖了一部分列。

pandas 2.0 开始,这个默认值改成了False。含义是:不再自动筛列,你想算哪些列就明确给出来。这时候如果全表有字符串列,行为就变成了抛TypeError,报错信息类似Could not convert string to float,而不再是那句DataError。很多人在新版环境里搜“No numeric types to aggregate”搜不到对应答案,就是因为错误信息已经变了,但根因完全一样。

版本差异带来的实际困扰是:同一份脚本,在本地开发机(可能是较新的 pandas)和服务器上的旧环境(可能是 1.x)跑出来,一个是TypeError,一个是DataError,一个是空结果。排查这类问题的第一步永远是确认版本:

import pandas as pd print(pd.__version__)

不确认版本就直接抄网上的解法,很容易在numeric_only这个参数上翻车——传了不支持的关键字,或者传了但语义和你以为的不一样。第 4 章给出的方案会尽量兼容两个大版本,但前提是你得知道自己站在哪一边。

3. 五步排查法:定位到具体是哪一列在捣鬼

3.1 第一步:看 dtype,别看数据长相

报错信息不会告诉你哪一列有问题,但dtypes会。这一步几乎是零成本,却经常被跳过。很多人习惯用df.head()看一眼数据,看到1,200就认为它是数字,这个判断在 pandas 里不成立。

df.info() df.dtypes

info()还会顺带告诉你每列的非空数量,这对判断“是不是整列都是空值导致被识别成 object”很有帮助。一列全是NaN的列,dtype 通常是float64,反而是数值类型;但一列全是空字符串""的列,dtype 是object,会被mean排除。

拿到dtypes之后,重点看两类:object和category。前者是伪数值的重灾区,后者常出现在从数据库或者 parquet 读出来的枚举列上。如果分组之后剩下的列全是这两类,报错就是必然的。

提醒一句:df.dtypes显示float64也不代表这一列能直接参与业务计算。浮点列里混着inf、-inf、NaN,mean会污染整组结果而不报错。类型正确只是第一关。

3.2 第二步:用 select_dtypes 和 is_numeric_dtype 交叉验证

dtypes是看整体,select_dtypes是拿子集,两者结合能快速缩小范围:

numeric_cols = df.select_dtypes(include="number").columns.tolist() object_cols = df.select_dtypes(include=["object", "category"]).columns.tolist() print("数值列:", numeric_cols) print("非数值列:", object_cols)

如果numeric_cols是空列表,报错原因就确定了。接下来要确认的是“哪些本该是数值的列被误判了”,这时用pd.api.types.is_numeric_dtype逐个判断更精确,因为它对可空类型的处理比dtype == "float64"这种字符串比较靠谱得多:

for col in df.columns: print(col, df[col].dtype, pd.api.types.is_numeric_dtype(df[col]))

这两种方式的差别在可空整数类型上很明显。Int64(注意大写 I)和int64是两个不同的东西,前者是 pandas 的可空整数类型,后者是 numpy 的原生类型。用dtype == "int64"判断会漏掉Int64,而is_numeric_dtype两者都认。这种细节在跨环境排查时特别容易踩。

3.3 第三步:把伪数值形态一次性揪出来

确定问题列之后,下一步是搞清楚它“脏”在哪。与其一列一列猜,不如写个一次性的扫描脚本,把可疑形态全扫出来:

import re def scan_dirty(series, name, sample=5): s = series.dropna() if s.empty: print(f"[{name}] 全为空") return non_numeric = s[~s.astype("string").str.fullmatch(r"-?\d+(\.\d+)?")] print(f"[{name}] 非纯数字占比 {len(non_numeric)/len(s):.2%}") print(non_numeric.head(sample).tolist()) for col in ["amount"]: scan_dirty(df[col], col)

跑一遍通常能看到几类典型脏数据:带千分位的1,200、带货币符号的¥1200、带单位的1200元、全角的1200、占位的-和暂无、以及首尾带空格的" 1200 "。这几类的清洗方式不一样,第 4 章会给一个统一处理的函数。

还有一种隐蔽情况:列里混了不可见字符,比如零宽空格或者从网页复制带进来的不换行空格。这种数据肉眼完全看不出来,str.strip()也去不掉,必须用正则匹配非数字字符才能暴露出来。我遇到过一列数字怎么转都失败,最后发现每个值后面跟了一个\u200b,处理方式是在清洗时统一replace掉。

3.4 第四步:排查 MultiIndex 列、空 DataFrame、单列选择

如果dtypes显示明明有数值列,mean还是报错,那问题多半出在“你看到的列”和“pandas 看到的列”不是同一批。

第一种情况是列被 MultiIndex 化了。做了df.groupby("k").agg({"v": ["mean", "sum"]})之后再去分组,列名会变成两层元组,这时候按字符串取列可能取不到,或者取到的是一个 DataFrame 而不是 Series,后续聚合的行为就完全变了。判断方法很简单:

print(type(df.columns), df.columns.nlevels)

如果nlevels大于 1,就需要先拍平:

df.columns = df.columns.get_level_values(0) # 或者更稳妥地拼接 df.columns = ["_".join(map(str, c)) if isinstance(c, tuple) else c for c in df.columns]

第二种情况是空 DataFrame。分组之后每个组都是空的,或者整个 df 是pd.DataFrame(),mean的行为会变得很奇怪,有时返回空结果,有时报错,取决于版本。这种情况建议在聚合前先判断df.empty。

第三种是选择方式带来的差异。df.groupby("k")["v"]返回的是SeriesGroupBy,df.groupby("k")[["v"]]返回的是DataFrameGroupBy。前者不存在“没有数值类型”这个判断,因为它就只有一列,没有筛选的必要;后者会走完整的筛选逻辑。如果你在两种写法之间来回切换,看到的行为不一样,原因就在这里。

3.5 第五步:确认分组键有没有被吃掉

一个不太常见但确实存在的坑:分组键本身被当成了需要聚合的列。正常情况下groupby会自动把分组键从聚合范围里排除,但如果列名有重复,或者分组键和聚合列同名,排除就可能失效。

print(df.columns.duplicated().any())

有重复列名的话,df["amount"]会返回一个 DataFrame 而不是 Series,后续所有操作的行为都会变。这个问题的解法是在读入阶段就处理掉重复列名,或者给列加前缀区分。检查成本很低,顺手加一行没坏处。

4. 解决方案:从三分钟止血到彻底根治

4.1 止血:显式选列 + numeric_only

如果现在就要让代码跑起来,最快的方式是两件事:明确指定参与聚合的列,以及按版本正确使用numeric_only。

# 方式一:显式选列,最稳,两个大版本都适用 df.groupby("city")["amount"].mean() # 方式二:先转换类型再聚合 df.assign(amount=pd.to_numeric(df["amount"].str.replace(",", ""), errors="coerce")) \ .groupby("city")["amount"].mean() # 方式三:DataFrameGroupBy 上显式声明 df.groupby("city").mean(numeric_only=True)

方式三需要注意版本兼容。numeric_only在 1.x 的默认值是True,在 2.x 是False,显式传True在两边都能用,语义也一致。但如果你传的是numeric_only=False,在 1.x 上就可能得到一个拼接了字符串的结果,和 2.x 的报错行为对不上。写跨版本代码时,显式传True是安全的选择。

还要提醒一点:numeric_only=True只是“不报错”,它会静默丢弃非数值列。如果被丢掉的那一列其实是你想算的,结果就是列少了但没人发现。止血之后一定要回头看一眼结果的列名和列数,确认没漏。

4.2 根治一:读入阶段就用 thousands、na_values、dtype

绝大多数“伪数值”问题,根源在read_csv那一步。pandas 其实提供了专门的参数来处理,只是很多人不知道:

df = pd.read_csv( "data.csv", thousands=",", # 处理千分位 na_values=["-", "--", "N/A", "暂无", ""], # 统一占位符 dtype={"id": "string"}, # 明确指定不该被推断的列 keep_default_na=True, encoding="utf-8", )

thousands这个参数是处理千分位最干净的方式,比读进来再str.replace高效得多,因为它发生在解析阶段,不产生中间的 object 列。配合na_values把各种占位符统一成NaN,很多列在解析完就已经是float64了。

如果列里还有货币符号或者单位,thousands就覆盖不了了,这时用converters:

def parse_amount(x): if pd.isna(x): return None return float(re.sub(r"[^\d.\-]", "", str(x))) df = pd.read_csv("data.csv", converters={"amount": parse_amount})

converters的代价是它是逐元素调用的 Python 函数,几十万行以上会明显变慢。数据量大时更推荐先用thousands+na_values做粗处理,剩下的脏数据在内存里用向量化方法清。

dtype参数还有一个额外好处:它能让read_csv跳过类型推断。类型推断本身要扫描全表,对大文件来说是一笔不小的开销。对明确知道类型的列指定dtype,读取速度能快一截。

4.3 根治二:一个通用的字符串转数值函数

实际项目里数据来源五花八门,写一个能复用的清洗函数比每次临时拼代码划算得多。下面这个版本我用了很久,覆盖了大部分常见形态:

import re import pandas as pd _NULL_TOKENS = {"", "-", "--", "—", "N/A", "NA", "null", "None", "NaN", "暂无", "无"} def to_number(s: pd.Series, percent: bool = False, unit_map: dict | None = None) -> pd.Series: """把可能含脏字符的 Series 转成 float,无法转换的变 NaN。""" if pd.api.types.is_numeric_dtype(s): return s.astype("float64") out = s.astype("string").str.strip() # 全角数字、全角空格、零宽字符 for ch in ["\u3000", "\u200b", "\xa0"]: out = out.str.replace(ch, "", regex=False) # 千分位与常见货币/单位符号 out = out.str.replace(",", "", regex=False) out = out.str.replace(",", "", regex=False) out = out.str.replace(r"[¥$€£]", "", regex=True) out = out.str.replace(r"(元|万|亿|%|%)$", "", regex=True) # 空值占位符统一 out = out.mask(out.isin(_NULL_TOKENS)) # 只保留数字、小数点、正负号、科学计数法 out = out.str.replace(r"[^0-9eE\.\-\+]", "", regex=True) result = pd.to_numeric(out, errors="coerce") if percent: result = result / 100.0 if unit_map: # 例如 {"万": 1e4, "亿": 1e8},需要先提取单位再映射,此处仅为示意 pass return result

这个函数里有两个细节值得说明。第一是用astype("string")而不是astype(str)。后者会把NaN转成字符串"nan",这个字符串在后面to_numeric的时候会变成NaN,看起来没影响,但如果你的清洗流程里有dropna,就会漏掉一批本该被删的行。astype("string")保留pd.NA,语义更准确。

第二是最后那步正则[^0-9eE\.\-\+]。它会把所有非数字字符干掉,代价是可能把一些本该保留的负号位置搞错,比如"1-200"会变成"1-200"然后转成NaN。所以如果你知道数据里不会有这种情况,用它是安全的;如果不确定,建议先用第 3 章的扫描脚本看一眼样本,确认没有异常形态再上。

4.4 特殊类型:Decimal、nullable Int64、布尔、百分号与单位

object类型里装Decimal是很常见的,尤其在金融场景,因为Decimal能保证精度。但 pandas 的_get_numeric_data不认它,mean依然会报错。处理方式是显式转换:

from decimal import Decimal df["amount"] = df["amount"].map(lambda x: float(x) if isinstance(x, Decimal) else x) # 或者用 to_numeric 直接吃 df["amount"] = pd.to_numeric(df["amount"].astype("string"), errors="coerce")

精度敏感的场景不建议转float,可以在分组内用apply单独算:

result = df.groupby("city")["amount"].apply(lambda s: sum(s) / len(s))

这条路径慢,但保住了Decimal的精度。

可空类型Int64和Float64是 pandas 明确认可的数值类型,mean能正常处理,结果是Float64。这类列的好处是能区分0和缺失值,做统计时不会把缺失当零算。如果你的数据里有这种需求,读入时直接用dtype={"col": "Int64"}指定。

布尔列的情况要留意。bool在select_dtypes(include=np.number)里不被包含,所以全是布尔列时mean会报错。但业务上“求某个标记的均值”其实是有意义的,等价于算比例。做法是先转成整数:

df["is_paid"] = df["is_paid"].astype("int8")

百分号的处理容易被忽略。"12.5%"直接转数值会变成12.5,如果你期望的是0.125,结果就差了一百倍,而且不报错。清洗时要么先判断有没有%再决定是否除以 100,要么就在函数里显式传参数,别让它默认蒙混过关。

单位后缀同理。"1.2万"去掉单位变成1.2,和真实值差了一万倍。这类问题不会报错,只会让你的报表看起来“数字怪怪的”。凡是带单位的列,清洗前先做一次value_counts看看有哪些单位,别直接上通用正则。

5. 性能与工程化:别让一次类型转换拖垮整条流水线

5.1 apply 与向量化的差距有多大

搜到的很多解法长这样:

df.groupby("city")["amount"].apply(lambda x: pd.to_numeric(x, errors="coerce").mean())

这段代码能跑通,但代价不小。apply会把每个分组的数据作为一个独立对象取出来,交给 Python 函数逐个处理,分组数越多,函数调用开销越大。而且它在每组内部重复做to_numeric,同一列的清洗逻辑被执行了 N 遍。

更合理的顺序是“先全表清洗,再分组聚合”:

df["amount"] = to_number(df["amount"]) df.groupby("city")["amount"].mean()

这样清洗只做一次,聚合走底层的向量化路径。在百万行量级的数据上,这种写法和apply版本的差距通常在一个数量级以上,具体倍数会随分组数、机器性能、数据分布变化,但方向是确定的:分组数越多,apply越吃亏。

如果清洗逻辑确实需要按组做(比如每组有自己的单位映射),那就退而求其次,用agg配合向量化函数,尽量把 Python 层的循环压到最少。

5.2 大数据量下的分块读取与 dtype 预设

数据量超过内存时,分块读取是常规操作,但分块会让类型处理变得微妙。某一块里恰好全是数字,pandas 推断成float64;另一块里混了一个"暂无",推断成object。最后拼起来,整列是object。

解法是在read_csv时就锁死类型,不给推断留空间:

chunks = pd.read_csv( "big.csv", chunksize=500_000, thousands=",", na_values=["-", "暂无"], dtype={"amount": "float64", "city": "string"}, ) parts = [] for chunk in chunks: parts.append(chunk.groupby("city", observed=True)["amount"].agg(["sum", "count"])) final = pd.concat(parts).groupby(level=0).sum() final["mean"] = final["sum"] / final["count"]

这里有个关键技巧:分块做mean是错的,因为均值不能跨块直接相加。正确的做法是分块算出sum和count,最后合并再相除。这个和流式计算里的“可合并聚合”概念是一回事,sum、count、min、max可合并,mean、median、std不行,需要拆开。很多人在这一步翻车,算出来的均值差了十万八千里,还以为数据有问题。

observed=True这个参数也值得提一句。当分组键是category类型时,默认会把所有类别组合都列出来,包括数据里根本不存在的组合,结果里出现大量NaN行,而且计算量成倍增加。加上observed=True只保留实际出现过的组合,速度和结果都更干净。

5.3 把类型断言塞进数据入口

根治这类问题最有效的手段,是在数据进入计算流程之前加一道断言,让问题在源头暴露,而不是等到某个聚合函数报一句含糊的错误。

import pandas as pd def assert_numeric(df: pd.DataFrame, cols: list[str]) -> None: bad = [c for c in cols if c not in df.columns or not pd.api.types.is_numeric_dtype(df[c])] if bad: detail = {c: str(df[c].dtype) if c in df.columns else "MISSING" for c in bad} raise TypeError(f"以下列不是数值类型,请检查清洗逻辑: {detail}")

这个函数放在数据加载之后、业务计算之前调用,成本是一次遍历列,收益是把“No numeric types to aggregate”这种没有上下文的报错,替换成一条直接指明哪一列、什么类型的错误。上线之后,排查时间能从半小时压到半分钟。

真要做工程化,还可以把“每列期望类型”写进配置文件,加载时统一校验,校验不过直接失败。这种做法在数据流水线里很常见,本质是把隐含假设显式化。pandas 的类型推断是“猜”,猜错了不会告诉你;显式声明类型是“约定”,违反了立刻能发现。

6. 踩坑实录与速查表

6.1 一张表覆盖 90% 的场景

下面这些场景我在不同项目里基本都碰到过,按从常见到罕见的顺序排列:

现象真实原因处理方式
mean报DataError,sum返回拼接字符串列是object,内容是带格式的数字用to_number清洗后再聚合
有数值列但mean仍报错分组后可见列全是非数值检查选列方式,确认取到的是数值列
pandas 2.x 报TypeError而非DataError版本差异,numeric_only默认值变了显式传numeric_only=True或明确选列
mean结果少了若干列1.x 静默丢弃非数值列聚合后核对列名和列数
列名是元组,按字符串取不到前一步agg产生了 MultiIndex 列先拍平列名再聚合
Int64列被判断为非数值用了dtype == "int64"做比较改用is_numeric_dtype
分块计算的均值对不上分块直接对mean求和拆成sum和count最后相除
Decimal列报错object类型不被识别为数值转float或用apply单独算
结果里出现大量NaN行分组键是category,产生了空组合加observed=True
百分比数值大了一百倍"12.5%"被当成12.5清洗时显式除以 100

6.2 几个我真实踩过的坑

第一个坑是“看起来是数字的 ID 列”。有一份数据里user_id是纯数字,mean把它算进了平均值,结果出来一个毫无意义的数字,还没人觉得奇怪。后来在数据入口加了列类型清单,明确标注哪些列不参与数值聚合,这类问题才彻底消失。这个经验是:不是所有长得像数字的列都该被当成度量值。

第二个坑是astype(str)和astype("string")混用。早期清洗函数里写的是s.astype(str),遇到NaN会变成字符串"nan",然后to_numeric再把它转成NaN,流程上看不出问题。但中间如果有一步dropna(),"nan"不是缺失值,不会被删掉,最后统计里就多了一批本该剔除的记录。改成astype("string")之后,NaN一路保持pd.NA,语义才对得上。

第三个坑是清洗用了str.replace(",", ""),结果把数字里本来合理的逗号也去掉了——这种情况在科学计数法或者某些区域格式里会出现。后来改成先用thousands参数在读取阶段处理,读取阶段解决不了的部分才在内存里清,问题少了很多。经验是:能在解析阶段做的清洗,别拖到内存里做,前者有类型信息辅助判断,后者只能靠猜。

第四个坑和observed有关。一个分类列有几百个类别,分组之后结果里冒出几万行NaN,跑一次要几十秒。加上observed=True之后,行数降到几百,耗时降到一秒以内。这个参数不改变计算结果,只改变输出形态,属于纯收益的优化。

最后分享一个习惯:每次拿到新数据,先跑三行代码——df.info()、df.select_dtypes("number").columns.tolist()、df.isna().sum()。这三行能解决掉大部分“为什么聚合报错”和“为什么结果不对”的问题,成本不到十秒。真正花时间的从来不是修复,是定位,而定位的前提是你知道自己手里的数据长什么样。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/3 8:33:16

AI智能体失控事件复盘:代码沙箱与行为边界的工程化约束

1. 一次“失控”的智能体实验,暴露了哪些真问题 澳洲政府网站被一个AI智能体当成试验场,这件事在圈子里传开的时候,我第一反应不是“AI要造反了”,而是“终于有人把智能体放到真实环境里跑了一圈,然后翻车了”。这其实…

作者头像 李华
网站建设 2026/11/3 8:33:16

网页打印变黑白?CSS强制彩色输出的完整解决方案

1. 项目概述:为什么网页打印总变黑白?这不是Bug,是默认策略 “网页打印出来全是黑白的,图片没了、颜色没了、表格边框也淡得看不见”——这是我在某高校数字教学平台做前端支持时,连续三个月被问得最多的问题。不是打印…

作者头像 李华
网站建设 2026/11/3 8:32:42

本地部署AnythingLLM:构建私有RAG与AI Agent工作区实战

1. 为什么我最终把知识库从云端搬回了本地第一次接触 AnythingLLM 是在一个挺尴尬的场景里。当时团队要给客户做一套内部知识问答系统,需求很明确:文档不能出内网,模型要能换,最好还能让非技术同事自己上传资料。我一开始想的是直…

作者头像 李华
网站建设 2026/11/3 8:32:26

图算法在计算机网络中的核心应用与实践

1. 计算机网络与图算法的奇妙结合 第一次听说计算机网络和图算法有关系时,我也很惊讶。直到某天调试一个路由问题时,突然意识到整个互联网不就是一张巨大的图吗?路由器是节点,链路是边,路由协议本质上就是在图上寻找最…

作者头像 李华
网站建设 2026/11/3 8:32:20

Maven 3.8.8安装配置与IDEA集成指南

1. Maven 3.8.8 安装与配置全攻略作为Java开发者必备的构建工具,Maven的安装配置看似简单,但新手常会在环境变量、IDEA集成等环节踩坑。本文将手把手带你完成Maven 3.8.8的完整安装流程,包含下载、环境配置、IDEA集成以及性能优化技巧。不同于…

作者头像 李华
网站建设 2026/11/3 8:32:20

大模型训练加速:通算融合技术解析与实践

1. 大模型训练加速器的技术突破 上周在实验室实测新架构时,训练速度突然从32 samples/sec飙到45 samples/sec,这个意外收获让我意识到通算融合技术真的开始兑现潜力了。当前主流的大模型训练框架普遍存在计算资源闲置问题,GPU在等待数据通信时…

作者头像 李华