Pandas DataFrame中连续行块计数的高效方法

聖光之護
发布: 2025-11-24 11:41:47
原创
360人浏览过

Pandas DataFrame中连续行块计数的高效方法

本文将深入探讨如何在pandas dataframe中高效地计算某一列连续相同值的行数,并将其作为新列添加。通过结合`shift()`、`cumsum()`和`groupby().transform('size')`等pandas核心函数,我们将展示一种优雅且强大的解决方案,精确识别并统计数据集中所有连续值块的长度,避免了传统`groupby`的局限性,适用于需要精细化连续数据分析的场景。

在数据处理和分析中,我们经常会遇到需要统计DataFrame中某一列连续相同值出现的次数。例如,给定一个序列 ['a', 'a', 'a', 'b', 'b', 'c', 'd', 'e', 'e', 'e'],我们希望得到的结果是 [3, 3, 3, 2, 2, 1, 1, 3, 3, 3],即每个连续块的长度。这与简单地按值分组并计数不同,因为同一个值在DataFrame中可能非连续地出现多次,而我们只关心连续出现的块。

问题场景与常见误区

假设我们有以下DataFrame:

import pandas as pd

data = {
    'class': ['a', 'a', 'a', 'b', 'b', 'c', 'd', 'e', 'e', 'e', 'f', 'a', 'c', 'd', 'd']
}
df = pd.DataFrame(data)
print("原始DataFrame:")
print(df)
登录后复制

输出:

原始DataFrame:
   class
0      a
1      a
2      a
3      b
4      b
5      c
6      d
7      e
8      e
9      e
10     f
11     a
12     c
13     d
14     d
登录后复制

我们期望的结果是为每行添加一个 consecutive_count 列,其值表示该行所属的连续块的长度:

   class  consecutive_count
0      a                  3
1      a                  3
2      a                  3
3      b                  2
4      b                  2
5      c                  1
6      d                  1
7      e                  3
8      e                  3
9      e                  3
10     f                  1
11     a                  1
12     c                  1
13     d                  2
14     d                  2
登录后复制

误区一:使用 groupby().transform('count')

直接按 class 列进行分组并使用 transform('count') 会统计每个 class 值在整个DataFrame中出现的总次数,而不是连续块的次数。

df['total_count'] = df.groupby('class')['class'].transform('count')
print("\n使用 transform('count') 的结果:")
print(df)
登录后复制

输出:

使用 transform('count') 的结果:
   class  total_count
0      a            4
1      a            4
2      a            4
3      b            2
4      b            2
5      c            2
6      d            3
7      e            3
8      e            3
9      e            3
10     f            1
11     a            4
12     c            2
13     d            3
14     d            3
登录后复制

显然,这不符合我们的需求,例如,第一个 'a' 块有3个,但结果显示为4,因为后面还有一个单独的 'a'。

误区二:使用 (df['class'] != df['class'].shift()).cumsum()

这个表达式可以为每个连续块生成一个唯一的标识符,但它本身不提供块的长度。

启科网络PHP商城系统
启科网络PHP商城系统

启科网络商城系统由启科网络技术开发团队完全自主开发,使用国内最流行高效的PHP程序语言,并用小巧的MySql作为数据库服务器,并且使用Smarty引擎来分离网站程序与前端设计代码,让建立的网站可以自由制作个性化的页面。 系统使用标签作为数据调用格式,网站前台开发人员只要简单学习系统标签功能和使用方法,将标签设置在制作的HTML模板中进行对网站数据、内容、信息等的调用,即可建设出美观、个性的网站。

启科网络PHP商城系统 0
查看详情 启科网络PHP商城系统
df['consecutive_group_id'] = (df['class'] != df['class'].shift()).cumsum()
print("\n使用 cumsum() 生成组ID的结果:")
print(df)
登录后复制

输出:

使用 cumsum() 生成组ID的结果:
   class  consecutive_group_id
0      a                     1
1      a                     1
2      a                     1
3      b                     2
4      b                     2
5      c                     3
6      d                     4
7      e                     5
8      e                     5
9      e                     5
10     f                     6
11     a                     7
12     c                     8
13     d                     9
14     d                     9
登录后复制

