簡體   English   中英

在 Pandas GroupBy 數據框中按 ID 計算兩個日期之間的行數

[英]Count Number of Rows Between Two Dates BY ID in a Pandas GroupBy 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”。

   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

我需要做的是以下內容:對於 min(stdt) 和 max(enddt) 之間的每個 date(newdate),計算 cid 發生的 jid 的數量,其中 newdate 在 stdt 和 enddt 之間。

生成的數據集應該是一個數據框,其中包含每個 cid、位於每個 cid 特定的 min(stdt) 和 max(enddt) 之間的日期列范圍 (newdate),以及數字的計數 (cnt) jid 的新日期介於 min(stdt) 和 max(enddt) 之間。 生成的 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(?) 以 Python 方式創建這個新數據框。

我目前為每個 cid 運行一個循環(我從主 df 中切出 cid 行),在循環中確定相關的日期范圍(每個 cid 幀的最小 stdt 和最大 enddt,然后為每個新日期(范圍思想) -maxdate) 它計算新日期在每個 jid 的 stdt 和 enddt 之間的 jid 的數量。然后我將每個結果數據集附加到一個新的數據幀中,如下所示。

但從資源和時間的角度來看,這是非常昂貴的。 在數百萬個 jid 上以數千個 cid 執行此操作實際上需要一整天的時間。 我希望這里有一個簡單的(r)pandas 解決方案。

對於這些問題,我通常的方法是根據改變累加器的事件進行調整和思考。 我們看到的每個新“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); 但是即使您必須調整細節,處理事件的基本思想也應該證明是有用的。

這是我想出的一個解決方案(這將遍歷唯一 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,沒有零值。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM