在按列A分组并按columnB聚合时获取columnC的相应值

时间:2017-02-22 18:33:48

标签: python pandas dataframe grouping

this answer为基础,并给予

>>> df
  columnA  columnB  columnC
0    cat1        3      400
1    cat1        2       20
2    cat1        5     3029
3    cat2        1      492
4    cat2        4       30
5    cat3        2      203
6    cat3        6      402
7    cat3        4      391

>>> df.groupby(['columnA']).agg({'columnA':'size','columnB':'min'}).rename(columns={'columnA':'size'})

         size  min
columnA           
cat1        3    2
cat2        2    1
cat3        3    2

我想获得一个DataFrame,它还包含columnC的值,对应于(在同一行)显示的columnB最小值,即:

         size  min  columnC
columnA           
cat1        3    2       20
cat2        2    1      492
cat3        3    2      203

当然,这只适用于那些从组中选择一个值而不是“聚合”(如总和或平均值)的聚合函数(如min或max)。

有任何线索吗?

提前致谢。

2 个答案:

答案 0 :(得分:3)

由于您要查找的结果基本上是['columnA', 'columnB']上的联接,因此您可以使用

获取所需的DataFrame
result = pd.merge(result, df, on=['columnA', 'columnB'], how='left')

如果我们使用正确的列名设置result

import pandas as pd

df = pd.DataFrame(
    {'columnA': ['cat1', 'cat1', 'cat1', 'cat2', 'cat2', 'cat3', 'cat3', 'cat3'],
     'columnB': [3, 2, 5, 1, 4, 2, 6, 4],
     'columnC': [400, 20, 3029, 492, 30, 203, 402, 391]})

result = df.groupby('columnA').agg({'columnA':'size', 'columnB':'min'})
result = result.rename(columns={'columnA':'size'})
result = result.reset_index()
result = pd.merge(result, df, on=['columnA', 'columnB'], how='left')
result = result.set_index('columnA')
result = result.rename(columns={'columnB':'min'})
print(result)

产量

         min  size  columnC
columnA                    
cat1       2     3       20
cat2       1     2      492
cat3       2     3      203

您可能希望使用pd.merge代替groupby/apply的原因是因为groupby/apply为每个组调用了一个函数。如果有很多组,这可能会很慢。

例如,如果您有一个包含1000个组的10000行DataFrame,

import numpy as np
import pandas as pd

N = 10000
df = pd.DataFrame(
    {'columnA': np.random.choice(['cat{}'.format(i) for i in range(N//10)], 
                                 size=N),
     'columnB': np.random.randint(10, size=N),
     'columnC': np.random.randint(100, size=N)})

然后using_merge(下方)比using_apply快〜250倍:

def using_merge(df):
    result = df.groupby('columnA').agg({'columnA':'size', 'columnB':'min'})
    result = result.rename(columns={'columnA':'size'})
    result = result.reset_index()
    result = pd.merge(result, df, on=['columnA', 'columnB'], how='left')
    result = result.set_index('columnA')
    result = result.rename(columns={'columnB':'min'})
    return result

def using_apply(df):
    return (df.groupby("columnA")
            .apply(lambda g: (g[g.columnB == g.columnB.min()]
                   .assign(size = g.columnA.size)
                   .rename(columns={'columnB': 'min'})
                   .drop('columnA', 1)))
            .reset_index(level=1, drop=True))
In [80]: %timeit using_merge(df)
100 loops, best of 3: 7.99 ms per loop

In [81]: %timeit using_apply(df)
1 loop, best of 3: 2.06 s per loop

In [82]: 2060/7.99
Out[82]: 257.8222778473091

答案 1 :(得分:2)

您可以使用idxmin来提取这些行的行索引:

In [11]: g = df.groupby(['columnA'])

In [12]: res = g.agg({'columnA': 'size', 'columnB': 'min'})

In [13]: g['columnB'].idxmin()
Out[13]:
columnA
cat1    1
cat2    3
cat3    5
Name: columnB, dtype: int64

In [14]: df["columnC"].iloc[g['columnB'].idxmin()]
Out[14]:
1     20
3    492
5    203
Name: columnC, dtype: int64

您可以将其作为列添加到res

In [15]: res["columnC"] = df["columnC"].iloc[g['columnB'].idxmin()].values

In [16]: res
Out[16]:
         columnA  columnB  columnC
columnA
cat1           3        2       20
cat2           2        1      492
cat3           3        2      203