如何使用前几行的数据在数据框列上应用函数?

sna*_*uid 5 python vectorization apply dataframe pandas

我有一个包含三列的数据框:nums有一些要使用的值,b它始终是1or0result当前除第一行之外的任何地方都为零的列(因为我们必须有一个初始值才能使用)。数据框如下所示:

   nums   b    result
0  20.0  1    20.0
1  22.0  0    0
2  30.0  1    0
3  29.1  1    0
4  20.0  0    0
...
Run Code Online (Sandbox Code Playgroud)

问题

我想从第二行开始查看数据框中的每一行,进行一些计算并将结果存储在result列中。由于我正在处理大文件,因此我需要一种方法来快速执行此操作,这就是为什么我想要类似apply.

我想要做的计算是采取值numsresult以前的行,如果在当前行的b山坳是0那么我想(例如)添加numresult从上一行。例如,如果b在该行中,1我想减去它们。

我尝试了什么?

我尝试使用apply但我无法访问前一行,遗憾的是,如果我确实设法访问了前一行,数据框直到最后才会更新结果列。

我也尝试使用这样的循环,但对于我正在使用的大文件来说太慢了:

       for i in range(1, len(df.index)):
            row = df.index[i]
            new_row = df.index[i - 1]  # get index of previous row for "nums" and "result"
            df.loc[row, 'result'] = some_calc_func(prev_result=df.loc[new_row, 'result'], prev_num=df.loc[new_row, 'nums'], \
                                     current_b=df.loc[row, 'b'])
Run Code Online (Sandbox Code Playgroud)

some_calc_func 看起来像这样(只是一个一般的例子):

def some_calc_func(prev_result, prev_num, current_b):
    if current_b == 1:
        return prev_result * prev_num / 2
    else:
        return prev_num + 17
Run Code Online (Sandbox Code Playgroud)

请回答有关 some_calc_func

Ben*_*n.T 8

如果您想保留该函数some_calc_func而不使用其他库,则不应尝试在每次迭代时访问每个元素,您可以zip在 nums 和 b 列上使用,并在尝试访问前一行中的 nums 和在每次迭代时将 prev_res 保留在内存中。此外,append到列表而不是数据框,并在循环后将列表分配给列。

prev_res = df.loc[0, 'result'] #get first result
l_res = [prev_res] #initialize the list of results
# loop with zip to get both values at same time, 
# use loc to start b at second row but not num
for prev_num, curren_b in zip(df['nums'], df.loc[1:, 'b']):
    # use your function to calculate the new prev_res
    prev_res = some_calc_func (prev_res, prev_num, curren_b)
    # add to the list of results
    l_res.append(prev_res)
# assign to the column
df['result'] = l_res
print (df) #same result than with your method
   nums  b  result
0  20.0  1    20.0
1  22.0  0    37.0
2  30.0  1   407.0
3  29.1  1  6105.0
4  20.0  0    46.1
Run Code Online (Sandbox Code Playgroud)

现在有了 5000 行的数据帧 df,我得到了:

%%timeit
prev_res = df.loc[0, 'result']
l_res = [prev_res]
for prev_num, curren_b in zip(df['nums'], df.loc[1:, 'b']):
    prev_res = some_calc_func (prev_res, prev_num, curren_b)
    l_res.append(prev_res)
df['result'] = l_res
# 4.42 ms ± 695 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Run Code Online (Sandbox Code Playgroud)

使用您的原始解决方案,速度慢了约 750 倍

%%timeit 
for i in range(1, len(df.index)):
    row = df.index[i]
    new_row = df.index[i - 1]  # get index of previous row for "nums" and "result"
    df.loc[row, 'result'] = some_calc_func(prev_result=df.loc[new_row, 'result'], prev_num=df.loc[new_row, 'nums'], \
                             current_b=df.loc[row, 'b'])
#3.25 s ± 392 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Run Code Online (Sandbox Code Playgroud)

numba如果该函数some_calc_func可以轻松地与 Numba 装饰器一起使用,则使用另一个名为 的库进行编辑。

