본문 바로가기

SK네트웍스 Family AI캠프 10기/Daily 회고

18일차. Data Visualization(Seaborn) & Data Cleaning

더보기

 

18일 차 회고.

 

 ADsP 시험 날짜가 2월 22일인데, SQLD 시험 날짜가 3월 8일이라서 시간이 엄청 촉박할 것 같다. 그런데 여기에 이제 토이 프로젝트 개발이 본격적으로 시작되어서 시간을 잘 활용해야 할 것 같다.

 

 

 

 

1. Data Visualization - Seaborn

 

 

!pip install koreanize-matplotlib

import pandas as pd
import numpy as np

import matplotlib.pyplot as plt
import koreanize_matplotlib

 

import seaborn as sns

sns.set_theme(style="darkgrid")

 

1-1. Relational plots

 

두 변수의 관계를 볼 때 사용한다.

 

scatterplot

tips = sns.load_dataset("tips")

tips.info()
# <class 'pandas.core.frame.DataFrame'>
# RangeIndex: 244 entries, 0 to 243
# Data columns (total 7 columns):
#  #   Column      Non-Null Count  Dtype   
# ---  ------      --------------  -----   
#  0   total_bill  244 non-null    float64 
#  1   tip         244 non-null    float64 
#  2   sex         244 non-null    category
#  3   smoker      244 non-null    category
#  4   day         244 non-null    category
#  5   time        244 non-null    category
#  6   size        244 non-null    int64   
# dtypes: category(4), float64(2), int64(1)
# memory usage: 7.4 KB

 

tips.head()

 

sns.replot(data=tips, x="total_bill", y="tip", kind="scatter")

 

sns.replot(data=tips, x="total_bill", y="tip", hue="smoker", style="time")

 

sns.replot(data=tips, x="total_bill", y="tip", hue="size", size="size", sizes=(15, 200))

 

sns.relplot(data=tips, x="total_bill", y="tip", hue="smoker", col="time")

 

lineplot

fmri = sns.load_dataset("fmri")

fmri.info()
# <class 'pandas.core.frame.DataFrame'>
# RangeIndex: 1064 entries, 0 to 1063
# Data columns (total 5 columns):
#  #   Column     Non-Null Count  Dtype  
# ---  ------     --------------  -----  
#  0   subject    1064 non-null   object 
#  1   timepoint  1064 non-null   int64  
#  2   event      1064 non-null   object 
#  3   region     1064 non-null   object 
#  4   signal     1064 non-null   float64
# dtypes: float64(1), int64(1), object(3)
# memory usage: 41.7+ KB

 

fmri.head()

 

sns.relplot(data=fmri, x="timepoint", y="signal", kind="line")

 

sns.relplot(data=fmri, x="timepoint", y="signal", kind="line", estimator=None)

 

sns.relplot(data=fmri, x="timepoint", y="signal", kind="line", 
            hue="region", style="event", dashes=False, markers=True)

 

sns.relplot(data=fmri, x="timepoint", y="signal", kind="line",
            hue="subject", col="region", row="event", height=3, estimator=None)

 

 

 

2. Data Preprocessing

 

 

2-1. Pandas EDA

 

데이터 종류

  • 정형 데이터(Structured Data)
    • 고정된 필드에 저장된 데이터
    • ex. 관계형 데이터베이스, 스프레드시트(엑셀) 등
  • 반정형 데이터(Semi-Structured Data)
    • 고정된 필드에 저장되어 있지는 않지만, 메타데이터나 스키마 등을 포함하는 데이터
    • ex. XML, HTML, JSON, 이메일 등
  • 비정형 데이터(Unstructured Data)
    • 고정된 필드에 저장되어 있지 않은 데이터
    • ex. 텍스트, 이미지, 동영상, 음성 데이터 등

 

데이터 유형

  • 수치형 데이터(Quantitative Data)
    • 수치형 데이터는 수치를 값으로 가지기 때문에, 수학적인 활용이 가능하다.
    • ex. 히스토그램 
    • 연속형 데이터(Continuous Data)
      • 더 작은 단위로 나눌 수 있다.
    • 이산형 데이터(Discrete Data)
      • 더 작은 단위로 나눌 수 없다.
  • 범주형 데이터(Categorical Data)
    • 범주형 데이터는 범주 또는 그룹으로 나타나는 데이터로, 수치를 가지더라도 수학적인 활용이 불가능하다.
    • ex. 막대 그래프
    • 순서형 데이터(Ordinal Data)
      • 순서 관계가 있다.
    • 명목형 데이터(Nominal Data)
      • 순서 관계가 없다.

 

