HOWTO · Pandas

计数 Pandas 中每组的唯一值

通过使用 Pandas 库中的 df.groupby().nunique(),df.groupby().agg()和 df.groupby().unique()方法对 DataFrame 中的列进行分组来对值进行计数

本页内容

分析 DataFrame 时,你通常需要知道每个组内有多少个不同的值,而不是整列中不同值的总数。例如,数据集可能记录 Countries 中每个条目使用了哪些 code 值,而你希望按国家各得到一个计数。pandas 的 groupby() 操作为这项任务提供了三种实用工具:

  1. df.groupby().nunique() 返回每个组中不同值的数量。
  2. df.groupby().agg() 通过通用聚合接口应用相同的计数。
  3. df.groupby().unique() 列出每个组中的不同值本身。

下面的所有示例都使用同一个 DataFrame,它由九行国家和代码配对组成。

运行以下示例:

本节中的代码以及显示的标签或输出仍为英文。

import pandas as pd

data = [
    [999, "Switzerland"],
    [113, "Switzerland"],
    [112, "Japan"],
    [112, "Switzerland"],
    [113, "Canada"],
    [114, "Japan"],
    [100, "Germany"],
    [114, "Japan"],
    [115, "Germany"],
]
df = pd.DataFrame(data, columns=["code", "Countries"])
print(df)
   code    Countries
0   999  Switzerland
1   113  Switzerland
2   112        Japan
3   112  Switzerland
4   113       Canada
5   114        Japan
6   100      Germany
7   114        Japan
8   115      Germany

使用 GroupBy.nunique() 统计唯一值

按组统计不同值最直接的方法是 nunique()。在 groupby() 之后选择 code 列会把分组对象转换为分组后的 Series,因此 nunique() 会统计每个国家对应行中的不同代码。

本节中的代码以及显示的标签或输出仍为英文。

import pandas as pd

data = [
    [999, "Switzerland"],
    [113, "Switzerland"],
    [112, "Japan"],
    [112, "Switzerland"],
    [113, "Canada"],
    [114, "Japan"],
    [100, "Germany"],
    [114, "Japan"],
    [115, "Germany"],
]
df = pd.DataFrame(data, columns=["code", "Countries"])
result = df.groupby("Countries")["code"].nunique()
print(result)
Countries
Canada         1
Germany        2
Japan          2
Switzerland    3
Name: code, dtype: int64

结果是一个 Series:其索引保存组名,值保存计数。瑞士使用三个不同的代码,德国和日本各使用两个,加拿大使用一个。由于分组键变成了索引,如果你想要一个扁平的两列 DataFrame,可以调用 reset_index();也可以使用下一节介绍的 agg() 形式。默认情况下,nunique() 不会统计缺失(NaN)条目;如果缺失值也应算作一个不同的值,请传入 dropna=False。

使用 GroupBy.agg() 统计唯一值

agg() 是分组对象的通用聚合方法。将 nunique() 作为聚合函数传入,可以通过 DataFrame.aggregate() 接口得到相同的计数,并能更灵活地控制结果的形状。

本节中的代码以及显示的标签或输出仍为英文。

import pandas as pd

data = [
    [999, "Switzerland"],
    [113, "Switzerland"],
    [112, "Japan"],
    [112, "Switzerland"],
    [113, "Canada"],
    [114, "Japan"],
    [100, "Germany"],
    [114, "Japan"],
    [115, "Germany"],
]
df = pd.DataFrame(data, columns=["code", "Countries"])
result = df.groupby(by="Countries", as_index=False).agg({"code": pd.Series.nunique})
print(result)
     Countries  code
0       Canada     1
1      Germany     2
2        Japan     2
3  Switzerland     3

字典 {"code": pd.Series.nunique} 告诉 agg() 要聚合哪一列以及应用哪个函数;pd.Series.nunique 与上一节直接调用的操作相同,以字符串形式传入它的名称——agg({"code": "nunique"})——效果完全相同。as_index=False 参数会将国家名称保留为普通列,而不是移入索引,这就是 result 是扁平 DataFrame 而不是带索引的 Series 的原因。当你需要 DataFrame 结果,或者不同值计数只是一次传递中要计算的多个聚合之一时,请使用这种形式。

使用 GroupBy["code"].unique() 列出唯一值

计数回答的是“有多少”,但有时你需要知道每个国家使用的是哪些代码。将 unique() 应用于分组后的 Series 会返回不同的值本身。

本节中的代码以及显示的标签或输出仍为英文。

import pandas as pd

data = [
    [999, "Switzerland"],
    [113, "Switzerland"],
    [112, "Japan"],
    [112, "Switzerland"],
    [113, "Canada"],
    [114, "Japan"],
    [100, "Germany"],
    [114, "Japan"],
    [115, "Germany"],
]
df = pd.DataFrame(data, columns=["code", "Countries"])
result = df.groupby("Countries")["code"].unique()
print(result)
Countries
Canada                   [113]
Germany             [100, 115]
Japan               [112, 114]
Switzerland    [999, 113, 112]
Name: code, dtype: object

结果中的每个条目都是一个数组,包含该国家出现的不同 code 值,并按出现顺序排列——瑞士的数组确认了之前统计出的三个代码:999、113 和 112。由于每个元素都是数组而不是单个数字,因此该 Series 的 dtype 为 object。有一个值得注意的行为差异:unique() 会在返回的数组中包含 NaN,而 nunique() 默认会跳过缺失值。

在统计和列出值之间选择

当问题偏重于数量——“每个国家使用多少个不同的代码?”——时,请选择 nunique()。它返回整洁的数值计数,你可以对其进行排序、筛选,或连接回原始行。若要把每行所属组的计数附加为新列,请使用 df.groupby("Countries")["code"].transform("nunique"),它会将每组的计数广播到每一行。

当值本身很重要时,请选择 unique()——例如,审计哪些代码被多个国家共用,或把值列表提供给后续处理步骤。如果你已经得到了 unique() 的结果并需要其长度,result.apply(len) 会重现与 nunique() 相同的计数。

最后,当不同值计数只是更大范围聚合的一部分时,请使用 agg(),例如 agg({"code": "nunique", "price": "sum"}),它会在一次传递中计算多个按组统计量。简而言之:需要计数时用 nunique(),需要值本身时用 unique(),需要 DataFrame 结果或想把计数与其他聚合组合时用 agg()。