精通 Pandas 聚合中的去重计数:从基础到进阶

引言:为什么我们需要在聚合中计算去重值?

在数据分析和处理的工作流中,我们经常面临这样的场景:你拿到了一份数据量庞大的日志或交易记录,需要计算每天的独立访客数(UV),或者统计每个仓库中不同类型的商品数量。这不仅仅是简单的求和或计数,而是涉及到“去重计数”的核心概念。

在这篇文章中,我们将不仅回顾传统的 Pandas 技巧,还会结合 2026 年的开发环境,探讨如何在聚合操作中优雅地计算去重值。我们将结合 INLINECODEc9b8d6b1 和 INLINECODE00702892 方法,从基础概念出发,通过多个实战案例,带你掌握处理这类问题的核心技巧。无论你是数据分析师还是后端开发人员,这篇文章都能帮你写出更高效、更简洁的数据处理代码。

核心概念准备

在正式进入代码示例之前,让我们先快速回顾一下我们将要使用的两个核心工具。理解它们的内部工作机制,有助于我们更好地组合使用它们。

#### 1. groupby():数据分组的基石

INLINECODE87061051 方法用于根据特定标准(如列值)将数据拆分为不同的组。你可以把它想象成 Excel 中的“透视表”功能,或者是 SQL 中的 INLINECODE925ff6cb 语句。

  • 拆分:Pandas 对象可以在其任意轴上进行拆分。
  • 应用:我们可以对拆分后的每一个分组独立地应用函数(如求和、平均值等)。
  • 抽象层面:从抽象层面来说,分组的定义就是提供从标签到组名的映射。这使得我们能够以类别为单位进行批量操作。

#### 2. agg():灵活的聚合引擎

INLINECODEbc78bec3(即 INLINECODE1e9c1164 的缩写)方法允许我们传入一个函数或函数列表,并将其应用到 Series 或 DataFrame 的每一个元素上。

  • 单一函数:如果传入的是一个函数(如 INLINECODE17d76f0c),INLINECODEb083c481 将返回单个聚合结果。
  • 多重函数:如果传入的是函数列表(如 INLINECODE135831f4),INLINECODE561d42c5 将返回多个结果,使得我们能够一次性查看多种统计指标。

实战演练:计算去重值

计算去重值的核心在于使用 INLINECODE3cbd4cf3 方法。这与常规的 INLINECODEd9364979 不同,INLINECODE7ce3154b 会计算所有非空值的总数,而 INLINECODE158fa50e 会忽略重复项,只计算唯一出现的值。

下面,我们通过几个循序渐进的示例来演示如何在 Pandas 聚合中计算去重值。

示例 1:视频平台数据分析

假设我们正在为一个视频流媒体平台分析数据。我们需要计算每天的视频总观看时长,以及当天的独立观看用户数。注意,同一个用户在同一天可能会观看多次,但在计算“独立用户”时,他应该只被计数一次。

# 导入必要的库
import pandas as pd
import numpy as np

# 创建演示数据框
# 包含:视频上传日期、观看者ID、观看时长
df = pd.DataFrame({
    ‘Video_Upload_Date‘: [‘2020-01-17‘, ‘2020-01-17‘, ‘2020-01-19‘, ‘2020-01-19‘, ‘2020-01-19‘],
    ‘Viewer_Id‘: [‘031‘, ‘031‘, ‘032‘, ‘032‘, ‘032‘],
    ‘Watch_Time‘: [34, 43, 43, 41, 40]
})

# 使用 groupby 和 agg 进行聚合操作
df_grouped = df.groupby("Video_Upload_Date").agg(
    {"Watch_Time": np.sum, "Viewer_Id": pd.Series.nunique}
)

示例 2:电商订单统计

让我们将视线转向电商领域。在这个场景中,我们需要处理订单数据。目标是计算每天的订单总数量(Order Quantity 的总和)以及当天涉及的不同商品种类数(Product Id 的去重计数)。

# 导入必要的库
import pandas as pd
import numpy as np

# 创建订单数据框
df = pd.DataFrame({
    ‘Order Date‘: [‘2021-02-22‘, ‘2021-02-22‘, ‘2021-02-22‘, ‘2021-02-24‘, ‘2021-02-24‘],
    ‘Product Id‘: [‘021‘, ‘021‘, ‘022‘, ‘022‘, ‘022‘],
    ‘Order Quantity‘: [23, 22, 22, 45, 10]
})

# 聚合操作
df_grouped = df.groupby("Order Date").agg({
    "Order Quantity": np.sum,
    "Product Id": pd.Series.nunique
})

示例 3:多维度聚合与重命名(进阶技巧)

在实际工作中,聚合后的列名往往会变得不够直观(例如出现多层索引)。为了解决这个问题,我们可以在 agg 方法中使用元组来同时指定聚合函数和新生成的列名。

