지난번 포스팅에서는 Python pandas의 fillna() method를 사용한

 

 - 결측값 여부 확인하기

 - 결측값 채우기, 결측값 대체하기

 

에 대해서 알아보았습니다.

 

이번 포스팅에서는 Python pandas의 dropna() method를 사용해서

 

 - 결측값이 들어있는 행 전체 제거

   (delete row with missing values)

 - 결측값이 들어있는 열 전체를 제거

   (delete column with missing values)

 - 특정 행 또는 열 만을 대상으로 결측값이 들어있으면 제거

   (delete specific row or column with missing values)

 

하는 방법을 소개하겠습니다.

 

관측값이 아주 많고 결측값이 별로 없는 경우에는 결측값이 들어있는 행 전체를 삭제하고 분석을 진행해도 무리가 없고 편리할 수 있습니다.

 

혹은 특정 변수의 결측값 비율이 매우 높고, 결측값을 채워넣을 만한 마땅한 방법이 없는 경우에는 분석의 신뢰성 확보를 위해서 그 변수(행, 칼럼)을 삭제하고 분석을 진행할 필요도 있습니다.

 

이때 dropna() method 를 사용하면 됩니다.

 

 

 

 

먼저, 필요한 모듈을 불러오고, 결측값(None 또는 np.nan)이 들어있는 DataFrame을 만들어보겠습니다.

 

 

# importing modules

In [1]: import pandas as pd


In [2]: import numpy as np


In [3]: from pandas import DataFrame

 

# making DataFrame

In [4]: df = DataFrame(np.random.randn(5, 4),

   ...: columns=['C1', 'C2', 'C3', 'C4'])


In [5]: df

Out[5]:

         C1        C2        C3        C4
0 -0.336659 -0.738247  0.598380  0.727832
1 -0.900631  0.073079  1.182290 -0.138224
2 -1.521049 -1.422940  0.330948 -0.275343
3  0.554677 -0.530208 -0.397182  0.990026
4 -0.332384 -1.979684  0.560655  0.833487

 

# inserting missing values

In [6]: df.ix[[0,1], 'C1'] = None


In [7]: df.ix[2, 'C2'] = np.nan


In [8]: df

Out[8]:

         C1        C2        C3        C4
0       NaN -0.738247  0.598380  0.727832
1       NaN  0.073079  1.182290 -0.138224
2 -1.521049       NaN  0.330948 -0.275343
3  0.554677 -0.530208 -0.397182  0.990026
4 -0.332384 -1.979684  0.560655  0.833487

 

 

 

 

 

이제 dropna() method를 사용해서 결측값이 들어있는 행 전체(axis = 0) 삭제, 혹은 결측값이 들어있는 열 전체(axis=1) 삭제해 보겠습니다.

 

 

  (1) 결측값이 들어있는 행 전체 삭제하기(delete row with NaN) : df.dropna(axis=0)

 

 

# delete row with missing values

In [9]: df_dr

df_dop_row = df.dropna(axis=0)


In [10]: df_drop_row

Out[10]:

C1 C2 C3 C4

         C1        C2        C3        C4
3  0.554677 -0.530208 -0.397182  0.990026
4 -0.332384 -1.979684  0.560655  0.833487

 

 

 

 

  (2) 결측값이 들어있는 열 전체 삭제하기 (delete column with NaN) : df.dropna(axis=1)

 

 

In [8]: df

Out[8]:

         C1        C2        C3        C4
0       NaN -0.738247  0.598380  0.727832
1       NaN  0.073079  1.182290 -0.138224
2 -1.521049       NaN  0.330948 -0.275343
3  0.554677 -0.530208 -0.397182  0.990026
4 -0.332384 -1.979684  0.560655  0.833487

 

# delete column with missing values

In [11]: df_drop_column = df.dropna(axis=1)


In [12]: df_drop_column

Out[12]:

         C3        C4
