본문 바로가기
Python/Pandas, Matplotlib, Seaborn

[Pandas]_Pandas library basic (3) 결측치를 어떻게 처리할까? 데이터 전처리

by ssolLEE 2023. 8. 9.
반응형

결측치 다루기

  • 결측치는 영어로 missing value라고 합니다. 값이 없는 것이죠. null이라고 표현하기도 하고 NA, NAN이라고 하기도 합니다. 

 

  • 데이터 만들기
import pandas as pd
import numpy as np

np.random.seed(0)  # 무작위로 숫자 추출하는데 첫 결과 고정. 실험조건을 동일하게 맞춰주기 위함
data = np.random.randint(0, 10, size=(10, 3)).astype(float)
data

  • 결측치 만들기 - 값이 3이하인 것은 nan 처리 하겠습니다.
data[data < 3] = np.nan
data

  • data를 데이터프레임으로 바꾸겠습니다. 이때 컬럼명은 col1, col2, col3로 설정하겠습니다. 
  • 그리고 습관적으로 .info()를 확인합니다. 새로운 데이터가 생겼다면 정보를 간단하게 확인할 수 있는 코드입니다. 
df = pd.DataFrame(data, columns=["col1", "col2", "col3"])
df.info()

여러분은 여기서 null의 존재를 확인합니다.

  • 이렇게도 null을 확인할 수 있습니다. 
df.isna().sum()

  • col2와 col3에 결측치가 존재합니다.
  • 결측치를 처리하는 방법은 여러 가지가 있습니다. 그 중에서 오늘은 평균으로 대치하는 방법을 쓰겠습니다. 
  • 나중에 결측치를 처리하는 여러 가지 방법에 대해 포스팅을 하겠습니다.
  • 두 가지 방법을 소개해드립니다. 
# 첫 번째 방법

df = df.fillna(df.mean())
df

# 조금 더 명시적인게 좋다고 함
# inplace는 지양한다고 함(파이썬에서)
col3_mean = df['col3'].mean()
# col3_mean
df['col3']= df['col3'].fillna(col3_mean)
df['col2']= df['col2'].fillna(df['col2'].mean())
df

결측치, 이상치 데이터 핸들링

데이터 불러오기

  • 우선 다음처럼 library를 import 합니다.
import pandas as pd
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import statsmodels
import scipy
import seaborn as sns

print(pd.__version__)
print(np.__version__)
print(matplotlib.__version__)
print(statsmodels.__version__)
print(scipy.__version__)
print(sns.__version__)
  • 데이터를 가지고 옵니다.
DATA_PATH = './'
covidtotals = pd.read_csv(DATA_PATH + "data/covidtotalswithmissings.csv")
covidtotals.head(5)
  • pandas에서 보는 옵션을 다음과 같이 설정합니다. 
pd.set_option('display.width', 40)
pd.set_option('display.max_columns', 20)
pd.set_option('display.max_rows', 20)
pd.options.display.float_format = '{:,.2f}'.format
  • covidtotals의 인덱스를 iso_code 열로 설정하겠습니다.
covidtotals = covidtotals.set_index('iso_code')
covidtotals.head()

결측치 처리하기

  • 인구 통계 컬럼에서 결측치 데이터를 확인합니다. 
  • 컬럼 방향으로 결측치의 갯수를 파악합니다.
  • 다섯가지 인구 통계 변수 중에서 3개가 누락된 곳, 4개가 누락된 곳을 확인합니다. 
totvars = ['location','total_cases','total_deaths','total_cases_pm','total_deaths_pm'] # 코비드 확진자 관련 변수
demovars = ['population','pop_density','median_age','gdp_per_capita', 'hosp_beds'] # 인구 통계 관련 변수

covidtotals[demovars].isnull().sum(axis=0)

컬럼 별 결측치의 수를 나타냅니다.

  • 위의 코드는 컬럼 별 결측치의 수를 알아보는 것입니다.(axis=0)
  • 각 행 별 결측치 수는 다음과 같이(axis=1) 할 수 있고, .value_counts()를 사용하면 요약 형태로 볼 수 있습니다.
demovars_misscnt = covidtotals[demovars].isnull().sum(axis=1)  
# demovars_misscnt
demovars_misscnt.value_counts()

결측치가 3개 이상인 데이터 추출하기

# covidtotals.loc[:, ['location'] + demovars].head(1)
# covidtotals.loc[demovars_misscnt >= 3, ['location'] + demovars].head()
covidtotals.loc[demovars_misscnt >= 3, ['location'] + demovars].T  # 행/열 뒤집기

