0

0

利用 Pandas 高效分配数据框中的分组值:一种可扩展的解决方案

碧海醫心

碧海醫心

发布时间:2025-12-04 11:19:19

|

871人浏览过

|

来源于php中文网

原创

利用 Pandas 高效分配数据框中的分组值:一种可扩展的解决方案

本文探讨了如何使用 pandas 高效且可扩展地处理数据框中按组分配值的场景。针对传统 `iloc` 手动赋值方式的局限性,文章提出了一种基于 `groupby().transform()` 的优化方法,通过一个简洁的函数实现了按优先级和最大值限制(如每人最多100个箱子,单人组全部分配)的智能分配,极大地提升了代码的可维护性和性能。

引言:数据框分组值分配的挑战

在数据分析和处理中,我们经常需要根据特定条件对数据框中的分组数据进行值分配。一个常见场景是,在每个组内,按照一定的优先级和限制(例如,每个工作者最多分配100个箱子,但如果只有一个工作者,则分配所有箱子),将总数分配给组内的各个成员。

传统上,一些开发者可能会尝试使用 iloc 结合 if/elif 语句来手动处理每个组内成员的赋值。然而,这种方法存在显著的局限性:

  1. 不可扩展性: 当组内的成员数量增加时,需要手动添加更多的 elif 分支和 iloc 索引,导致代码冗长且难以维护。
  2. 效率低下: 大量的条件判断和逐个索引操作可能导致性能瓶颈,尤其是在处理大型数据集时。

本文将介绍一种更为高效和可扩展的 Pandas 解决方案,它利用 groupby().transform() 结合一个简洁的函数来优雅地解决这个问题。

初始数据准备

首先,我们创建一个示例 Pandas 数据框,它包含商店(store)、工作者(worker)、现有箱子数量(boxes)以及待填充的优化箱子数量(optimal_boxes)。

import pandas
import numpy

data_stack_exchange = {'store': ['A','B', 'B', 'C', 'C', 'C', 'D', 'D', 'D', 'D'],
        'worker': [1,1,2,1,2,3,1,2,3,4],
        'boxes': [105, 90, 100, 80, 10, 200, 70, 210, 50, 0],
        'optimal_boxes': [0,0,0,0,0,0,0,0,0,0]}
df_stack_exchange = pandas.DataFrame(data_stack_exchange)

print("原始数据框:")
print(df_stack_exchange)

输出的原始数据框如下:

  store  worker  boxes  optimal_boxes
0     A       1    105              0
1     B       1     90              0
2     B       2    100              0
3     C       1     80              0
4     C       2     10              0
5     C       3    200              0
6     D       1     70              0
7     D       2    210              0
8     D       3     50              0
9     D       4      0              0

我们的目标是根据以下规则填充 optimal_boxes 列:

  • 工作者优先级按 worker 列的数值顺序。
  • 每个工作者最多分配100个箱子。
  • 如果一个商店只有一个工作者,则该工作者获得所有箱子,即使超过100个。
  • 箱子分配完为止。

低效的 iloc 解决方案回顾

为了说明问题,我们简要回顾一下原始问题中提出的基于 iloc 的解决方案。虽然它能达到预期结果,但其扩展性极差。

# 这是一个低效且不推荐的解决方案,仅用于说明问题
def box_optimizer_inefficient(x):
    total_boxes_in_group = x['boxes'].sum()
    group_size = x['optimal_boxes'].count()

    # 初始化一个与组大小相同的列表来存储分配结果
    assigned_boxes = [0] * group_size
    remaining_to_assign = total_boxes_in_group

    if group_size == 1:
        assigned_boxes[0] = total_boxes_in_group
    else:
        for i in range(group_size):
            if remaining_to_assign >= 100:
                assigned_boxes[i] = 100
                remaining_to_assign -= 100
            else:
                assigned_boxes[i] = remaining_to_assign
                remaining_to_assign = 0
            # 确保不会分配负数
            if remaining_to_assign < 0:
                remaining_to_assign = 0

    x['optimal_boxes'] = assigned_boxes
    return x

# df_stack_exchange_function = df_stack_exchange.groupby('store', as_index=False, group_keys=False).apply(box_optimizer_inefficient)
# print(df_stack_exchange_function)

上述代码的 if/elif 结构在原始问题中更加繁琐,需要为每个可能的组大小编写重复的代码块。这种方法不仅代码量大,而且在组大小变动时需要不断修改函数,难以维护。

优化方案:使用 groupby().transform()

为了解决上述问题,我们可以设计一个更通用的函数,并利用 Pandas 的 groupby().transform() 方法。transform() 方法的特点是它会将函数的结果广播回原始数据框的相应位置,确保输出的长度与输入组的长度一致。

核心分配函数 assign_boxes

我们定义一个 assign_boxes 函数,它接收一个 Pandas Series(代表一个组的 boxes 列),并返回一个与该 Series 长度相同的列表或 Series,其中包含计算出的 optimal_boxes 值。

