【问题标题】:Counting Consecutive Duplicates For By Group按组计算连续重复
【发布时间】:2020-01-23 05:23:25
【问题描述】:

我有一个包含 id 列、日期列和值的数据集。我想计算连续日期范围内 id 的连续出现/重复值。

我的问题很像Count consecutive duplicate values by group,但在 Python 中。 此外,这个问题与How to find duplicates in pandas dataframe 不同,因为我需要基于两列不同的列进行计数 - 它是日期(它会发生变化,但如果它是连续的,我想计算它)

这是一个示例数据集:

ID      tDate            value
79  2019-06-21 00:00:00  397
79  2019-07-13 00:00:00  404
79  2019-07-18 00:00:00  405
79  2019-07-19 00:00:00  406
79  2019-08-02 00:00:00  410
79  2019-08-09 00:00:00  413

我希望得到的数据集是:

ID      tDate            val  consec_count
79  2019-06-21 00:00:00  397  0
79  2019-07-13 00:00:00  404  0
79  2019-07-18 00:00:00  405  1
79  2019-07-19 00:00:00  406  2
79  2019-08-02 00:00:00  410  0
79  2019-08-09 00:00:00  413  0

我将“单曲”标记为 0 而不是 1,因为我需要将两者区分开来。我会以不同于单条记录的方式处理成批的“重复”。

谢谢!

