Python分组去重计数:掌握nunique()函数,提升数据分析效率
在数据分析工作中,按组统计唯一值数量是一项常见且关键的任务。例如,分析每个产品类别下的独立访客数,或计算每个销售区域每年上架的不同商品种类。此时,pandas库中的nunique()函数便成为高效解决此类问题的首选工具。
nunique()是pandas中专门用于分组后计算唯一值数量的聚合方法。默认情况下自动忽略NaN空值,可通过dropna=False参数将NaN视为一个独立类别。使用时需明确指定目标列,且不适用于列表、字典等不可哈希的数据类型。

使用groupby结合nunique()快速实现分组去重统计
在pandas.DataFrame.groupby进行分组聚合操作时,直接调用nunique()方法是为“唯一值计数”场景量身定制的解决方案。相比先使用unique()获取唯一值列表再用len()计算长度的传统方法,它更加简洁且性能更优,尤其在处理缺失值NaN时拥有清晰的默认逻辑——自动排除。
首先需要明确一个常见误区:切勿将nunique()与count()或size()混淆。后两者分别统计非空值数量和总行数,均不具备去重功能。
- NaN缺失值处理:
nunique()默认忽略所有NaN值。若业务分析中需要将“空值”作为一个独立的分类进行统计,只需设置参数dropna=False即可。 - 支持的列数据类型:该函数对数值型列和字符串列均可直接使用。但需注意,如果目标列包含列表、字典等可变嵌套结构,直接调用会引发
TypeError: unhashable type错误。 - 执行效率对比:相较于使用
apply(lambda x: x.nunique())这种通用但效率较低的方式,直接调用内置的nunique()方法在处理大规模数据集时能显著提升计算速度。
单列分组与单目标列统计:基础应用模式
这是最典型的使用场景。例如,按照category(产品类别)进行分组,并统计每个类别下有多少个不同的user_id(用户ID):
df.groupby('category')['user_id'].nunique()
上述代码将返回一个pandas.Series对象,其索引为各个分组类别,对应的值为该组内用户ID的去重计数。若希望将结果转换为标准的DataFrame格式,可在其后链式调用.reset_index(name='nunique_user')进行重置。
立即学习“Python免费学习笔记(深入)”;
- 关于NaN的补充说明:若目标列中存在大量
NaN,且业务逻辑要求将“空用户ID”视为一种有效状态,务必使用.nunique(dropna=False)。 - 关键操作细节:必须准确指定目标列。错误地写成
df.groupby('category').nunique()会导致对所有数值列进行去重计数,而非仅针对某一列。务必使用['col']或[['col']]的语法来明确指定需要统计的列。
多列分组与多目标列分别统计:进阶聚合分析
面对更复杂的分析需求,例如需要同时按照“地区”和“年份”进行双重分组,并分别计算“产品ID”、“卖家ID”和“国家”的唯一值数量时,可以通过字典形式将聚合规则传递给agg()函数,使代码逻辑清晰明了:
df.groupby(['region', 'year']).agg({
'product_id': 'nunique',
'seller_id': 'nunique',
'country': 'nunique'
})
执行后将得到一个具有多层索引(MultiIndex)的DataFrame,其列名为之前指定的各个字段,数值则为对应的nunique统计结果。
- 代码书写规范:聚合字典中的字段名和聚合函数名均需使用引号包裹(例如
'nunique')。若遗漏引号直接书写nunique,将引发NameError错误。 - 混合聚合操作:若需对同一列同时计算唯一值数量和最大值,可写作
'product_id': ['nunique', 'max']。但需注意,这样生成的列名会变为多级索引。 - 代码风格建议:尽量避免在同一个
agg字典中混合使用字符串函数名和lambda表达式。虽然{'a': 'nunique', 'b': lambda x: x.nunique()}的写法可以执行,但在代码可读性和执行性能上,统一使用字符串函数名是更佳的选择。
解决“unhashable type”不可哈希类型错误
这是使用nunique()时可能遇到的一个典型障碍。当目标列的数据类型为列表(list)、字典(dict)或集合(set)等不可哈希(unhashable)的类型时,直接调用会抛出TypeError: unhashable type: 'list'异常。其根本原因在于nunique()的底层实现依赖于set()进行去重,而列表等可变类型无法直接作为集合的元素。
解决方案并非修改函数本身,而是在调用前将数据转换为可哈希的表示形式:
- 列表列的处理:若列表内的元素本身可哈希(如字符串、数字),可将其转换为元组(
tuple)。例如:df['tags_tuple'] = df['tags'].apply(lambda x: tuple(x) if isinstance(x, list) else None),然后对生成的tags_tuple列使用nunique()。 - 字典列的处理:可考虑进行序列化。例如使用
frozenset(dict.items()),或更稳健地使用json.dumps(sorted(dict.items()))将其转换为JSON字符串(对字典项排序是为了确保顺序一致,避免因键的顺序不同导致相同的字典被误判为不同值)。 - 通用替代方案:如果数据转换较为复杂,一个更直接但效率稍低的方法是:先基于分组键和目标列进行去重(使用
drop_duplicates),然后再进行分组计数。例如:df.drop_duplicates(['group_col', 'target_col']).groupby('group_col').size()。
总结来说,nunique()函数虽看似简单,但目标列的数据类型、NaN值的处理方式以及对嵌套数据结构的应对策略是三个最易出错的环节。在实际应用中,养成良好的数据探查习惯,先通过df['col'].dtype查看数据类型,并用df['col'].head()预览数据样本,往往能事半功倍,有效避免踩坑。
