본문 바로가기
데이터 분석

파이썬으로 데이터 분석하기(EDA -Exploratory Data Analysis/데이터 시각화/그래프 만들기/csv 파일/공공데이터)

by 오 복 이 2023. 12. 30.

데이터를 기계학습에 사용하기 위해 전처리하고
데이터에서 통찰을 얻기 위한 데이터 시각화 및 분석하는 글입니다.
구글 코랩에서 작성하였고 파이썬을 사용합니다.
사용한 데이터는 공공 데이터 포털의 건강검진 데이터 (링크) 이고 csv 파일 입니다. 


만약 csv 형식의 다른 데이터를 사용하시기 원하신다면 
아래의 코드에서 변수명을 적절히 수정하시면 될 것 같습니다. 

 

기본 설정

  • 모듈 설치
  • 필수 모듈 불러오기

koreanize-matplotlib 설치

!pip install koreanize-matplotlib

 

python, sklearn, numpy 등 모듈 불러오기

# 파이썬 ≥3.5
import sys
assert sys.version_info >= (3, 5)

# 사이킷런 ≥0.20
import sklearn
assert sklearn.__version__ >= "0.20"

# 공통 모듈 임포트
import numpy as np
import os

# 깔금한 그래프 출력을 위해
%matplotlib inline
import matplotlib as mpl
import matplotlib.pyplot as plt
import koreanize_matplotlib
import seaborn as sns

 

데이터 기본 정보 확인

데이터셋 기본 정보 확인하기 데이터를 제공받은 공공 데이터 포털의 데이터 설명을 통해 기본 정보를 확인하였습니다.

  • 국민건강보험공단_건강검진정보 데이터
  • 국민건강보험의 직장가입자와 40세 이상의 피부양자, 세대주인 지역가입자와 40세 이상의 지역가입자의 「일반검강검진」 결과와 이들 일반건강검진 대상자 중에 만 40세와 만 66세에 도달한 이들이 받게 되는「생애전환기건강진단」의 결과
  • 총 34개의 변수로 가입자 일렬번호와 ① 수진자 기본정보 : 성, 연령, 거주지 시도코드와 같은 기본정보 ② 건강검진결과 및 문진정보 : 신체, 몸무게, 허리둘레 등 신체사이즈 정보와 혈압, 혈당, 콜레스테롤, 요단백, 감마지피티와 같은 병리검사결과 시력과 청력, 구강검사와 같은 진단검사결과 그 외 음주와 흡연 여부에 대한 문진결과로 구성

데이터 설명

