【问题标题】:Chaining grouping, filtration and aggregation链接分组、过滤和聚合
【发布时间】:2016-04-03 19:11:43
【问题描述】:

DataFrameGroupby.filter 方法过滤组,并返回包含通过过滤器的行的DataFrame

但是我可以做些什么来获得一个新的DataFrameGroupBy 对象而不是过滤后的DataFrame

例如,假设我有一个 DataFrame df 有两列 AB。我想为列A 的每个值获取列B 的平均值,只要该组中至少有5 行:

# pandas 0.18.0
# doesn't work because `filter` returns a DF not a GroupBy object
df.groupby('A').filter(lambda x: len(x)>=5).mean()
# works but slower and awkward to write because needs to groupby('A') twice
df.groupby('A').filter(lambda x: len(x)>=5).reset_index().groupby('A').mean()
# works but more verbose than chaining
groups = df.groupby('A')
groups.mean()[groups.size() >= 5]

【问题讨论】:

    标签: python python-3.x pandas dataframe grouping


    【解决方案1】:

    你可以这样做:

    In [310]: df
    Out[310]:
        a  b
    0   1  4
    1   7  3
    2   6  9
    3   4  4
    4   0  2
    5   8  4
    6   7  7
    7   0  5
    8   8  5
    9   8  7
    10  6  1
    11  3  8
    12  7  4
    13  8  0
    14  5  3
    15  5  3
    16  8  1
    17  7  2
    18  9  9
    19  3  2
    20  9  1
    21  1  2
    22  0  3
    23  8  9
    24  7  7
    25  8  1
    26  5  8
    27  9  6
    28  2  8
    29  9  0
    
    In [314]: r = df.groupby('a').apply(lambda x: x.b.mean() if len(x)>=5 else -1)
    
    In [315]: r
    Out[315]:
    a
    0   -1.000000
    1   -1.000000
    2   -1.000000
    3   -1.000000
    4   -1.000000
    5   -1.000000
    6   -1.000000
    7    4.600000
    8    3.857143
    9   -1.000000
    dtype: float64
    
    In [316]: r[r>0]
    Out[316]:
    a
    7    4.600000
    8    3.857143
    dtype: float64
    

    单线,返回数据帧而不是系列:

    df.groupby('a') \
      .apply(lambda x: x.b.mean() if len(x)>=5 else -1) \
      .to_frame() \
      .rename(columns={0:'mean'}) \
      .query('mean > 0')
    

    与具有 100.000 行的 DF 的 Timeit 比较:

    def maxu():
        r = df.groupby('a').apply(lambda x: x.b.mean() if len(x)>=5 else -1)
        return r[r>0]
    
    def maxu2():
        return df.groupby('a') \
                 .apply(lambda x: x.b.mean() if len(x)>=5 else -1) \
                 .to_frame() \
                 .rename(columns={0:'mean'}) \
                 .query('mean > 0')
    
    def alexander():
        return df.groupby('a', as_index=False).filter(lambda group: group.a.count() >= 5).groupby('a').mean()
    
    def alexander2():
        vc = df.a.value_counts()
        return df.loc[df.a.isin(vc[vc >= 5].index)].groupby('a').mean()
    

    结果:

    In [419]: %timeit maxu()
    1 loop, best of 3: 1.12 s per loop
    
    In [420]: %timeit maxu2()
    1 loop, best of 3: 1.12 s per loop
    
    In [421]: %timeit alexander()
    1 loop, best of 3: 34.9 s per loop
    
    In [422]: %timeit alexander2()
    10 loops, best of 3: 66.6 ms per loop
    

    检查:

    In [423]: alexander2().sum()
    Out[423]:
    b   19220943.162
    dtype: float64
    
    In [424]: maxu2().sum()
    Out[424]:
    mean   19220943.162
    dtype: float64
    

    结论:

    明显的赢家是alexander2()函数

    @Alexander,恭喜!

    【讨论】:

    • @Alexander,谢谢!我也很喜欢你的回答,所以我也投了赞成票。让我timeit你的新解决方案...
    【解决方案2】:

    这是一些可复制的数据:

    np.random.seed(0)
    
    df = pd.DataFrame(np.random.randint(0, 10, (10, 2)), columns=list('AB'))
    
    >>> df
       A  B
    0  5  0
    1  3  3
    2  7  9
    3  3  5
    4  2  4
    5  7  6
    6  8  8
    7  1  6
    8  7  7
    9  8  1
    

    一个示例过滤器应用程序,证明它适用于数据。

    gb = df.groupby('A')
    >>> gb.filter(lambda group: group.A.count() >= 3)
       A  B
    2  7  9
    5  7  6
    8  7  7
    

    以下是您的一些选择:

    1) 也可以先根据值计数过滤,再分组。

    vc = df.A.value_counts()
    
    >>> df.loc[df.A.isin(vc[vc >= 2].index)].groupby('A').mean()
              B
    A          
    3  4.000000
    7  7.333333
    8  4.500000
    

    2)在过滤器之前和之后执行两次groupby:

    >>> (df.groupby('A', as_index=False)
           .filter(lambda group: group.A.count() >= 2)
           .groupby('A')
           .mean())
              B
    A          
    3  4.000000
    7  7.333333
    8  4.500000
    

    3) 鉴于您的第一个 groupby 返回组,您还可以过滤这些组:

    d = {k: v 
         for k, v in df.groupby('A').groups.items() 
         if len(v) >= 2}  # gb.groups.iteritems() for Python 2
    
    >>> d
    {3: [1, 3], 7: [2, 5, 8], 8: [6, 9]}
    

    这有点小技巧,但应该相对有效,因为您不需要重新组合。

    >>> pd.DataFrame({col: [df.ix[d[col], 'B'].mean()] for col in d}).T.rename(columns={0: 'B'})
              B
    3  4.000000
    7  7.333333
    8  4.500000
    

    100k 行的时序

    np.random.seed(0)
    df = pd.DataFrame(np.random.randint(0, 10, (100000, 2)), columns=list('AB'))
    
    %timeit df.groupby('A', as_index=False).filter(lambda group: group['A'].count() >= 5).groupby('A').mean()
    100 loops, best of 3: 18 ms per loop
    
    %%timeit
    vc = df.A.value_counts()
    df.loc[df.A.isin(vc[vc >= 2].index)].groupby('A').mean()
    100 loops, best of 3: 15.7 ms per loop
    

    【讨论】:

    • 但我想在过滤后得到每个组的平均值。您将获得整个剩余数据集的平均值。
    • 我猜它会产生错误的结果...尝试另一个数据集,其中至少有两个不同的 A 值组
    • @MaxU 我有一个对gb 的旧引用,它应该只是df.groupby('A')。另外,请确保您使用正确的 Python2/3 版本。反正很慢……
    猜你喜欢
    • 2017-07-03
    • 1970-01-01
    • 1970-01-01
    • 2020-12-24
    • 2018-04-18
    • 2011-09-09
    • 2019-03-13
    • 1970-01-01
    • 2017-08-08
    相关资源
    最近更新 更多