Pandas:应用具有2个数组的函数作为输入并返回单个值

时间:2018-02-23 10:59:10

标签: python pandas hierarchical

我有一个带有分层列名的pandas 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 个答案:

答案 0 :(得分:1)

我认为->的第一级需要groupbyxs的自定义功能需要MultiIndex的第二级:

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

EDIT1:

解决方案应该更简化:

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

答案 1 :(得分:0)

感谢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

为此,我已经应用了一些面向对象的编程来将resample与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)

此解决方案已经完成,因为我们希望将groupby和resample一起应用,但我还没有在pandas中找到合适的解决方案。

我希望这个解决方案对某些人有用;当然还有改进的空间,所以随意发布替代和更有效的解决方案!

感谢。马可