0  0.598380  0.727832
1  1.182290 -0.138224
2  0.330948 -0.275343
3 -0.397182  0.990026
4  0.560655  0.833487

 

 

 

 

  (3) 특정 행 또는 열을 대상으로 결측값이 들어있으면 제거

       (delete specific row or column with missing values) : df[ ].dropna()

 

DataFrame의 행 또는 열을 indexing 한 후에 dropna() method를 적용하면 됩니다.  dropna() 와 dropna(axis=0)은 동일합니다 (즉, axis=0 은 생략 가능).

 

아래에 4가지의 예를 들어보았습니다.  원래의 df DataFrame을 어떻게 indexing 하느냐에 따라 결측값이 들어있는 행과 열이 제거되는지 유심히 살펴보시면 금방 이해가 될거예요.

 

 

In [8]: df

Out[8]:

         C1        C2        C3        C4
0       NaN -0.738247  0.598380  0.727832
1       NaN  0.073079  1.182290 -0.138224
2 -1.521049       NaN  0.330948 -0.275343
3  0.554677 -0.530208 -0.397182  0.990026
4 -0.332384 -1.979684  0.560655  0.833487

 

# delete specific column with missing values

In [13]: df['C1'].dropna()

Out[13]:

2   -1.521049
3    0.554677
4   -0.332384
Name: C1, dtype: float64


In [14]: df[['C1', 'C2', 'C3']].dropna() # the same with dropna(axis=0)

Out[14]:

         C1        C2        C3
3  0.554677 -0.530208 -0.397182
4 -0.332384 -1.979684  0.560655


In [15]: df[['C1', 'C2', 'C3']].dropna(axis=0) # by default, the same with dropna()

Out[15]:

         C1        C2        C3
3  0.554677 -0.530208 -0.397182
4 -0.332384 -1.979684  0.560655


In [16]: df[['C1', 'C2', 'C3']].dropna(axis=1)

Out[16]:

         C3
0  0.598380
1  1.182290
2  0.330948
3 -0.397182
4  0.560655

 

 

In [17]: df.ix[[2, 4], ['C1', 'C2', 'C3']].dropna(axis=0)

Out[17]:

         C1        C2        C3
4 -0.332384 -1.979684  0.560655

 

 

 

마지막으로 첨언하자면, 결측값이 들어있는 행 전체, 혹은 열 전체를 삭제하는 것은 데이터 소실, 혹은 데이터 모집단 왜곡의 위험도 있는 만큼 분석에 영향도를 한번 생각해보고 나서 결정하시면 좋겠습니다.

 

그리고 만약을 대비해서 원본 데이터 (source data)는 그대로 남겨 놓은 상태에서 행 삭제 혹은 열 삭제 후의 DataFrame은 다른 이름으로 copy 해서 사용하실 것을 권합니다. 

 

다음번 포스팅에서는 결측값 보간(interpolation)에 대해서 알아보겠습니다.

 

많은 도움 되었기를 바랍니다.

 

 

 

728x90
Posted by R Friend Rfriend