Index Alignment

  • 인덱스 값끼리 연산이 이뤄지는 것입니다. Pandas에서 제공합니다. 
  • 다음 코드와 같이 인덱스 순서가 달라도, 같은 인덱스끼리 값이 합쳐지는 것을 볼 수 있습니다. 결과는 >>> 아랫줄에 썼습니다.  
s1 = pd.Series([1, 2, 3, 4], index=['a', 'b', 'c', 'd'])
s2 = pd.Series([4, 3, 2, 1], index=['d', 'c', 'b', 'a'])

# print(s1, "\n", s2)
print(s1 + s2)

>>> 
a    2
b    4
c    6
d    8
dtype: int64
  • s1 + s2를 하면 순서대로 5, 5, 5, 5가 나올 것 같지만 a끼리, b끼리 더해져서 2, 4, 6, 8의 결괏값을 가집니다. 

코로나 관련 변수만 추출하기 

  • totvars를 사용하여 결측값이 있는 국가를 찾아봅시다. 
# 방법 1
demovars_misscnt = covidtotals[covidtotals[totvars].isna().any(axis=1)]
demovars_misscnt

# 방법 2
covidtotals[totvars].isnull().sum(axis=0)
totvarsmisscnt = covidtotals[totvars].isnull().sum(axis=1)
covidtotals.loc[totvarsmisscnt>0].T

 

방법 1 과 방법 2의 결과물 차이를 직접 확인해보세요.

데이터의 구간 나누기 (qcut)

  • total_cases와 total_deaths가 숫자로 나와있는데 이걸 인식하기 쉽게 다섯 개의  구간을 나누어 매우낮음~매우높음으로 설정하겠습니다. 
  • qcut은 빈도수에 따라 구간을 나누는 것입니다. (cut은 구간 길이에 따라 나눕니다.)
  • q 값 만큼 구간을 등분합니다.
covidtotalsonly = covidtotals.loc[:, totvars]
# covidtotalsonly
covidtotalsonly['total_cases_q'] = pd.qcut(covidtotalsonly['total_cases'],
                                         labels = ['매우 낮음', '낮음', '중간', '높음', '매우 높음'],
                                         q=5, precision=0)

covidtotalsonly['total_deaths_q'] = pd.qcut(covidtotalsonly['total_deaths'],
                                         labels = ['매우 낮음', '낮음', '중간', '높음', '매우 높음'],
                                         q=5, precision=0)
covidtotalsonly.head(2)

  • 두 컬럼을 시각화해보겠습니다.
pd.crosstab(covidtotalsonly['total_cases_q'], covidtotalsonly['total_deaths_q'])

  • 여기서 이상치는 무엇일까요? 우리가 눈여겨봐야 하는 건 그 이상치입니다. 여기에 해당하는 국가를 코드로 구현해서 찾을 것입니다. 

  • 주황색으로 표시한 부분은 당연한 결과입니다. 아무래도 케이스가 많을수록 사망자 수도 많을 것입니다. 이러한 경향성에서 멀수록 우리는 해석할 이유가 있다는 것입니다. 
  • 예를 들어 total_cases가 '낮음'이고 total_deaths가 '높음'인 한 국가가 어느 나라인지 다음의 코드로 찾을 수 있습니다. 
covidtotals.loc[(covidtotalsonly.total_cases_q == '낮음') & (covidtotalsonly.total_deaths_q == '높음')].T

행 추가하기

  • 다음 데이터프레임을 입력합니다.
df = pd.DataFrame({
    "이름": ["Evan", "Sara", "Chris", "Messi", "Woods", "Jordan"], 
    "국어": [70, 90, None, None, 100, 85], 
    "영어": [65, 100, None, 90, 70, 80]
})

df

  • 인덱스 6에 해당하는 행을 추가하겠습니다.
df.loc[6, "이름":"영어"] = ["kim", 50, 60]
df

  • 국어 점수가 90점 이상이면 A, 80점 이상이면 B, C 나머지는 F인 열을 추가하는 것은 어떻게 할까요?
# 방법 1

df['등급'] = df['국어'].apply(lambda score: 'A' if score >= 90 else ('B' if score >= 80 else ('C' if score >= 70 else 'F')))
df

# 방법 2
# pandas & numpy
# numpy에 where 조건식이 있음. 한번만 적용하면 됨. - 참일 때, 거짓일 때
# 다중 조건식일 때는?
# np.where(조건식, 참, np.where(조건식, 참))

# np.select(다중조건식, 다중결괏값, default ==> else 조건)

condition_list = [(df["국어"] >= 90), (df['국어'] >= 80) & (df['국어'] < 90), (df['국어'] >= 70) & (df['국어'] < 80)]
choice_list = ["A", "B", "C"]
df['점수'] = np.select(condition_list, choice_list, default = "F")
df

감사합니다.