取空​​值的最小值和最大值 - pandas groupby

Take min and max with null values - pandas groupby

我有一个 pandas.DataFrame,看起来像这样:

| id |       start       |        end       |
|:--:|:-----------------:|:----------------:|
|  a |  1/1/20 12:00 AM  |  1/2/20 12:00 AM |
|  b |   1/1/20 6:37 PM  |  1/2/20 7:11 PM  |
|  b |   1/4/20 1:17 AM  |                  |
|  c |  2/4/20 12:00 AM  | 7/13/20 12:00 AM |
|  d |  4/19/20 8:45 PM  | 4/23/20 12:13 AM |
|  d | 11/21/20 12:00 AM |  3/2/21 12:00 AM |

我正在尝试为每个 id 确定 min() startmax() end。我的问题是,在某些情况下,end 可以为空,在这种情况下,它应该算作最大值(即问题未关闭)。

理想情况下,结果应该是这样的:

| id |      start      |        end       |
|:--:|:---------------:|:----------------:|
|  a | 1/1/20 12:00 AM |  1/2/20 12:00 AM |
|  b |  1/1/20 6:37 PM |                  |
|  c | 2/4/20 12:00 AM | 7/13/20 12:00 AM |
|  d | 4/19/20 8:45 PM |  3/2/21 12:00 AM |

我查看了 this question 以获得灵感,但没有找到解决方案。

MRE 下面:

import pandas as pd, numpy as np

df = pd.DataFrame.from_dict({'id': {0: 'a', 1: 'b', 2: 'b', 3: 'c', 4: 'd', 5: 'd'}, 'start': {0: '1/1/20 12:00 AM', 1: '1/1/20 6:37 PM', 2: '1/4/20 1:17 AM', 3: '2/4/20 12:00 AM', 4: '4/19/20 8:45 PM', 5: '11/21/20 12:00 AM'}, 'end': {0: '1/2/20 12:00 AM', 1: '1/2/20 7:11 PM', 2: np.nan, 3: '7/13/20 12:00 AM', 4: '4/23/20 12:13 AM', 5: '3/2/21 12:00 AM'}})

df['start'] = pd.to_datetime(df['start'])
df['end'] = pd.to_datetime(df['end'])

starts = df.groupby('id')['start'].min().reset_index()
ends = df.groupby('id')['end'].max().reset_index()

_df = pd.merge(starts, ends, on='id')

然而,这会产生:

| id |      start      |       end      |
|:--:|:---------------:|:--------------:|
|  a |  1/1/2020 0:00  |  1/2/2020 0:00 |
|  b |  1/1/2020 18:37 | 1/2/2020 19:11 |
|  c |  2/4/2020 0:00  | 7/13/2020 0:00 |
|  d | 4/19/2020 20:45 |  3/2/2021 0:00 |

我怎样才能达到我想要的结果?

IIUC,DataFrame.mask 设置 NaN,其中每个组都有任何 nan 和 col

new_df = \
df.groupby('id')\
  .agg({'start':'min', 'end':'max'})\
  .mask(df[['start', 'end']].isna()
                            .groupby(df['id'])
                            .max())\
  .reset_index()

print(new_df)
  id               start        end
0  a 2020-01-01 00:00:00 2020-01-02
1  b 2020-01-01 18:37:00        NaT
2  c 2020-02-04 00:00:00 2020-07-13
3  d 2020-04-19 20:45:00 2021-03-02

详情:

print(df[['start', 'end']].isna()
                            .groupby(df['id'])
                            .max())

    start    end
id              
a   False  False
b   False   True
c   False  False
d   False  False

在多列分组的情况下:

new_df = \
df.groupby(['id', 'status'])\
  .agg({'start':'min', 'end':'max'})\
  .mask(df[['start', 'end']].isna()
                            .groupby([df['id'], df['status']])
                            .max())\
  .reset_index()

按日期排序并使用 iloc 获取最后一个值

df.sort_values(["start", "end"]).groupby("id").agg({"start": "first",
                                                    "end": lambda x: x.iloc[-1]})
#                  start        end
# id                               
# a  2020-01-01 00:00:00 2020-01-02
# b  2020-01-01 18:37:00        NaT
# c  2020-02-04 00:00:00 2020-07-13
# d  2020-04-19 20:45:00 2021-03-02