Pandas适用,但访问先前计算的值

时间:2017-09-26 08:54:56

标签: python pandas apply shift

假设我有一个像这样的DataFrame(或系列):

f

我希望创建一个新的结果列。

每个结果的值由之前的值,通过任意函数f确定。

如果以前的值不可用(无或NaN),我希望使用上一个结果(当然也可以使用shift)。

使用之前的值很简单,我只需要使用df['Result'] = df['Value'].shift(1).apply(f) 。但是,访问之前的结果似乎并不那么简单。

例如,以下代码计算结果,但如果需要,则无法访问以前的结果。

f

请假设cumsum是任意的,因此无法使用df['Result'] = None for i in range(1, len(df)): value = df.iloc[i-1, 'Value'] if math.isnan(value) or value is None: value = df.iloc[i-1, 'Result'] df.iloc[i, 'Result'] = f(value) 之类的解决方案。

显然,这可以通过迭代完成,但我想知道是否存在更多Panda-y解决方案。

f = lambda x: x+1

示例输出,给定 Value Result 0 0.5 NaN 1 0.8 1.5 2 -0.2 1.8 3 NaN 0.8 4 NaN NaN 5 NaN NaN

为:

   Value    Result
0    0.5       NaN
1    0.8       1.5
2   -0.2       1.8
3    NaN       0.8
4    NaN       1.8   <-- previous Value not available, used f(previous result)
5    NaN       2.8   <-- same

好:

{{1}}

4 个答案:

答案 0 :(得分:3)

看起来它必须是我的循环。我厌恶循环...所以当我循环时,我使用numba

  

Numba使用直接用Python编写的高性能函数为您提供加速应用程序的能力。通过一些注释,面向数组和数学的Python代码可以及时编译为本机机器指令,性能类似于C,C ++和Fortran,无需切换语言或Python解释器。

https://numba.pydata.org/

from numba import njit


@njit
def f(x):
    return x + 1

@njit
def g(a):
    r = [np.nan]
    for v in a[:-1]:
        if np.isnan(v):
            r.append(f(r[-1]))
        else:
            r.append(f(v))
    return r

df.assign(Result=g(df.Value.values))

   Value  Result
0    0.5     NaN
1    0.8     1.5
2   -0.2     1.8
3    NaN     0.8
4    NaN     1.8
5    NaN     2.8

答案 1 :(得分:0)

我认为这可行,但我不确定。它将先前计算的值存储在闭包中。

def use_previous_if_none(f):
    prev = None
    def wrapped(val):
        nonlocal prev
        if math.isnan(val) or val is None:
                val = prev
        res = f(val)
        prev = res
        return res
    return wrapped

df['Result'] = df.Value.shift(1).apply(use_previous_if_none(f))

答案 2 :(得分:0)

我建议没有显式循环的解决方案。而不是引用之前的值,而是ffil()'s NaNs,然后根据f的余额,仅在NaNs的值上应用f

我们首先定义将调用n def apply_f_n_times(arg): x = arg[0] n = int(arg[1]) for i in range(n): x = f(x) return x df = pd.DataFrame({'value': [1, 2, 3, 5, None, None, 12, 9, None, 6, 1, None, None, None]}) df['Result'] = df['Value'].shift(1).apply(f) # the following 2 lines will create counter of consecutive NaNs tmp = df['Result'].isnull() df['Apply_times'] = tmp * (tmp.groupby((tmp != tmp.shift()).cumsum()).cumcount() + 1) # fill NaNs with previous good value df['Result'] = df['Result'].ffill() # apply N times df['Result'] = df[['Result', 'Apply_times']].apply(apply_f_n_times, axis=1) 次的帮助功能:

Out[2]:
      Value  Result  Apply_times
0     1.0     nan            1
1     2.0     2.0            0
2     3.0     3.0            0
3     5.0     4.0            0
4     nan     6.0            0
5     nan     7.0            1
6    12.0     8.0            2
7     9.0    13.0            0
8     nan    10.0            0
9     6.0    11.0            1
10    1.0     7.0            0
11    nan     2.0            0
12    nan     3.0            1
13    nan     4.0            2

结果:

typedef boost::variant<int, float, std::string > Variant;
using Func = std::function<std::vector<unsigned char>(std::vector<Variant>)>;  

void addexecutorfunc( Func callback, const auto&...args )
{
    std::vector<Variant> vec = {args...}; 
    executor.add(vec, std::move(callback));
}

答案 3 :(得分:-1)

这可能适合熊猫编码风格。但是,效率方面,我认为需要进一步测试。这不适用于一般功能。这个  不知何故欺骗了加1功能。

import pandas as pd
import numpy as np


df = pd.DataFrame({'Value':[0.5,0.8,-0.2,None,None,None]})
index = df['Value'].index[df['Value'].apply(np.isnan)]
window = max(index)-min(index)+1
df['next'] =df['Value'].shift(1)


def getX(x):
    last = np.where(~np.isnan(x))[0][-1]
    return (x[last])+len(x)-last



df['plus_one'] = df['next'].rolling(window=3,min_periods=1).apply(lambda x: getX(x))