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

时间:2015-08-02 14:26:26

标签: python pandas lambda dataframe

我有以下测试DataFrame:

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

#create test range of dates
rng=pd.date_range(datetime.date(2015,1,1),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],
    'stdt':[rnglist[random.randint(0,len(rng))] for _ in testpts]}
df=pd.DataFrame(d)
df['enddt'] = df['stdt']+timedelta(days=random.randint(2,32))

其中包含如下所示的数据框,公司ID列为“cid”,唯一的id列为“jid”,开始日期为“stdt”,enddt为“enddt”。

   cid  jid       stdt      enddt
0    1  100 2015-07-06 2015-07-13
1    1  101 2015-07-15 2015-07-22
2    2  102 2015-07-12 2015-07-19
3    2  103 2015-07-07 2015-07-14
4    2  104 2015-07-14 2015-07-21
5    1  105 2015-07-11 2015-07-18
6    1  106 2015-07-12 2015-07-19
7    2  107 2015-07-01 2015-07-08
8    2  108 2015-07-10 2015-07-17
9    2  109 2015-07-09 2015-07-16
  

我需要做的是:计算cid发生的jid数,对于min(stdt)之间的每个日期(newdate)   和max(enddt),其中newdate位于stdt和。之间   结束。

结果数据集应该是每个cid具有的数据帧,特定于每个cid的min(stdt)和max(enddt)之间的列日期范围(newdate)和count(cnt) newdate介于min(stdt)和max(enddt)之间的jid数量。生成的DataFrame应该是这样的(这仅适用于使用上述数据的1个cid):

cid newdate cnt
1   2015-07-06  1
1   2015-07-07  1
1   2015-07-08  1
1   2015-07-09  1
1   2015-07-10  1
1   2015-07-11  2
1   2015-07-12  3
1   2015-07-13  3
1   2015-07-14  2
1   2015-07-15  3
1   2015-07-16  3
1   2015-07-17  3
1   2015-07-18  3
1   2015-07-19  2
1   2015-07-20  1
1   2015-07-21  1
1   2015-07-22  1

我相信应该有一种方法可以使用pandas groupby(groupby cid)和某种形式的lambda(?)来pythonically创建这个新的数据帧。

我目前为每个cid运行一个循环(我将cid行切换到主df之外),在循环中确定相关的日期范围(每个cid帧的最小stdt和最大enddt,然后是每个新的日期) (range mindate-maxdate)它计算newdate在每个jid的stdt和enddt之间的jid数。然后我将每个结果数据集附加到一个新的数据框中,如上所示。

但从资源和时间的角度来看,这是非常昂贵的。对数以千计的cid进行数以百计的jid这样做需要一整天。我希望这里有一个简单的(r)熊猫解决方案。

3 个答案:

答案 0 :(得分:6)

我对这些问题的常用方法是根据事件改变累加器进行调整和思考。我们看到的每个新“stdt”都会增加+1;每个“enddt”我们看到加-1。 (第二天加上-1,至少如果我以你的方式解释“之间”。有些日子我认为我们应该禁止使用这个词太模糊了。)

IOW,如果我们将你的框架变成类似

的框架
>>> df.head()
    cid  jid  change       date
0     1  100       1 2015-01-06
1     1  101       1 2015-01-07
21    1  100      -1 2015-01-16
22    1  101      -1 2015-01-17
17    1  117       1 2015-03-01

然后我们想要的只是change的累积总和(在适当的重组后)。例如,像

df["enddt"] += timedelta(days=1)
df = pd.melt(df, id_vars=["cid", "jid"], var_name="change", value_name="date")
df["change"] = df["change"].replace({"stdt": 1, "enddt": -1})
df = df.sort(["cid", "date"])

df = df.groupby(["cid", "date"],as_index=False)["change"].sum()
df["count"] = df.groupby("cid")["change"].cumsum()

new_time = pd.date_range(df.date.min(), df.date.max())

df_parts = []
for cid, group in df.groupby("cid"):
    full_count = group[["date", "count"]].set_index("date")
    full_count = full_count.reindex(new_time)
    full_count = full_count.ffill().fillna(0)
    full_count["cid"] = cid
    df_parts.append(full_count)

df_new = pd.concat(df_parts)

给了我类似的东西

>>> df_new.head(15)
            count  cid
2015-01-03      0    1
2015-01-04      0    1
2015-01-05      0    1
2015-01-06      1    1
2015-01-07      2    1
2015-01-08      2    1
2015-01-09      2    1
2015-01-10      2    1
2015-01-11      2    1
2015-01-12      2    1
2015-01-13      2    1
2015-01-14      2    1
2015-01-15      2    1
2015-01-16      1    1
2015-01-17      0    1