데이터 누수(Data Leakage)

데이터 누수는 미래에 대한 전혀 알 수 없는 정보가 모델학습에서 사용된 경우를 말한다.

즉, 평가 데이터가 모델의 학습에 이용된 경우로, 학습 데이터와 평가 데이터를 물리적으로 분리해야 한다.

 

탐색적 데이터 분석(EDA; Exploratory Data Analysis)

EDA는 데이터를 분석하고 결과를 도출하는 과정에 있어서 지속적으로 해당 데이터에 대한 탐색과 이해를 기본적으로 가져야 한다는 것을 말한다.

 

분석 방법

  • 상관계수 
    • 두 변수 사이의 상관관계의 정도를 나타내는 수치
    • 특징
      • 상관계수는 항상 -1과 1 사이에 있다.
      • 상관계수의 절댓값의 크기는 직선관계에 가까운 정도를 나타낸다.
      • 상관계수의 부호는 관계의 방향을 나타낸다.
      • 상관계수가 0에 가까울수록 상관관계가 없다.
      • 상관계수는 단위가 없다.
    • 인지하지 못한 여러 잠재변수가 있을 수 있기 때문에, 큰 상관계수 값이 항상 두 변수 사이의 인과관계를 의미하지는 않는다.

  • 왜도
    • 데이터 분포의 비대칭도를 나타내는 통계량
    • 해석
      • -0.5 ~ 0.5: 상당히 대칭적
      • -1 ~ -0.5 또는 0.5 ~ 1: 적당히 치우침
      • -1보다 작거나 1보다 큰 경우: 상당히 치우침
    • 분포가 왼쪽으로 치우쳐져 있고, 오른쪽으로 긴 꼬리를 가진 경우, 왜도는 양수의 값을 가진다.
      • 평균 > 중앙값 > 최빈값
    • 정규분포와 같이 좌우 대칭인 경우, 왜도는 0에 가까워진다.
      • 평균 = 중앙값 = 최빈값
    • 분포가 오른쪽으로 치우쳐져 있고, 왼쪽으로 긴 꼬리를 가진 경우, 왜도는 음수의 값을 가진다.
      • 평균 < 중앙값 < 최빈값

  • 첨도
    • 확률 분포의 뾰족한 정도를 나타내는 지표
    • 해석
      • Mesokurtic(첨도 = 3): 정규 분포와 유사한 첨도 통계량
      • Leptokurtic(첨도 > 3): 피크가 Mesokurtic보다 높고 날카롭기 때문에 특이치가 많다는 것을 의미한다.
      • Platykurtic(첨도 < 3): 피크가 Mesokurtic보다 낮고 넓기 때문에, 특이치가 부족하다는 것을 의미한다.
    • 첨도값이 0보다 작을 경우, 정규분포보다 더 완만하고 납작한 분포를 가진다.
    • 첨도값이 0보다 클 경우, 정규분포보다 더 뾰족한 분포를 가진다.
  • 이상치
    • 데이터에 이상치가 있으면 왜곡된 의미를 전달할 가능성이 높다.
    • 박스플롯을 활용하면 이상치가 얼마나 포함되어 있는지를 쉽게 판단할 수 있다.

 

타이타닉 - Load Dataset

 

# 데이터 분석 라이브러리
import pandas as pd
import numpy as np

# 시각화 라이브러리
import matplotlib.pyplot as plt
import seaborn as sns

 

df = sns.load_dataset('titanic')

df.shape				# (891, 15)

df.info()
# <class 'pandas.core.frame.DataFrame'>
# RangeIndex: 891 entries, 0 to 890
# Data columns (total 15 columns):
#  #   Column       Non-Null Count  Dtype   
# ---  ------       --------------  -----   
#  0   survived     891 non-null    int64   
#  1   pclass       891 non-null    int64   
#  2   sex          891 non-null    object  
#  3   age          714 non-null    float64 
#  4   sibsp        891 non-null    int64   
#  5   parch        891 non-null    int64   
#  6   fare         891 non-null    float64 
#  7   embarked     889 non-null    object  
#  8   class        891 non-null    category
#  9   who          891 non-null    object  
#  10  adult_male   891 non-null    bool    
#  11  deck         203 non-null    category
#  12  embark_town  889 non-null    object  
#  13  alive        891 non-null    object  
#  14  alone        891 non-null    bool    
# dtypes: bool(2), category(2), float64(2), int64(4), object(5)
# memory usage: 80.7+ KB

 

df_head()

 

df_tail()

 

타이타닉 - 수치형 데이터

