在 groupby 之后,从一列中的最小值对应另一列中的值创建一个 Series

After groupby, create a Series from the smallest values in one column corresponding to a value in another column

我有一些这样的数据:

df = pd.DataFrame({'x':[1,2,3,1,1,2,3,3,2],
                   'y':['n', 'n', 'p', 'p', 'n', 'n', 'n', 'p', 'n'], 
                   'z':[52,75,77,68,92,32,62,70,34]})

我想先按 x 分组,然后检查每个组的任何行中是否存在 p,并向原始数据框添加另一列(或分组的,然后以某种方式将其压平?)如果该组中没有 p,则它具有 None,或者对应于 p 的最小数字来自 z列。

所以这里是:

   x  y   z  t
0  1  n  52 68
3  1  p  68 68
4  1  n  92 68

   x  y   z  t
1  2  n  75 None
5  2  n  32 None
8  2  n  34 None

   x  y   z  t
2  3  p  77 70
6  3  n  62 70
7  3  p  70 70

或扁平化:

   x  y   z  t
0  1  n  52 68
3  1  p  68 68
4  1  n  92 68
1  2  n  75 None
5  2  n  32 None
8  2  n  34 None
2  3  p  77 70
6  3  n  62 70
7  3  p  70 70

所以我们首先要做

g = df.groupby('x')

但是我不确定如何进行。 我只是很难绕过它,运行 陷入各种 pandas 错误。

一个选项是仅过滤 DataFrame 中 y 为 p 的行。然后使用 groupby min to get the minimal z value per group (of remaining rows). Then join 回到 x 上的 DataFrame。 NaN 将自动添加任何缺失值(没有任何值等于 p 的组)。

df = df.join(
    df[df['y'].eq('p')].groupby('x')['z'].min().rename('t'),
    on='x'
)

   x  y   z     t
0  1  n  52  68.0
1  2  n  75   NaN
2  3  p  77  70.0
3  1  p  68  68.0
4  1  n  92  68.0
5  2  n  32   NaN
6  3  n  62  70.0
7  3  p  70  70.0
8  2  n  34   NaN

*rename is used here to change the name of the column to the desired before join回来了。


如果需要将 x 个值组合在一起,我们也可以按 xsort_values 排序:

df = df.sort_values('x', ignore_index=True).join(
    df[df['y'].eq('p')].groupby('x')['z'].min().rename('t'), 
    on='x'
)

   x  y   z     t
0  1  n  52  68.0
1  1  p  68  68.0
2  1  n  92  68.0
3  2  n  75   NaN
4  2  n  32   NaN
5  2  n  34   NaN
6  3  p  77  70.0
7  3  n  62  70.0
8  3  p  70  70.0

根据 DataFrame 的大小,select 最初使用 loc:

z 列可能更有效
df = df.sort_values('x', ignore_index=True).join(
    df.loc[df['y'].eq('p'), 'z'].groupby(df['x']).min().rename('t'),
    on='x'
)

   x  y   z     t
0  1  n  52  68.0
1  1  p  68  68.0
2  1  n  92  68.0
3  2  n  75   NaN
4  2  n  32   NaN
5  2  n  34   NaN
6  3  p  77  70.0
7  3  n  62  70.0
8  3  p  70  70.0

@HenryEcker 涵盖了所有不错的直观解决方案。这个只是为了好玩。

基本思路是过滤“y”为 'p' 的行,并在这些行中为每个“x”找到“z”的最小值。然后将其映射回“x”:

df['t'] = df['x'].map(df[df['y'].eq('p')].groupby('x')['z'].min())
df = df.sort_values(by='x')

使用 eq + where 的替代方法。基本思想是屏蔽对应于“y”列中非“p”值的“z”值;然后 groupby "x" 并变换最小 "z":

df['t'] = df['z'].where(df['y'].eq('p')).groupby(df['x']).transform('min')

输出:

   x  y   z     t
0  1  n  52  68.0
3  1  p  68  68.0
4  1  n  92  68.0
1  2  n  75   NaN
5  2  n  32   NaN
8  2  n  34   NaN
2  3  p  77  70.0
6  3  n  62  70.0
7  3  p  70  70.0