将sum()或mean()分配给函数

时间:2017-08-08 14:37:52

标签: python function pandas

我怀疑这是Python中的一个非常基本的功能,我已经查看了可能已经有答案的问题下的建议,但我不认为这是一个重复的问题。我会删除它。

挑战:

我想在一个函数中包含df.groupby(pd.TimeGrouper(freq='M')).sum(),以便我可以在该函数中指定sum()mean()count()作为参数。我之前已经问过类似的问题here,但我不认为在这种情况下我可以使用相同的技术。

以下是一个可重复输入的代码段:

# Imports
import pandas as pd
import numpy as np

# Dataframe with 1 or zero
# 100 rows and 4 columns
# Indexed by dates
np.random.seed(12345678)
df = pd.DataFrame(np.random.randint(0,2,size=(100, 4)), columns=list('ABCD'))
datelist = pd.date_range(pd.datetime(2017, 1, 1).strftime('%Y-%m-%d'), periods=100).tolist()
df['dates'] = datelist 
df = df.set_index(['dates'])
df.index = pd.to_datetime(df.index)
print(df.head(10))

给出了:

enter image description here

我们可以这样做:

df2 = df.groupby(pd.TimeGrouper(freq='M')).sum()
print(df2)

得到:

enter image description here

或者我们可以这样做:

df3 = df.groupby(pd.TimeGrouper(freq='M')).mean()
print(df3)

得到:

enter image description here

以下部分程序包含在一个函数中:

# My function
def function1(df):
    df = df.groupby(pd.TimeGrouper(freq='M')).sum()
    return df

# Function1 call
df4 = function1(df = df)
print(df4)

这很好用:

enter image description here

当我尝试在Function2中添加sum()mean()作为参数时出现问题,如下所示:

# My function with sum() as an argument
def function2(df, fun):
    df = df.groupby(pd.TimeGrouper(freq='M')).fun
    return df

我的第一次尝试引发了一个TypeError:

# Function2 test 1
df5 = function2(df = df, fun = sum())

enter image description here

我的第二次尝试引发属性错误:

# Function2 test 2
df6 = function2(df = df, fun = 'sum()')

enter image description here

是否可以对此设置进行一些调整以使其正常工作? (我尝试使用' M'作为freq的参数的另一个版本,并且工作得很好)。或者这不是这些事情的完成方式吗?

感谢您的任何建议!

以下是简单复制和粘贴的全部内容:

#%%

# Imports
import pandas as pd
import numpy as np

# Dataframe with 1 or zero
# 100 rows across 4 columns
# Indexed by dates
np.random.seed(12345678)
df = pd.DataFrame(np.random.randint(0,2,size=(100, 4)), columns=list('ABCD'))
datelist = pd.date_range(pd.datetime(2017, 1, 1).strftime('%Y-%m-%d'), periods=100).tolist()
df['dates'] = datelist 
df = df.set_index(['dates'])
df.index = pd.to_datetime(df.index)
print(df.head(10))

# Calculate sum per month
df2 = df.groupby(pd.TimeGrouper(freq='M')).sum()
print(df2)

# Or calculate average per month
df3 = df.groupby(pd.TimeGrouper(freq='M')).mean()
print(df3)

# My function
def function1(df):
    df = df.groupby(pd.TimeGrouper(freq='M')).sum()
    return df

# Function1 test
df4 = function1(df = df)
print(df4)
# So far so good
#%%
# My function with sum() as argument
def function2(df, fun):
    print(fun)
    df = df.groupby(pd.TimeGrouper(freq='M')).fun
    return df

# Function2 test 1
# df5 = function2(df = df, fun = sum())

# Function2 test 2
# df6 = function2(df = df, fun = 'sum()')

# Function2 test 3
# df7 = function2(df = df, fun = sum)

2 个答案:

答案 0 :(得分:4)

您需要使用apply

def function2(df, fun):
    return df.groupby(pd.TimeGrouper(freq='M')).apply(fun)

确保fun是一个可调用的pd.DataFrame

但是,您应该使用agg。如果fun将列缩减为类似于summean的标量,那么这应该可行。需要考虑的事情。

df.groupby(pd.TimeGrouper('M')).agg(['sum', 'mean', fun])

答案 1 :(得分:2)

根据@BlackJack的评论,这是一个更简单的实现,它使用getattr(gb, foo)来获取foo groupby对象上的方法gb。如果不存在这样的方法,则会引发AttributeError。根据用途,您可能希望控制哪些函数可以作为foo参数的参数传递(参见下面的第二个示例)。

def function(df, foo):
    gb = df.groupby(pd.TimeGrouper(freq='M'))
    try:
        foo = getattr(gb, foo)
    except AttributeError:
        raise('{} cannot be performed on this object'.format(foo))
    return foo()

这是另一种方法。这使用eval因为安全问题而很邪恶。 然而,它首先确保foo是一种已知的函数类型,可以安全地应用于 pd.core.groupby.SeriesGroupBypd.core.groupby.DataFrameGroupBy对象。

def function2(df, foo):
    safe_functions = ('sum', 'mean', 'count')
    if foo not in safe_functions:
        raise ValueError('foo is not safe')
    gb = df.groupby(pd.TimeGrouper(freq='M'))
    if not isinstance(gb, (pd.core.groupby.SeriesGroupBy, pd.core.groupby.DataFrameGroupBy)):
        raise ValueError('Unexpected groupby result')
    return eval('gb.{}()'.format(foo))

>>> function(df, 'sum')
             A   B   C   D
dates                     
2017-01-31  18  15  14  14
2017-02-28  15  15  12  17
2017-03-31  18  17  16  17
2017-04-30   8   3   3   7