본문 바로가기
Study/CODE 3기 [파이썬으로 배우는 데이터 사이언스]

[4조:불사조] 건강검진데이터로 가설검정하기

by jihyeonii 2024. 5. 28.
건강검진데이터로 가설검정하기

①번 가설) 음주 여부는 건강검진 수치와 차이가 있을까?

②번 가설) 신장 또는 허리둘레의 크기가 체중과 상관관계가 있을까?

데이터 분석에 앞서 가장 먼저 해야되는 일  >> "라이브러리 로드"

pandas는 데이터 분석용 라이브러리, numpy는 수치 계산 라이브러리, seaborn과 matplotlib은 데이터의 시각화를 위한

라이브러리입니다.

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

%matplotlib inline

import를 통해 외부 라이브러리를 불러왔습니다.

df = pd.read_csv("/content/drive/MyDrive/NHIS_OPEN_GJ_2017_v1.1.csv", encoding="cp949")
#이때 encoding="cp949"는 문자를 컴퓨터가 읽을 수 있는 바이트 형식으로 변환한건데 cp949는 11124개의 글자를 표현함

파이썬으로 건강검진 데이터를 불러오고 df(data frame)이라는 변수에 데이터 저장해두었습니다.

#건강검진 데이터를 이용한 기초 문법

df.shape =>> 데이터의 행과 열을 알 수 있음
df.head() =>> 데이터 맨 위의 값 5개만을 볼 수 있음
df.tail() =>> 데이터 맨 아래의 값 5개만을 볼 수 있음
df.sample() =>> 데이터들 중 무작위 하나의 값을 볼 수 있음
df.info() =>> 데이터의 인덱스들을 확인할 수 있음(컬럼들, 데이터타입들, 유효데이터들 수)
df.columns =>> 데이터의 컬럼만을 확인할 수 있음
df.dtypes() =>> 컬럼들의 데이터타입만을 확인할 수 있음

이렇게 데이터들의 기본 정보를 확인해볼 수 있습니다.

결측치 확인하기

데이터를 활용하기 전에 우선적으로 데이터를 확인하기위해서 결측치를 확인함. 

데이터의 품질, 분석 결과의 신뢰성 확보등을 위해 결측치의 처리가 필요하다

결측치란 데이터에서 값이 존재하지 않는 경우를 뜻한다.

df.isnull().sum() >> isnull()으로 결측치를 bool(T or F)값으로 표시하고, sum()으로 결측치의 수를 세어줌
df.isna().sum() >> isna()는 위의 isnull()과 똑같은 역할을 행함

True 값은 1을 나타낸다 True 값을 다 합치면 결측치의 양을 나타낼 수 있다.

이런식으로 각 컬럼별 결측치가 얼마나 있는지 확인할 수 있음

df.isnull().sum().plot.barh(figsize=(10,9)
#plot하면 데이터 시각화 가능, bar()는 막대그래프인데 항목들이 세로로, barh는 가로로

이렇게 간단한 컬럼별 결측치 시각화가 가능함

df[["(혈청지오티)ALT","(혈청지오티)AST"]].head()
#[] 한개면 시리즈형태로, [[]]면  데이터프레임으로 출력됨
df[["(혈청지오티)ALT","(혈청지오티)AST"]].describe()
#info, describe를 통해 요약 가능
df["성별코드"].value_counts()
#value_counts()로 데이터의 빈도수 체크 가능

이러한 코드들을 통해 데이터들 중 원하는 데이터만 따와서 데이터들을 요약해보는게 가능합니다.

2차원으로 2개의 컬럼을 행과 열 형태로 나타낸 기본 형태

groupby vs pivot_table

- 2개 이상의 데이터를 보기 위해서 사용함 공통점과 차이점 확실히 알아둡시다.

df.groupby(["음주여부"])["감마지티피"].agg(["count","mean","median"]) 
그룹화된 음주여부에 대해 감마지피티 열의 개수를 세어 집계 결과를 반환함
#이때,agg(aggregate)메서드를 사용해 다양한 집계연산 가능 - count(개수), mean(평균), median(중앙값)

pd.pivot_table(df,index="음주여부",values="감마지티피",aggfunc=["count","mean","median"])
#index: 그룹화할 열, values: 집계할 열, aggfunc: 적용할 집계 함수 목록

=> 공통점) 데이터 그룹화하고 집계연산을 수행하는데 사용되고 유연하게 복잡한 데이터 분석 가능합니다.

