【问题标题】:Pandas Conditional Rolling CountPandas 条件滚动计数
【发布时间】:2021-09-21 07:00:33
【问题描述】:

我有一个来自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(乳腺炎次数)

Cow Lact 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 时才增加计数。

为了合并多个条件来重置每头奶牛泌乳 (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'])

产生以下输出

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

我希望了解如何在累积计数中添加另一个条件,该条件考虑到乳腺炎事件之间的时间(同一 Lact 内奶牛的行之间的 DIM 差异)。如果同一头奶牛和泌乳行之间的 DIM 差异小于 7,则计数不应增加。

我正在寻找的输出在下表中称为“已调整”。

Cow Lact DIM xmast Lxmast 已调整 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 lact 2 的示例中,当昏暗从 25 变为 28 时,计数不会增加,因为这两个事件之间的差异小于 7 天。奶牛 2 lact 2 从 95 到 98 时也是如此。对于较大的增量,从 70 到 95 和从 98 到 120,计数会增加。

感谢您的帮助

约翰

【问题讨论】:

  • 请包括您的预期输出并尝试改写您的问题,不清楚您想要什么。
  • 感谢弗兰克的反馈,希望我的编辑和示例能让它更清晰。约翰

标签: python pandas dataframe


【解决方案1】:

实际上,如果您使用在referenced question 中获得最高票数的解决方案,您设置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

现在,继续下面以粗体突出显示的要求的最后一部分:

我想做的是重新开始计算每头牛(牛) 哺乳期(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()取绝对值,然后用@987654324检查它是否>7 @在代码片段['DIM'].diff().abs().gt(7)中。然后我们再次按相同的分组.groupby([m_Cow, m_Lact]) 分组,因为这第三个条件在前两个条件的分组内。最后一步,我们在第三个条件上使用.cumsum(),这样只有当第三个条件为真时,我们才会增加计数。

以防万一您只想在 DIM增加 > 7(例如 70 到 78)时增加计数,并排除 减少 > 7 的情况strong>(不是从 78 到 70),您可以删除上面代码中的 .abs() 部分:

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

编辑(可能根据您的数据顺序进行简化)

由于您的示例数据的主要分组键 CowLact 在某种程度上已经按排序顺序排列,因此有机会进一步简化代码。

不同于referenced question的样本数据,其中:

   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 个中的最后一个count 继续计数(成为 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 3 列中的结果和输出值相同

【讨论】:

  • 感谢您的回答并花时间解释它是如何工作的。我是 python 新手,所以解释非常有帮助。非常感激。约翰
  • @user16776897 John,很高兴该解决方案适合您。我很乐意提供帮助!
  • @user16776897 John,如果您想了解更多关于我们应该何时应用哪种分组代码的信息,请参阅上面的最新编辑。在这里,如果您的示例数据已经处于某种排序序列中,则可以使用更简单的代码来处理它们。仅供您学习参考。
  • SeaBean 感谢您,很高兴看到如何简化代码。我将原始代码带回更大的数据集并遇到了障碍。我将编辑上面的问题来说明
  • @user16776897 好的,让我看看。待会儿会回来,因为我目前正在从事一些任务。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-07-26
  • 2021-03-10
  • 2020-04-11
  • 1970-01-01
  • 1970-01-01
  • 2020-10-05
  • 2019-01-03
相关资源
最近更新 更多