ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Python] pandas 누락된 데이터 처리 (NA, NaN, None)
    언어/파이썬 & 장고 2022. 8. 22. 01:31

    용어

    NA: Not Available의 약자로 누락된 데이터 = 결측값을 의미. 여기에는 NaN, None이 모두 포함된 개념

    NaN: Not a Number의 약자로 숫자 형태의 누락된 데이터 = 결측값을 표현

    None: 파이썬에서 누락된 데이터 = 결측값을 표현

    inf: infinite의 약자로 무한대를 의미. https://brownbears.tistory.com/549 에서 설명되어 있음

    null: NA와 동일하게 누락된 데이터를 의미. pandas에서는 해당 개념이 isnull(), notnull() 같은 함수 형태로만 나옴

    누락된 데이터 처리 방식의 트레이드오프

    표나 DataFrame의 누락된 데이터 존재를 나타내기 위해 여러가지 방식이 개발됐는데 일반적으로 누락된 값을 전체적으로 가리키는 마스크를 사용하거나 누락된 항목 하나를 가리키는 센티널 값을 선택하는 두 전략 중 하나를 중심으로 합니다.

    마스킹 방식에서 마스크는 완전히 별개의 부울 배열일 수도 있고 지역적으로 값의 null 상태를 가리키기 위해 데이터 표현에서 1비트를 전용으로 사용할 수도 있습니다.

    센티널 방식에서 센티널 값은 누락된 정숫값을 -9999나 보기 드문 비트 패턴으로 표시하는 등 데이터에 트고하된 표시법일 수도 있고 누락된 부동 소수점 값을 IEEE 부동 소수점 표준을 따르는 특수 값인 NaN(Not a Number)으로 표시하는 것과 같은 좀 더 일반적인 표시법일 수도 있습니다.

    이 방식들은 모두 장단점이 있는데 별도의 마스크 배열을 사용하면 추가적인 부울 배열 할당이 필요한데, 이는 스토리지와 연산에 있어 오버헤드를 일으킵니다. 센티널 값은 표시할 수 있는 유효값의 범위를 줄이고 CPU와 GPU 산술 연산에 별도의 로직이 필요할 수도 있습니다. NaN과 같은 보편적인 특수 값은 모든 데이터 타입에서 사용할 수 있는 것은 아닙니다.

    pandas에서 누락된 데이터

    pandas에서 누락된 값을 처리하는 방식은 pandas의 기반이 되는 numpy 패키지가 부동 소수점이 아닌 다른 데이터 타입에는 NA 값 표기법이 기본으로 없다는 사실로 인해 제약을 받습니다.

    numpy는 인코딩 방식에 있어 정밀도, 부호, 엔디언을 고려하면 14가지의 기본 정수형을 지원합니다. numpy의 모든 데이터 타입에 대해 특정 비트 패턴을 예약하게 되면 다양한 데이터 타입에 대한 여러가지 연산으로 많은 오버헤드가 발생해서 이를 해결하기 위해 새로운 유형의 numpy 패키지가 필요할 수도 있습니다. 게다가 작은 데이터 타입(8비트 정수)에서 마스크로 사용하기 위해 1비트를 별도로 뺀다면 표현할 수 있는 값의 범위가 상당히 줄어듭니다.

    numpy는 마스킹된 배열, 즉 좋은 또는 나쁨으로 데이터를 마스킹하기 위해 부착된 별도의 부울 마스크 배열을 가진 배열을 지원합니다. pandas가 이로부터 파생됐을 수는 있지만 저장과 연산, 코드 유지보수에서의 오버헤드는 그 방법의 매력을 떨어뜨립니다.

    이 같은 제약을 고려해 pandas는 누락된 데이터에 대해 센티널을 사용하고, 아울러 기존의 두 가지 파이썬 널 값인 특수 부동 소수점 NaN 값과 파이썬 None 객체를 사용합니다. 이 방식은 몇 가지 부작용이 있지만 실무에서 이해관계가 충돌하는 대부분의 경우에 좋은 절충안이 됩니다.

    None: 파이썬의 누락된 데이터

    pandas가 사용한 첫 번째 센티널 값은 None입니다. 이는 파이썬의 싱글턴 객체로 파이썬 코드에서 누락된 데이터를 위해 사용됩니다. None은 파이썬 객체이므로 임의의 numpy나 pandas 배열에서 사용할 수 없고 데이터 타입이 object 인 배열 즉, 파이썬 객체의 배열에서만 사용할 수 있습니다.

    import numpy as np
    
    data = np.array([1, None, 3, 4])
    
    print(data, data.dtype)
    # [1 None 3 4] object

    이 코드에서 dtype = object는 numpy가 배열의 내용에 대해 추론할 수 있는 가장 일반적인 타입 표현이 파이썬 객체라는 것을 의미합니다. 이러한 객체 배열이 몇 가지 목적에서는 유용하지만, 데이터에 대한 연산은 파이썬 수준에서 이뤄지며 기본 데이터 타입의 배열에서 볼 수 있는 전형적으로 빠른 연산보다 훨씬 더 많은 오버헤드가 발생합니다.

    import timeit
    
    import numpy as np
    
    def object_type():
        np.arange(1E6, dtype='object').sum()
    
    def int_type():
        np.arange(1E6, dtype='int').sum()
    
    t1 = timeit.repeat('object_type()', number=1, repeat=10, globals=globals())
    print('object_type: ', sum(t1) / 10)
    # object_type:  0.06752700559991354
    
    t2 = timeit.repeat('int_type()', number=1, repeat=10, globals=globals())
    print('int_type: ', sum(t2) / 10)
    # int_type:  0.0022014179000052537

    배열에서 파이썬 객체를 사용한다는 것은 None 값을 가진 배열에서 sum()이나 min()같은 집계 연산을 하면 일반적으로 오류가 발생할 것이라는 뜻이기도 합니다.

    import numpy as np
    
    data = np.array([1, None, 3, 4])
    print(data.sum())
    # TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'

    NaN: 누락된 숫자 데이터

    NaN은 표준 IEEE 부동 소수점 표기를 사용하는 모든 시스템이 인식하는 특수 부동 소수점 값입니다.

    import numpy as np
    
    data = np.array([1, np.nan, 3, 4])
    print(data, data.dtype)
    # [ 1. nan  3.  4.] float64

    numpy가 이 배열에 대해 기본 부동 소수점 타입을 선택한 것을 주목할 필요가 있습니다. 앞에서 설명한 객체 배열과 달리 이 배열은 컴파일된 코드에 삽입된 빠른 연산을 지원한다는 의미입니다. NaN과 접촉한 모든 객체 즉, 어떤 연산이든 NaN이 포함된 산술 연산의 결과는 NaN이 됩니다.

    print(1 + np.nan)
    # nan
    print(0 * np.nan)
    # nan

    numpy는 NaN을 무시하는 특별한 집계 연산을 제공합니다.

    import numpy as np
    
    data = np.array([1, np.nan, 3, 4])
    
    print(data.sum())
    # nan
    print(np.nansum(data))
    # 8.0

    NaN은 부동 소수점 값이므로 정수나 문자열 등 다른 타입에는 NaN에 해당하는 값이 없습니다.

    pandas에서 NaN과 None

    NaN과 None은 각자가 맡은 역할이 있으며 pandas는 이 둘을 거의 호환성 있게 처리하고 적절한 경우에는 서로 변환할 수 있게 합니다.

    import pandas as pd
    import numpy as np
    
    data = pd.Series([1, np.nan, 2, None])
    print(data)
    # 0    1.0
    # 1    NaN
    # 2    2.0
    # 3    NaN
    # dtype: float64

    사용할 수 있는 센티널 값이 없는 타입의 경우 NA, 값이 있으면 pandas가 자동으로 타입을 변환합니다. 예를 들어 정수 배열의 값을 np.nan으로 설정하면 NA를 수용할 수 있도록 부동 소수점 타입으로 자동 상향 변환합니다.

    import pandas as pd
    import numpy as np
    
    data = pd.Series(range(2), dtype=int)
    print(data)
    # 0    0
    # 1    1
    # dtype: int64
    
    data[0] = None
    print(data)
    # 0    NaN
    # 1    1.0
    # dtype: float64

    정수 배열을 부동 소수점으로 변환하는 것 외에도 자동으로 None을 NaN 값으로 변환합니다.

    다음은 타입별 NA 값 처리 방식 표입니다.

    타입클래스 NA 값을 저장할 떄의 변환 NA 센티널 값
    floating 변경 없음 np.nan
    object 변경 없음 None 또는 np.nan
    integer float64로 변환 np.nan
    boolean object로 변환 None 또는 np.nan

    pandas에서 문자열 데이터는 항상 object dtype으로 저장되는 것을 명심해야 합니다.

    댓글