( EDIT для включения (частично?) Векторизованного подхода)
( EDIT2 для включения некоторых таймингов)
Простейшее сопоставление решений требуемый ввод / вывод осуществляется путем перебора строк:
import numpy as np
def ffill_loop(arr, fill=0):
mask = np.isnan(arr[0])
arr[0][mask] = fill
for i in range(1, len(arr)):
mask = np.isnan(arr[i])
arr[i][mask] = arr[i - 1][mask]
return arr
print(ffill_loop(arr.copy()))
# [[5. 0. 0. 7. 2. 6. 5.]
# [3. 0. 1. 8. 2. 5. 5.]
# [4. 9. 6. 8. 2. 5. 7.]]
Вы также можете использовать векторизованный подход, который может быть быстрее для больших входных данных (чем меньше nan
ниже друг друга, тем лучше):
import numpy as np
def ffill_roll(arr, fill=0, axis=0):
mask = np.isnan(arr)
replaces = np.roll(arr, 1, axis)
slicing = tuple(0 if i == axis else slice(None) for i in range(arr.ndim))
replaces[slicing] = fill
while np.count_nonzero(mask) > 0:
arr[mask] = replaces[mask]
mask = np.isnan(arr)
replaces = np.roll(replaces, 1, axis)
return arr
print(ffill_roll(arr.copy()))
# [[5. 0. 0. 7. 2. 6. 5.]
# [3. 0. 1. 8. 2. 5. 5.]
# [4. 9. 6. 8. 2. 5. 7.]]
Время для этой функции можно получить (включая решение без l oop, предложенное в @ ответе Дивакара ):
import numpy as np
from numpy import nan
funcs = ffill_loop, ffill_roll, ffill_cols
sep = ' ' * 4
print(f'{"shape":15s}', end=sep)
for func in funcs:
print(f'{func.__name__:>15s}', end=sep)
print()
for n in (1, 5, 10, 50, 100, 500, 1000, 2000):
k = l = n
arr = np.array([[ 5., nan, nan, 7., 2., 6., 5.] * k,
[ 3., nan, 1., 8., nan, 5., nan] * k,
[ 4., 9., 6., nan, nan, nan, 7.] * k] * l)
print(f'{arr.shape!s:15s}', end=sep)
for func in funcs:
result = %timeit -q -o func(arr.copy())
print(f'{result.best * 1e3:12.3f} ms', end=sep)
print()
shape ffill_loop ffill_roll ffill_cols
(3, 7) 0.009 ms 0.063 ms 0.026 ms
(15, 35) 0.043 ms 0.074 ms 0.034 ms
(30, 70) 0.092 ms 0.098 ms 0.055 ms
(150, 350) 0.783 ms 0.939 ms 0.786 ms
(300, 700) 2.409 ms 4.060 ms 3.829 ms
(1500, 3500) 49.447 ms 105.379 ms 169.649 ms
(3000, 7000) 169.799 ms 340.548 ms 759.854 ms
(6000, 14000) 656.982 ms 1369.651 ms 1610.094 ms
Указывает, что ffill_loop()
на самом деле является самым быстрым для данных входов в большинстве случаев. Вместо этого ffill_cols()
постепенно становится самым медленным подходом по мере увеличения размера ввода.