这里 consecutive_group_id 成功地将不同的连续块区分开来(例如,第一个 'a' 块的ID是1,而第11行的 'a' 块的ID是7)。这是解决问题的关键一步,但还需要进一步处理才能得到块的长度。

解决方案:组合动态分组与 transform('size')

解决此问题的核心在于创建一个能够同时识别值和其连续性的分组键。我们可以利用 (df['class'] != df['class'].shift()).cumsum() 生成的连续组ID,并将其与原始的 class 值结合起来作为 groupby 的键。

步骤详解:

  1. 识别连续块的起始点:df['class'].shift() 将 class 列向下移动一行,使得当前行的值可以与上一行的值进行比较。 df['class'] != df['class'].shift() 会生成一个布尔序列,True 表示当前行的值与上一行的值不同(即一个新的连续块开始),False 表示相同。对于第一行,shift() 会产生 NaN,与任何值比较都会是 True,这恰好符合其作为第一个块起始点的逻辑。

  2. 生成唯一的连续块ID: 对上述布尔序列应用 .cumsum()。由于 True 在求和时被视为1,False 被视为0,cumsum() 会为每个新的连续块分配一个递增的唯一整数ID。这样,即使同一个值(如 'a')在DataFrame中多次非连续地出现,它们也会被赋予不同的连续块ID。

  3. 构建复合分组键: 现在我们有了两个关键信息:原始的 class 值,以及它所属的连续块的唯一ID。我们将这两者结合起来作为 groupby 的键: df.groupby(['class', (df['class'] != df['class'].shift()).cumsum()]) 这个 groupby 操作会创建一个组,其中每个组都由一个唯一的 (class值, 连续块ID) 对定义。例如,第一个 'a' 块会形成一个组 ('a', 1),而第11行的 'a' 块会形成另一个组 ('a', 7)。

  4. 计算并广播组大小: 在分组之后,我们使用 .transform('size')。transform('size') 的作用是计算每个组的元素数量(即连续块的长度),然后将这个计算结果广播回原始DataFrame中属于该组的所有行。这样,每个属于同一个连续块的行都会得到该块的正确长度。

完整代码示例:

import pandas as pd

# 原始数据
data = {
    'class': ['a', 'a', 'a', 'b', 'b', 'c', 'd', 'e', 'e', 'e', 'f', 'a', 'c', 'd', 'd']
}
df = pd.DataFrame(data)

# 核心解决方案
df['consecutive_count'] = df.groupby(['class', (df['class'] != df['class'].shift()).cumsum()]).transform('size')

print("\n最终结果DataFrame:")
print(df)
登录后复制

输出:

最终结果DataFrame:
   class  consecutive_count
0      a                  3
1      a                  3
2      a                  3
3      b                  2
4      b                  2
5      c                  1
6      d                  1
7      e                  3
8      e                  3
9      e                  3
10     f                  1
11     a                  1
12     c                  1
13     d                  2
14     d                  2
登录后复制

这个结果与我们最初期望的完全一致。

注意事项与总结

  • shift() 处理 NaN: df['class'].shift() 在DataFrame的第一行会产生 NaN。当与 df['class'] 的第一个元素比较时,'a' != NaN 会被评估为 True(在Pandas和Python中,'a' == NaN 是 False),因此 cumsum() 从1开始计数是正确的。
  • 性能: 这种方法利用了Pandas底层的优化,对于大型DataFrame来说效率很高。
  • 通用性: 这种方法不仅适用于字符串类型,也适用于数值型或其他可比较的数据类型。
  • transform() 的强大: transform() 函数是Pandas中一个非常强大的工具,它允许在 groupby 操作后,将聚合结果(如 size, sum, mean 等)广播回原始DataFrame,保持DataFrame的原始形状,这在许多数据转换场景中都非常有用。

通过理解并运用 shift()、cumsum() 和 groupby().transform('size') 的组合,我们能够优雅且高效地解决Pandas DataFrame中连续行块计数的问题,这在时间序列分析、日志处理等领域具有广泛的应用价值。

以上就是Pandas DataFrame中连续行块计数的高效方法的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

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