【问题标题】:one year rolling count of unique values by group in pandas熊猫中按组分组的唯一值的一年滚动计数
【发布时间】:2020-01-22 14:09:02
【问题描述】:

所以我有以下数据框:

Period      group     ID    
20130101     A        10
20130101     A        20
20130301     A        20
20140101     A        20
20140301     A        30
20140401     A        40

20130101     B        11
20130201     B        21
20130401     B        31
20140401     B        41
20140501     B        51

我需要计算去年group 有多少不同的ID。所以我想要的输出应该是这样的:

Period      group     num_ids_last_year
20130101     A            2 # ID 10 and 20 in the last year
20130301     A            2 
20140101     A            2 
20140301     A            2 # ID 30 enters, ID 10 leaves
20140401     A            3 # ID 40 enters

20130101     B            1
20130201     B            2
20130401     B            3
20140401     B            2 # ID 11 and 21 leave 
20140501     B            2 # ID 31 leaves, ID 51 enters

期间为日期时间格式。我尝试了很多事情:

df.groupby(['group','Period'])['ID'].nunique() # Get number of IDs by group in a given period.
df.groupby(['group'])['ID'].nunique() # Get total number of IDs by group.

df.set_index('Period').groupby('group')['ID'].rolling(window=1, freq='Y').nunique()

但最后一个甚至是不可能的。有没有直接的方法可以做到这一点?我在想可能是cumcount()pd.DateOffsetge(df.Period - dt.timedelta(365) 的某种组合,但我找不到答案。

谢谢。

编辑:添加了一个事实,即我可以在给定的Period 中找到多个ID

【问题讨论】:

  • 滚动窗口需要一个固定窗口。 “M”和“Y”(由于闰年)都不是固定频率。在这个答案中,我解释了如何“规范化”您的时间序列以允许一个月的滚动窗口:stackoverflow.com/questions/59569963/…
  • 为什么 20130101 B 1 设置为 1 ?不应该是 2 吗?如果不是,那么为什么该逻辑不适用于 2013 年 A 组 - df.groupby([df['Period'].dt.year,'group'])['ID'].nunique()
  • 因为在 20130101 组 B 中,只有 1 个 ID:11。在这种情况下,我正在计算从 20120101 到 20130101 有多少个 ID。我计算的是从今天开始的 1 年窗口,按组有多少个不同的 ID

标签: python pandas group-by


【解决方案1】:

查看您的数据结构,我猜您有 许多重复项,所以从删除它们开始。 drop_duplicates 往往很快

我假设 df['Period'] 列的 dtype 为 datetime64[ns]

df = df.drop_duplicates()
results = dict()
for start in df['Period'].drop_duplicates():
    end = start.date() - relativedelta(years=1)
    screen = (df.Period <= start) & (df.Period >= end)  # screen for 1 year of data
    singles = df.loc[screen, ['group', 'ID']].drop_duplicates()  # screen for same year ID by groups
    x = singles.groupby('group').count()
    results[start] = x
results = pd.concat(results, 0)


results
                  ID
           group    
2013-01-01 A       2
           B       1
2013-02-01 A       2
           B       2
2013-03-01 A       2
           B       2
2013-04-01 A       2
           B       3
2014-01-01 A       2
           B       3
2014-03-01 A       2
           B       1
2014-04-01 A       3
           B       2
2014-05-01 A       3
           B       2

这样更快吗?

附言如果df['Period'] 不是日期时间:

df['Period'] = pd.to_datetime(df['Period'],format='%Y%m%d', errors='ignore')

【讨论】:

  • 遗憾的是没有重复的 Period-ID,所以这可能需要很长时间。无论如何我都会尝试一下,以防它比我预期的要快。
  • @JuanC 你说总共有 77 个周期可以在一个循环中管理。我要排除的重复项是 ID - 组组合尽可能快,祝你好运!
  • 啊啊啊是啊!这将是 77 次 groupby 操作,可能大约需要 40 分钟,所以还不错。你知道在dask 上运行它是否会更快?
  • @JuanC 抱歉,不确定 dask 是否会是更好的方法
  • 没问题,有时间我两个都试试。谢谢,无论如何!
【解决方案2】:

这里使用groupbyrolling 的解决方案。注意:您想要的输出从YYYY0101 到下一年YYYY0101,因此您需要滚动366D 而不是365D

df['Period'] = pd.to_datetime(df.Period, format='%Y%m%d')
df = df.set_index('Period')

df_final = (df.groupby('group')['ID'].rolling(window='366D')
                                     .apply(lambda x: np.unique(x).size, raw=True)
                                     .reset_index(name='ID_count')
                                     .drop_duplicates(['group','Period'], keep='last'))

Out[218]:
   group     Period  ID_count
1      A 2013-01-01       2.0
2      A 2013-03-01       2.0
3      A 2014-01-01       2.0
4      A 2014-03-01       2.0
5      A 2014-04-01       3.0
6      B 2013-01-01       1.0
7      B 2013-02-01       2.0
8      B 2013-04-01       3.0
9      B 2014-04-01       2.0
10     B 2014-05-01       2.0

注意:在 18M+ 行上,我认为这个解决方案不会在 10 分钟内完成。我希望大约需要 30 分钟。

【讨论】:

    【解决方案3】:
    from dateutil.relativedelta import relativedelta
    df.sort_values(by=['Period'], inplace=True) # if not already sorted
    
    # create new output df
    df1 = (df.groupby(['Period','group'])['ID']
           .apply(lambda x: list(x))
           .reset_index())
    df1['num_ids_last_year'] = df1.apply(lambda x: len(set(df1.loc[(df1['Period'] >= x['Period']-relativedelta(years=1)) & (df1['Period'] <= x['Period']) & (df1['group'] == x['group'])].ID.apply(pd.Series).stack())), axis=1)
    df1.sort_values(by=['group'], inplace=True)
    df1.drop('ID', axis=1, inplace=True)
    df1 = df1.reset_index(drop=True)
    

    【讨论】:

    • 很抱歉我之前没有回答这个问题。这对我的需要来说似乎太慢了,已经运行了大约 20 分钟 df1 线,但它还没有完成,这对于我正在做的事情来说是不可能的,可悲的是!
    • 1 小时后它仍在运行,所以很遗憾它肯定是不行
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-08-13
    • 2021-12-23
    • 2018-03-30
    • 1970-01-01
    • 2019-02-13
    • 2018-09-30
    • 1970-01-01
    相关资源
    最近更新 更多