def assign_boxes(s):
    """
    根据业务规则为每个工作者组分配箱子。

    参数:
    s (pandas.Series): 一个商店中所有工作者的 'boxes' 值的 Series。

    返回:
    list: 包含每个工作者分配到的 'optimal_boxes' 值的列表。
    """
    total_boxes = s.sum()  # 计算当前组(商店)的总箱子数
    num_workers = len(s)   # 当前组(商店)的工作者数量

    # 特殊情况:如果只有一个工作者,他将获得所有箱子
    if num_workers == 1:
        return [total_boxes]

    # 计算可以分配满100个箱子的工作者数量
    # min(total_boxes // 100, num_workers - 1) 是关键:
    # total_boxes // 100 给出理论上能分满100箱子的工人数量
    # num_workers - 1 确保至少有一个工人会获得剩余的箱子(而不是被计算为满100箱子)
    # 这样,即使总箱子数超过 (num_workers - 1) * 100,最后一个工人也会得到剩余的箱子,而不是0
    num_full_100_boxes_workers = min(total_boxes // 100, num_workers - 1)

    # 构建分配结果列表
    # 1. 前 num_full_100_boxes_workers 个工作者获得 100 个箱子
    assigned = [100] * num_full_100_boxes_workers

    # 2. 计算剩余箱子,分配给下一个工作者
    remaining_boxes = total_boxes - 100 * num_full_100_boxes_workers
    assigned.append(remaining_boxes)

    # 3. 如果还有未分配的工作者,他们获得 0 个箱子(因为箱子已经分完)
    # (num_workers - num_full_100_boxes_workers - 1) 是指除了那些分到100个和分到剩余箱子的工人之外的工人数量
    assigned.extend([0] * (num_workers - num_full_100_boxes_workers - 1))

    return assigned

# 应用函数到数据框
df_stack_exchange['optimal_boxes'] = df_stack_exchange.groupby('store')['boxes'].transform(assign_boxes)

print("\n优化后的数据框:")
print(df_stack_exchange)

输出结果

经过 assign_boxes 函数处理后的数据框 df_stack_exchange 如下所示:

  store  worker  boxes  optimal_boxes
0     A       1    105            105
1     B       1     90            100
2     B      2    100             90
3     C       1     80            100
4     C       2     10            100
5     C       3    200             90
6     D       1     70            100
7     D       2    210            100
8     D       3     50            100
9     D       4      0             30

这个结果与预期完全一致。

assign_boxes 函数详解

让我们深入分析 assign_boxes 函数的关键逻辑:

  1. total_boxes = s.sum() 和 num_workers = len(s):

    • s.sum() 计算当前商店(组)中所有工作者的 boxes 总和。
    • len(s) 获取当前商店的工作者数量。
  2. if num_workers == 1: return [total_boxes]:

    • 这是处理单工作者商店的特殊规则。如果只有一个工作者,他将获得 total_boxes 的所有箱子,无论是否超过100。
  3. num_full_100_boxes_workers = min(total_boxes // 100, num_workers - 1):

    云点滴客户关系管理CRM OA系统
    云点滴客户关系管理CRM OA系统

    云点滴客户解决方案是针对中小企业量身制定的具有简单易用、功能强大、永久免费使用、终身升级维护的智能化客户解决方案。依托功能强大、安全稳定的阿里云平 台,性价比高、扩展性好、安全性高、稳定性好。高内聚低耦合的模块化设计,使得每个模块最大限度的满足需求,相关模块的组合能满足用户的一系列要求。简单 易用的云备份使得用户随时随地简单、安全、可靠的备份客户信息。功能强大的报表统计使得用户大数据分析变的简单,

    下载
    • 这是算法的核心。
    • total_boxes // 100:计算理论上可以分到完整100个箱子的工作者数量。
    • num_workers - 1:确保至少有一个工作者会得到剩余的箱子。通过将其作为 min 函数的第二个参数,我们可以防止所有工作者都被分配100个箱子后,没有工作者来接收剩余的零散箱子。例如,如果有4个工作者和330个箱子,total_boxes // 100 是3。num_workers - 1 是3。min(3, 3) 结果是3。这意味着前3个工作者各分100,最后一个工作者分剩余的30。如果 num_workers - 1 不存在,且总数是300,total_boxes // 100 是3,num_workers 是3,那么所有工人都会被分到100,但这样无法处理剩余的箱子。这个 num_workers - 1 巧妙地保证了最后一个有资格获得箱子的工作者会得到所有剩余的箱子。
  4. *`assigned = [100] num_full_100_boxes_workers`**:

    • 创建一个列表,其中前 num_full_100_boxes_workers 个元素都是100,代表这些工作者各分得100个箱子。
  5. *`remaining_boxes = total_boxes - 100 num_full_100_boxes_workers`**:

    • 计算在分配完那些获得100个箱子的工作者后,还剩下多少箱子。
  6. assigned.append(remaining_boxes):

    • 将剩余的箱子分配给下一个工作者(即 num_full_100_boxes_workers 之后的第一个工作者)。
  7. *`assigned.extend([0] (num_workers - num_full_100_boxes_workers - 1))`**:

    • 如果还有其他工作者(即除了那些分到100个和分到剩余箱子的工作者之外),他们将获得0个箱子,因为所有箱子都已分配完毕。

示例演练

为了更好地理解,我们以 store 'D' 的数据为例: s = pd.Series([70, 210, 50, 0]) (原始 boxes 值)

  • total_boxes = s.sum() = 70 + 210 + 50 + 0 = 330
  • num_workers = len(s) = 4
  1. num_workers 不等于1。

  2. num_full_100_boxes_workers = min(330 // 100, 4 - 1)

    • 330 // 100 = 3
    • 4 - 1 = 3
    • min(3, 3) = 3。这意味着有3个工作者将获得100个箱子。
  3. assigned = [100] * 3 -> [100, 100, 100]

  4. remaining_boxes = 330 - (100 * 3) = 330 - 300 = 30

  5. assigned.append(30) -> [100, 100, 100, 30]

  6. assigned.extend([0] * (4 - 3 - 1)) -> assigned.extend([0] * 0) -> [] (没有额外的0需要添加)

最终返回 [100, 100, 100, 30],这正是 store 'D' 的预期 optimal_boxes 值。

总结与注意事项

通过 groupby().transform() 结合一个精心设计的函数,我们成功地实现了一个可扩展且高效的数据框分组值分配方案。这种方法避免了手动 iloc 索引和冗长的 if/elif 结构,使代码更简洁、更易于维护,并能更好地适应未来数据规模和规则的变化。

注意事项:

  • transform() 方法要求你的函数返回一个与输入组长度相同的 Series 或列表。
  • 在设计函数时,要充分考虑所有边缘情况,例如本例中的单工作者组和箱子不足以分满100的情况。
  • 对于非常大的数据集,虽然 transform 已经很高效,但如果业务逻辑极其复杂,可能需要考虑使用 Numba 或 Cython 等工具进一步优化计算密集型部分。

掌握 groupby().transform() 是 Pandas 高级数据处理的关键技能之一,它在许多需要对分组数据进行计算并广播结果的场景中都非常有用。

相关专题

更多
Python 时间序列分析与预测
Python 时间序列分析与预测

本专题专注讲解 Python 在时间序列数据处理与预测建模中的实战技巧,涵盖时间索引处理、周期性与趋势分解、平稳性检测、ARIMA/SARIMA 模型构建、预测误差评估,以及基于实际业务场景的时间序列项目实操,帮助学习者掌握从数据预处理到模型预测的完整时序分析能力。

52

2025.12.04

if什么意思
if什么意思

if的意思是“如果”的条件。它是一个用于引导条件语句的关键词,用于根据特定条件的真假情况来执行不同的代码块。本专题提供if什么意思的相关文章,供大家免费阅读。

755

2023.08.22

if什么意思
if什么意思

if的意思是“如果”的条件。它是一个用于引导条件语句的关键词,用于根据特定条件的真假情况来执行不同的代码块。本专题提供if什么意思的相关文章,供大家免费阅读。

755

2023.08.22

append用法
append用法

append是一个常用的命令行工具,用于将一个文件的内容追加到另一个文件的末尾。想了解更多append用法相关内容,可以阅读本专题下面的文章。

343

2023.10.25

python中append的用法
python中append的用法

在Python中,append()是列表对象的一个方法,用于向列表末尾添加一个元素。想了解更多append的更多内容,可以阅读本专题下面的文章。

1073

2023.11.14

python中append的含义
python中append的含义

本专题整合了python中append的相关内容,阅读专题下面的文章了解更多详细内容。

175

2025.09.12

页面置换算法
页面置换算法

页面置换算法是操作系统中用来决定在内存中哪些页面应该被换出以便为新的页面提供空间的算法。本专题为大家提供页面置换算法的相关文章,大家可以免费体验。

403

2023.08.14

数据分析的方法
数据分析的方法

数据分析的方法有:对比分析法,分组分析法,预测分析法,漏斗分析法,AB测试分析法,象限分析法,公式拆解法,可行域分析法,二八分析法,假设性分析法。php中文网为大家带来了数据分析的相关知识、以及相关文章等内容。

466

2023.07.04

Java JVM 原理与性能调优实战
Java JVM 原理与性能调优实战

本专题系统讲解 Java 虚拟机(JVM)的核心工作原理与性能调优方法,包括 JVM 内存结构、对象创建与回收流程、垃圾回收器(Serial、CMS、G1、ZGC)对比分析、常见内存泄漏与性能瓶颈排查,以及 JVM 参数调优与监控工具(jstat、jmap、jvisualvm)的实战使用。通过真实案例,帮助学习者掌握 Java 应用在生产环境中的性能分析与优化能力。

19

2026.01.20

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Java 教程
Java 教程

共578课时 | 48.6万人学习

国外Web开发全栈课程全集
国外Web开发全栈课程全集

共12课时 | 1.0万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号