将 df 与用户定义的函数聚合

mar*_*cus 2 python aggregate function dataframe

我有一个关于将 pandas 数据帧与用户定义的函数聚合的问题。如果我有一个数据帧并在有或没有 groupby 的情况下运行 agg,则在使用内置函数时会聚合结果。另一方面,如果我使用自定义函数,则在使用 groupby 时它会按预期工作。当不使用 groupby 时,不会进行聚合。有没有一种方法可以在不使用 groupby 并使用自定义函数的情况下进行聚合?我知道可以只添加一个虚拟变量,但这不是首选的解决方案。测试 1-3 按预期工作,但测试 4 不工作。

df = pd.DataFrame(columns=['a', 'b', 'c'])
n=1000
np.random.seed(0)

df['a'] = np.random.rand(n)
df['a'] = np.random.rand(n)
df['c'] = np.random.randint(1, 4, size=n)

def CoV(_s):
    return pd.Series({'CoV' : np.std(_s)/np.mean(_s)})

test1 = df.agg({'a':['std', np.mean]})
print(test1)

test2 = df.groupby(['c']).agg({'a':['std', np.mean]})
print(test2)

test3 = df.groupby(['c']).agg({'a':[CoV]})
print(test3)

# does not work as intended, no aggregation
test4 = df.agg({'a':[CoV]})
print(test4)
Run Code Online (Sandbox Code Playgroud)

Jam*_*mes 5

这里的答案都没有解决为什么失败的原因。如果您深入研究 pandas 代码,当 UDF 传递给 时df.agg,每列的 Series 对象将传递给 UDF。

在您的情况下,使用字典选择 Series 对象(一列),然后将 UDF 传递给 Series 对象的Series.agg函数。因为它不是已知函数(如 string 'mean'),所以它最终被传递给Series.apply,后者将函数映射到 Series 对象中的每个值。这就是您看到的结果。

幸运的是,UDF 的传递Series.apply发生在一个try/except块中。如果使用 失败Series.apply(func),它会转而通过 将 Series 对象传递给函数func(Series)。如果传递的对象不是 Series 或 DataFrame,您可以使用它来修改代码以引发错误。

def CoV(_s):
    if not isinstance(_s, (pd.Series, pd.DataFrame, np.array)):
        raise TypeError()
    return pd.Series({'CoV' : np.std(_s)/np.mean(_s)})
Run Code Online (Sandbox Code Playgroud)

现在将其传递给.agg您所期望的作品。这是一个很棘手的解决方法,但它确实有效。

df.agg({'a': CoV})
# returns:
            a
CoV  0.584645
Run Code Online (Sandbox Code Playgroud)

编辑:

不幸的是,为了让它与其他函数(例如 )一起使用'mean',您还必须将它们作为 UDF 传递。更糟糕的是,UDF 的结果累积与内置函数的结果累积不同。Pandas 只是使用分层列索引水平堆叠它们。一个简单的方法stackreset_index解决了这个问题。

def check_input(fn):
    def wrapper(_s, *args, **kwargs):
        if not isinstance(_s, (pd.Series, pd.DataFrame, np.array)):
            raise TypeError()
        return fn(_s, *args, **kwargs)
    wrapper.__name__ = fn.__name__
    return wrapper

@check_input
def Mean(_s):
    return pd.Series({'Mean': np.mean(_s)})

@check_input
def CoV(_s):
    return pd.Series({'CoV' : np.std(_s)/np.mean(_s)})

df.agg({'a': [CoV, Mean], 'c': Mean}).stack().reset_index(level=-1, drop=True)
# returns:
             a      c
CoV   0.584645    NaN
Mean  0.511350  2.011
Run Code Online (Sandbox Code Playgroud)