댓글을 달아 주세요

  1. lee 2020.10.17 04:05  댓글주소  수정/삭제  댓글쓰기

    올려주신 글 덕분에 dropna()라는 함수에 대해 개념을 확실히 알 수 있었습니다. 감사합니다

  2. 2020.10.17 04:20  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

    • R Friend Rfriend 2020.10.17 12:19 신고  댓글주소  수정/삭제

      아래 코드가 궁금하셔서 질문하셨는데요,
      vals, counts= np.unique(data[split_attribute_name],return_counts=True)

      Weighted_Entropy = np.sum([(counts[i]/np.sum(counts))*entropy(data.where(data[split_attribute_name]==vals[i]).dropna()[target_name]) for i in range(len(vals))])


      순서대로 살펴보면요,

      (1) vals, counts= np.unique(data[split_attribute_name],return_counts=True)

      는 data 의 split_attribute_name 별로 유일한 코드값과 개수를 배열로 반환합니다.
      가령 'x_3' 이라는 이름의 split_attribute_name 에 [1, 2, 3] 이라는 코드가 있고, '1' 코드는 3개, '2'코드는 8개, '3' 코드는 15개의 관측치가 있다고 하면 vals, counts = (array([1, 2, 3]), array([3, 8, 15])) 를 반환하는 식입니다.


      (2) 두번째 코드인 가중 엔트로피를 구하는 코드에서 안쪽 부분의
      data.where(data[split_attribute_name]==vals[i])
      는 split_attribute_name에 해당하는 칼럼 내 원소(코드값)의 값과 vals[i]의 값이 같은 경우의 행이 위치(where)의 인덱스를 가지고 data의 행 전체의 모든 칼럼 값을 가져옵니다. data.where() 는 data에서 where()에 해당하는 index 위치의 값을 가져오라는 뜻입니다.
      코드 제일 뒤에 list comprehension 형태로 해서 for i in range(len(vals) 가 있어서 split_attribute_name 칼럼 내의 코드값 vals 의 개수만큼 for loop을 돌면서 위의 (1)번에서 생성한 vals 안의 코드 값을 하나씩 돌면서 다 순서대로 적용합니다.
      (위의 'x_3' 칼럼 예의 경우 '1', '2', '3' 코드값을 순서대로 돌면서 적용)


      (3) data.where(data[split_attribute_name]==vals[i]).dropna()

      뒤에 .dropna() 은 (2)번에서 가져온 데이터셋에서 결측값 NaN 이 있는 행은 제거하게 됩니다.

      가령, 위의 'x_3' 칼럼의 예의 경우, for loop에서 i=0 이면 --> vals[0] 일 경우 --> 1번 행은 'x_3' 칼럼의 값이 '1' 이어서 값이 존재하는데, 2번 행은 'x_3' 칼럼의 값이 '3' 인 경우 NaN 이어서 dropna()에 의해서 행이 전체가 제거됩니다.

      for loop을 돌고 돌아서 i=2 이면 --> vals[2] 일 경우 --> 'x_3' 칼럼의 값이 '3'인 코드값을 가진 2번 행은 '3' 코드값이 존재하므로 유지되는 반면, 1번 행은 코드 값이 '1'이므로 NaN 이어서 제거됩니다. 'x_3' 칼럼의 코드값이 '3'인 관측치가 (1)번에서 15개라고 했으므로 이들 15개 관측치만 남고, 나머지 코드 '1'값인 3개 관측치와, 코드 '2'인 8개 관측치는 NaN 이어서 제거됩니다.

      즉, Decision tree에서 특정 칼럼의 코드값별로 분할이 되었을 때 코드값에 해당하는 관측치만 남겨서 코드값별로 엔트로피를 각각 계산하게 됩니다. 그래야 분할 기준별로 엔트로피와 information gain을 각각 구할 수 있을 테니깐요.


      (4) data.where(data[split_attribute_name]==vals[i]).dropna()[target_name]

      뒤에 [target_name] 은 target_name 칼럼을 인덱싱해서 가져오라는 뜻입니다. 위의 (1), (2), (3)의 연산이 모두 끝난 후의 결과에 대해서 칼럼 인덱싱을 해옵니다.


      (5)entropy(data.where(data[split_attribute_name]==vals[i]).dropna()[target_name])
      미리 정의해둔 entropy() 함수로 분할된 노드의 엔트로피를 계산합니다.


      제가 코드 전체를 가지고 있지 못하고, 또 데이터도 없어서 정확하게 말씀드린건지 확신을 할 수는 없는데요, 일단 공유해주신 코드만 보면 위의 설명대로 인거 같습니다.

      코드를 이해할 때 원래 수식과 psuedo code를 먼저 보고나서 이해하고, 간단한 샘플 데이터로 코드 한줄씩 실행시켜보고, 코드 옵션을 바꿔가면서 결과값을 눈으로 확인/비교해보면 코드를 이해하기가 한결 쉽울 거예요.