我有一个数据框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
我需要做的是:计算(
cnt
)jid
的数量 由ctid
cid
发生的,对于每个日期(newdate
)min(stdt)
和max(enddt)
,newdate
位于stdt
之间enddt
和cid
。
生成的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}}的大小。喜欢你的投入......
答案 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
分成几组并在其上运行一个函数。因此,此代码采用df
,groupsby
cid
和apply
函数cidloop
。
在cidloop
中,以下内容按行发生:
1)抓住cid
以备将来使用。
2,3)通过分配所需的列来建立核心partdf
4)从索引创建jid
5)运行pd.melt
,通过为jid
和stdt
的每个enddt
创建一行来展平数据框。
6)创建一个'change'
列,为stdt
分配+1,为enddt
分配-1。
7)使newdate
成为datetimeindex
(更容易进一步处理)
8)按ctid
和newdate
对我们所拥有的内容进行分组,对change
进行求和
9)再次按ctid
分组,将最后一个值替换为0(这只是我不需要特定的问题)
10)按组cnt
和ctid
按组创建cumsumming
11)从newdate
创建新索引
12,13)格式化列/名称
14)ctid
上的另一个群组,但是通过高低日期重新编制索引,填补了空白。
15)从新的newdate
值中分配reindex
16,17,18)填补各种值以填补空白(我需要此增强功能)
19)再次从第1行收集的顶部变量cid
中分配coid
。
通过最后一行代码cid
gb=df.groupby.....
执行此操作
感谢@Scott波士顿的尝试。当然它有效,但对我来说花了太长时间。
向@DSM致敬,感谢他的解决方案HERE,这是我解决方案的基础。