【问题标题】:Returning two values from pandas.rolling_apply从 pandas.rolling_apply 返回两个值
【发布时间】:2014-04-08 17:34:02
【问题描述】:

我正在使用 pandas.rolling_apply 将数据拟合到分布并从中获取值,但我还需要它报告滚动拟合优度(特别是 p 值)。目前我正在这样做:

def func(sample):
    fit = genextreme.fit(sample)
    return genextreme.isf(0.9, *fit)

def p_value(sample):
    fit = genextreme.fit(sample)
    return kstest(sample, 'genextreme', fit)[1]

values = pd.rolling_apply(data, 30, func)
p_values = pd.rolling_apply(data, 30, p_value)
results = pd.DataFrame({'values': values, 'p_value': p_values})

问题是我有很多数据,而 fit 函数很昂贵,所以我不想为每个样本调用两次。我宁愿做的是这样的:

def func(sample):
    fit = genextreme.fit(sample)
    value = genextreme.isf(0.9, *fit)
    p_value = kstest(sample, 'genextreme', fit)[1]
    return {'value': value, 'p_value': p_value}

results = pd.rolling_apply(data, 30, func)

其中 results 是带有两列的 DataFrame。如果我尝试运行它,我会得到一个异常: TypeError: a float is required。是否有可能实现这一目标,如果可以,如何实现?

【问题讨论】:

  • 如果你返回一个系列而不是一个字典,它会起作用吗?
  • @AndyHayden 不,这给了TypeError: cannot convert the series to <class 'float'>
  • @Jeff 这是一个不同的问题。那是关于接受两个输入。这个问题是关于给出 2 个输出。
  • 有人给你一个好的答案了吗?我可以编写自己的更通用的滚筒,但如果有标准解决方案,我会更喜欢。

标签: python pandas


【解决方案1】:

我遇到了类似的问题,并通过在应用期间使用单独的帮助类的成员函数来解决它。该成员函数根据需要返回单个值,但我将其他计算结果存储为该类的成员,然后可以使用它。

简单示例:

class CountCalls:
    def __init__(self):
        self.counter = 0

    def your_function(self, window):
        retval = f(window)
        self.counter = self.counter + 1


TestCounter = CountCalls()

pandas.Series.rolling(your_seriesOrDataframeColumn, window = your_window_size).apply(TestCounter.your_function)

print TestCounter.counter

假设您的函数 f 将返回一个包含两个值 v1、v2 的元组。然后您可以返回 v1 并将其分配给 column_v1 到您的数据框。第二个值 v2 您只需在助手类中的 Series series_val2 中累积。之后,您只需将该系列作为新列添加到您的数据框。 JML

【讨论】:

  • 迄今为止最好和最简单的解决方案。
【解决方案2】:

我也有同样的问题。我通过生成一个全局数据框并从滚动函数中提供它来解决它。在以下示例脚本中,我生成了一个随机输入数据。然后,我使用单个滚动应用函数计算最小值、最大值和平均值。

import pandas as pd
import numpy as np

global outputDF
global index

def myFunction(array):

    global index
    global outputDF

    # Some random operation
    outputDF['min'][index] = np.nanmin(array)
    outputDF['max'][index] = np.nanmax(array)
    outputDF['mean'][index] = np.nanmean(array)

    index += 1
    # Returning a useless variable
    return 0

if __name__ == "__main__":

    global outputDF
    global index

    # A random window size
    windowSize = 10

    # Preparing some random input data
    inputDF = pd.DataFrame({ 'randomValue': [np.nan] * 500 })
    for i in range(len(inputDF)):
        inputDF['randomValue'].values[i] = np.random.rand()


    # Pre-Allocate memory
    outputDF = pd.DataFrame({ 'min': [np.nan] * len(inputDF),
                              'max': [np.nan] * len(inputDF),
                              'mean': [np.nan] * len(inputDF)
                              })   

    # Precise the staring index (due to the window size)
    d = (windowSize - 1) / 2
    index = np.int(np.floor( d ) )

    # Do the rolling apply here
    inputDF['randomValue'].rolling(window=windowSize,center=True).apply(myFunction,args=())

    assert index + np.int(np.ceil(d)) == len(inputDF), 'Length mismatch'

    outputDF.set_index = inputDF.index

    # Optional : Clean the nulls
    outputDF.dropna(inplace=True)

    print(outputDF)

【讨论】:

    【解决方案3】:

    我之前也遇到过类似的问题。这是我的解决方案:

    from collections import deque
    class your_multi_output_function_class:
        def __init__(self):
            self.deque_2 = deque()
            self.deque_3 = deque()
    
        def f1(self, window):
            self.k = somefunction(y)
            self.deque_2.append(self.k[1])
            self.deque_3.append(self.k[2])
            return self.k[0]    
    
        def f2(self, window):
            return self.deque_2.popleft()   
        def f3(self, window):
            return self.deque_3.popleft() 
    
    func = your_multi_output_function_class()
    
    output = your_pandas_object.rolling(window=10).agg(
        {'a':func.f1,'b':func.f2,'c':func.f3}
        )
    

    【讨论】:

      【解决方案4】:

      我使用并喜欢@yi-yu 的答案,所以我把它变成了通用的:

      from collections import deque
      from functools import partial
      
      def make_class(func, dim_output):
      
          class your_multi_output_function_class:
              def __init__(self, func, dim_output):
                  assert dim_output >= 2
                  self.func = func
                  self.deques = {i: deque() for i in range(1, dim_output)}
      
              def f0(self, *args, **kwargs):
                  k = self.func(*args, **kwargs)
                  for queue in sorted(self.deques):
                      self.deques[queue].append(k[queue])
                  return k[0]
      
          def accessor(self, index, *args, **kwargs):
              return self.deques[index].popleft()
      
          klass = your_multi_output_function_class(func, dim_output)
      
          for i in range(1, dim_output):
              f = partial(accessor, klass, i)
              setattr(klass, 'f' + str(i), f)
      
          return klass
      

      给定一个熊猫系列的函数f(窗口但不一定)返回n值,你可以这样使用它:

      rolling_func = make_class(f, n)
      # dict to map the function's outputs to new columns. Eg:
      agger = {'output_' + str(i): getattr(rolling_func, 'f' + str(i)) for i in range(n)} 
      windowed_series.agg(agger)
      

      【讨论】:

      • 我无法在我的情况下使用它。我收到IndexError: pop from an empty deque。你也忘了从functools导入partial
      猜你喜欢
      • 2012-04-28
      • 1970-01-01
      • 1970-01-01
      • 2020-03-19
      • 2011-04-18
      • 2017-09-09
      • 1970-01-01
      • 2010-11-30
      • 2013-06-06
      相关资源
      最近更新 更多