=> 차이점) groupby는 데이터를 그룹으로 나누어 각 그룹에 대해 집계연산을 수행하여 출력결과가 시리즈나 데이터프레임 형태로 반환되는 반면  pivot_table은 데이터를 재구조화하여 요약 테이블의 데이터프레임으로 반환됩니다.

이제 그래프들을 이용해 수치 데이터들을 시각화해보겠습니다.

 

h = df.hist(figsize=(12, 12))
#df.hist는 데이터프레임의 히스토그램을 그리는 명령어, figsize=(12,12)은 생성되는 히스토그램의 전체 크기를 설정함

전체 데이터의 시각화

h = df.iloc[:,12:24].hist(figsize=(12,12),bins=100)
#iloc은 데이터프레임의 행과 열을 숫자 인덱스로 선택할 때
사용하는 판다스의 인덱싱 메서드라 특정부분[행,열]만을 선택해 히스토그램을 그릴 수 있음
보통 행에 데이터들이 들어가기때문에 행 전체를 보여주는 일이 많음
#bins를 사용하면 막대수의 개수를 지정할 수 있어 데이터의 분포를 더욱 세밀하게 볼 수 있음

특정 부분의 데이터 시각화

seaborn을 사용한 데이터 시각화

seaborn을 사용하기 전에 데이터의 양이 너무 커 데이터 중  1000개를 추출하여 사용하는 코드 작성하겠습니다.

df_sample = df.sample(1000, random_state=1)
#1000은 데이터 샘플링의 양을 
#random_state는 동일한 데이터를 이용해서 재현하기 위해서 난수변수를 1로 하는것 똑같은 데이터 1000개를 샘플링한다.
df["음주여부"].value_counts().plot.bar()
#위의 코드는 판다스와 매트플립을 통해 직관적이고 쉽게 그래프를 생성했음
sns.countplot(x="음주여부", data=df)
#위의 코드는 매트플립을 기반으로 한 씨본을 사용해서 좀 더 세련되고 일관된 스타일을 제공함

음주여부 값을 세서 그래프 형성한것

df_chol = df.loc[df["총콜레스테롤"].notnull(), "총콜레스테롤"]
#데이터프레임에서 총콜레스테롤에 해당하는 null(결측치)가 아닌 행만을 선택하고 총콜레스테롤 열을 선택해서 df.chol변수에 넣음

※countplot

x축, y축 둘다 범주형 데이터의 빈도수를 분석하고 막대그래프로 시각화하는 타입

예를 들어

plt.figure(figsize=(15,4))
sns.countplot(data=df, x = "신장(5Cm단위)", hue="성별코드")
# 그래프의 크기는 (15,4)로 하고, countplot을 통해 x축은 신장, y축은 개수, hue는 성별코드에 따라 색상을 구분함

이때 성별코드 1은 남자, 2는 여자

 

※barplot

x축은 범주형 데이터, y축은 수치형 데이터로 각 범주에 대한 수치형 변수의 평균을 막대그래프로 시각화하는 타입

예를 들어

plt.figure(figsize=(15,4))
sns.barplot(data=df, x = "연령대코드(5세단위)", y= "총콜레스테롤", hue = "흡연상태")
#그래프의 크기는 (15,4)로, barplot을 통해 x축은 연령대코드, y축은 총콜레스테롤, hue는 흡연상태에 따라 색상 구분하기

이때, 흡연상태 1.0은 흡연 안하는 사람, 2.0은 흡연했다가 끊은 사람, 3.0은 흡연하는 사람

+) ci를 통해 신뢰구간을 설정할 수도 있음 - ex) ci = None : 신뢰구간 비활성화, ci = 99 : 신뢰구간 99%

이런 식으로
sns.barplot(data=df, x="연령대코드(5세단위)", y="트리글리세라이드", hue = "음주여부",ci = None)

 

lineplot

연속형 데이터의 경향성과 변화를 시각화하는데 선 그래프 함수로 나타내는 타입 

예를 들어

sns.lineplot(data=df, x="연령대코드(5세단위)", y="신장(5Cm단위)", hue = "성별코드",ci = "sd")
#lineplot을 통해, x축은 연령대코드, y축은 신장, hue는 성별코드에 따라 색상 구분, ci = "sd"로 설정하여 표준편차를 기반으로 신뢰구간 계산

이때 성별코드 1은 남자, 2는 여자

연령대코드는 5가 20~25세 까지를 포함함.

pointplot

주로 범주형 변수에 대한 평균값 비교와 그 신뢰구간을 나타내기 위해 시각화하는 타입

