【问题标题】:Count Number of Rows GroupBy within a GroupBy Between Two Dates in Pandas Dataframe在 Pandas Dataframe 中的两个日期之间计算 GroupBy 中的行数 GroupBy
【发布时间】:2017-05-16 19:45:45
【问题描述】:

我有一个数据框df,可以使用以下代码创建:

import random
from datetime import timedelta
import pandas as pd
import datetime

#create test range of dates
rng=pd.date_range(datetime.date(2015,7,15),datetime.date(2015,7,31))
rnglist=rng.tolist()
testpts = range(100,121)
#create test dataframe
d={'jid':[i for i in range(100,121)], 
   'cid':[random.randint(1,2) for _ in testpts],
   'ctid':[random.randint(3,4) for _ in testpts],       
    'stdt':[rnglist[random.randint(0,len(rng))] for _ in testpts]}
df=pd.DataFrame(d)[['jid','cid','ctid','stdt']]
df['enddt'] = df['stdt']+timedelta(days=random.randint(2,16))

df 看起来像这样:

      jid  cid  ctid       stdt      enddt
0   100    1     4 2015-07-28 2015-08-11
1   101    2     3 2015-07-31 2015-08-14
2   102    2     3 2015-07-31 2015-08-14
3   103    1     3 2015-07-24 2015-08-07
4   104    2     4 2015-07-27 2015-08-10
5   105    1     4 2015-07-27 2015-08-10
6   106    2     4 2015-07-24 2015-08-07
7   107    2     3 2015-07-22 2015-08-05
8   108    2     3 2015-07-28 2015-08-11
9   109    1     4 2015-07-20 2015-08-03
10  110    2     3 2015-07-29 2015-08-12
11  111    1     3 2015-07-29 2015-08-12
12  112    1     3 2015-07-27 2015-08-10
13  113    1     3 2015-07-21 2015-08-04
14  114    1     4 2015-07-28 2015-08-11
15  115    2     3 2015-07-28 2015-08-11
16  116    1     3 2015-07-26 2015-08-09
17  117    1     3 2015-07-25 2015-08-08
18  118    2     3 2015-07-26 2015-08-09
19  119    2     3 2015-07-19 2015-08-02
20  120    2     3 2015-07-22 2015-08-05