df_number = df.select_dtypes(include=np.number)
df_number.shape					# (891, 6)

df_number.describe()

 

  • 수치형 데이터 중 범주형 데이터: survived, pclass, sibsp, parch
  • 수치형 데이터: age, fare

 

타이타닉 - 범주형 데이터

df_object = df_select_dtypes(exclude=np.number)
df_object.shape					# (891, 9)

df_object.describe()

 

  • 삭제할 데이터: embarked, class, alive
  • 확인할 데이터: who, adult_male
  • 결측치가 있는 데이터: deck, embark_town

 

타이타닉 - 데이터 수정

df['survived'] = df['survived'].astype(str)
df['pclass'] = df['pclass'].astype(str)
df['sibsp'] = df['sibsp'].astype(str)
df['parch'] = df['parch'].astype(str)

df.drop(['embarked', 'alive', 'class'], axis=1, inplace=True)

df.info()
# <class 'pandas.core.frame.DataFrame'>
# RangeIndex: 891 entries, 0 to 890
# Data columns (total 12 columns):
#  #   Column       Non-Null Count  Dtype   
# ---  ------       --------------  -----   
#  0   survived     891 non-null    object  
#  1   pclass       891 non-null    object  
#  2   sex          891 non-null    object  
#  3   age          714 non-null    float64 
#  4   sibsp        891 non-null    object  
#  5   parch        891 non-null    object  
#  6   fare         891 non-null    float64 
#  7   who          891 non-null    object  
#  8   adult_male   891 non-null    bool    
#  9   deck         203 non-null    category
#  10  embark_town  889 non-null    object  
#  11  alone        891 non-null    bool    
# dtypes: bool(2), category(1), float64(2), object(7)
# memory usage: 65.7+ KB

 

타이타닉 - 수치형 데이터 분석 - 통계 분석

df_number.describe()

 

타이타닉 - 수치형 데이터 분석 - 왜도 분석

df_number['age'].skew()				# 0.38910778230082704

df_number['age'].plot.hist(bins=50)

 

df_number['age_log'] = np.log(df_number['age'])

df_number['age'].skew(), df_number['age_log'].skew()
# (0.38910778230082704, -2.299848848658492)

sns.distplot(df_number['age_log'])

 

df_number['age_sqrt'] = np.sqrt(df_number['age'])

df_number['age'].skew(), df_number['age_sqrt'].skew()
# (0.38910778230082704, -0.6609802729981862)

sns.distplot(df_number['age_sqrt'])

 

타이타닉 - 수치형 데이터 분석 - 첨도 분석

df_number['fare'].kurt()				# 33.39814088089868

df_number['fare'].plot.hist(bins=50)

 

df_number['fare_log'] = np.log1p(df_number['fare'])

df_number['fare'].kurt(), df_number['fare_log'].kurt()
# (33.39814088089868, 0.976142106683104)

sns.distplot(df_number['fare_log'])

 

df_number['fare_sqrt'] = np.log1p(df_number['fare'])

df_number['fare'].kurt(), df_number['fare_sqrt'].kurt()
# (33.39814088089868, 6.282211915236795)

sns.distplot(df_number['fare_sqrt'])

 

타이타닉 - 수치형 데이터 분석 - 이상치 분석

sns.boxplot(data_df_number, y=df_number['age'])
sns.boxplot(data_df_number, y=df_number['age_log'])
sns.boxplot(data_df_number, y=df_number['age_sqrt'])

 

sns.boxplot(data_df_number, y=df_number['fare'])
sns.boxplot(data_df_number, y=df_number['fare_log'])
sns.boxplot(data_df_number, y=df_number['fare_sqrt'])

 

타이타닉 - 상관관계 분석

df_number.corr()

 

df_number.plot(kind='scatter', x='age_log', y='age_sqrt')

 

df_number.plot(kind='scatter', x='age_log', y='fare_log')

 

sns.heatmap(
    df_number.corr(), vmin=-1, vmax=1, annot=True,
    linewidths=0.2, cmap="coolwarm"
)

 

df['survived'] = df['survived'].astype(int)
df['pclass'] = df['pclass'].astype(int)

df_number = df.select_dtypes(include=np.number)

df_number.columns
# Index(['survived', 'pclass', 'age', 'fare'], dtype='object')

df_number.corr()

 

df_number.plot(kind='scatter', x='pclass', y='fare')

 

sns.heatmap(
    df_number.corr(), vmin=-1, vmax=1, annot=True,
    linewidths=0.2, cmap="coolwarm"
)

 

타이타닉 - 교차분석

