Pandas条件滚动计数

4

我有一个问题,它延伸自Pandas: conditional rolling count。 我想在数据帧中创建一个新列,反映满足多个条件的行的累计计数。

使用来自stackoverflow 25119524的以下示例和代码:

import pandas as pd


l1 =["1", "1", "1", "2", "2", "2", "2", "2"]
l2 =[1, 2, 2, 2, 2, 2, 2, 3]
l3 =[45, 25, 28, 70, 95, 98, 120, 80]
cowmast = pd.DataFrame(list(zip(l1, l2, l3))) 

cowmast.columns =['Cow', 'Lact', 'DIM']

def rolling_count(val):
    if val == rolling_count.previous:
        rolling_count.count +=1
    else:
        rolling_count.previous = val
        rolling_count.count = 1
    return rolling_count.count
rolling_count.count = 0 #static variable
rolling_count.previous = None #static variable

cowmast['xmast'] = cowmast['Cow'].apply(rolling_count) #new column in dataframe

cowmast


输出每头奶牛的乳腺炎次数(xmast)。
  奶牛  泌乳天数  DIM  xmast
0   1      1     45    1
1   1      2     25    2
2   1      2     28    3
3   2      2     70    1
4   2      2     95    2
5   2      2     98    3
6   2      2     120   4
7   2      3     80    5

对于每头奶牛(cow)的泌乳过程(Lact),我想要在每行之间的天数(DIM)大于7时重新开始计数并增加计数。
为了重置每头奶牛(cow)的每个泌乳过程(Lact)的计数,并仅在满足多个条件时才进行递增,我使用了以下代码。

def count_consecutive_items_n_cols(df, col_name_list, output_col):
    cum_sum_list = [
        (df[col_name] != df[col_name].shift(1)).cumsum().tolist() for col_name in col_name_list
    ]
    df[output_col] = df.groupby(
        ["_".join(map(str, x)) for x in zip(*cum_sum_list)]
    ).cumcount() + 1
    return df

count_consecutive_items_n_cols(cowmast, ['Cow', 'Lact'], ['Lxmast'])


产生以下输出:
奶牛 泌乳期 产后天数 乳房发炎次数 上次发炎时间 调整后次数
0     1     1      45      1       1
1     1     2      25      2       1
2     1     2      28      3       2
3     2     2      70      1       1
4     2     2      95      2       2
5     2     2      98      3       2
6     2     2      120     4       3
7     2     3      80      5       1

我希望了解如何在累计计数中添加另一个条件,该条件考虑了乳房炎症事件之间的时间差(相同泌乳期内的奶牛行之间的DIM差异)。 如果相同奶牛和泌乳期内行之间的DIM差小于7,则计数不应递增。
我要查找的输出在下面的表格中称为“调整后次数”。
  奶牛 泌乳期 产后天数 乳房发炎次数 上次发炎时间 调整后次数
0     1     1      45      1       1       1
1     1     2      25      2       1       1
2     1     2      28      3       2       1
3     2     2      70      1       1       1
4     2     2      95      2       2       2
5     2     2      98      3       3       2
6     2     2      120     4       4       3
7     2     3      80      5       1       1

在上述示例中,对于奶牛1泌乳期2,当DIM从25到28时,计数未递增,因为两个事件之间的差小于7天。 当奶牛2泌乳期2从95到98时同样如此。 对于较大的增量70到95和98到120,计数会增加。
谢谢您的帮助。
John

请包含您期望的输出并尝试重新表达您的问题,目前不清楚您想要什么。 - Frank
谢谢反馈,Frank。希望我的编辑和示例能让它更清晰明了。John - JohnH
1个回答

5

实际上,如果您使用了在 参考问题 中点赞最高的解决方案,那么设置 xmastLxmast 的代码可以大大简化。

将你的数据框 cowmast 重命名为 df,你可以按照以下方式设置 xmast

df['xmast'] = df.groupby((df['Cow'] != df['Cow'].shift(1)).cumsum()).cumcount()+1

同样地,要设置Lxmast,您可以使用:

df['Lxmast'] = (df.groupby([(df['Cow'] != df['Cow'].shift(1)).cumsum(), 
                            (df['Lact'] != df['Lact'].shift()).cumsum()])
                  .cumcount()+1
               )

数据输入

l1 =["1", "1", "1", "2", "2", "2", "2", "2"]
l2 =[1, 2, 2, 2, 2, 2, 2, 3]
l3 =[45, 25, 28, 70, 95, 98, 120, 80]
cowmast = pd.DataFrame(list(zip(l1, l2, l3))) 

cowmast.columns =['Cow', 'Lact', 'DIM']

df = cowmast

输出

print(df)

  Cow  Lact  DIM  xmast  Lxmast
0   1     1   45      1       1
1   1     2   25      2       1
2   1     2   28      3       2
3   2     2   70      1       1
4   2     2   95      2       2
5   2     2   98      3       3
6   2     2  120      4       4
7   2     3   80      5       1