【问题讨论】:

    标签: python pandas dataframe duplicates


    【解决方案1】:

    示例

    df = pd.DataFrame({'ID': [79, 79, 79, 79, 79, 79, 80, 80, 80, 80, 80, 80, 80], 
                       'tDate': [pd.Timestamp('2019-07-12 00:00:00'),
                                 pd.Timestamp('2019-07-13 00:00:00'),
                                 pd.Timestamp('2019-07-18 00:00:00'),
                                 pd.Timestamp('2019-07-19 00:00:00'),
                                 pd.Timestamp('2019-07-20 00:00:00'),
                                 pd.Timestamp('2019-08-03 00:00:00'), 
                                 pd.Timestamp('2019-06-21 00:00:00'), 
                                 pd.Timestamp('2019-06-22 00:00:00'), 
                                 pd.Timestamp('2019-07-18 00:00:00'), 
                                 pd.Timestamp('2019-07-19 00:00:00'), 
                                 pd.Timestamp('2019-07-26 00:00:00'), 
                                 pd.Timestamp('2019-08-02 00:00:00'), 
                                 pd.Timestamp('2019-08-03 00:00:00')],
                       'value':[397, 404, 405, 406, 408, 413, 397, 404, 405, 406, 408, 410, 413]})
    
    print (df)
        ID      tDate  value
    0   79 2019-07-12    397
    1   79 2019-07-13    404
    2   79 2019-07-18    405
    3   79 2019-07-19    406
    4   79 2019-07-20    408
    5   79 2019-08-03    413
    6   80 2019-06-21    397
    7   80 2019-06-22    404
    8   80 2019-07-18    405
    9   80 2019-07-19    406
    10  80 2019-07-26    408
    11  80 2019-08-02    410
    12  80 2019-08-03    413
    

    解决方案

    a = df.groupby('ID')['tDate'].diff().eq(pd.Timedelta(1, unit='d'))
    s = (~a).cumsum()
    df['consec_count']=np.where(a.groupby(s).transform('any'), df.groupby(s).cumcount(1).add(1),0)
    
    print (df)
        ID      tDate  value  consec_count
    0   79 2019-07-12    397             1
    1   79 2019-07-13    404             2
    2   79 2019-07-18    405             1
    3   79 2019-07-19    406             2
    4   79 2019-07-20    408             3
    5   79 2019-08-03    413             0
    6   80 2019-06-21    397             1
    7   80 2019-06-22    404             2
    8   80 2019-07-18    405             1
    9   80 2019-07-19    406             2
    10  80 2019-07-26    408             0
    11  80 2019-08-02    410             1
    12  80 2019-08-03    413             2
    

    说明

    首先通过DataFrameGroupBy.diff创建掩码以比较每组的差异:

    print (df.assign(diff= df.groupby('ID')['tDate'].diff(),
                     a = df.groupby('ID')['tDate'].diff().eq(pd.Timedelta(1, unit='d'))))
        ID      tDate  value    diff      a
    0   79 2019-07-12    397     NaT  False
    1   79 2019-07-13    404  1 days   True
    2   79 2019-07-18    405  5 days  False
    3   79 2019-07-19    406  1 days   True
    4   79 2019-07-20    408  1 days   True
    5   79 2019-08-03    413 14 days  False
    6   80 2019-06-21    397     NaT  False
    7   80 2019-06-22    404  1 days   True
    8   80 2019-07-18    405 26 days  False
    9   80 2019-07-19    406  1 days   True
    10  80 2019-07-26    408  7 days  False
    11  80 2019-08-02    410  7 days  False
    12  80 2019-08-03    413  1 days   True
    

    Series.cumsum 创建唯一组,~ 反转条件:

    print (df.assign(diff= df.groupby('ID')['tDate'].diff(),
                     a = df.groupby('ID')['tDate'].diff().eq(pd.Timedelta(1, unit='d')),
                     a_neg = ~a,
                     s = (~a).cumsum()))
    
        ID      tDate  value    diff      a  a_neg  s
    0   79 2019-07-12    397     NaT  False   True  1
    1   79 2019-07-13    404  1 days   True  False  1
    2   79 2019-07-18    405  5 days  False   True  2
    3   79 2019-07-19    406  1 days   True  False  2
    4   79 2019-07-20    408  1 days   True  False  2
    5   79 2019-08-03    413 14 days  False   True  3
    6   80 2019-06-21    397     NaT  False   True  4
    7   80 2019-06-22    404  1 days   True  False  4
    8   80 2019-07-18    405 26 days  False   True  5
    9   80 2019-07-19    406  1 days   True  False  5
    10  80 2019-07-26    408  7 days  False   True  6
    11  80 2019-08-02    410  7 days  False   True  7
    12  80 2019-08-03    413  1 days   True  False  7
    

    GroupBy.transformDataFrameGroupBy.any 的克里特掩码用于测试每个组是否至少包含一个True - 然后组的所有值都设置为Trues:

    print (df.assign(diff= df.groupby('ID')['tDate'].diff(),
                     a = df.groupby('ID')['tDate'].diff().eq(pd.Timedelta(1, unit='d')),
                     a_neg = ~a,
                     s = (~a).cumsum(),
                     mask = a.groupby(s).transform('any')))
    
        ID      tDate  value  consec_count    diff      a  a_neg  s   mask
    0   79 2019-07-12    397             1     NaT  False   True  1   True
    1   79 2019-07-13    404             2  1 days   True  False  1   True
    2   79 2019-07-18    405             1  5 days  False   True  2   True
    3   79 2019-07-19    406             2  1 days   True  False  2   True
    4   79 2019-07-20    408             3  1 days   True  False  2   True
    5   79 2019-08-03    413             0 14 days  False   True  3  False
    6   80 2019-06-21    397             1     NaT  False   True  4   True
    7   80 2019-06-22    404             2  1 days   True  False  4   True
    8   80 2019-07-18    405             1 26 days  False   True  5   True
    9   80 2019-07-19    406             2  1 days   True  False  5   True
    10  80 2019-07-26    408             0  7 days  False   True  6  False
    11  80 2019-08-02    410             1  7 days  False   True  7   True
    12  80 2019-08-03    413             2  1 days   True  False  7   True
    

    GroupBy.cumcount 为每个组创建计数器s

    print (df.assign(diff= df.groupby('ID')['tDate'].diff(),
                     a = df.groupby('ID')['tDate'].diff().eq(pd.Timedelta(1, unit='d')),
                     a_neg = ~a,
                     s = (~a).cumsum(),
                     mask = a.groupby(s).transform('any'),
                     c = df.groupby(s).cumcount(1).add(1)))
    
        ID      tDate  value  consec_count    diff      a  a_neg  s   mask  c
    0   79 2019-07-12    397             1     NaT  False   True  1   True  1
    1   79 2019-07-13    404             2  1 days   True  False  1   True  2
    2   79 2019-07-18    405             1  5 days  False   True  2   True  1
    3   79 2019-07-19    406             2  1 days   True  False  2   True  2
    4   79 2019-07-20    408             3  1 days   True  False  2   True  3
    5   79 2019-08-03    413             0 14 days  False   True  3  False  1
    6   80 2019-06-21    397             1     NaT  False   True  4   True  1
    7   80 2019-06-22    404             2  1 days   True  False  4   True  2
    8   80 2019-07-18    405             1 26 days  False   True  5   True  1
    9   80 2019-07-19    406             2  1 days   True  False  5   True  2
    10  80 2019-07-26    408             0  7 days  False   True  6  False  1
    11  80 2019-08-02    410             1  7 days  False   True  7   True  1
    12  80 2019-08-03    413             2  1 days   True  False  7   True  2
    

    最后添加 0 by numpy.where 和掩码 mask:

    print (df.assign(diff= df.groupby('ID')['tDate'].diff(),
                     a = df.groupby('ID')['tDate'].diff().eq(pd.Timedelta(1, unit='d')),
                     a_neg = ~a,
                     s = (~a).cumsum(),
                     mask = a.groupby(s).transform('any'),
                     c = df.groupby(s).cumcount(1).add(1),
                     out =  np.where(mask, df.groupby(s).cumcount(1).add(1), 0)))
    
        ID      tDate  value  consec_count    diff      a  a_neg  s   mask  c  out
    0   79 2019-07-12    397             1     NaT  False   True  1   True  1    1
    1   79 2019-07-13    404             2  1 days   True  False  1   True  2    2
    2   79 2019-07-18    405             1  5 days  False   True  2   True  1    1
    3   79 2019-07-19    406             2  1 days   True  False  2   True  2    2
    4   79 2019-07-20    408             3  1 days   True  False  2   True  3    3
    5   79 2019-08-03    413             0 14 days  False   True  3  False  1    0
    6   80 2019-06-21    397             1     NaT  False   True  4   True  1    1
    7   80 2019-06-22    404             2  1 days   True  False  4   True  2    2
    8   80 2019-07-18    405             1 26 days  False   True  5   True  1    1
    9   80 2019-07-19    406             2  1 days   True  False  5   True  2    2
    10  80 2019-07-26    408             0  7 days  False   True  6  False  1    0
    11  80 2019-08-02    410             1  7 days  False   True  7   True  1    1
    12  80 2019-08-03    413             2  1 days   True  False  7   True  2    2
    

    【讨论】:

    • 非常感谢!这样就解决了!您介意更详细地解释解决方案吗?我不关注它...
    • @sa_zy - 第一个解决方案?
    • 第二种解决方案。我不关注 groupby((~a).cumsum()) 尤其是在选择了 df[a] 之后,~a 还在那里吗?
    • @sa_zy - 添加解释以回答。
    • Andy L. 的解决方案中没有出现您的实现中的错误。我认为 for date-diff -1 中的 or 条件是多余的。
    【解决方案2】:

    您也可以尝试在IDor 的groupby 上创建掩码,并使用shift(-1) 标记所有连续行True 并分配给掩码s1。最后,在s1s1.groupby.cumsum 上使用np.where

    s = df.groupby('ID').tDate.diff().eq(pd.Timedelta(days=1))
    s1 = s | s.shift(-1, fill_value=False)
    df['consec_count'] = np.where(s1, s1.groupby(df.ID).cumsum(), 0)
    
    Out[185]:
       ID      tDate  value  consec_count
    0  79 2019-06-21    397             0
    1  79 2019-07-13    404             0
    2  79 2019-07-18    405             1
    3  79 2019-07-19    406             2
    4  79 2019-08-02    410             0
    5  79 2019-08-09    413             0
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-08
      • 2020-05-18
      • 2021-11-07
      • 2011-07-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多