복붙노트

[PYTHON] numpy 배열로 NaN 값을 전달하는 가장 효율적인 방법

PYTHON

numpy 배열로 NaN 값을 전달하는 가장 효율적인 방법

간단한 예제로 아래에 정의 된 것처럼 numpy 배열 arr을 고려하십시오.

import numpy as np
arr = np.array([[5, np.nan, np.nan, 7, 2],
                [3, np.nan, 1, 8, np.nan],
                [4, 9, 6, np.nan, np.nan]])

콘솔 출력에서 ​​다음과 같이 보입니다.

array([[  5.,  nan,  nan,   7.,   2.],
       [  3.,  nan,   1.,   8.,  nan],
       [  4.,   9.,   6.,  nan,  nan]])

이제 array arr의 nan 값을 'forward-fill'하고 싶습니다. 그 말은 각 나노 값을 가장 가까운 유효한 값으로 바꾸는 것을 의미합니다. 원하는 결과는 다음과 같습니다.

array([[  5.,   5.,   5.,  7.,  2.],
       [  3.,   3.,   1.,  8.,  8.],
       [  4.,   9.,   6.,  6.,  6.]])

for-loops를 사용해 보았습니다.

for row_idx in range(arr.shape[0]):
    for col_idx in range(arr.shape[1]):
        if np.isnan(arr[row_idx][col_idx]):
            arr[row_idx][col_idx] = arr[row_idx][col_idx - 1]

또한 pandas 데이터 프레임을 중간 단계로 사용하여 시도해 보았습니다 (pandas 데이터 프레임에는 앞으로 채우기를위한 매우 내장 된 메서드가 있으므로).

import pandas as pd
df = pd.DataFrame(arr)
df.fillna(method='ffill', axis=1, inplace=True)
arr = df.as_matrix()

위의 두 가지 전략 모두 원하는 결과를 얻을 수 있지만 궁금한 점이 있습니다. 바로 numpy 벡터화 연산 만 사용하는 전략이 가장 효율적입니까?

numpy 배열에서 nan 값을 'forward-fill'하는 또 다른 효율적인 방법이 있습니까? (예를 들어, numpy 벡터화 연산을 사용함으로써)

지금까지 모든 솔루션의 시간을 맞추려고했습니다. 이것은 나의 셋업 스크립트이다.

import numba as nb
import numpy as np
import pandas as pd

def random_array():
    choices = [1, 2, 3, 4, 5, 6, 7, 8, 9, np.nan]
    out = np.random.choice(choices, size=(1000, 10))
    return out

def loops_fill(arr):
    out = arr.copy()
    for row_idx in range(out.shape[0]):
        for col_idx in range(1, out.shape[1]):
            if np.isnan(out[row_idx, col_idx]):
                out[row_idx, col_idx] = out[row_idx, col_idx - 1]
    return out

@nb.jit
def numba_loops_fill(arr):
    '''Numba decorator solution provided by shx2.'''
    out = arr.copy()
    for row_idx in range(out.shape[0]):
        for col_idx in range(1, out.shape[1]):
            if np.isnan(out[row_idx, col_idx]):
                out[row_idx, col_idx] = out[row_idx, col_idx - 1]
    return out

def pandas_fill(arr):
    df = pd.DataFrame(arr)
    df.fillna(method='ffill', axis=1, inplace=True)
    out = df.as_matrix()
    return out

def numpy_fill(arr):
    '''Solution provided by Divakar.'''
    mask = np.isnan(arr)
    idx = np.where(~mask,np.arange(mask.shape[1]),0)
    np.maximum.accumulate(idx,axis=1, out=idx)
    out = arr[np.arange(idx.shape[0])[:,None], idx]
    return out

이 콘솔 입력이 뒤 따른다 :

%timeit -n 1000 loops_fill(random_array())
%timeit -n 1000 numba_loops_fill(random_array())
%timeit -n 1000 pandas_fill(random_array())
%timeit -n 1000 numpy_fill(random_array())

이 콘솔 출력 결과 :

1000 loops, best of 3: 9.64 ms per loop
1000 loops, best of 3: 377 µs per loop
1000 loops, best of 3: 455 µs per loop
1000 loops, best of 3: 351 µs per loop

해결법

  1. ==============================

    1.하나의 접근법이 있습니다.

    하나의 접근법이 있습니다.

    mask = np.isnan(arr)
    idx = np.where(~mask,np.arange(mask.shape[1]),0)
    np.maximum.accumulate(idx,axis=1, out=idx)
    out = arr[np.arange(idx.shape[0])[:,None], idx]
    

    다른 배열을 만들고 arr 자체의 NaN 만 채우고 싶지 않으면 마지막 단계를 this로 바꿉니다.

    arr[mask] = arr[np.nonzero(mask)[0], idx[mask]]
    

    샘플 입력, 출력 -

    In [179]: arr
    Out[179]: 
    array([[  5.,  nan,  nan,   7.,   2.,   6.,   5.],
           [  3.,  nan,   1.,   8.,  nan,   5.,  nan],
           [  4.,   9.,   6.,  nan,  nan,  nan,   7.]])
    
    In [180]: out
    Out[180]: 
    array([[ 5.,  5.,  5.,  7.,  2.,  6.,  5.],
           [ 3.,  3.,  1.,  8.,  8.,  5.,  5.],
           [ 4.,  9.,  6.,  6.,  6.,  6.,  7.]])
    
  2. ==============================

    2.Numba를 사용하십시오. 이것은 상당한 속도 향상을 제공해야합니다 :

    Numba를 사용하십시오. 이것은 상당한 속도 향상을 제공해야합니다 :

    import numba
    @numba.jit
    def loops_fill(arr):
        ...
    
  3. ==============================

    3.앞으로 작성한 후 np.nan을 선도하는 데 문제가있는 사람들에게 다음과 같은 작업이 이루어집니다.

    앞으로 작성한 후 np.nan을 선도하는 데 문제가있는 사람들에게 다음과 같은 작업이 이루어집니다.

    mask = np.isnan(arr)
    first_non_zero_idx = (~mask!=0).argmax(axis=1) #Get indices of first non-zero values
    arr = [ np.hstack([
                 [arr[i,first_nonzero]]*(first_nonzero), 
                 arr[i,first_nonzero:]])
                 for i, first_nonzero in enumerate(first_non_zero_idx) ]
    
  4. from https://stackoverflow.com/questions/41190852/most-efficient-way-to-forward-fill-nan-values-in-numpy-array by cc-by-sa and MIT license