barplot 과 유사하지만 점으로 표현된다는 차이가 있다.

예를 들어

sns.pointplot(data=df, x="연령대코드(5세단위)", y="신장(5Cm단위)", hue = "성별코드",ci = "sd")
#pointplot을 통해, x축은 연령대코드, y축은 신장, hue는 성별코드에 따라 색상 구분, ci= "sd"로 표준편차를 기반으로 신뢰구간 계산

이때 성별코드 1은 남자, 2는 여자

 

※boxplot

데이터의 전반적인 분포와 이상치를 파악하는데 유용하게 쓰여 시각화하는 타입

예를 들어

plt.figure(figsize=(15,4))
sns.boxplot(data=df, x="신장(5Cm단위)", y= "체중(5Kg단위)", hue="성별코드")
#그래프의 크기는 (15,4)로, boxplot을 통해 x축은 신장, y축은 체중, hue는 성별코드에 따라 색상 구분

이때 성별코드 1은 남자, 2는 여자

상자와 수염으로 표시된다. 

상자는 데이터의 중앙값 ,25% 값 75%값으로 구성되고 수염은 일반적인 데이터의 범위를 나타낸다.
이상치는 수염밖에 개별 점으로 표시된다

 

 

※violinplot

데이터의 분포와 밀도를 동시에 파악하는데 유용하게 쓰여 시각화하는 타입

plt.figure(figsize=(20,4))
sns.violinplot(data=df, x="신장(5Cm단위)", y= "체중(5Kg단위)", hue= "음주여부", split=True)
#그래프의 크기는 (20,4)로, violinplot을 통해 x축은 신장, y축은 체중, hue는 음주여부에 따른 색상 구분, split 기능은 바이올린 플롯을 hue변수에 따라 분할하여 같은 x축 값에서 두 분포를 비교할 수 있게함

 

※swarmplot

범주형 변수의 각 카테고리에 대해 하나하나 표시하여 데이터의 분포와 밀도를 시각화하는 타입

예를 들어

sns.swarmplot(data = df.sample, x="신장(5Cm단위)", y= "체중(5Kg단위)", hue= "음주여부")
#swarmplot을 통해, 추출된 샘플 데이터를 가지고 x축은 신장, y축은 체중, hue는 음주여부에 따라 색상 구분

 

※ scatterplot

x축, y축 둘다 수치형 데이터의 상관관계를 볼 때 유용하게 쓰여 시각화하는 타입 : 산점도

예를 들어

sns.scatterplot(data = df.sample, x = "(혈청지오티)AST", y = "(혈청지오티)ALT", hue = "음주여부", size = "체중(5Kg단위)")
#scatterplot을 통해, 추출된 샘플 데이터를 가지고, x축은 AST, y축은 ALT, hue는 음주여부에 따라 색상 구분, size는 데이터 포인트의 크기를 체중에 따라 다르게 표시함

 

※ lmplot

산점도로 나타나는 scatterplot와 함께 선형 회귀선을 포함하여 데이터의 전반적 경향을 시각적으로 표현하는 타입

예를 들어

sns.lmplot(data = df.sample, x = "신장(5Cm단위)", y = "체중(5Kg단위)", hue = "성별코드", col= "음주여부")
#lmplot을 통해, 추출된 샘플 데이터를 가지고, x축은 신장, y축은 체중, hue는 성별코드에 따른 색상 구분
facets라는 건 데이터를 여러개의 서브플롯으로 나누어 시각화를 하는데 이때 row라면 행 방향,col이면 열 방향
#따라서 위의 코드에서는 음주여부에 따라 열 방향으로 서브 플롯을 나누어 시각화한 것

회귀선의 기울기는 독립 변수가 한 단위 증가할 때 종속 변수가 평균적으로 얼마나 변화하는지를 나타낸다.

즉 신장의 변화에 따라서 체중이 변하는 평균적인 증가율을 의미한다.

 

이상치 다루기

예를 들어

AST ALT 데이터에 대해서 요약해서 본것.

df_ASLT = df.sample[(df.sample["(혈청지오티)AST"] < 400) & (df.sample["(혈청지오티)ALT"] < 400)]
#혈청지오티 AST, 혈청지오티 ALT 둘다 400이하인 값만 가져와서 이상치를 제거한 데이터로만 바라봄

 

sns.lmplot(data = df_ASLT, x = "(혈청지오티)AST", y = "(혈청지오티)ALT", hue = "음주여부", ci = None)
#lmplot을 통해, 이상치를 제거한 데이터만을 가지고 데이터 시각화를 함

 

