Ни один из ответов здесь не адресован , почему это терпит неудачу. Если вы покопаетесь в коде панд, когда UDF передается в df.agg
, объект Series для каждого столбца будет передан в UDF.
В вашем случае при использовании словаря выбирается объект Series (столбец), а затем UDF передается в функцию Series.agg
объекта Series. Поскольку это не известная функция (например, строка '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)})
Теперь передача его в .agg
работает так, как вы ожидаете. Это обходной обходной путь, но он работает.
df.agg({'a': CoV})
# returns:
a
CoV 0.584645
РЕДАКТИРОВАТЬ:
Чтобы заставить его работать с другими функциями, такими как 'mean'
, вам нужно будет передать их какUDFs также, к сожалению. Хуже того, накопление результатов для UDF отличается от встроенных функций. Панды просто складывают их горизонтально с помощью иерархического индекса столбца. Простые stack
и reset_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