对于您的期望,可能存在一个一个一个的差异;关于如何在同一时间窗口中处理多个重叠jid,你可能会有不同的想法(这里它们会算作2);但即使你必须调整细节,使用这些事件的基本思想也应该有用。

答案 1 :(得分:1)

这是我提出的解决方案(这将循环显示独特的cid和日期范围的排列以获得您的计数):

from itertools import product
df_new_date=pd.DataFrame(list(product(df.cid.unique(),pd.date_range(df.stdt.min(), df.enddt.max()))),columns=['cid','newdate'])
df_new_date['cnt']=df_new_date.apply(lambda row:df[(df['cid']==row['cid'])&(df['stdt']<=row['newdate'])&(df['enddt']>=row['newdate'])]['jid'].count(),axis=1)

>>> df_new_date.head(20) 
    cid    newdate  cnt
0     1 2015-07-01    0
1     1 2015-07-02    0
2     1 2015-07-03    0
3     1 2015-07-04    0
4     1 2015-07-05    0
5     1 2015-07-06    1
6     1 2015-07-07    1
7     1 2015-07-08    1
8     1 2015-07-09    1
9     1 2015-07-10    1
10    1 2015-07-11    2
11    1 2015-07-12    3
12    1 2015-07-13    3
13    1 2015-07-14    2
14    1 2015-07-15    3
15    1 2015-07-16    3
16    1 2015-07-17    3
17    1 2015-07-18    3
18    1 2015-07-19    2
19    1 2015-07-20    1

如果您不想要它们,则可以删除零。但是,我认为这比原来的解决方案要好得多。

我建议您对@DSM解决方案提供的循环使用以下改进:

df_parts=[]
for cid in df.cid.unique():
    full_count=df[(df.cid==cid)][['cid','date','count']].set_index("date").asfreq("D", method='ffill')[['cid','count']].reset_index()
    df_parts.append(full_count[full_count['count']!=0])

df_new = pd.concat(df_parts)

>>> df_new
         date  cid  count
0  2015-07-06    1      1
1  2015-07-07    1      1
2  2015-07-08    1      1
3  2015-07-09    1      1
4  2015-07-10    1      1
5  2015-07-11    1      2
6  2015-07-12    1      3
7  2015-07-13    1      3
8  2015-07-14    1      2
9  2015-07-15    1      3
10 2015-07-16    1      3
11 2015-07-17    1      3
12 2015-07-18    1      3
13 2015-07-19    1      2
14 2015-07-20    1      1
15 2015-07-21    1      1
16 2015-07-22    1      1
0  2015-07-01    2      1
1  2015-07-02    2      1
2  2015-07-03    2      1
3  2015-07-04    2      1
4  2015-07-05    2      1
5  2015-07-06    2      1
6  2015-07-07    2      2
7  2015-07-08    2      2
8  2015-07-09    2      2
9  2015-07-10    2      3
10 2015-07-11    2      3
11 2015-07-12    2      4
12 2015-07-13    2      4
13 2015-07-14    2      5
14 2015-07-15    2      4
15 2015-07-16    2      4
16 2015-07-17    2      3
17 2015-07-18    2      2
18 2015-07-19    2      2
19 2015-07-20    2      1
20 2015-07-21    2      1

只有对@DSM提供的真正改进是,这将避免需要为循环创建一个groubby对象,这也将获得每个cid号的所有min stdt和max enddt,没有零值。

答案 2 :(得分:0)

0

我有一个df数据框,其中包含每个事件示例的开始日期和结束日期:

start     end
08:08:20  08:09:20
08:08:11  08:13:99
08:09:15  08:10:50
08:11:10  08:12:00
08:11:10  08:13:00

我希望每分钟具有同时发生的事件数:我会在最小开始和最大结束之间每分钟生成一个数据帧df1天线,而我要做的是:df.date_fin> df.Time和df.date_deb

我的代码是:

df["nb_events"]=0

for i in range (0,df1.shape[0]):
    for j in range (0,df.shape[0]):
        if  df.end[j]>df1.Time[i]:  
            if df.start[j]<df1.Time[i]:
                df1["nb_events"][i]+=1

所需结果df1:

Time              nb_event
.
.
.
08:08:00            2
08:09:00            2
08:10:00            1
08:11:00            2
08:12:00            3
08:13:00            1
.
.
.

我的代码可以正常工作,并且返回期望的结果,但我要处理的数据量很大,并且运行时间很长,您能提供另一种方法吗?谢谢