pd.crosstab(index=df['sex'], columns=df['survived'], margins=True, normalize="all")

 

pd.crosstab(index=df['sex'], columns=df['survived'], margins=True, normalize="columns")

 

pd.crosstab(index=df['sex'], columns=df['survived'], margins=True, normalize="index")

 

df_pivot = pd.pivot_table(
    df, index='pclass', columns='sex', values='survived',
    aggfunc=['mean', 'sum']
)

df_pivot

 

 

2-2. Data Cleaning

 

Data Cleaning

  • 완결성
    • 가능하면 결측값을 제거한다.
  • 유일성
    • 중복 데이터가 있으면 안 된다.
  • 통일성
    • 동일한 데이터 형식 및 단위로 기록되어 있어야 한다.
    • 가능하면 정규화를 한다.
    • 가능하면 이상치를 제거한다.

 

결측치(Missing Value)

  • 결측치 표현
    • NaN(Not a Number): 숫자가 아님
    • Null: 존재하지 않음
    • undefined: 정의되어 있지 않음
  • 결측치 유형
    • 완전 무작위 결측(MCAR; Missing Completely At Random)
      • 어떤 특성의 결측치가 다른 특성의 값들과 아무런 상관관계가 없는 경우 
      • 데이터를 입력한 이가 실수를 했거나, 전산상의 에러가 난 경우
    • 무작위 결측(MAR; Missing At Random)
      • 다른 특성의 값에 따라 결측치의 발생 확률이 계산되지만, 값 자체의 상관관계는 알 수 없는 경우
    • 비무작위 결측(NMAR; Not Missing At Random)
      • 결측치가 일어난 특성의 값이 다른 특성의 값과 상관관계가 있는 경우 

 

결측치 처리

  • 제거
    • 결측치 비율: 10% 미만
    • 결측치가 발생한 행 또는 열을 삭제한다.
  • 치환
    • 결측치 비율: 10% 미만
    • 결측치를 적당한 방법(평균, 중앙값, 최빈값 등)으로 대체한다.
  • 모델 기반 처리
    • 결측치 비율: 10% 이상
    • 결측치를 예측하는 새로운 모델을 구성하고, 이를 기반으로 결측치를 채운다.

 

타이타닉 - Load Dataset

import numpy as np
import pandas as pd

import matplotlib.pyplot as plt
import seaborn as sns

 

df = sns.load_dataset('titanic')

df.shape				# (891, 15)

 

타이타닉 - 유일성 처리

df.drop_duplicates(keep='first', inplace=True, ignore_index=True)

df.shape				# (784, 15)

 

타이타닉 - 완결성 처리

(df.isnull().sum(axis=0) / df.shape[0]).round(4).sort_values(ascending=False)

 

df.drop(['deck'], axis=1, inplace=True)

df.shape				# (784, 14)

df.dropna(subset=['embarked', 'embark_town'], inplace=True)

df.shape				# (782, 14)

(df.isnull().sum(axis=0) / df.shape[0]).round(4).sort_values(ascending=False)

 

df['age'].mean(), df['age'].median()
# (29.809792899408283, 28.0)

df['age_mean'] = df['age'].fillna(df['age'].mean())
df['age_median'] = df['age'].fillna(df['age'].median())

df['age'].mean(), df['age_mean'].mean(), df['age_median'].mean()
# (29.809792899408283, 29.809792899408286, 29.564475703324806)

df[['age', 'age_mean', 'age_median']].isnull().sum().sort_values(ascending=False)

 

from sklearn.impute import SimpleImputer

imputer = SimpleInputer(strategy="mean")

imputer.fit(df[["age"]])
df['age_simple_mean'] = imputer.transform(df[["age"]])

df[['age', 'age_simple_mean']].isnull().sum()

 

from sklearn.impute import KNNImputer

imputer = KNNImputer(n_neighbors=5)

imputer.fit(df[["age"]])
df['age_knn'] = imputer.transform(df[["age"]])

df[['age', 'age_knn']].isnull().sum()

 

from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer

imputer = IterativeImputer(random_state=42)

imputer.fit(df[["age"]])
df['age_iter_none'] = imputer.transform(df[["age"]])

df[['age', 'age_iter_none']].isnull().sum()

 

from sklearn.ensemble import RandomForestRegressor

imputer = IterativeImputer(
    estimator=RandomForestRegressor(verbose=0, random_state=42),
    max_iter=10, verbose=0,
    imputation_order='ascending', random_state=42)

imputer.fit(df[["age"]])
df['age_random'] = imputer.transform(df[["age"]])

df[['age', 'age_randome']].isnull().sum()