我需要做的是:计数(cntjid 的数量 在ctidcid 之间发生的每个日期(newdatemin(stdt)max(enddt),其中 newdatestdtenddt

生成的 DataFrame 应该看起来像(这仅适用于 1 cid 和 1 ctid 使用上述数据)(在这种情况下,这将复制 cid 1/ctid 4、cid 2/ ctid3、cid2/ctid4):

cid ctid    newdate cnt
1   3   7/21/2015   1
1   3   7/22/2015   1
1   3   7/23/2015   1
1   3   7/24/2015   2
1   3   7/25/2015   3
1   3   7/26/2015   4
1   3   7/27/2015   5
1   3   7/28/2015   5
1   3   7/29/2015   6
1   3   7/30/2015   6
1   3   7/31/2015   6
1   3   8/1/2015    6
1   3   8/2/2015    6
1   3   8/3/2015    6
1   3   8/4/2015    6
1   3   8/5/2015    5
1   3   8/6/2015    5
1   3   8/7/2015    5
1   3   8/8/2015    4
1   3   8/9/2015    3
1   3   8/10/2015   2
1   3   8/11/2015   1
1   3   8/12/2015   1

上一个问题(也是我的)Count # of Rows Between Dates 非常相似,并使用pd.melt 回答。我很确定 melt 可以再次使用,或者可能有更好的选择,但我不知道如何完成“两层 groupby”,它计算每个 jid 的大小 ctid ,对于每个cid,对于每个newdate。喜欢你的意见...

【问题讨论】:

    标签: python pandas dataframe melt


    【解决方案1】:

    在尝试@Scott Boston 的答案后,对于 1.8m 记录 df,第一行

    df_out = pd.concat([pd.DataFrame(index=pd.date_range(df.iloc[i].stdt,df.iloc[i].enddt)).assign(**df.iloc[i,0:3]) for i in pd.np.arange(df.shape[0])]).reset_index()
    

    1 小时后仍在运行,并慢慢消耗记忆。所以我想我会尝试以下方法:

    def reindex_by_date(df):
        dates = pd.date_range(df.index.min(), df.index.max())
        return df.reindex(dates)
    def replace_last_0(group):
        group.loc[max(group.index),'change']=0
        return group
    
    def ctidloop(partdf): 
            coid=partdf.cid.max()
            cols=['cid', 'stdt', 'enddt']
            partdf=partdf[cols]
            partdf['jid']=partdf.index
            partdf = pd.melt(partdf, id_vars=['ctid', 'jid'],var_name='change', value_name='newdate')
            partdf['change'] = partdf['change'].replace({'stdt': 1, 'enddt': -1})
            partdf.newdate=pd.DatetimeIndex(partdf['newdate'])
            partdf=partdf.groupby(['ctid', 'newdate'],as_index=False)['change'].sum()
            partdf=partdf.groupby('ctid').apply(replace_last_0).reset_index(drop=True)
            partdf['cnt'] = partdf.groupby('ctid')['change'].cumsum()
            partdf.index=partdf['newdate']
            cols=['ctid', 'change', 'cnt', 'newdate']
            partdf=partdf[cols]
            partdf=partdf.groupby('ctid').apply(reindex_by_date).reset_index(0, drop=True)
            partdf['newdate']=partdf.index
            partdf['ctid']=partdf['ctid'].fillna(method='ffill')
            partdf.cnt=partdf.cnt.fillna(method='ffill')
            partdf.change=partdf.change.fillna(0)
            partdf['cid']=coid
            return partdf
    gb=df.groupby('cid').apply(ctidloop)
    

    这段代码返回了正确的结果:

    %timeit gb=df.groupby('cid').apply(ctidloop)
    1 loop, best of 3: 9.74 s per loop 
    

    说明: 基本上,melt 非常快。所以我想把第一个groupby 分成几组并在上面运行一个函数。所以这段代码接受df,然后是groupsbycidapply函数cidloop

    cidloop 中,逐行发生以下情况: 1) 获取cid 以备将来使用。 2,3)通过分配所需的列来建立核心partdf进行处理 4) 从索引创建jid 5) 运行pd.melt,它通过为stdtenddt 的每个jid 创建一行来展平数据框。 6) 创建一个'change' 列,将+1 分配给stdt,将-1 分配给enddt。 7) 使newdate 成为datetimeindex (更易于进一步处理) 8) 通过ctidnewdate 对我们拥有的内容进行分组,将change 相加 9) 再次按ctid 分组,用 0 替换最后一个值(这只是我需要的,不是针对问题的) 10)通过ctidcumsumming分组创建cnt变化 11)从newdate创建新索引 12,13)​​ 格式化列/名称 14)ctid 上的另一个 groupby,但通过 hi 和 low 日期重新索引,填补了空白。 15) 从新的reindex 值分配newdate 16,17,18)填充各种值以填补空白(我需要此增强功能) 19) 从第 1 行收集的顶部变量 coid 再次分配 cid

    对每个 cid 执行此操作,直到代码的最后一行 gb=df.groupby.....

    感谢@Scott Boston 的尝试。当然它有效,但对我来说花了太长时间。

    感谢@DSM 他的解决方案HERE,这是我解决方案的基础。

    【讨论】:

    • 我担心大型数据集可能会出现这种情况。您和帝斯曼的解决方案是最好的。
    猜你喜欢
    • 2015-10-24
    • 1970-01-01
    • 2021-07-17
    • 1970-01-01
    • 1970-01-01
    • 2023-02-02
    • 2015-08-23
    • 2013-07-14
    相关资源
    最近更新 更多