FoO의 개발 블로그

데이터 분석과 결측치 제거(feat. Kaggle) 본문

학과공부/기계학습

데이터 분석과 결측치 제거(feat. Kaggle)

FoO__511 2023. 10. 17. 14:04

1학기 시작 후 7주차의 절반쯤 지난 지금, 기계학습 보다는 데이터 분석부터 공부 중이다. 각종 파이썬 라이브러리로 데이터를 분석, 시각화, 이상치 및 결측치 제거 등을 공부했다. 중간고사가 지나면 scikit-learn이라는 라이브러리를 통해 기계학습을 본격적으로 배우기 시작할 것 같다.

 

오늘은 중간고사 대비 겸으로 결측치 제거를 캐글의 데이터셋을 분석하며 정리하려고 한다. 

 

import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd

 

기본적으로 위의 라이브러리들을 불러온 상태에서 시작한다.

데이터 불러오기

https://www.kaggle.com/datasets/rush4ratio/video-game-sales-with-ratings

먼저 캐글에서 데이터를 다운로드 받고, 판다스의 read_csv()함수를 이용해 데이터를 불러온다.

 

위 데이터셋은 "Video Game Sales with Ratings"으로, 심의 등급별 비디오 게임 판매량과 게임의 부가적인 정보들을 제공한다. 

 

pandas.read_csv()

sales = pd.read_csv("데이터 경로")

 

read_csv()에 인자로 index_col을 지정할 수 있는데, 그 경우엔 해당 인덱스의 열이 이 데이터 프레임의 인덱스 역할을 하게 된다. 

 

데이터 분석

pandas.DataFrame.head()

head()함수를 통해 데이터의 첫 몇 행을 볼 수 있다. 끝 행을 보려면 tail()을 이용한다.

 

sales.head(3)

 

데이터셋을 좀 더 직관적으로(예쁘게) 볼 수 있다.

 

pandas.DataFrame.info()

info()함수를 이용하면 각 열의 이름, null이 아닌 데이터의 개수, 데이터 타입을 볼 수 있다. 

 

sales.info()

 

null이 아닌 데이터 개수에 차이가 있음에서 결측치의 존재를 유추할 수 있다.

 

pandas.DataFrame.count()

각 열의 데이터 개수만 알려주는 함수도 있다.

 

sales.count()

pandas.DataFrame.describe()

describe()함수를 이용하면 각 열의 개수, 평균, 표준편차, 최소값, 최대값, 25%, 50%, 75%값을 알 수 있다.

 

sales.describe()

 

pandas.DataFrame.value_count()

각 열의 데이터 종류와 해당 종류의 데이터가 몇 개 있는지 알 수 있다.

출력값이 지저분해져 한 열에 대해서 적용한 예를 든다.

 

sales['Rating'].value_counts()

해당 값은 심의 등급이며, 다음 링크에 더 자세한 설명이 있다.

 

pandas.DataFrame.corr()

correlation의 줄임말이며, 각 열들이 서로 어떤 선형 상관관계에 있는지 계산한 배열을 반환한다.

 

sales.corr()

 

위 행렬은 각 column끼리의 상관을 계산한 것이기 때문에 대칭행렬이며 전치하여도 완전히 같은 행렬이 된다. 

 

1이나 -1에 가까울수록 선형적인 상관관계가 크며, 0에 가까운건 두 데이터가 상관이 없다는 뜻이 아니라 상관을 선형으로 표현하기 힘들다는 의미이다. 

 

상관계수가 양수일 때 양의 상관관계를 가진다고 하며, 음수일 때는 음의 상관관계를 가진다.

 

pandas.DataFrame.hist()

hist()함수를 이용해 숫자인 값을 히스토그램으로 표현할 수도 있다.

 

sales.hist(bins=50, figsize=(20, 15))

bins막대의 개수이며, figsize한 히스토그램의 크기를 결정한다. 막대의 가로는 데이터의 구간이며 세로는 해당 구간에 몇 개의 데이터가 있느냐이다. 히스토그램을 통해 각 값들의 분포를 쉽게 확인할 수 있다.

 

seaborn.displot()

seaborn라이브러리의 displot함수를 통해 가우시안 커널밀도 추정함수를 함께 그릴 수 있다.(kde라고 하는데, 아직 뭔지 안배웠다.) 책에서는 distplot으로 나와있지만 지금은 displot으로 쓰기를 권장한다.

 

sns.displot(sales['User_Score'], kde=True, bins=50)

pandas.plotting.scatter_matrix()

각 열을 두 개씩 모든 경우의 수로 조합해 scatter_matrix()를 그려볼 수 있다.

 

pd.plotting.scatter_matrix(sales, figsize=(12, 8))

각 값들이 서로 어떤 관계를 보이는지 바로 확인할 수 있다. 

1차 함수 모양을 이루는 것들은 선형 관계를 가진다고 볼 수 있으며, 피어슨 상관계수(corr()으로 구한것)도 높게 나타난다.

 

seaborn.heatmap()

위에서 구한 상관계수 행렬을 시본 라이브러리의 히트맵으로 표현하면 각 열의 상관관계를 한눈에 파악할 수 있다.

 

