条件填充pandas数据框

5

我有一个数据框 df,其中列 A 中有浮点值。我想添加另一列 B,使得:

  1. B[0] = A[0]

    对于i > 0...

  2. B[i] = if(np.isnan(A[i])) then A[i] else Step3
  3. B[i] = if(abs((B[i-1] - A[i]) / B[i-1]) < 0.3) then B[i-1] else A[i]

可以按照以下方式生成示例数据框 df

import numpy as np
import pandas as pd
df = pd.DataFrame(1000*(2+np.random.randn(500, 1)), columns=list('A'))
df.loc[1, 'A'] = np.nan
df.loc[15, 'A'] = np.nan
df.loc[240, 'A'] = np.nan
df.loc[241, 'A'] = np.nan

1
你的示例数据框中没有任何NaN值。最好硬编码一个包含约10行数据的数据框。 - Paul H
这个问题可能没有不用循环的解决方法;而且对于第0行的行为是不确定的,正如所描述的那样。 - CJR
解决了两个注释的问题。 - Gerry
如果 B[i-1]np.nan,你如何确定要在 B[i] 中放置什么?因为那个 if 语句是不确定的。 - CJR
4个回答

3
这可以通过Numba相当高效地完成。如果您无法使用Numba,请省略@njit,然后您的逻辑将作为Python级别的循环运行。
import numpy as np
import pandas as pd
from numba import njit

np.random.seed(0)
df = pd.DataFrame(1000*(2+np.random.randn(500, 1)), columns=['A'])
df.loc[1, 'A'] = np.nan
df.loc[15, 'A'] = np.nan
df.loc[240, 'A'] = np.nan

@njit
def recurse_nb(x):
    out = x.copy()
    for i in range(1, x.shape[0]):
        if not np.isnan(x[i]) and (abs(1 - x[i] / out[i-1]) < 0.3):
            out[i] = out[i-1]
    return out

df['B'] = recurse_nb(df['A'].values)

print(df.head(10))

             A            B
0  3764.052346  3764.052346
1          NaN          NaN
2  2978.737984  2978.737984
3  4240.893199  4240.893199
4  3867.557990  4240.893199
5  1022.722120  1022.722120
6  2950.088418  2950.088418
7  1848.642792  1848.642792
8  1896.781148  1848.642792
9  2410.598502  2410.598502

@PaulH,如果可以的话,请发一篇解决方案!我会很有兴趣看到性能差异的! - jpp
@jpp 有没有一种非Numba的方法,因为Numba在我的当前环境中不可用且无法使用。 - Gerry
我认为需要将 if abs((1 - x[i]) / out[i-1]) < 0.3 更改为 if abs((out[i-1] - x[i]) / out[i-1])) < 0.3,以符合问题所述的逻辑。 - CJR
@CJ59,好的观点,现在已经更正了,你可以使用1 - x[i] / out[i-1] - jpp

3

不确定您对第一个B-1和除以NaN的情况想要做什么:

df = pd.DataFrame([1,2,3,4,5,None,6,7,8,9,10], columns=['A'])
b1 = df.A.shift(1)
b1[0] = 1
b = list(map(lambda a,b1: a if np.isnan(a) else (b1 if abs(b1-a)/b1 < 0.3 else a), df.A, b1 ))
df['B'] = b

df
       A    B
0    1.0  1.0
1    2.0  2.0
2    3.0  3.0
3    4.0  4.0
4    5.0  4.0
5    NaN  NaN
6    6.0  6.0
7    7.0  6.0
8    8.0  7.0
9    9.0  8.0
10  10.0  9.0

根据@jpp的建议,您也可以为列表b编写一个列表推导式版本:
b = [a if np.isnan(a) or abs(b-a)/b >= 0.3 else b for a,b in zip(df.A,b1)]

我建议使用列表推导式而不是map(可能需要使用自定义函数而不是lambda)。 - jpp

1
我能提供的简单解决方案如下。我想知道是否有更符合Python风格的方式来完成这些事情:
 a = df['A'].values
 b = []
 b.append(t[0])
 for i in range(1, len(a)):
     if np.isnan(a[i]):
         b.append(a[i])
     else:
         b.append(b[i-1] if abs(1 - a[i]/b[i-1]) < 0.3 else a[i])
 df['B'] = b

0

因此,这可能对真实世界的数据更快,但也有一个真正糟糕的最坏情况(如果第0行>>其余数据,则while循环将迭代N次)。

df['B'] = df['A']
to_be_fixed = pd.Series(True, index=df.index)
while to_be_fixed.any():
    # Shift column B and the rows that need to be logically tested
    diff = df['B'].shift(1)
    to_be_fixed = to_be_fixed.shift(1)

    # Test the rows to see which need to be replaced
    to_be_fixed = to_be_fixed & (np.abs(1 - df['A'] / diff) < 0.3)

    # Replace data
    df.loc[to_be_fixed, 'B'] = diff.loc[to_be_fixed]

    # Fix np.nan that has been introduced into column B
    b_na = pd.isnull(df['B'])
    df.loc[b_na, 'B'] = df.loc[b_na, 'A']

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接