그래프를 토대로 가설검증하기(1) -신장또는 허리둘레가 체중과 관련이 있을까?

상관관계

가설검증을 위해 변수들끼리의 상관관계를 우선적으로 확인해야한다.

상관계수는 -1부터 1 사이로 구성되고 0.3부터 0.7 사이면 뚜렷한 양적 선형관계, 0.7 이상이면 강한 양적 상관관계이다.

columns= [ '연령대코드(5세단위)', '시도코드', '신장(5Cm단위)',
       '체중(5Kg단위)', '허리둘레', '시력(좌)', '시력(우)', '청력(좌)', '청력(우)', '수축기혈압',
       '이완기혈압', '식전혈당(공복혈당)', '총콜레스테롤', '트리글리세라이드', 'HDL콜레스테롤', 'LDL콜레스테롤',
       '혈색소', '요단백', '혈청크레아티닌', '(혈청지오티)AST', '(혈청지오티)ALT', '감마지티피', '흡연상태']
      #columns라는 변수에 리스트로 데이터 속 관심있는 컬럼들을 다 넣는다.
df_small = df_sample[columns]               #이후 df_small이라는 데이터에 리스트에 저장된 컬럼만 저장하여 사용한다
df_corr = df_small.corr()                   #이후 df_samll의 상관관계를 분석할 수 있다.

 

df_corr.loc[df_corr["체중(5Kg단위)"] > 0.1, "체중(5Kg단위)"]
#체중(5Kg단위)의 상관계수가 0.1 이상인 feature만을 가져옴

 

체중에 대한 상관관계가 0.1보다 높은 것들을 불러옴

*Heatmap

Heatmap은 데이터 값을 행과 열의 형태로 배열하고 각 셀을 색상으로 표현하여 데이터를 시각화하는 방법

-> 데이터의 패턴과 관계를 직관적으로 파악하는데 매우 유용

예를 들어

plt.figure(figsize= (20,7))
sns.heatmap(df_corr, annot=True, fmt=".2f", cmap="Blues")
#그래프의 크기는 (20,7)로, heatmap을 통해, df_corr 변수를 가지고, annot은 히트맵 각 셀에 값을 표시할 지 여부를 지정함 - True면 각 셀에 값 표시
#fmt는 annot이 True일 때, .2f라면 소수점 둘째자리까지 각 셀에 표시, cmap은 히트맵 색상을 지정할 수 있음

mask = np.triu(np.ones_like(df_corr, dtype=bool))
plt.figure(figsize= (20,7))
sns.heatmap(df_corr, annot=True, fmt=".2f", cmap="Blues", mask=mask)
#mask는 대각선 위의 값을 True로 설정해서 상삼각 행렬 부분을 가림

mask 변수를 이용해서 대각선위에 부분을 없애 가독성을 높였음.

1번 가설) 신장 또는 허리둘레의 크기가 체중과 상관관계가 있을까?

체중과 신장: 0.66, 허리둘레와 체중: 0.79
따라서 신장과 체중은 뚜렷한 양적 선형관계를 가지지만 허리둘레와 체중은 강한 양적 상관관계가 있다.

 

 

그래프를 통해 가설설정하기 (2) - 음주여부가 건강검진 상태에 영향을 줄까?

lineplot

위 그래프를 볼 때, 술을 마시는 그룹(1)은 술을 안 마시는 그룹(0)보다 모든 연령대에서 평균 체중이 더 높은 경향을 보임.

swarmplot

혈색소는 적혈구 내에 존재하는 단백질로, 산소를 폐에서 신체의 다른 부위로 운반하고, 이산화탄소를 신체의 다른 부위에서 폐로 운반하는 역할을 함.

 

  • 각 연령대에서 술을 마시는 그룹(1)과 술을 안 마시는 그룹(0)의 혈색소 수치 분포가 겹쳐져 있어서 명확한 차이를 발견하기 어려움.
  • 특정 연령대에서 술을 마시는 그룹과 술을 안 마시는 그룹의 혈색소 수치 차이가 보이지만, 이는 전반적인 경향이라고 보기 어려움.

즉, 음주여부가 혈색소에 영향을 미친다고 보기엔 여러움.

 

lmplot

 

혈청지오티(AST,ALT)는 간 기능을 평가하는 데 사용되는 효소임. 두 효소는 간세포 손상 시 혈액 내로 방출됨. 즉, 두 수치가 높을수록 간 손상이 많이 된 것을 의미함.