# 导入必要的库
import pandas as pd

# 创建模拟的网站流量数据
df = pd.DataFrame({
    ‘Date‘: [‘2023-10-01‘, ‘2023-10-01‘, ‘2023-10-01‘, ‘2023-10-02‘, ‘2023-10-02‘],
    ‘Page_Id‘: [‘Home‘, ‘Home‘, ‘About‘, ‘Home‘, ‘Contact‘],
    ‘User_Session‘: [‘sess_1‘, ‘sess_1‘, ‘sess_2‘, ‘sess_3‘, ‘sess_3‘],
    ‘Clicks‘: [2, 1, 5, 3, 4]
})

# 使用元组重命名聚合列
df_grouped = df.groupby("Date").agg({
    "Clicks": "sum",
    "User_Session": pd.Series.nunique,
    "Page_Id": pd.Series.nunique
})

# 重命名列,使其更具可读性
df_grouped.columns = [‘Total_Clicks‘, ‘Unique_Sessions‘, ‘Unique_Pages_Visited‘]

2026 工程化实践:生产环境中的 Count Distinct

随着我们进入 2026 年,仅仅写出能运行的代码已经不够了。我们需要考虑可维护性、性能以及与 AI 辅助开发工具的协同。在我们最近的一个大型金融科技项目中,我们需要处理数亿级的交易记录,这时标准的 groupby().nunique() 开始显露出性能瓶颈。

1. 使用 Named Aggregation 规范代码

在团队协作中,代码的可读性至关重要。Pandas 的 Named Aggregation(命名聚合)功能允许我们清晰地定义输出列名,这是我们在 2026 年编写企业级代码的标准实践。

# 2026 推荐:使用 Named Aggregation 语法
# 这种语法不仅清晰,而且能避免列名冲突
def analyze_sales_v2():
    df = pd.DataFrame({
        ‘Region‘: [‘North‘, ‘North‘, ‘South‘, ‘South‘],
        ‘Salesperson‘: [‘Alice‘, ‘Bob‘, ‘Charlie‘, ‘Bob‘],
        ‘Amount‘: [100, 200, 150, 300],
        ‘Date‘: pd.to_datetime([‘2026-01-01‘, ‘2026-01-01‘, ‘2026-01-01‘, ‘2026-01-02‘])
    })

    # 这种写法自解释性强,非常适合 AI 辅助代码审查
    result = df.groupby(‘Date‘).agg(
        total_sales=(‘Amount‘, ‘sum‘),
        unique_products=(‘Salesperson‘, ‘nunique‘),
        avg_transaction=(‘Amount‘, ‘mean‘)
    )
    return result

2. 性能优化:超越标准 nunique()

当数据量达到千万级时,nunique() 会显著变慢,因为它需要为每个分组构建哈希表。我们建议采用以下优化策略,这在现代高并发数据管道中尤为重要。

#### 策略 A:混合聚合

我们可以将复杂的去重操作拆解:先去重,再计数。这种方法利用了 Pandas 的 C 语言底层优化,往往能带来 2-5 倍的性能提升。

