Python 按ID计算GroupBy数据帧中两个日期之间的行数

Python 按ID计算GroupBy数据帧中两个日期之间的行数,python,pandas,lambda,dataframe,Python,Pandas,Lambda,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 da

我有以下测试数据帧:

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在最小值(stdt)之间的每个日期(newdate)发生的jid数 和max(enddt),其中newdate介于stdt和 enddt

生成的数据集应该是一个数据帧,每个cid都有一个日期列范围(newdate),该日期列范围在每个cid的最小值(stdt)和最大值(enddt)之间,新日期列范围在最小值(stdt)和最大值(enddt)之间。生成的数据帧应如下所示(这仅适用于使用上述数据的1个cid):

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

我当前运行一个循环,对于每个cid(我从主df中分割cid行),在循环中确定相关的日期范围(每个cid帧的最小stdt和最大enddt,然后是每个新日期(范围mindate maxdate)它计算jid的数量,其中newdate在每个jid的stdt和enddt之间


但从资源和时间的角度来看,这是非常昂贵的。在数以百万计的jid上为数千个cid执行此操作实际上需要一整天的时间。我希望这里有一个简单的(r)解决方案。

我解决这些问题的通常方法是以事件为中心,并从改变累加器的角度进行思考。每一个新的“stdt”我们看到计数加上+1;我们看到的每个“enddt”都加上-1。(第二天加上-1,至少如果我用你的方式解释“介于”之间的话。有时我认为我们应该禁止使用这个词,因为它太模糊了。)

瞧,如果我们把你的身体变成这样

>>> 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
>>> 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
然后我们想要的只是
更改的累积和(经过适当的重新组合后)

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.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
>>> 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
s,您可能有不同的想法(这里它们将计为2);但是,即使您必须调整细节,处理事件的基本思想也应该被证明是有用的。

我提出了一个解决方案(这将通过唯一cid和日期范围的排列循环获得您的计数):


与@DSM提供的方法相比,唯一真正的改进是,这将避免为循环创建一个不愉快的对象,并且还将获得每个cid编号的所有最小stdt和最大enddt,并且没有零值。

我喜欢这种方法。我现在将使用它,并将进行检查。谢谢@DSM…这非常有效。制作了一个建议进行少量调整。需要在循环中包括新的时间,以便为每个cid创建唯一的stdt、enddt时间索引日期。如果可以使用
pd.melt
,我会+10。这是一个多么棒的功能…谢谢@DSM…lg这也是一个很好的答案。我不确定哪一个在时间/内存方面表现更好+将在我获得更多声誉积分时显示…非常棒的解决方案!谢谢。
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