【问题标题】:Pandas aggregating average while excluding current rowPandas 汇总平均值,同时排除当前行
【发布时间】:2015-05-16 10:32:53
【问题描述】:

如何聚合得到ba的平均值,同时排除当前行(目标结果在c)?

a b   c

1 1   0.5   # (avg of 0 & 1, excluding 1)
1 1   0.5   # (avg of 0 & 1, excluding 1)
1 0   1     # (avg of 1 & 1, excluding 0)

2 1   0.5   # (avg of 0 & 1, excluding 1)
2 0   1     # (avg of 1 & 1, excluding 0)
2 1   0.5   # (avg of 0 & 1, excluding 1)

3 1   0.5   # (avg of 0 & 1, excluding 1)
3 0   1     # (avg of 1 & 1, excluding 0)
3 1   0.5   # (avg of 0 & 1, excluding 1)

数据转储:

import pandas as pd
data = pd.DataFrame([[1, 1, 0.5], [1, 1, 0.5], [1, 0, 1], [2, 1, 0.5], [2, 0, 1], 
                     [2, 1, 0.5], [3, 1, 0.5], [3, 0, 1], [3, 1, 0.5]],
                     columns=['a', 'b', 'c'])

【问题讨论】:

    标签: python pandas aggregate


    【解决方案1】:

    假设一个组有值x_1, ..., x_n

    整个组的平均值为

    m = (x_1 + ... + x_n)/n
    

    没有x_i 的组的总和是

    (m*n - x_i)
    

    没有x_i 的组的平均值是

    (m*n - x_i)/(n-1)
    

    因此,您可以使用

    计算所需的值列
    import pandas as pd
    df = pd.DataFrame([[1, 1, 0.5], [1, 1, 0.5], [1, 0, 1], [2, 1, 0.5], [2, 0, 1], 
                         [2, 1, 0.5], [3, 1, 0.5], [3, 0, 1], [3, 1, 0.5]],
                         columns=['a', 'b', 'c'])
    
    grouped = df.groupby(['a'])
    n = grouped['b'].transform('count')
    mean = grouped['b'].transform('mean')
    df['result'] = (mean*n - df['b'])/(n-1)
    

    产生

    In [32]: df
    Out[32]: 
       a  b    c  result
    0  1  1  0.5     0.5
    1  1  1  0.5     0.5
    2  1  0  1.0     1.0
    3  2  1  0.5     0.5
    4  2  0  1.0     1.0
    5  2  1  0.5     0.5
    6  3  1  0.5     0.5
    7  3  0  1.0     1.0
    8  3  1  0.5     0.5
    
    In [33]: assert df['result'].equals(df['c'])
    

    根据下面的 cmets,在 OP 的实际用例中,DataFrame 的 a 列 包含字符串:

    def make_random_str_array(letters, strlen, size):
        return (np.random.choice(list(letters), size*strlen)
                .view('|S{}'.format(strlen)))
    
    N = 3*10**6
    df = pd.DataFrame({'a':make_random_str_array(letters='ABCD', strlen=10, size=N),
                       'b':np.random.randint(10, size=N)})
    

    所以在 300 万个中,df['a'] 中有大约 100 万个唯一值 总计:

    In [87]: uniq, key = np.unique(df['a'], return_inverse=True)
    In [88]: len(uniq)
    Out[88]: 988337
    
    In [89]: len(df)
    Out[89]: 3000000
    

    在这种情况下,上面的计算需要(在我的机器上)大约 11 秒

    In [86]: %%timeit
       ....: grouped = df.groupby(['a'])
    n = grouped['b'].transform('count')
    mean = grouped['b'].transform('mean')
    df['result'] = (mean*n - df['b'])/(n-1)
       ....:    ....:    ....:    ....: 
    1 loops, best of 3: 10.5 s per loop
    

    Pandas converts all string-valued columns to object dtype。但我们可以转换 DataFrame 列到具有固定宽度 dtype 的 NumPy 数组,以及组 根据这些价值观。

    这是一个基准测试表明,如果我们将具有 object dtype 的 Series 转换为具有固定宽度 string dtype 的 NumPy 数组,则计算需要不到 2 秒

    In [97]: %%timeit
       ....: grouped = df.groupby(df['a'].values.astype('|S4'))
    n = grouped['b'].transform('count')
    mean = grouped['b'].transform('mean')
    df['result'] = (mean*n - df['b'])/(n-1)
       ....:    ....:    ....:    ....: 
    1 loops, best of 3: 1.39 s per loop
    

    请注意,您需要知道 df['a'] 中字符串的最大长度才能选择适当的固定宽度 dtype。在上面的示例中,所有字符串的长度均为 4,因此 |S4 有效。如果您对某个整数使用|Sn n 并且n 小于最长的字符串,那么这些字符串将被静默截断而没有错误警告。这可能会导致不应组合在一起的值分组。因此,您有责任选择正确的固定宽度 dtype。

    你可以使用

    dtype = '|S{}'.format(df['a'].str.len().max())
    grouped = df.groupby(df['a'].values.astype(dtype))
    

    确保转换使用正确的数据类型。

    【讨论】:

    • 另外,如果只有 1 个案例会怎样?我想将其作为所有其他未失败案例的平均值。
    • 所以之后替换。
    • 虽然速度非常慢(400 万行)。我在想也许更好的方法是只聚合包括行,然后减去,按计数加权(你的第一条评论有助于这个想法)?
    • 我没有看到您的真实用例有一些不同之处。我已经用df = pd.concat([df]*1000000) 运行了上面的代码,所以len(df) 是900 万,%timeit df.groupby(['a'])['b'].transform(ave_others) 在 1.18 秒内完成。
    • 可能与唯一组的数量有关? 300 万个独特的群组中有 100 万个。
    【解决方案2】:

    您可以通过逐组迭代手动计算统计信息:

    # Set up input
    import pandas as pd
    df = pd.DataFrame([
            [1, 1, 0.5], [1, 1, 0.5], [1, 0, 1], 
            [2, 1, 0.5], [2, 0, 1], [2, 1, 0.5], 
            [3, 1, 0.5], [3, 0, 1], [3, 1, 0.5]
        ], columns=['a', 'b', 'c'])
    df
       a  b    c
    0  1  1  0.5
    1  1  1  0.5
    2  1  0  1.0
    3  2  1  0.5
    4  2  0  1.0
    5  2  1  0.5
    6  3  1  0.5
    7  3  0  1.0
    8  3  1  0.5
    
    # Perform grouping, excluding the current row
    results = []
    grouped = df.groupby(['a'])
    for key, group in grouped:
        for idx, row in group.iterrows():
            # The group excluding current row
            group_other = group.drop(idx)  
            avg = group_other['b'].mean()
            results.append(row.tolist() + [avg])
    
    # Compare our results with what is expected
    results_df = pd.DataFrame(
        results, columns=['a', 'b', 'c', 'c_new']
    )
    results_df
       a  b    c  c_new
    0  1  1  0.5    0.5
    1  1  1  0.5    0.5
    2  1  0  1.0    1.0
    3  2  1  0.5    0.5
    4  2  0  1.0    1.0
    5  2  1  0.5    0.5
    6  3  1  0.5    0.5
    7  3  0  1.0    1.0
    8  3  1  0.5    0.5
    

    这样你就可以使用任何你想要的统计数据。

    【讨论】:

      猜你喜欢
      • 2017-09-21
      • 1970-01-01
      • 2016-06-21
      • 2017-11-24
      • 1970-01-01
      • 2019-07-22
      • 1970-01-01
      • 2015-03-12
      • 2013-02-25
      相关资源
      最近更新 更多