首页 > 解决方案 > Groupby diff() 日期,groupby 大小并检查 pandas 中其他列的序列

问题描述

我有如下所示的数据框

    ID  Status   Date       Cost
0   1   F      22-Jun-17    500
1   1   M      22-Jul-17    100
2   2   M      29-Jun-17    200
3   3   M      20-Mar-17    300
4   4   M      10-Aug-17    800
5   2   F      29-Sep-17    600
6   2   F      29-Jan-18    500
7   1   F      22-Jun-18    600
8   3   F      20-Jun-18    700
9   1   M      22-Aug-18    150
10  1   F      22-Mar-19    750
11  3   M      20-Oct-18    250
12  4   F      10-Jun-18    100
13  4   F      10-Oct-18    500
14  4   M      10-Jan-19    200
15  4   F      10-Jun-19    600
16  2   M      29-Mar-18    100
17  2   M      29-Apr-18    100
18  2   F      29-Dec-18    500

F=失败

M=维护

然后使用以下代码根据 ID、Date 对数据进行排序。

df['Date'] = pd.to_datetime(df['Date'])
df = df.sort_values(['ID', 'Date', 'Status'])

然后我想过滤 ID 的故障不止一次,它们之间至少有一次维护。

预期的 DF 如下所示。

    ID  Status  Date    Cost
0   1   F   2017-06-22  500
1   1   M   2017-07-22  100
2   1   F   2018-06-22  600
3   1   M   2018-08-22  150
4   1   F   2019-03-22  750
5   2   F   2018-01-29  500
6   2   M   2018-03-29  100
7   2   M   2018-04-29  100
8   2   F   2018-12-29  500
10  4   F   2018-10-10  500
11  4   M   2019-01-10  200
12  4   F   2019-06-10  600

使用的逻辑高于 DF,如下所示。

让上面的 DF 为 sl9。

  1. 选择具有超过 1 个 F 并且它们之间至少有一个 M 的 ID。

  2. 如果 ID wise 第一个状态为 M,则删除该行。

  3. 如果 ID 明智的最后状态为 M,则删除该行。

  4. 如果 ID 有两个连续的 FF,则忽略第一个 F 行。

然后我运行下面的代码来计算持续时间。

sl9['Date'] = pd.to_datetime(sl9['Date'])
sl9['D'] = sl9.groupby('ID')['Date'].diff().dt.days

       ID   Status  Date    Cost    D
    0   1   F   2017-06-22  500    nan
    1   1   M   2017-07-22  100    30.00
    2   1   F   2018-06-22  600    335.00
    3   1   M   2018-08-22  150    61.00
    4   1   F   2019-03-22  750    212.00
    5   2   F   2018-01-29  500    nan
    6   2   M   2018-03-29  100    59.00
    7   2   M   2018-04-29  100    31.00
    8   2   F   2018-12-29  500    244.00
    10  4   F   2018-10-10  500    nan
    11  4   M   2019-01-10  200    92.00
    12  4   F   2019-06-10  600    151.00

从上面的 DF,我想创建一个 DF,如下所示。

ID      Total_Duration   No_of_F  No_of_M
1             638           3        2
2             334           2        2
4             243           2        2

尝试了以下代码。

df1 = sl9.groupby('ID', sort=False)["D"].sum().reset_index(name ='Total_Duration')

输出如下所示

ID  Total_Duration
0   1   638.00
1   2   334.00
2   4   243.00

标签: pandaspandas-groupby

解决方案


想法是为每个掩码创建新列以便于调试,因为复杂的解决方案:

df['Date'] = pd.to_datetime(df['Date'])
df = df.sort_values(['ID', 'Date', 'Status'])

#removed M groups if first or last groups per ID
m1 = df['Status'].eq('M')
df['g'] = df['Status'].ne(df.groupby('ID')['Status'].shift()).cumsum()

df['f'] = df.groupby('ID')['g'].transform('first').eq(df['g']) & m1
df['l'] = df.groupby('ID')['g'].transform('last').eq(df['g']) & m1

df1 = df[~(df['f'] |  df['l'])].copy()

#count number of M and F and compare by ge for >=
df1['noM'] = df1['Status'].eq('M').groupby(df1['ID']).transform('size').ge(1)
df1['noF'] = df1['Status'].eq('F').groupby(df1['ID']).transform('size').ge(2)
#get non FF values for removing duplicated FF
df1['dupF']  = ~df.groupby('ID')['Status'].shift(-1).eq(df['Status']) | df1['Status'].eq('M')


df1 = df1[df1['noM'] &  df1['noF'] & df1['dupF']]
df1 = df1.drop(['g','f','l','noM','noF','dupF'], axis=1)

print (df1)
    ID Status       Date  Cost
0    1      F 2017-06-22   500
1    1      M 2017-07-22   100
7    1      F 2018-06-22   600
9    1      M 2018-08-22   150
10   1      F 2019-03-22   750
6    2      F 2018-01-29   500
16   2      M 2018-03-29   100
17   2      M 2018-04-29   100
18   2      F 2018-12-29   500
13   4      F 2018-10-10   500
14   4      M 2019-01-10   200
15   4      F 2019-06-10   600

接着:

#difference of days
df1['D'] = df1.groupby('ID')['Date'].diff().dt.days

#aggregate sum
df2 = df1.groupby('ID')['D'].sum().astype(int).to_frame('Total_Duration')
#count values by crosstab
df3 = pd.crosstab(df1['ID'], df1['Status']).add_prefix('No_of_')
#join together
df4 = df2.join(df3).reset_index()
print (df4)
   ID  Total_Duration  No_of_F  No_of_M
0   1             638        3        2
1   2             334        2        2
2   4             243        2        1

推荐阅读