计算Pandas Dataframe中两个日期之间GroupBy中的行数GroupBy

时间:2017-05-16 19:45:45

标签: python pandas dataframe melt

我有一个数据框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的数量   由ctid cid发生的,对于每个日期(newdate)   min(stdt)max(enddt)newdate位于stdt之间   enddtcid

生成的DataFrame应该是这样的(这仅适用于使用上述数据的1 ctid 1 cid)(在这种情况下,这会复制ctid 1 / {{1} 4,cid 2 / ctid 3,cid 2 / ctid 4):

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可以再次使用,或者可能有更好的选择,但我无法弄清楚如何获得'两层组'已完成,针对每个jid计算每个ctid cid的{​​{1}}的大小。喜欢你的投入......

1 个答案:

答案 0 :(得分:1)

在尝试@Scott波士顿回答后,获得了1.8米记录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分成几组并在其上运行一个函数。因此,此代码采用dfgroupsby cidapply函数cidloop

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

通过最后一行代码cid

为每个gb=df.groupby.....执行此操作

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

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