더보기
  • 기준년도 : 해당 정보의 기준년도
  • 가입자 일련번호 : 해당가입자에 부여한 일련번호
  • 시도코드 : 해당 수진자 거주지의 시도코드
  • 성별코드: 해당 정보 대상자의 성별을 제공함 1(남자), 2(여자)
  • 연령대 코드(5세단위) : 기준년도에 수진자의 나이를 5세 단위로 그룹화(범주화)하여 구분한 코드
  • 신장(5Cm단위) : 검진자의 키(5CM 단위)
    • 예) 100~104CM -> 100CM
  • 체중(5Kg 단위) : 검진자의 몸무게(5KG 단위)
    • 예) 25~29KG -> 25KG
  • 허리둘레 : 검진자의 허리둘레
  • 시력(좌) : 수검자의 좌측 눈의 시력
    • 0.1~2.5 사이의 값으로 표기하며 0.1 이하의 시력은 0.1, 실명은 9.9로 표기
  • 시력(우) : 수검자의 우측 눈의 시력
    • 0.1~2.5 사이의 값으로 표기하며 0.1 이하의 시력은 0.1, 실명은 9.9로 표기
  • 청력(좌) : 수검자의 좌측 귀의 청력
    • 1: 정상, 2:질환의심, 3: 측정 불가
  • 청력(우) : 수검자의 우측 귀의 청력
    • 1: 정상, 2:질환의심, 3: 측정 불가
  • 수축기 혈압 : 검진자의 최고 혈압으로 심장이 수축해서 강한 힘으로 혈액을 동맥에 보낼 때의 혈관 내압
  • 이완기 혈압 : 검진자의 최저 혈압으로 심장의 완기시의 혈압
  • 식전혈당(공복혈당) : 검진자 식사 전 혈당(혈액 100ml당 함유 되어 있는 포도당의 농도) 수치
  • 총 콜레스테롤 : 혈청 중의 에스텔형, 비에스테형(유리)콜레스테롤의 합 ­ 정상치는 150~250mg/dL 약 1/3이 비에스텔형(유리)콜레스테롤이며 나머지가 콜레스테롤에스테르
  • 트리글리세라이드: 단순지질 혹은 중성지질을 뜻함 ­ 글리세롤에 3분자 지방산이 에스테르 합한 것으로서 자연계에서 찾아낼 수 있는 지방유도체 가운데 가장 분포가 넓음 ­ 정상치는 30135mg/dL(0.341.52-mmol/ℓ)
    • 트리클리세라이드항목은 2008년부터 건강검진 문진항목으로 추가되었기 때문에, 기준년도가 2002년부터 2007년까지 인 경우 해당 항목 값이 결측 처리되어 제공됨
  • HDL 콜레스테롤 : HDL(고밀도 리포단백질)에 포함되는 콜레스테롤 ­ 작은 입자의 콜레스테롤로 세포에 이끌려간 콜레스테롤을 간으로 돌려주고 혈관 벽에 쌓인 나쁜 콜레스테롤을 없애는 역할을 하는 성분 ­ 정상치는 30~65mg/dL
    • HDL콜레스테롤 항목은 2008년부터 건강검진 문진항목으로 추가되었기 때문에, 기준년도가 2002년부터 2007년까지 인 경우 해당 항목 값이 결측 처리되어 제공됨
  • LDL 콜레스테롤 : LDL(저밀도 리포단백질)에 함유된 콜레스테롤 ­ 입자가 매우 큰 콜레스테롤로 양이 과도하게 증가할 경우,혈관벽에 쌓여서 동맥경화나 각종 질병을 야기 하는 성분 ­ 170mg/dL 이상일 경우 일반적으로 고LDL혈증으로 봄
    • LDL콜레스테롤 항목은 2008년부터 건강검진 문진항목으로 추가되었기 때문에,기준년도가 2002년부터 2007년까지 인 경우 해당 항목 값이 결측 처리되어 제공됨
  • 혈색소 : 혈액이나 혈구 속에 존재하는 색소단백으로 글로빈(globin)과 엠(heme)으로 구성되며 혈중의 산소운반체로서의 역할 수행
  • 요단백 : 소변에 단백질이 섞여 나오는 것 ­ - 1(-), 2(±), 3(+1), 4(+2), 5(+3), 6(+4)로 표기됨
  • 혈청크레아티닌 : 크레아티닌은 크레아틴의 탈수물로 내인성 단백대사의 종말산물로서 신장에서 배설되고 그 증감은 음식물에 관계없이 근육의 발육과 운동에 관계함 ­ 혈청크레아티닌 농도는 신기능장애에 의해 증량함 ­ 정상치 0.8~1.7mg/dL
    • 혈청크레아티닌 항목은 2008년부터 건강검진 문진항목으로 추가되었기 때문에, 기준년도가 2002년부터 2007년까지 인 경우 해당 항목 값이 결측 처리되어 제공됨
  • (혈청지오티)AST : 간 기능을 나타내는 혈액검사상의 수치, 간세포 이외에 심장, 신장, 뇌 , 근육 등에도 존재하는 효소로 이러한 세포들이 손상을 받는 경우 농도가 증가함 ­ - 정상치 0~40IU/L
  • (혈청지오티)ALT : 간 기능을 나타내는 혈액검사상의 수치, ALT는 주로 간세포 안에 존재하는 효소로, 간세포가 손상을 받는 경우 농도가 증가함 ­ - 정상치 0~40IU/L
  • 감마 지티피 : 간 기능을 나타내는 혈액검사상의 수치, 간 내의 쓸개관(담관)에 존재하는 효소로 글루타민산을 외부에 펩티드나 아미노산 등으로 옮기는 작용을 함, 쓸개즙(담즙) 배설 장애, 간세포 장애 발생 시 혈중에 증가하게 됨 ­ - 정상치 남성 1163IU/L, 여성 835IU/L
  • 흡연상태 : 해당 수검자의 흡연 상태 여부 ­ - 1(피우지 않는다), 2(이전에 피웠으나 끊었다), 3(현재도 피우고 있다)
  • 음주여부 : 해당 수검자의 음주 상태 여부 ­ - 0(마시지 않는다), 1(마신다) ­ - N(마시지 않는다), Y(마신다)
  • 구강검진 수검여부 : 해당 검진자가 구강검진을 선택하여 검진하였는지 여부에 대한 항목 ­ - 0(미수검), 1(수검) ­ - N(미수검), Y(수검)
  • 치아우식증유무 : 해당 수검자의 치아우식증 유무에 대한 항목 ­ - 0(없음), 1(있음)
  • 치석 : 해당 수검자의 치석 여부
    • 0(없음), 1(있음) ­ - N(없음), Y(있음)
  • 데이터 공개일자 : 데이터 작성 기준일자

 