술을 마시는 그룹(1)은 전반적으로 높은 AST와 ALT 수치를 보이는 경향이 있음. 특히, 일부 데이터는 매우 높은 AST와 ALT 수치를 보이며, 이는 심각한 간 손상을 나타냄.

 종합적으로, 음주 여부는 혈청지오티(AST, ALT) 수치에 영향을 미치는 것으로 보임. 술을 마시는 경우(1) 높은 AST와 ALT 수치를 보이는 경향이 있으며, 이는 알코올 섭취가 간 손상을 일으킬 수 있음을 시사함.

 

이 그래프는 총콜레스테롤 수치를 보여줌. 

이 그래프를 봤을 때 음주 여부에 따른 총콜레스테롤 수치의 분포 차이는 거의 없음. 또한 두 그룹의 분포 곡선이 거의 겹쳐져 있음

-> 음주여부가 총콜레스테롤 수치에 미치는 영향은 거의없음.

2번 가설) 음주 여부는 건강검진 수치 차이가 있을까?

실제로 데이터시각화도 해보고, 상관관계 분석을 통해 음주여부에 따라 건강에 차이가 있는 걸 알 수 있음.

 

총콜레스테롤과 체중, 허리둘레는 어떠한 관계를 가질까?

우선  총콜레스테롤, 체중, 허리둘레에 대해서 따로 데이터프레임을 만든다.

이상치 판별법을 사용해 측정되는 이상치들을 없애주었음.

 

허리둘레를 예시로 이상치가 아닌 값들의 범위를 구해보자면, 

여기서 1.5*IQR은 (87.8 - 74.1)*1.5 = 20.55 임. 이를 통해 허리둘레 데이터의 범위를 53.55 ~ 108.35로 제한하였다.

그 결과 총콜레스테롤 수치의 범위는 94~294, 허리둘레 수치의 범위는 54~108 값 내에서 데이터를 다루었다.

우선 콜레스테롤과 체중의 관계를 알아보면,

lmplot
scatterplot

이와 같이 두 개의 그래프로 시각해보았을때,

 lmplot 그래프만 봤을 때, 체중이 증가함에 따라 총콜레스테롤 수치가 증가하는 경향이 약간 있지만, 전체적으로 보면 명확한 상관관계는 보이지 않는다.

다음으로 콜레스테롤과 허리둘레의 상관관계를 알아보면,

이 두 그래프를 해석해보면, 총콜레스테롤 수치와 허리둘레 사이에는 약한 상관관계가 존재하지만, 정말 매우 약한편입니다.

즉, 허리둘레가 총콜레스테롤 수치에 미치는 영향은 미미하며, 다른 요인들이 더 큰 영향을 미칠것입니다.

마지막으로 아까 저장해둔 총콜레스테롤에 대한 상관관계값을 이용하여 히트맵으로 표현해보자면,

보이다시피, 상관계수는 매우 작은것을 알 수 있다.

정리하자면, 총콜레스테롤과 체중, 총콜레스테롤과 허리둘레는 명확한 상관관계를 보여주지 않는다.

총콜레스테롤에는 체중, 허리둘레보단 다른 요인들이 영향을 미칠거란 것을 알 수 있다.

 

마무리

이번 프로젝트를 통해 파이썬과 판다스, 시각화 라이브러리들을 활용하여 데이터를 효율적으로 다루고 분석할 수 있다는 경험을 해볼 수 있어 좋았습니다. 또한 시각화를 통해 데이터를 쉽게 이해하고 가설을 검증하는데 큰 도움이 된다는 것을 느꼈고, 단순히 데이터를 보는 것에서 나아가 통계적으로 유의미한 결과를 도출하기 위해 어떠한 접근법을 사용해야하는지 고민을 할 수 있는 시간들이 유의미했습니다.

이번 프로젝트에서는 특정 연도만의 건강검진 데이터를 가지고 변수들의 인과관계를 보았다면 시간이 지남에 따라 건강 상태가 어떻게 변화하는지, 또한 그 결과에는 어떠한 요인이 영향을 미치는 지에 대해 분석해보고 싶습니다.

구체적으로 예를 들면 5년 동안의 건강검진 데이터를 통해 다양한 변수들의 변화를 추적하여 장기적으로 건강 추세를 파악하고 건강 상태가 어떻게 변해가는지 예측하여 예방적 조치를 취할 수 있게끔 하는 적용들을 해보고싶습니다.