groupby后需先选取列再调用nunique,如df.groupby('a')['b'].nunique();它返回每组中该列去重计数,默认忽略nan,多列统计用df.groupby('a')[['b','c']].nunique()。

groupby后直接用nunique会报错:'Series' object has no attribute 'nunique'
这个错误通常出现在你对 groupby 结果误用了 nunique——它不是 SeriesGroupBy 或 DataFrameGroupBy 对象的直接方法,而是作用于单个 Series 或 DataFrame 的。常见写法错误如:df.groupby('A').nunique()(错),或 df.groupby('A')['B'].nunique()(对)。
正确路径是:先 groupby 得到分组对象,再选取列(返回 SeriesGroupBy),最后调用 nunique。它本质是对每个分组内部的该列做去重计数。
-
df.groupby('category')['user_id'].nunique()→ 返回每个 category 下不重复的 user_id 数量 - 若想对多列分别统计去重数,写成
df.groupby('category')[['user_id', 'product_id']].nunique() - 注意:
nunique默认忽略NaN;如需把NaN当作一个独立值计数,加参数dropna=False
想统计每组中某列的「去重值列表」而非数量?别用nunique
nunique 只返回整数计数,不提供具体有哪些值。如果你需要每组的去重集合(比如查每个部门有哪些职级),得换方法:
- 用
.apply(set):df.groupby('dept')['role'].apply(set)→ 返回每个 dept 对应的set对象 - 用
.unique()(保留首次出现顺序):df.groupby('dept')['role'].unique()→ 返回numpy.ndarray,更常用 - 两者都支持链式操作,但
.unique()性能略好、结果可索引;set无序且不可直接切片 - 如果后续要转成字符串拼接(如“经理,主管”),推荐
.str.join()配合.unique():df.groupby('dept')['role'].unique().str.join(', ')
groupby + nunique在空组或全NaN组下的行为
当某组内目标列全部为 NaN(或为空),nunique 默认返回 0(因为 dropna=True 是默认值)。这容易造成误解——你以为该组有数据但去重后为 0,实际可能是全空。
- 验证方式:对比
.count()(非空计数)和.nunique(),若前者为 0 而后者非 0,说明有NaN被算进去了(仅当dropna=False时) - 安全做法:显式控制
dropna参数,例如df.groupby('tag')['id'].nunique(dropna=False),这样空组返回1(把NaN算作一个值) - 注意:Pandas 1.1+ 中,
nunique对空Series(长度为 0)始终返回0,与dropna无关
性能敏感场景下,nunique比手动set慢吗?
是的,在小数据量上差异不大,但在百万级分组+高频调用时,nunique 内部做了类型推断和缺失值检查,比裸写 lambda x: len(set(x.dropna())) 慢 20%–40%。不过官方实现更健壮,推荐优先用 nunique,除非压测确认它是瓶颈。
- 提速技巧:若已知列无
NaN,加dropna=False反而可能略快(跳过空值扫描逻辑) - 替代方案(仅限纯数值/字符串且确定无 NaN):
df.groupby('key')['col'].apply(lambda x: x.nunique(dropna=False))—— 实际并无收益,不建议 - 真正有效的是预过滤:先
df = df.dropna(subset=['col']),再groupby(...).nunique(),减少每组处理负担
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