데이터 불러오기

  • Google Drive에서 파일 불러오기
  • csv 파일을 판다스 데이터프레임으로 불러오기
from google.colab import drive
drive.mount("/content/drive")
import pandas as pd
DATA_PATH ="/content/data/국민건강보험공단_건강검진정보_20211229.CSV"
def load_data(data_path= DATA_PATH):
    csv_path = os.path.join(data_path)
    return pd.read_csv(csv_path, encoding="cp949" )

데이터 확인

info(), describe() 사용

original_medical_checkup = load_data()
original_medical_checkup

실행 결과

original_medical_checkup.info()

실행 결과

 

 

original_medical_checkup.describe()

 

실행 결과

결과를 살펴보면 가입자 일련번호 별 건강검진 결과 데이터가 있으며,
1000000개의 관측치, 31개의 특성으로 구성되어 있습니다.

 

결측치 확인 

isnull() 사용

original_medical_checkup.isnull().sum()

 

실행 결과

 

original_medical_checkup.isnull().sum().plot.barh(figsize=(10,9))

 

 

결측값이 전체 표본의 50%를 넘어가는 특성은  콜레스테롤, 트리글리세라이드, HDL 콜레스테롤 ,LDL 콜레스테롤, 치아우식증유무, 치석이고 총 6개입니다. 특성 중 "총 콜레스테롤, 트리글리세라이드, HDL 콜레스테롤, LDL 콜레스테롤"은 데이터 설명에 따르면 2008년부터 건강검진 문진항목으로 추가되었기 때문에, 기준연도가 2002년부터 2007년까지 인 경우 해당 항목 값이 결측 처리되어 제공된 것으로 예상됩니다. 만약 기계학습에 사용할 것이라면 결측값이 높은 경우 영향을 미칠 수 있기 때문에 사용하지 않고 제외하거나 결측값을 중앙값이나 최빈값으로 대체해 볼 수 있습니다.

 

만약에 삭제 한다면

# 결측값이 전체 표본의 50%를 넘어가는 6개의 특성 삭제
missing_cols = ["총 콜레스테롤","트리글리세라이드", "HDL 콜레스테롤" ,"LDL 콜레스테롤", "치아우식증유무", "치석"]
prepare_medical_checkup = original_medical_checkup.drop(missing_cols, axis = "columns")

 

 

각 특성의 유니크 값 개수 확인

data_unique_cnt = []
data_unique = []
for i in prepare_medical_checkup.columns.values:
    data_unique_cnt.append(len(prepare_medical_checkup[i].unique()))
    data_unique.append(prepare_medical_checkup[i].unique())
unique_train = pd.DataFrame()
unique_train['Columns'] = list(prepare_medical_checkup.columns)
unique_train['Unique_value_Count'] = data_unique_cnt
unique_train['Unique_value'] = data_unique

unique_train

 

 

실행 결과

기준연도, 데이터 공개일자는 하나의 값으로 구성되어 있습니다.

 

 


 

특성 유형 분리

  • 우선 분석할 필요가 없는 특성은 제외합니다.
    • 데이터 공개일자와 기준 년도 : 관측치 모두 같은 값을 가짐
    • 가입자 일련번호 : 임의로 부여된 값
  • 특성을 범주형과 수치형으로 나누어 분석했습니다.
    • 범주형 특성 : 시도코드, 성별 코드, 청력(좌), 청력(우), 흡연상태, 음주여부, 구강검진 수검여부
    • 수치형 특성 : 연령대 코드(5세단위), 신장(5Cm단위), 체중(5Kg 단위), 허리둘레, 시력(좌), 시력(우),수축기 혈압,이완기 혈압,식전혈당(공복혈당),혈색소,요단백,혈청크레아티닌,(혈청지오티)AST, (혈청지오티)ALT, 감마 지티피

drop() 사용

missing_cols = ["데이터 공개일자","기준년도","가입자 일련번호"]
prepare_medical_checkup = prepare_medical_checkup.drop(missing_cols, axis = "columns")

 

categorical = ['시도코드', '성별코드', '청력(좌)', '청력(우)', '흡연상태', '음주여부', '구강검진 수검여부']