现在,继续你的要求的最后部分,如下所示,用粗体突出显示:

我想做的是对于每头奶牛(cow)的泌乳期(Lact),重新开始计数,并且只有在两行之间的天数(DIM)大于7时才增加计数。

我们可以按照以下方式完成它:
为了使代码更易读,让我们为我们已经有的代码定义2个分组序列。
m_Cow = (df['Cow'] != df['Cow'].shift()).cumsum()
m_Lact = (df['Lact'] != df['Lact'].shift()).cumsum()

然后,我们可以将代码重写为以下更易读的格式来设置 Lxmast

df['Lxmast'] = df.groupby([m_Cow, m_Lact]).cumcount()+1

现在,让我们转向这里的主要工作。假设我们为其创建了另一列Adjusted

df['Adjusted'] = (df.groupby([m_Cow, m_Lact])
                   ['DIM'].diff().abs().gt(7)
                   .groupby([m_Cow, m_Lact])
                   .cumsum()+1
                )

结果:

print(df)

  Cow  Lact  DIM  xmast  Lxmast  Adjusted
0   1     1   45      1       1         1
1   1     2   25      2       1         1
2   1     2   28      3       2         1
3   2     2   70      1       1         1
4   2     2   95      2       2         2
5   2     2   98      3       3         2
6   2     2  120      4       4         3
7   2     3   80      5       1         1

在执行 df.groupby([m_Cow, m_Lact]) 之后,我们提取列 DIM 并使用 .diff() 计算每行与前一行的差异,并使用 .abs() 取绝对值,然后使用代码片段 ['DIM'].diff().abs().gt(7) 中的 .gt(7) 判断是否大于 7。由于第三个条件是在前两个条件的分组内进行的,所以我们再次按相同的分组方式进行分组 .groupby([m_Cow, m_Lact])。最后一步是使用 .cumsum() 对第三个条件进行操作,这样只有当第三个条件为真时才会增加计数。
如果您只想在 DIM 增加了 > 7(例如从 70 到 78),并且不包括 减少了 > 7 的情况(不是从 78 到 70),则可以在上述代码中删除 .abs() 部分。
df['Adjusted'] = (df.groupby([m_Cow, m_Lact])
                   ['DIM'].diff().gt(7)
                   .groupby([m_Cow, m_Lact])
                   .cumsum()+1
                )

编辑(根据您的数据序列可能需要简化)

由于您的样本数据已经将主要分组键CowLact有些排序,因此有机会进一步简化代码。

参考问题中的样本数据不同,其中:

   col count
0  B   1
1  B   2
2  A   1 # Value does not match previous row => reset counter to 1
3  A   2
4  A   3
5  B   1 # Value does not match previous row => reset counter to 1

在这里,最后一行中的最后一个 B与其他B分开,需要将计数重置为1,而不是继续使用前一个B的计数2(即变成3)。因此,必须将当前行与上一行进行比较,以获得正确的分组。否则,在使用.groupby()进行处理时,如果对B的值进行分组,则可能无法将count值正确地重置为最后一个条目的1。

如果对于主分组键CowLact的数据已经在数据构建过程中自然排序,或者已经按照指示进行了排序:

df = df.sort_values(['Cow', 'Lact'])

接下来,我们可以简化代码,如下所示:

(当数据已按 [Cow, Lact] 排序时):

df['xmast'] = df.groupby('Cow').cumcount()+1
df['Lxmast'] = df.groupby(['Cow', 'Lact']).cumcount()+1
               
df['Adjusted'] = (df.groupby(['Cow', 'Lact'])
                    ['DIM'].diff().abs().gt(7)
                    .groupby([df['Cow'], df['Lact']])
                    .cumsum()+1
                 )

在三列中,xmastLxmastAdjusted的结果和输出值相同。

谢谢您的回答并花时间解释它的工作原理。我是Python的新手,所以这些解释非常有帮助。非常感谢。John - JohnH
@user16776897 好的,让我看看。由于我目前正在处理一些任务,稍后会回来。 - SeaBean
@user16776897 你好 John,我在你的编辑底部使用了你的新数据。结果没有出现任何 NaN 值。请问你能否再次检查一下你的数据是否存在任何设置问题? - SeaBean
SeaBean,感谢您的关注。当我重新创建df时,它也对我有效。但是,当我回到原始文件时,除非我使用dict并重新创建df,否则它会给我NA值。在输出和重建过程中,数据框架似乎有一些重新排列。尝试了df = newm.copy()和newm直接赋值,但仍然得到NA。我将继续在日期设置方面努力,看看是否能找到解决方法。感谢您的帮助。John - JohnH
@user16776897 这种情况非常有趣。无论如何,如果您重新创建df时也能正常工作,那么这与问题和解决方案无关。我建议通过删除最新的编辑来清理问题。这可以帮助未来类似问题的读者更轻松地阅读问题并避免混淆。祝您修复数据问题的根本原因好运。 - SeaBean
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接