Панды: применить функцию с массивом 2 в качестве ввода и вернуть единственное значение
У меня есть DataFrame панды с иерархическими именами столбцов, как это
import pandas as pd
import numpy as np
np.random.seed(1542)
dates = pd.date_range('29/01/17', periods = 6)
pd.DataFrame(np.random.randn(6,6), index = dates,\
columns = [['g1', 'g1', 'g1', 'g2', 'g2', 'g2'],\
['c1', 'c2', 'c3', 'c1', 'c2', 'c3']])
И я хочу применить функцию, которая для каждой группы в первом уровне столбцов принимает столбцы "c2" и "c3" и возвращает одно значение.
Примером функции (которая в реальном случае является более сложной) может быть
def function(first_column, second_column):
return(max(first_column) - max(second_column))
Когда я применяю его к моему DataFrame, я хочу получить обратно DataFrame, который сообщает мне вывод 'function' для каждой группы, поэтому, в данном случае, только 2 числа для 'g1' и 'g2'.
Обратите внимание, что я хочу, чтобы он работал также в случае gorupby(), чтобы в этом случае я получил результат функции для каждой группы ('g1' и 'g2') и для каждого подгруппы groupby.
Для случая выше, если я хочу агрегировать по месяцам, результат должен быть:
g1 g2
1 0.909464 1.638375
2 0.698515 0.33819
2 ответа
Спасибо Jezrael за ваш полезный вклад. Основываясь на этом, я написал решение проблемы: примените сложную функцию, которая принимает два или более массивов в качестве входных данных, и возвращает одно значение, и примените его к кадру данных с иерархическими именами столбцов вместе с повторной выборкой на основе индексации по времени и дате.
Во-первых, вот таблица, которую я буду использовать для примера
mat = np.random.randint(0, 101, size = (10, 6))
index = pd.date_range(start = '25 Jan 2018', periods = 10)
first_column_name = ['Group1']*3 + ['Group2']*3
second_column_name = ['Col1', 'Col2', 'Col3']*2
df = pd.DataFrame(mat, index = index, columns = [first_column_name,\
second_column_name])
Group1 Group2
Col1 Col2 Col3 Col1 Col2 Col3
2018-01-25 11 36 80 88 31 33
2018-01-26 30 32 61 53 55 43
2018-01-27 64 26 21 63 33 93
2018-01-28 52 59 23 54 91 60
2018-01-29 93 88 27 16 88 7
2018-01-30 28 76 48 5 38 1
2018-01-31 7 29 45 86 53 96
2018-02-01 18 89 69 3 34 34
2018-02-02 0 7 94 99 5 68
2018-02-03 29 13 98 25 51 44
Теперь я хочу применить функцию:
def my_fun(arr1, arr2):
arr1 = np.array(arr1)
arr2 = np.array(arr2)
tmp = np.abs(arr1 - arr2)
return(np.sum(tmp))
Обратите внимание, что это простой случай: в реальном случае функция чрезвычайно сложна и обходной путь не может быть принят!
Когда я применяю функцию к 'Col1' 'Col 3', желаемым результатом будет следующее:
Group1 Group2
2018-01-31 296 124
2018-02-28 214 81
Для этого я применил немного объектно-ориентированного программирования, чтобы объединить повторную выборку с groupby.
Итак, я создал этот класс
class ApplyFunction():
def __init__(self, column_names, fun, resample = None):
self.cn = column_names
self.fun = fun
self.resample = resample
# Initialize the stored values
self.stored_values = dict()
for name in self.cn:
self.stored_values[name] = []
def __store(self, x):
self.stored_values[self.to_store].append(x.values.copy())
def wrapper_with_resample(self, x):
if self.resample is None:
print('Can not use this function with resample = None')
return np.nan
# Get the names of the group
group_name = x.columns.levels[0][x.columns.labels[0][0]]
# Get the time-steps output of resample (doing a dumm operation)
self.timesteps = x.resample(self.resample).apply(lambda x : len(x)).index
# Store the resampled variables
for name in self.cn:
self.to_store = name
x[(group_name, name)].resample(self.resample).apply(self.__store)
# Create a new DataFrame for the output
out = []
for i in range(len(self.timesteps)):
out.append(self.fun(*[self.stored_values[name][i] for name in self.cn]))
out = pd.Series(out, index = self.timesteps)
# Reset self.stored_values
for name in self.cn:
self.stored_values[name] = []
return out
И тогда я использую это следующим образом:
f = ApplyFunction(column_names = ['Col1', 'Col3'], fun = my_fun, resample = 'M')
output = df.groupby(level = 0, axis = 1).apply(f.wrapper_with_resample)
Это решение было сделано, потому что здесь мы хотим применить групповую обработку и повторную выборку, и я не нашел подходящего решения в пандах.
Я надеюсь, что это решение кому-то пригодится; Конечно, есть возможности для улучшения, поэтому не стесняйтесь размещать альтернативные и более эффективные решения!
Благодарю. Marco
Я думаю тебе нужно groupby
по первому уровню MultiIndex
с пользовательской функцией с xs
для выбора второго уровня MultiIndex
:
np.random.seed(1542)
df = pd.DataFrame(np.random.randn(6,6), index = range(6),\
columns = [['g1', 'g1', 'g1', 'g2', 'g2', 'g2'],\
['c1', 'c2', 'c3', 'c1', 'c2', 'c3']])
print (df)
g1 g2
c1 c2 c3 c1 c2 c3
0 -0.556376 -0.295627 0.618673 -0.409434 0.107020 -1.143460
1 -0.145909 0.017417 0.117667 -0.301128 0.880918 -1.027282
2 2.287448 1.528137 -1.528636 0.052728 -1.842634 -0.757457
3 -0.651587 -1.075176 1.128277 0.632036 -0.240965 0.421812
4 -1.620718 0.146108 0.030500 -0.446294 -0.206774 0.819859
5 -0.757296 1.826793 -0.352837 -2.048026 1.362865 1.024671
def f(x):
a = x.xs('c2', axis=1, level=1)[x.name].max()
b = x.xs('c3', axis=1, level=1)[x.name].max()
#print (a)
return a - b
s = df.groupby(level=0, axis=1).apply(f)
print (s)
g1 0.698516
g2 0.338194
dtype: float64
Аналогичное решение:
def f(x):
a = x.xs('c2', axis=1, level=1).squeeze()
b = x.xs('c3', axis=1, level=1).squeeze()
return a.max() - b.max()
a = df.groupby(level=0, axis=1).apply(f)
print (a)
g1 0.698516
g2 0.338194
dtype: float64
РЕДАКТИРОВАТЬ:
def f(x):
a = x.xs('c2', axis=1, level=1)[x.name]
b = x.xs('c3', axis=1, level=1)[x.name]
#print (a)
return a - b
s = df.resample('M').max().groupby(level=0, axis=1).apply(f)
print (s)
g1 g2
2017-01-31 0.909464 1.638375
2017-02-28 0.698516 0.338194
print (df.resample('M').max())
g1 g2
c1 c2 c3 c1 c2 c3
2017-01-31 2.287448 1.528137 0.618673 0.052728 0.880918 -0.757457
2017-02-28 -0.651587 1.826793 1.128277 0.632036 1.362865 1.024671
EDIT1:
Решение должно быть упрощено больше:
a = df.resample('M').max()
b = a.xs('c2', axis=1, level=1)
c = a.xs('c3', axis=1, level=1)
d = b - c
print (d)
g1 g2
2017-01-31 0.909464 1.638375
2017-02-28 0.698516 0.338194