quantitative = ['연령대 코드(5세단위)', '신장(5Cm단위)', '체중(5Kg 단위)', '허리둘레', '시력(좌)', '시력(우)','수축기 혈압','이완기 혈압','식전혈당(공복혈당)','혈색소','요단백','혈청크레아티닌','(혈청지오티)AST', '(혈청지오티)ALT', '감마 지티피']

print('범주형 columns : {0} 개'.format(len(categorical)))
print('수치형 columns : {0} 개'.format(len(quantitative)))

 

 

실행 결과

범주형 데이터 탐색

범주형 특성 7개를 막대그래프로 시각화하여 데이터를 탐색

fig, axes = plt.subplots(4, 2, figsize=(20,15))
fig.suptitle('Distribution of customers per category', fontsize=40)

for ax, feature in zip(axes.flatten(), categorical):
    sns.countplot(data = prepare_medical_checkup, x = feature, ax=ax)
plt.show()

실행 결과

시도코드 그래프에 따르면 41(경기도) 관측치가 가장 많고 11(서울특별시) 관측치가 두 번째로 많습니다.

성별 코드 그래프를 보면 남녀 비율을 거의 같으며 여성이 조금 더 적습니다.

청력(좌)과 청력(우) 그래프는 비슷한 양상을 보이며 2(비정상) 관측치가 적고 3(측정불가) 관측치는 거의 없습니다.

흡연 상태 그래프에 따르면 1( 피우지 않는다) 가 가장 많고 2(이전에 피웠으나 끊었다)와 3( 현재도 피우고 있다)가 거의 같습니다.

­음주여부 그래프에서는 0(마시지 않는다)가 약 40% 1(마신다) 약 60%를 차지하고 있습니다.

구강검진 수검여부는 ­ 0(미수검)이 약 65% 1(수검) 이 35%를 차지하고 있습니다.

 

 

수치형 데이터 탐색

수치형 특성 15개를 막대그래프로 시각화하여 데이터를 탐색

 

fig, axes = plt.subplots(5, 3, figsize=(20,20))
fig.suptitle('Distribution of quantitative features', fontsize=40)
#plt.tight_layout()

for ax,feature in zip(axes.flatten(),quantitative):
    sns.histplot(data = prepare_medical_checkup, x = feature, ax=ax, color='#f55354', edgecolor='#f15354')
plt.show()

 

실행 결과

신장, 체중, 혈색소는 정규 분포를 따릅니다.

연령대 코드를 보면 전체 1 (0 ~ 4세) ~ 18(85세+) 연령대 중에서 9(40 ~ 44세)~18(85세+)의 관측치만 존재하는 것을 알 수 있습니다.

 

수치형 특성 15개를 Boxplot 그래프로 시각화 하여 데이터를 탐색

  • 위의 그래프에서 잘 보이지 않는 데이터 분산을 시각화 합니다.
  • 이상치를 확인합니다.

 

fig, axes = plt.subplots(5, 3, figsize=(20,20))
fig.suptitle('Distribution of quantitative features', fontsize=40)
#plt.tight_layout()

for ax,feature in zip(axes.flatten(),quantitative):
    sns.boxenplot(data = prepare_medical_checkup, x = feature, ax=ax, color='#f15354')
plt.show()

 

실행 결과

 

­ 혈청크레아틴 그래프를 보면 특정값에 중심적으로 0.8~1.7mg/dL

시력(좌)와 시력(우) 그래프에서 9.9 값인 이상치가 존재하는데 데이터 설명에 따르면 ­ "0.1~2.5 사이의 값으로 표기하며 0.1 이하의 시력은 0.1, 실명은 9.9로 표기"라고 명시되어 있습니다.

이는 실명을 9.9 값으로 범주형으로 입력한 것입니다.

따라서 실명을 낮은 수의 값으로 변경합니다.

prepare_medical_checkup.loc[(prepare_medical_checkup["시력(좌)"]== 9.9 ) | (prepare_medical_checkup["시력(우)"]== 9.9 ) ] = 2.0

 

from scipy import stats
def IQR(column):
    Q1 = column.quantile(0.25)
    Q3 = column.quantile(0.75)
    IQR = Q3 - Q1
    lower_bound = Q1 -(1.5 * IQR)
    upper_bound = Q3 +(1.5 * IQR)
    return lower_bound, upper_bound

outliers = prepare_medical_checkup[quantitative].apply(lambda column: IQR(column))
print('Range of outliers by method')
outliers

 