corr_matrix = sales.corr() 
plt.figure(figsize=(15,15))
sns.heatmap(corr_matrix, annot=True, fmt = '.2f', linewidths=.5, cmap='Blues')

annot=True옵션으로 각 셀의 수치를 표시(이 경우엔 각 열끼리의 상관계수)할 수 있다. fmt 옵션으로 숫자를 포맷팅한다. 

 

pandas.DataFrame.plot()

plot()함수로 간단히 시각화를 할 수 있다. kind="scatter"옵션을 줬는데, pandas.DataFrame.plot.scatter()과 같은 뜻이다.

옵션 중 c는 color, cmap은 color map을 뜻한다. s는 size이다. 

 

결측치 찾기

 

결측치를 찾기 위해 isna()나 isnull()함수를 사용할 수 있다.

 

sales.isna()

 

이렇게 boolean 배열을 반환하는데, 원래 데이터프레임에서 해당 칸이 null이나 NaN이면 이 boolean배열에서 그 칸은 True가 된다. 유효한 값이 있으면 그 칸은 False가 된다.

 

isna()나 isnull()으로 얻은 boolean 배열에 sum()함수를 이용해 좀 더 보기 편하게 바꿀 수 있다. 

 

sales.isna().sum()

 

이제 각 행별로 결측치가 몇 개 있는지 한 눈에 보인다.

 

특정 행의 결측치를 알고 싶을 때는 해당 행을 인덱스로 지정하고 위와 똑같이 isna()함수를 사용하면 된다. 그러면 boolean 1차 배열을 반환한다.

sales['Year_of_Release'].isna()

이때 위의 배열을 데이터 프레임의 인덱스로 넣으면, 그 값이 True인 인덱스의 열만 추출할 수 있다.

 

sales[sales['Year_of_Release'].isna()]

결측치 제거

결측치는 크게 두 가지 방법으로 처리할 수 있다.

 

1. 결측치가 있는 행이나 열제거하기.

2. 특정 값으로 결측치를 모두 채우기

 

결측치가 있는 행이나 열을 제거

결측치가 있는 열을 제거한다는 것은 데이터 손실을 불러와 학습에 영향을 줄 수 있음에 유의하여야 한다.

현재 데이터는 위에서 구한 것처럼 출시년도, 제작사, 비판 점수, 비판 개수, 유저 점수, 유저 개수, 등급에 결측치가 있다.

이 중에서 특정 값으로 대체하기 애매한 출시년도제작사가 있는 행을 제거해보자.

 

pandas.DataFrame.dropna()

dropna()함수를 이용하면 결측치가 있는 행을 모두 없앨 수 있다. 

 

test = sales.dropna()
test.isna().sum()

보다시피 결측치가 싹 사라졌다. 하지만 우리가 원하는 것은 출시년도와 제작사의 결측치를 없애는 것뿐이니 dropna()의 subset 옵션을 사용하도록 하자.

 

sales = sales.dropna(subset=['Year_of_Release', 'Publisher'])
sales.isna().sum()

참고로 isna().sum()끝에 인덱싱을 통해 원하는 결과만 볼 수 있다. (sales뒤에 넣든 isna()뒤에 넣든 sum()뒤에 넣든 상관없다.)

 

특정 값으로 결측치를 모두 채우기

결측치가 있는 행을 없애지 않고 해당 칸을 다른 값으로 보충하는 경우에는 보충할 값을 잘 선택해 원하는 학습 결과를 훼손하지 않도록 주의해야 한다.

남아 있는 값들 중에 Rating은 RP(Rating Pending 등급 미정)으로 채우고 나머지에는 평균값을 넣어보자.

 

pandas.DataFrame.fillna()

fillna()함수의 첫번째 인자로 특정 값을 넣으면 그 값으로 NaN값이 채워진다. 이때 inplace=True옵션을 주면 원본 배열이 결측치가 채워진 배열로 덮어써지게 된다. 옵션을 주지 않으면 원본 배열은 변하지 않는다.

 

sales['Rating'].fillna("RP", inplace=True)
sales["Rating"].isna().sum()

이제 Rating열의 결측치는 모두 "RP"로 채워졌다. 만약 데이터프레임에 인덱스없이 바로 fillna()함수를 적용한다면 모든 결측치가 "RP"로 채워졌을 것이다.

 

결측치가 있는 나머지 행들에는 평균값을 넣어보자. fillna()첫번째 인자데이터프레임의 평균값 배열을 넣으면 된다.

 

result = sales.fillna(sales.mean(numeric_only=True))
result.isna().sum()

 

mean()함수의 numeric_only=True 옵션은 해당 데이터프레임에서 숫자값을 가지는 칼럼에 대해서만 평균값계산하겠다는 뜻이다. 옵션을 주지 않으면 아래 경고를 준다.

 

FutureWarning: Dropping of nuisance columns in DataFrame reductions (with 'numeric_only=None') is deprecated; in a future version this will raise TypeError. Select only valid columns before calling the reduction.

 

이렇게 결측치 제거가 끝났다.

 

 

 

다음 글에선 이산치 제거를 다룰 생각이다.