from numba import jit
# decorate your function
@jit
def some_calc_func(prev_result, prev_num, current_b):
    if current_b == 1:
        return prev_result * prev_num / 2
    else:
        return prev_num + 17

# create a function to do your job
# numba likes numpy arrays
@jit
def with_numba(prev_res, arr_nums, arr_b):
    # array for results and initialize
    arr_res = np.zeros_like(arr_nums)
    arr_res[0] = prev_res
    # loop on the length of arr_b
    for i in range(len(arr_b)):
        #do the calculation and set the value in result array
        prev_res = some_calc_func (prev_res, arr_nums[i], arr_b[i])
        arr_res[i+1] = prev_res
    return arr_res
Run Code Online (Sandbox Code Playgroud)

最后,称之为

df['result'] = with_numba(df.loc[0, 'result'], 
                          df['nums'].to_numpy(),  
                          df.loc[1:, 'b'].to_numpy())
Run Code Online (Sandbox Code Playgroud)

随着时间的推移,我比使用 zip 的方法快了大约 9 倍,并且速度可能会随着大小的增加而增加

%timeit df['result'] = with_numba(df.loc[0, 'result'], 
                                  df['nums'].to_numpy(),  
                                  df.loc[1:, 'b'].to_numpy()) 
# 526 µs ± 45.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Run Code Online (Sandbox Code Playgroud)

请注意,根据您的实际情况,使用 Numba 可能会出现问题 some_calc_func


Cyt*_*rak 2

国际大学学院:

>>> df['result'] = (df[df.result.eq(0)].b.replace({0: 1, 1: -1}) * df.nums
                    ).fillna(df.result).cumsum()

>>> df
   nums  b  result
0  20.0  1    20.0
1  22.0  0    42.0
2  30.0  1    12.0
3  29.1  1   -17.1
4  20.0  0     2.9
Run Code Online (Sandbox Code Playgroud)

解释:

# replace 0 with 1 and 1 with -1 in column `b` for rows where result==0
>>> df[df.result.eq(0)].b.replace({0: 1, 1: -1})
1    1
2   -1
3   -1
4    1
Name: b, dtype: int64

# multiply with nums
>>> (df[df.result.eq(0)].b.replace({0: 1, 1: -1}) * df.nums)
0     NaN
1    22.0
2   -30.0
3   -29.1
4    20.0
dtype: float64

# fill the 'NaN' with the corresponding value from df.result (which is 20 here)
>>> (df[df.result.eq(0)].b.replace({0: 1, 1: -1}) * df.nums).fillna(df.result)
0    20.0
1    22.0
2   -30.0
3   -29.1
4    20.0
dtype: float64

# take the cumulative sum (cumsum)
>>> (df[df.result.eq(0)].b.replace({0: 1, 1: -1}) * df.nums).fillna(df.result).cumsum()
0    20.0
1    42.0
2    12.0
3   -17.1
4     2.9
dtype: float64
Run Code Online (Sandbox Code Playgroud)

根据您在评论中的要求,我想不出没有循环的方法:

c1, c2 = 2, 1
l = [df.loc[0, 'result']]            # store the first result in a list

# then loop over the series (df.b * df.nums)

for i, val in (df.b * df.nums).iteritems():
    if i:                            # except for 0th index
        if val == 0:                 # (df.b * df.nums) == 0 if df.b == 0
            l.append(l[-1])          # append the last result
        else:                        # otherwise apply the rule
            t = l[-1] *c2 + val * c1
            l.append(t)

>>> l
[20.0, 20.0, 80.0, 138.2, 138.2]

>>> df['result'] = l

   nums  b  result
0  20.0  1    20.0
1  22.0  0    20.0
2  30.0  1    80.0   # [ 20 * 1 +   30 * 2]
3  29.1  1   138.2   # [ 80 * 1 + 29.1 * 2]
4  20.0  0   138.2
Run Code Online (Sandbox Code Playgroud)

看起来足够快,没有测试大样本。