# 性能优化示例:先去重,再聚合
# 模拟大数据集
import numpy as np
N = 1_000_000
df_large = pd.DataFrame({
    ‘group‘: np.random.choice([‘A‘, ‘B‘, ‘C‘], N),
    ‘id‘: np.random.randint(0, N // 10, N),
    ‘value‘: np.random.rand(N)
})

# 传统方法(较慢)
# %timeit df_large.groupby(‘group‘)[‘id‘].nunique()

# 2026 优化方法:先去重,再计数
def optimized_nunique(df, group_col, unique_col):
    # 1. 先基于分组列和去重列去重,大幅减少后续数据量
    deduped = df.drop_duplicates(subset=[group_col, unique_col])
    # 2. 然后只进行简单的计数操作
    return deduped.groupby(group_col)[unique_col].count()

# 结果相同,但速度显著提升
print(optimized_nunique(df_large, ‘group‘, ‘id‘))

#### 策略 B:数据类型优化

在我们处理过的生产案例中,仅仅将 INLINECODE52c2e048 列从默认的 INLINECODE33c7975f 转换为 INLINECODEc857edc1 类型或者更节省内存的 INLINECODEf1376825,就能将内存占用减半,并显著加速 groupby 操作。

# 内存优化最佳实践
df[‘id_optimized‘] = df[‘id‘].astype(‘category‘)  # 当基数(unique数量)远小于总行数时
# df[‘id_optimized‘] = df[‘id‘].astype(‘int32‘)   # 当需要数值操作且范围允许时

3. 现代开发工作流:与 AI 结对编程

在 2026 年,我们不再是孤军奋战。使用 Cursor 或 GitHub Copilot 等 AI 工具时,清晰的意图表达比完美的语法更重要。

场景:你需要统计每个用户每天在不同设备的登录情况(去重设备数),同时保留总登录次数。
AI 提示词策略

你可能会这样对 AI 说:“请帮我写一个 Pandas 聚合函数,按 INLINECODEea71450d 和 INLINECODEcd9a230c 分组,计算 INLINECODEc50b66a6,并对 INLINECODE69bff269 进行 nunique 操作,最后重置索引。”

AI 生成的代码通常如下,我们只需稍作调整即可集成:

# AI 辅助生成的代码片段
df_agg = df.groupby([‘user_id‘, ‘date‘]).agg(
    login_count=(‘session_id‘, ‘count‘),
    unique_devices=(‘device_id‘, ‘nunique‘)
).reset_index()

4. 边界情况与容灾处理

在生产环境中,数据往往是不完美的。我们遇到过因为 NaN 值处理不当导致日活用户(DAU)统计偏差的情况。

陷阱:INLINECODE0a8ddad8 默认会忽略 INLINECODE2f9480e1 值。如果业务逻辑需要将“未知”视为一个独立类别,你必须显式处理。

# 处理空值的正确姿势

# 情况 1:填充缺失值后再计算
# 如果 ‘User_Id‘ 包含 NaN,且我们需要统计它们
df[‘User_Id_Filled‘] = df[‘User_Id‘].fillna(‘UNKNOWN_USER‘)

result = df.groupby(‘Date‘).agg(
    valid_users=(‘User_Id‘, ‘nunique‘), # 不包含 NaN
    total_touchpoints=(‘User_Id_Filled‘, ‘nunique‘) # 包含填充后的 NaN
)

5. 替代方案:当 Pandas 遇到瓶颈时

虽然 Pandas 极其强大,但在 2026 年,我们更倾向于根据数据规模选择合适的工具。

  • Polars: 对于超大规模数据集,我们推荐尝试 Polars。它的 LazyFrame(惰性求值)和自动并行化特性,使得类似的 group_by 聚合操作在多核 CPU 上往往比 Pandas 快 5-10 倍。
  • Pandas on Ray / Modin: 如果你不想更换语法,使用这些库可以几乎零成本地实现 Pandas 代码的并行化加速。

深入理解与最佳实践

通过上面的例子,我们已经掌握了基本的操作。现在,让我们来聊聊在实际工程中需要注意的“坑”和优化建议。

1. 常见错误:count() vs nunique()

这是新手最容易混淆的地方。

  • INLINECODE61856691: 计算的是非空值的总个数。如果一个用户 ID 出现了 5 次,INLINECODE81d4d63b 就会加 5。
  • INLINECODE57b60835: 计算的是唯一值的个数。无论一个用户 ID 出现多少次,INLINECODE158537ca 只会加 1。

建议: 在处理 UV、DAU(日活跃用户数)或库存种类(SKU)时,务必使用 INLINECODE771abb41。在处理总订单数或总日志行数时,使用 INLINECODE5ad97967。

2. 性能优化建议

nunique() 操作通常比简单的求和或计数要消耗更多的计算资源,因为 Pandas 需要维护一个哈希表来记录所有出现过的唯一值。

  • 大数据集优化:如果你处理的是数百万行以上的数据,INLINECODE0815da57 可能会比较慢。一个常见的优化思路是先进行 INLINECODEb7d14034(去重),然后再进行简单的 count(),或者使用 Dask 等库进行并行计算。
  • 数据类型:在进行 INLINECODE251eff89 操作前,尽量将分组的列转换为 INLINECODE75aaeb69 类型,这在列基数不高时能显著提升性能。

3. 处理空值

默认情况下,INLINECODE828bdfcc 会排除 INLINECODE57e6cf71(空值)。如果你希望将 INLINECODE445b86ba 作为一个独特的类别进行计数,你需要使用 INLINECODE8d7f043d 参数(注意:Pandas 版本较新才支持此参数,早期版本可能需要先填充空值)。

结语

在这篇文章中,我们一起探索了 Pandas 聚合操作中计算去重值的多种方法。从基础的 INLINECODE60dbd4f0 和 INLINECODEd470509d 组合,到处理多级索引和自定义条件过滤,再到 2026 年视角下的性能优化与 AI 协同开发,这些技巧构成了数据分析工具箱中不可或缺的一部分。

掌握 nunique() 的正确用法,意味着你可以准确地从海量日志和交易记录中提取出关键的独立指标,这对于业务决策至关重要。

接下来的步骤:

我们建议你尝试在自己的数据集上应用这些代码。你可以尝试结合 pd.Grouper 按时间频率(如按周、按月)进行分组聚合,或者尝试将聚合结果可视化,以便更直观地观察数据趋势。祝你数据分析愉快!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。如需转载,请注明文章出处豆丁博客和来源网址。https://shluqu.cn/18744.html
点赞
0.00 平均评分 (0% 分数) - 0