Pandas 条件滚动计数

Joh*_*hnH 4 python dataframe pandas

我有一个从 Pandas 延伸出来的问题:条件滚动计数。我想在数据框中创建一个新列,反映满足多个条件的累积行数。

使用以下示例和来自 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

Run Code Online (Sandbox Code Playgroud)

输出是每头牛的 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

我想做的是重新开始计算每头奶牛的哺乳期 (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'])

Run Code Online (Sandbox Code Playgroud)

这会产生以下输出

奶牛乳 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

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

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

  奶牛 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 乳 2 的示例中,当暗度从 25 变为 28 时,计数不会增加,因为两个事件之间的差异小于 7 天。当奶牛 2 乳 2 从 95 增加到 98 时,情况也是如此。对于较大的增量(70 到 95 以及 98 到 120),计数会增加。

感谢您的帮助

约翰

Sea*_*ean 5

实际上,如果您使用了所引用问题中得票最高的解决方案,那么您要设置的代码可以大大xmast简化。Lxmast

将数据框重命名cowmastdf,您可以按如下方式设置xmast

df['xmast'] = df.groupby((df['Cow'] != df['Cow'].shift(1)).cumsum()).cumcount()+1
Run Code Online (Sandbox Code Playgroud)

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

df['Lxmast'] = (df.groupby([(df['Cow'] != df['Cow'].shift(1)).cumsum(), 
                            (df['Lact'] != df['Lact'].shift()).cumsum()])
                  .cumcount()+1
               )
Run Code Online (Sandbox Code Playgroud)

数据输入

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
Run Code Online (Sandbox Code Playgroud)

输出

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
Run Code Online (Sandbox Code Playgroud)

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

我想做的是重新启动每头牛(奶牛)泌乳期(Lact)的计数,并且仅当行之间的天数(DIM)超过 7 时才增加计数

我们可以这样做:

为了使代码更具可读性,我们为目前的代码定义 2 个分组序列:

m_Cow = (df['Cow'] != df['Cow'].shift()).cumsum()
m_Lact = (df['Lact'] != df['Lact'].shift()).cumsum()
Run Code Online (Sandbox Code Playgroud)

然后,我们可以重写代码,以Lxmast更易读的格式进行设置,如下所示:

df['Lxmast'] = df.groupby([m_Cow, m_Lact]).cumcount()+1
Run Code Online (Sandbox Code Playgroud)

现在,转向这里的主要作品。假设我们Adjusted为其创建另一个新列:

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

结果:

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
Run Code Online (Sandbox Code Playgroud)

这里,在之后df.groupby([m_Cow, m_Lact]),我们取列DIM并检查每一行与前一行的差异.diff()并取绝对值by ,然后在代码片段中.abs()检查它是否> 7 。然后,我们再次按相同的分组进行分组,因为第三个条件位于前两个条件的分组内。我们在第三个条件上使用最后一步,以便只有当第三个条件为真时我们才会增加计数。.gt(7)['DIM'].diff().abs().gt(7).groupby([m_Cow, m_Lact]).cumsum()

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

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

编辑(可能的简化取决于您的数据序列)

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

与引用问题的样本数据不同,其中:

   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
Run Code Online (Sandbox Code Playgroud)

B在这里,最后一行中的最后一个与B其他的分开,并且需要将计数重置为 1,而不是从前count2 个中的最后一个继续B(变为 3)。因此,分组需要将当前行与前一行进行比较以获得正确的分组。否则,当我们在处理过程中使用.groupby()和 的值B分组在一起时,count最后一个条目的值可能无法正确重置为 1。

如果您的主分组键的数据在数据构建期间Cow已经Lact自然排序,或者已经按照如下指令排序:

df = df.sort_values(['Cow', 'Lact'])
Run Code Online (Sandbox Code Playgroud)

然后,我们可以简化我们的代码,如下所示:

(当数据已按 [ 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
                 )
Run Code Online (Sandbox Code Playgroud)

3 列中的结果和输出值相同xmastLxmast并且 Adjusted