【问题标题】:How to use previous N values in pandas column to fill NaNs?如何使用 pandas 列中的前 N ​​个值来填充 NaN?
【发布时间】:2018-06-27 08:45:56
【问题描述】:

假设我有如下的时间序列数据。

 df
       priceA    priceB
  0     25.67    30.56
  1     34.12    28.43
  2     37.14    29.08
  3     Nan       34.23
  4     32          Nan
  5     18.75    41.1
  6     Nan       45.12
  7     23          39.67
  8     Nan       36.45
  9      36         Nan

现在我想通过取列中先前 N 个值的平均值来填充列 priceA 中的 NaN。在这种情况下,取 N=3。 对于列 priceB,我必须用上面的 M 行值(当前索引-M)填充 Nan。

我尝试为其编写 for 循环,这不是一个好习惯,因为我的数据太大。有没有更好的方法来做到这一点?

N=3
M=2
def fillPriceA( df,indexval,n):
      temp=[ ]
      for i in range(n):
          if i < 0:
                continue
          temp.append(df.loc[indexval-(i+1), 'priceA'])

      return np.nanmean(np.array(temp, dtype=np.float))

def fillPriceB(df,  indexval, m):
        return df.loc[indexval-m, 'priceB']

for idx, rows for df.iterrows():
         if idx< N: 
               continue
         else:
                if rows['priceA']==None:
                     rows['priceA']= fillPriceA(df, idx,N)
                if rows['priceB']==None:
                     rows['priceB']=fillPrriceB(df,idx,M)

预期输出:

        priceA      priceB
0      25.67        30.56
1      34.12        28.43
2      37.14        29.08
3      32.31        34.23
4      32             29.08
5      18.75       41.1
6       27.68      45.12
7       23            39.67
8       23.14      36.45
9       36            39.67

【问题讨论】:

  • 转置值矩阵以允许您对列而不是行进行操作是否可行? This answer 建议这可能是一种实用的方法,但您必须进行试验。

标签: python python-3.x pandas dataframe


【解决方案1】:

您可以使用 NA 掩码来执行每列所需的操作:

import pandas as pd
import numpy as np

df = pd.DataFrame({'a': [1,2,3,4, None, 5, 6], 'b': [1, None, 2, 3, 4, None, 7]})
df

#     a b
# 0 1.0 1.0
# 1 2.0 NaN
# 2 3.0 2.0
# 3 4.0 3.0
# 4 NaN 4.0
# 5 5.0 NaN
# 6 6.0 7.0

for col in df.columns:
    s = df[col]
    na_indices = s[s.isnull()].index.tolist()
    prev = 0
    for k in na_indices:
        s[k] = np.mean(s[prev:k])
        prev = k

    df[col] = s

print(df)

    a   b
# 0 1.0 1.0
# 1 2.0 1.0
# 2 3.0 2.0
# 3 4.0 3.0
# 4 2.5 4.0
# 5 5.0 2.5
# 6 6.0 7.0

虽然这仍然是一个自定义操作,但我很确定它会稍微快一些,因为它不会遍历每一行,只是遍历 NA 值,我假设与实际数据相比会是稀疏的

【讨论】:

    【解决方案2】:

    一种解决方案可能是只使用nan 索引(请参阅dataframe boolean indexing):

    param = dict(priceA = 3, priceB = 2) #Number of previous values to consider
    
    for col in df.columns:
        for i in df[np.isnan(df[col])].index: #Iterate over nan index 
            _window = df.iloc[max(0,(i-param[col])):i][col] #get the nth expected elements
            df.loc[i][col] = _window.mean() if col == 'priceA' else _window.iloc[0] #Replace with right method
    
    print(df)
    

    结果:

          priceA  priceB
    0  25.670000   30.56
    1  34.120000   28.43
    2  37.140000   29.08
    3  32.310000   34.23
    4  32.000000   29.08
    5  18.750000   41.10
    6  27.686667   45.12
    7  23.000000   39.67
    8  23.145556   36.45
    9  36.000000   39.67
    

    注意
    1.使用np.isnan() 意味着您的列是数字的。如果之前没有使用pd.to_numeric() 转换您的列:

    ...
    for col in df.columns:
        df[col] = pd.to_numeric(df[col], errors = 'coerce')
        ...
    

    或者使用pd.isnull() 代替(参见下面的示例)。注意性能(numpy 更快):

    from random import randint
    
    #A sample with 10k elements and some np.nan
    arr = np.random.rand(10000)
    for i in range(100):
        arr[randint(0,9999)] = np.nan
    
    #Performances
    %timeit pd.isnull(arr)
    10000 loops, best of 3: 24.8 µs per loop
    
    %timeit np.isnan(arr)
    100000 loops, best of 3: 5.6 µs per loop
    

    2. 更通用的替代方法是定义方法和窗口大小以应用于dict 中的每一列:

    import pandas as pd
    
    param = {}
    param['priceA'] = {'n':3,
                       'method':lambda x: pd.isnull(x)}
    
    param['priceB'] = {'n':2,
                       'method':lambda x: x[0]}
    

    param 现在包含 n 元素的数量和 method 一个 lambda 表达式。相应地重写你的循环:

    for col in df.columns:
        for i in df[np.isnan(df[col])].index: #Iterate over nan index 
            _window = df.iloc[max(0,(i-param[col]['n'])):i][col] #get the nth expected elements
            df.loc[i][col] = param[col]['method'](_window.values) #Replace with right method
    
    print(df)#This leads to a similar result.
    

    【讨论】:

    • 我尝试了您的第一个解决方案,但它在for i in df[np.isnan(df[col])].indexTypeError: ufunc isnan not supported for the input types, and the inputs could not be safely coerced to any supported types according to casting rule 线上出现错误
    • 您的df[col].dtype 似乎不是数字(请参阅stackoverflow.com/a/36001191/3941704)。您可以尝试使用pd.to_numeric(df[col], errors = 'coerce') 转换它,还是将np.isnan 替换为pd.isnull
    【解决方案3】:

    要填写价格A,请使用rolling,然后使用shift,并在fillna中使用此结果,

    # make some data
    df = pd.DataFrame({'priceA': range(10)})
    
    #make some rows missing
    df.loc[[4, 6], 'priceA'] = np.nan
    
    n = 3
    
    df.priceA = df.priceA.fillna(df.priceA.rolling(n, min_periods=1).mean().shift(1))
    

    这里唯一的边缘情况是两个 nan 彼此在 n 内,但它似乎像您的问题一样处理这个问题。

    对于priceB,只需使用shift

    df = pd.DataFrame({'priceB': range(10)})
    df.loc[[4, 8], 'priceB'] = np.nan
    
    m = 2
    
    df.priceB = df.priceB.fillna(df.priceB.shift(m))
    

    像以前一样,有一个极端情况,即在另一个 nan 之前正好有一个 nan m

    【讨论】:

    • @AkshayNevrekar 我假设。这就是我提到边缘情况的原因。尝试添加到您的问题中,告诉我们在这些情况下该怎么做。否则,此答案可以解决您的问题中所述的问题。
    猜你喜欢
    • 2022-08-14
    • 2019-12-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-07-22
    • 1970-01-01
    • 2021-12-14
    相关资源
    最近更新 更多