def IQRsum(column):
    Q1 = column.quantile(0.25)
    Q3 = column.quantile(0.75)
    IQR = Q3 - Q1
    lower_bound = Q1 -(1.5 * IQR)
    upper_bound = Q3 +(1.5 * IQR)
    return ((column < lower_bound) | (column > upper_bound)).sum()

outliers = prepare_medical_checkup[quantitative].apply(lambda column: IQRsum(column))
print('Number of outliers by method')
outliers

실행 결과

 

 

데이터 전처리

 

categorical = ['시도코드', '성별코드', '청력(좌)', '청력(우)', '흡연상태', '음주여부', '구강검진 수검여부']

quantitative = ['연령대 코드(5세단위)', '신장(5Cm단위)', '체중(5Kg 단위)', '허리둘레', '시력(좌)', '시력(우)','식전혈당(공복혈당)','혈색소','요단백','혈청크레아티닌','(혈청지오티)AST', '(혈청지오티)ALT', '감마 지티피']

print('범주형 columns : {0} 개'.format(len(categorical)))
print('수치형 columns : {0} 개'.format(len(quantitative)))

수치형 특성 전처리 - 결측치

  • 특성에 존재하는 결측치 처리법 결정합니다.
  • 누락 치를 특성 중앙값으로 채웁니다.
for col in quantitative:
   median = prepare_medical_checkup[col].median()
   prepare_medical_checkup[col].fillna(median, inplace=True)

수치형 특성 전처리 - Data Scaling

from sklearn.preprocessing import StandardScaler

# 객체생성
scaler = StandardScaler()

for col in quantitative:
  scaled_data = scaler.fit_transform(prepare_medical_checkup[col].values.reshape(-1,1))
  prepare_medical_checkup[col] = scaled_data
prepare_medical_checkup

 

 

 

범주형 특성 전처리 - 결측치

  • 관측치 중 범주형 특성에 결측치가 있는 경우 최빈값 입력합니다.
for col in categorical:
  most_value=prepare_medical_checkup[col].value_counts(dropna=True).idxmax()
  prepare_medical_checkup[col].fillna(most_value,inplace=True)

 

범주형 특성 전처리하기

  • 원-핫 인코딩을 활용하여 범주형 데이터를 전처리합니다.
  • 수치화된 번호는 범주들의 인덱스에 해당합니다. 하지만 인덱스 숫자의 크기가 모델 훈련 과정에 잘못된 영향을 줄 수 있기 때문에 이를 방지하기 위해 원-핫-인코딩을 활용합니다.
    • 범주 수 만큼의 새로운 특성 추가
    • 해당되는 범주와 관련된 특성값은 1, 나머지 특성값은 0.
for col in categorical:
  one_hot = pd.get_dummies(prepare_medical_checkup[col], prefix=col)
  prepare_medical_checkup= prepare_medical_checkup.drop([col],axis=1)
  prepare_medical_checkup=  pd.concat([prepare_medical_checkup,one_hot],axis = 1)

 

prepare_medical_checkup.columns

 

 

상관관계 조사

  • 예측하고자 하는 값(Target)을 label 칼럼으로 지정하고 상관관계를 확인할 수 있습니다.
  • 피어슨 상관관계 분석 방법을 이용하여, feature들 간의 상관관계를 히트맵을 그려 어떤 칼럼들이 높은 상관관계를 갖는지 알아보겠습니다.

corr() 사용

train_corr = prepare_medical_checkup[quantitative]
from sklearn.preprocessing import MinMaxScaler
# 수치형 데이터 상관관계 히트맵 시각화

scaler= MinMaxScaler()
train_corr[train_corr.columns] = scaler.fit_transform(train_corr[train_corr.columns])
corr28 = train_corr.corr(method= 'pearson')

plt.figure(figsize=(12,10))
sns.heatmap(data = corr28, annot=True, fmt = '.2f', linewidths=.5, cmap='Blues')
plt.title('Correlation between features', fontsize=30)

실행 결과

# Target과 피쳐들의 상관관계
train_corr = corr28.unstack()
df_temp28 = pd.DataFrame(train_corr['label'].sort_values(ascending=False), columns=['label'])
df_temp28.style.background_gradient(cmap='viridis')

실행 결과

이렇게 전처리된 데이터로 어떠한 특성 값을 다른 특성 값들만을 사용하여 예측하도록 머신러닝에 사용할 수 있습니다.

포스팅 봐주셔서 감사합니다!

 

 

 

 

<참고한 문서>

https://github.com/codingalzi/handson-ml2 

 

 

 

728x90
반응형