使用列和行索引函数填充数据帧缺失元素的最有效方法

时间:2016-06-17 21:08:06

标签: python performance pandas numpy

我有一个缺少值的数据框。

import pandas as pd
import numpy as np

np.random.seed([3,1415])
df = pd.DataFrame(np.random.choice((0, np.nan), (5, 5)))
print df

     0    1    2    3    4
0  0.0  NaN  0.0  NaN  0.0
1  0.0  NaN  0.0  NaN  NaN
2  NaN  NaN  0.0  NaN  NaN
3  0.0  NaN  0.0  0.0  0.0
4  0.0  0.0  0.0  0.0  0.0

问题

如何在传递缺少单元格的行和列索引值时,使用函数返回的内容有效地填充缺失值。

假设我的函数f定义为:

f = lambda i, j: i ** 2 - np.sqrt(abs(j))

我希望得到:

     0    1    2         3    4
0  0.0 -1.0  0.0 -1.732051  0.0
1  0.0  0.0  0.0 -0.732051 -1.0
2  4.0  3.0  0.0  2.267949  2.0
3  0.0  8.0  0.0  0.000000  0.0
4  0.0  0.0  0.0  0.000000  0.0

到目前为止,我已经创建了两个函数来生成此输出:

def pir1(df, f):
    dfi = df.stack(dropna=False).index.to_series().unstack()
    return df.combine_first(dfi.applymap(lambda x: f(*x)))

def pir2(df, f):
    dfc = df.copy()
    for i in dfc.index:
        for j in dfc.columns:
            dfv = df.get_value(i, j)
            dfc.at[i, j] = dfv if pd.notnull(dfv) else f(i, j)
    return dfc

时序

%%timeit
pir1(df, f)

100 loops, best of 3: 3.74 ms per loop
%%timeit
pir2(df, f)

1000 loops, best of 3: 714 µs per loop

任何人都可以改进吗?

2 个答案:

答案 0 :(得分:2)

arraymap中的pir1pir2中的双重for循环调用f一次 每对索引和列值。如果f可以被矢量化 - 即定义 以便接受NumPy数组作为输入而不是标量,然后是大输入 通过将整个2D数组传递给f,可以大大加快计算速度。

对于您发布的f,无需进行任何更改; f已经过矢量化 - 它可以 接受数组作为输入就像标量一样容易。

import numpy as np
import pandas as pd

np.random.seed([3,1415])
df = pd.DataFrame(np.random.choice((0, np.nan), (5, 5)))
def f(i, j): return i ** 2 - np.sqrt(abs(j))

def using_meshgrid(df, f):
    I, J = np.meshgrid(df.index, df.columns, sparse=True, indexing='ij')
    return df.combine_first(pd.DataFrame(f(I, J), index=df.index, columns=df.columns))

def pir1(df, f):
    dfi = df.stack(dropna=False).index.to_series().unstack()
    return df.combine_first(dfi.applymap(lambda x: f(*x)))

def pir2(df, f):
    dfc = df.copy()
    for i in dfc.index:
        for j in dfc.columns:
            dfv = df.get_value(i, j)
            dfc.at[i, j] = dfv if pd.notnull(dfv) else f(i, j)
    return dfc

对于小输入,pir2仍然是最快的:

In [290]: %timeit using_meshgrid(df, f)
100 loops, best of 3: 2.01 ms per loop

In [291]: %timeit pir1(df, f)
100 loops, best of 3: 4.61 ms per loop

In [292]: %timeit pir2(df, f)
1000 loops, best of 3: 825 µs per loop

但是对于大输入,using_meshgrid更快:

In [293]: df = pd.DataFrame(np.random.choice((0, np.nan), (500, 500)))

In [294]: %timeit using_meshgrid(df, f)
10 loops, best of 3: 160 ms per loop

In [295]: %timeit pir1(df, f)
1 loop, best of 3: 1.15 s per loop

In [296]: %timeit pir2(df, f)
1 loop, best of 3: 4.79 s per loop

答案 1 :(得分:2)

本文中列出了一种方法np.nonzero来获取这些索引并计算这些索引的函数输出。此外,我们将使用df.values将基础数据作为数组作为视图提取出来,从而为我们带来两个好处 -

  • 可以使用NumPy funcs / operations来获得良好的性能。

  • 只需将值分配到提取的数组中,就可以根据需要为此问题设置输出值,这是输入数据帧的视图。

因此,我们会有一个实现,如此 -

def using_nonzeros(df, f):
    a = df.values
    r,c = np.nonzero(np.isnan(a))
    a[r,c] = f(r,c)

并且函数f是可自定义的,并且对于如此定义的所述问题 -

def f(i, j): return i ** 2 - np.sqrt(abs(j))

运行时测试 -

In [223]: df = pd.DataFrame(np.random.choice((0, np.nan), (1000, 1000)))

In [224]: %timeit using_meshgrid(df,f) # @unutbu's soln
1 loops, best of 3: 823 ms per loop

In [225]: %timeit using_nonzeros(df,f) # This changes df
100 loops, best of 3: 4.61 ms per loop