본문 바로가기
Study/CODE 2기 [프로젝트로 배우는 데이터사이언스]

[Trillion(1조)] 프로젝트로 배우는 데이터사이언스_2주차

by 임규민 2024. 3. 18.

2.1.1 당뇨병 데이터셋 미리보기

 

우리가 살펴 볼 데이터셋 출처는 다음과 같다.

 

Google Colab 종료 중

 

colab.research.google.com

 

데이터 구성

  • Pregnancies : 임신 횟수
  • Glucose : 2시간 동안의 경구 포도당 내성 검사에서 혈장 포도당 농도
  • BloodPressure : 이완기 혈압 (mm Hg)
  • SkinThickness : 삼두근 피부 주름 두께 (mm), 체지방을 추정하는데 사용되는 값
  • Insulin : 2시간 혈청 인슐린 (mu U / ml)
  • BMI : 체질량 지수 (체중kg / 키(m)^2)
  • DiabetesPedigreeFunction : 당뇨병 혈통 기능
  • Age : 나이
  • Outcome : 768개 중에 268개의 결과 클래스 변수(0 또는 1)는 1이고 나머지는 0입니다.

라이브러리와 데이터를 로드하고 미리보기를 실행하면 다음과 같이 볼 수 있다.

# 데이터 분석을 위한 pandas, 수치계산을 위한 numpy
# 시각화를 위한 seaborn, matplotlib.pyplot 을 로드합니다. 
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt

df = pd.read_csv("데이터 경로")
df.shape
# 위에서 5개만 미리보기 합니다.
df.head()
(768, 9)

 

데이터의 타입, 결측치, 메모리 사용량 등의 정보를 보기 위해서는 .info() 함수를 사용하면 된다.

df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 768 entries, 0 to 767
Data columns (total 9 columns):
Pregnancies                 768 non-null int64
Glucose                     768 non-null int64
BloodPressure               768 non-null int64
SkinThickness               768 non-null int64
Insulin                     768 non-null int64
BMI                         768 non-null float64
DiabetesPedigreeFunction    768 non-null float64
Age                         768 non-null int64
Outcome                     768 non-null int64
dtypes: float64(2), int64(7)
memory usage: 54.1 KB

이렇게 info()함수를 통해 데이터에 대한 정보를 한눈에 볼 수 있다.

info()함수에서는 non_null을 통해 결측치가 없다고 하였는데 이를 다시 확인해보자.

df_null = df.isnull()
df_null.sum()
Pregnancies                 0
Glucose                     0
BloodPressure               0
SkinThickness               0
Insulin                     0
BMI                         0
DiabetesPedigreeFunction    0
Age                         0
Outcome                     0
dtype: int64

결측치의 합(True=1, False=0)을 보았을 때 모두 0인 것을 보아 모두 False로 결측치가 없다는 것을 알 수 있다.

이렇게 sum을 통해 결측치의 수를 확인할 수 있다.

 

describe() 함수를 통해 수치데이터에 대한 요악을 볼 수 있다.

df.describe()

 

함수 안에 include="object" 옵션을 넣으면 object 타입이 있을 때 포함하여 출력한다.

최대, 최소, 개수, 중간값 등을 확인할 수 있다.

여기서 1사분위는 25%, 2사분위는 50%, 3사분위수는 75%이다.

평균값이 중위값보다 높다는 것은 max 값이 꽤 높다는 뜻입니다.

 

min을 보면 최소값이 모두 0인데 글루코스, 혈압, skinthickness(피부주름두께), BMI은 0일 수 없으므로 결측치일 가능성이 높다.

이렇게 결측값이 없다고 나와도 데이터를 다시 한 번 봐야하는 이유이다.

 

요약값을 통해서는 데이터를 정확히 알 수 없기 떄문에 시각화를 해보자.

먼저 label인 Outcome을 제외하고 feature_columns이라는 변수에 컬럼을 담아보자.

feature_columns = df.columns[:-1].tolist()
feature_columns

['Pregnancies',
 'Glucose',
 'BloodPressure',
 'SkinThickness',
 'Insulin',
 'BMI',
 'DiabetesPedigreeFunction',
 'Age']

 

2.1.2 결측치 보기

 

값을 요약해 보면 최솟값이 0인 것들이 있다. 이때 0이 나올 수 없는 값(인슐린, 혈압 등)에서 0이 나오면 그 값을 결측치라고 볼 수 있을 것이다.

따라서 0인 값을 결측치로 처리하고 시각화 해보고자 한다.

col로 값을따로 만들어 주고 df에서col만 불러와보겠다.

그리고 결측치 여부를 나타내는 데이터 프레임 df_null을 만들고

0을 np.nan(결측치)으로 reaplace()함수를 통해 대체해보겠다.

cols = feature_columns[1:]
df_null = df[cols].replace(0, np.nan)
df_null = df_null.isnull()
df_null.sum()
Glucose                       5
BloodPressure                35
SkinThickness               227
Insulin                     374
BMI                          11
DiabetesPedigreeFunction      0
Age                           0

null값에 mean()함수를 적용하면 결측치의 비율을 구할 수 있고 여기에 100을 곱하면 퍼센트지를 알 수 있다.

df_null.mean() * 100
Glucose                      0.651042
BloodPressure                4.557292
SkinThickness               29.557292
Insulin                     48.697917
BMI                          1.432292
DiabetesPedigreeFunction     0.000000
Age                          0.000000
dtype: float64

여기에 plot.barh()함수를 통해 결측치의 갯수를 막대 그래프로 시각화를 해보자

df_null.sum().plot.barh()

 

결측치를 heatmap으로 시각화 해보자.

 

plt.figure(figsize=(15, 4))
sns.heatmap(df_null, cmap="Greys_r")

 

검은색일때 0 False, 하야면 1 True이다.

cmap="Greys_r"을 통해 결측치만 흰색으로 시각화해보았다.

 

2.1.3 훈련과 예측에 사용할 정답값을 시각화로 보기

 

정답값의 개수를 확인해보자.

df["Outcome"].value_counts()

0    500
1    268
Name: Outcome, dtype: int64

비율은 normalize=True 옵션을 사용하면 된다

df["Outcome"].value_counts(normalize=True)

0    0.651042
1    0.348958
Name: Outcome, dtype: float64

발병하지 않는 경우가 65%, 발병하는 경우가 34%인 것을 확인할 수 있다.

 

다른 변수인 임신 횟수와 함께 이를 비교 해보겠다.

groupby로 임신횟수에 따라서 발병비율을 보고자한다. 이때 index에 올 값을 () 안에 넣고, 그 뒤에 있는 []에 value를 넣는다. 이때 그 평균을 확인하면 다음과 같다.

df_po = df.groupby(["Pregnancies"])["Outcome"].agg(["mean", "count"]).reset_index()
df_po

임신횟수가 늘수록 당뇨병 발생 확률이 느는 것을 볼 수 있다. 이때 100%인 경우는 데이터 빈도수와 함께 보면 그 이유를 알 수 있다. (빈도수가 적어 확률이 높게 나옴)

이 비율들을 시각화 해보자.

df_po.plot()

값이 달라 같이 표현하기 어려우니 특정 값인 mean만 표현하고자 한다면 다음과 같이 사용하면 된다.

df_po["mean"].plot()

이를 통해 임신횟수가 증가할수록 당뇨병 발병도 느는 것을 볼 수 있다.

다음은 막대그래프로 이를 표현한 것이다.

df_po["mean"].plot.bar(rot=0)

(rot=0은 글씨 바로세우기)

 

이제 countplot으로 그래프를 그려보고자 한다.

당뇨병 발병 빈도수를 비교해보자

sns.countplot(data=df, x="Outcome")

발병 비율이 발병하지 않는 비율보다 빈도수가 낮다.

임신 횟수에 따른 당뇨병 발병 빈도수를 비교해보자.

sns.countplot(data=df, x="Pregnancies", hue="Outcome")

hue 라는 옵션은 다른 색상으로 outcome을 표현하고자 하는 것이다.

위에서와 다르게 임신횟수에 따른 빈도를 나타낸것이다.

~6까지는 발병하지 않는 것이 더 높은 것을 확인할 수 있다. 7이 넘어가면 발병이 더 높다.

 

조건의 숫자의 범위가 많을수록 가지가 너무 많이 갈라지고, 오버피팅이 생길 수 있다. 따라서 범주로 묶어주는 것이 좋다.

df["Pregnancies_high"] = df["Pregnancies"] > 6
df[["Pregnancies", "Pregnancies_high"]].head()

Pregnancies_high 변수를 통해 특정 임신횟수 이상/이하를 기준으로 데이터를 봐보겠다.

 

이 값을 시각화 해보겠다.

sns.countplot(data=df, x="Pregnancies_high", hue="Outcome")

임신횟수가 적은 그룹에서는 발병 횟수가 더 낮고, 임신횟수가 높은 그룹에서는 발병한 횟수가 발병하지 않은 횟수보다 더 높은것을 확인할 수 있다.

2.1.4 두 개의 변수를 정답값에 따라 시각화 해보기

 

barplot

 

barplot은 bar 모양의 그래프를 그려주는 함수이며, bar 모양으로 나타나기 때문에 비교 대상 간의 차이를 더욱 쉽게 알 수 있다는 장점이 있다.

 

하지만, 수치의 변화를 나타내는 경우에는 옳지 않은 그래프이기도 하다.

 

강의에서는 barplot을 사용해 당뇨병 여부에 따라 다양한 신체 수치가 어떻게 차이가 나는 가에 대해 알아보았다.

 

Outcome과 BMI 수치 간의 관계 파악

 

data는 데이터셋을 저장한 "df"를 사용하고, x축에는 당뇨병 여부를 True=1 / False=0으로 구분한 "Outcome"의 결과를 넣었고, y축에는 BMI 수치를 삽입하였으며, 코드와 결는 다음과 같다.

sns.barplot(data=df, x="Outcome", y="BMI")

 

 

 

그래프에서는, 당뇨병에 걸린 환자가 걸리지 않은 환자보다 BMI 수치가 약 5 정도 더 높음을 알 수 있다.

 

Outcome과 Glucose 수치 간의 관계 파악

 

알기 전에 먼저 Glucose(포도당) 수치에 대해 알아야 한다.

 

포도당 수치는 혈당 수치와 같으며, 식후 2시간에서의 정상적인 포도당 수치는 90~140 mg/dl이며, 

그 이상은 조절을 필요로 한다.

 

이번에는 당뇨병 여부에 따라 GLucose(포도당) 수치가 어떻게 차이가 나는 지 비교해보았다.

 

x축은 고정이며, y축은 "Glucose" 컬럼을 사용하였다.

 

 

 

#당뇨병 발병에 따른 포도당(Glucose)수치를 비교

sns.barplot(data=df, x="Outcome", y="Glucose")

 

그래프를 통해 당뇨병에 걸린 사람이 그렇지 않은 사람보다 포도당 수치가 약 30 정도 높은 145임을 알 수 있으며, 이는 당뇨병 환자들이 혈당 조절을 해야 함을 알 수 있다.   

 

 

Outcome과 Insulin 수치 간의 관계 파악

 

위와 같이 x축은 고정, y축은 Insulin으로 지정하였다.

 

#Insulin 수치가  0 이상인 관측치에 대해서 당뇨병 발병을 비교함.

sns.barplot(data=df, x="Outcome", y="Insulin")

 

 

당뇨병 환자가 아닌 사람보다 인슐린 수치가 약 30 이상 높은 것을 알 수 있다.

 

그래프에서 bar 위에 검은색 선이 있는데, 이는 데이터 일부를  샘플링하여 95%의 신뢰구간을 나타낸다.

 

그래프를 통해 신뢰구간의 차이가 매우 크다고 볼 수 있다.

 

Outcome과 Pregnancies 간의 평균값 파악

임신 횟수와 당뇨병 발병 비율을 비교해보았다. x축은 임신 횟수를 나타내는 "Pregnancies"로 설정하고, y축은 당뇨병 발병 확률을 비율로 환산했다.

 

#임신횟수에 대해서 당뇨병 발병 비율을 비교함.

sns.barplot(data=df, x="Pregnancies", y="Outcome")

 

 

그래프에서 임신 횟수가 14회 이상인 사람은 100% 확률로 당뇨병이 발병함을 알 수 있으며, 추가적으로 타 횟수보다 7~11 회 출산한 사람의 당뇨병 발병 확률이 평균 50% 이상으로 높은 것을 알 수 있다.

 

Outcome 별로 나누어 Pregnancies에 따른 Glucose 수치 시각화

임신 횟수와 Glucose 간의 상관관계를 Outcome 별로 나누어 분석하였다. 

또한 "hue" 매개변수를 통해 당뇨병 발병 환자는 주황색으로, 아닌 사람은 파란색으로 표시하여 나타냈다.

#임신 횟수(Pregnancies)에 따른 포도당(Glucose) 수치를 당뇨병 발병 여부(Outcome)에 따라 시각화함.
sns.barplot(data=df, x="Pregnancies", y="Glucose", hue="Outcome")

 

 

임신 횟수에 상관 없이 동일 출산 횟수 대비 당뇨병 환자가 정상인보다 Glucose가 높음을 알 수 있다.

(왜 정상인 데이터에는 임신 횟수가 13회까지밖에 없는가?)

 

Outcome 여부를 기반으로 Pregnancies에 따른 BMI 지수 수치 확인

위 함수와 거의 동일하며, y축을 BMI로 변경해주었다.

#임신 횟수에 따른 체질량지수를 당뇨병 발병 여부에 따라 시각화함
sns.barplot(data=df, x="Pregnancies", y="BMI", hue="Outcome")

 

그래프를 통해 당뇨병 발병 환자가 정상인보다 BMI 높은 것을 알 수 있었지만, 임신 횟수에 따른 BMI 수치는 크게 변함이 없는 것을 알 수 있다.

 

Outcome 여부를 기반으로 Pregnancies에 따른 Insulin 수치 확인

 

sns.barplot(data=df, x="Pregnancies", y="Insulin", hue="Outcome")

 

 

그래프에서 볼 수 있듯이, 당뇨병 발병 환자들이 정상인보다 인슐린 수치가 더 높음을 알 수 있다.

또한 평균값이 y축으로 나타나고, 신뢰구간의 차이가 다른 상관관계보다 큰 것을 볼 수 있다.

 

Outcome 여부를 기반으로 Pregnancies에 따른 Insulin 수치 확인

 

 

 

boxplot

 

boxplot이란 데이터의 분포를 시각화하는 그래프이다. 데이터 집합의 중앙값, 사분위수, 최대-최소값 등을 표시한다.

 

Outcome 여부를 기반으로 Pregnancies와 Insulin 수치 간의 여러 상관 계수 확인-1

boxplot을 사용하여 x축에 Pregnancies를, y축에는 Insulin을 넣었고, 당뇨병 발병 여부로 나눠 나타내었다.

 

sns.boxplot(data=df, x="Pregnancies", y="Insulin", hue="Outcome")

 

 

그래프에서, 인슐린 값이 0인 경우가 많아서 boxplot이 아래로 주저앉는 현상이 발생한다. 또한 1사분위수와 3사분위수의 차이가 크며, 최대-최소값의 차이도 크게 나타난다.

 

Outcome 여부를 기반으로 Pregnancies와 Insulin 수치 간의 여러 상관 계수 확인-2

그래프가 아래로 주저앉는 현상을 해결하기 위해, Insulin 값이 0보다 큰 값들만 사용해 코드를 작성해보았다.

 

sns.boxplot(data=df[df["Insulin"]>0], x="Pregnancies", y="Insulin", hue="Outcome")

 

인슐린 수치를 0 초과인 값들만 사용하니, 그래프가 주저앉는 현상이 사라지게 되었다. 또한 임신 횟수가 늘어남에 따라 인슐린 수치가 높아짐을 알 수 있다.

 

violinplot

violinplot이란 boxplot과 kernel density가 결합한 형태로, 다른 유형보다 데이터의 분포를 자세하게 이해할 수 있게끔 해줍니다.

 

Pregnancies과 Insulin 간의 분포도를 확인하기

violinplot을 사용하여 임신 횟수에 따른 Insulin 수치 분포를 확인해보았다. 또한. 당뇨병 발병 환자와 정상인을 구별하기  "split=True" 함수를 사용해 violin이 양 옆으로 나타내게 했다.

 

#위의 그래프를 violinplot으로 시각화함.
plt.figure(figsize=(15,4))
sns.violinplot(data=df[df["Insulin"]>0], x="Pregnancies", y="Insulin", hue="Outcome", split=True)

 

 

google colab의 근본적인 문제인지 모르겠지만, 부스트코스에서 제시해준 그래프와 달리 굉장히 안 예쁘게 나왔다..

 

또한 임신 횟수가 11회 이상일 때부터는 정상적인 violinplot 그래프가 그려지지 않는다는 점도 파악했다.

 

swarmplot

swarmplot이란 범주형 데이터와 연속형 데이터의 관계를 시각화하는데 사용되며, 산포도를 그리는 데 적합하다.

 

 

Pregnancies과 Insulin 간의 산포도를 확인하기

violinplot과 같은 과정을 거치지만, split함수는 violinplot에만 사용할 수 있기 때문에 swarmplot에서는 사용하지 않는다.

 

#위의 그래프를 swarmplot으로 시각화함.

plt.figure(figsize=(15,4))
sns.swarmplot(data=df[df["Insulin"]>0],
               x="Pregnancies", y="Insulin", hue="Outcome") #split은 바이올린에서만 사용 가능

 

 

다음 그래프를 통해 임신 횟수와 당뇨병 발병률이 양의 상관관계를 가짐을 알 수 있게 되었다.

 

 

2.1.5 수치형 변수의 분포를 정답값에 따라 시각화 해보기

 

distplot

 

distplot이란 1개의 수치형 변수를표현할 때 사용하는 시각화 그래프이다. 

 

distplot의 특징은 타  시각화 그래프와 달리 data에 옵션을 다는 것이 아니라 바로 데이터 변수의 Series를 넣는다.

 

Pregnancies에  따른 당뇨병 발병률 확인하기

 

distplot을 통해 임신 횟수에 따른 당뇨병 발병 여부를 확인하고자 한다. 그 전에, "Outcome" 값이 0인 경우(정상인)와 

1인 경우(당뇨병 환자)인 경우를 각각 다른 변수에 지정하면 distplot로 비교할 수 있다. 나는 "Outcome"인 경우가 0인 경우를 "df_0" 으로, 1인 경우를 "df_1"로 지정한 후 distplot을 사용하려고 한다.

 

df_0=df[df["Outcome"]==0]
df_1=df[df["Outcome"]==1]
df_0.shape, df_1.shape

 

 

확인했을 때, "df_o"에는 500개의 행과 10개의 열이 있으므로 총 5000개의 관측치가 있으며, "df_1"에는 268개의 행과 10개의 열이 있으므로 총 2680개의 관측치가 있음을 알 수 있다.

 

이제 두 변수에 대해 distplot을 실시해보자.

 

#임신횟수에 따른 당뇨병 발병 여부를 시각화 함.

sns.distplot(df_0["Pregnancies"])
sns.distplot(df_1["Pregnancies"])

 

 

다음 그래프에서 임신 횟수가 5회가 넘어가면 당뇨병 발병률이 더 높아짐을 알 수 있다.

 

 

Age에  따른 당뇨병 발병률 확인하기

이번엔 나이에 따른 당뇨병 발병률을 확인해보자.

 

sns.distplot(df_0["Age"])
sns.distplot(df_1["Age"])

 

 

다음 그래프를 통해 30세 이후에 당뇨병이 발병하는 케이스가 증가함을 알 수 있다.

 

 

만약, 히스토그램을 포함하지 않고 부드러운 곡선만을 나타내고 싶을 땐

hist=True(or)False 를 사용하면 된다.

 

sns.distplot(df_0["Age"],hist=False)
sns.distplot(df_1["Age"],hist=False)  #히스토그램 제외

 

 

히스토그램을 제거함으로써 수치의 변화가 더욱 자연스러워졌다.(히스토그램이 곡선을 뚫고 올라가는 경우가 없어짐.)

 

또한, 주어진 데이터의 개별 관측치를 x축에 나타내고 싶다면

함수에 rug=True(or)False 를 포함하면 된다.

 

sns.distplot(df_0["Age"],hist=False, rug=True)
sns.distplot(df_1["Age"],hist=False, rug=True)

 

 

rug 함를 통해 "df_0"과 "df_1"에서 데이터가 어느 구간에 분포해있는지 시각적으로도 확인할 수 있게 되었다. 

 

여기서 어떤 색상이 어떤 데이터를 나타내는지 한 눈에 볼 수 있게 각 distplot에  label을 부여해 상단에 색상을 노출되게 한다.

 

sns.distplot(df_0["Age"],hist=False, rug=True, label=0)
sns.distplot(df_1["Age"],hist=False, rug=True, label=1)

 

 

(함수를 잘 실행하였는데, 예시와 달리 상단에 label이 노출되지 않았다..)

2.1.6 서브플롯으로 모든 변수 한 번에 시각화 하기

Subplots

13.1 pandas를 통한 hisplot 그리기

    - pandas를 사용하면 모든 변수에 대한 서브플롯을 한 번에 그려줍니다.

df["Pregnancies_high"] = df["Pregnancies_high"].astype(int)
df

   해당 코드에 대한 설명 전 코드 첫 번째 줄에서 astype()을 이용하는 이유를 한 번 알아보고자 한다. histplot을 그릴 때 boolean 값을 지원하지 않기 때문에 코드 첫 번째 줄에서 .astype(int)을 써주는 것이다.

따라서 결괏값은

다음과 같이 pregnancies_high가 0과1로 바뀐 것을 확인할 수 있다.

 

그리고 이를 시각화해보면

다음과 같은 결과가 나오는 것을 확인할 수 있다.

위의 시각화 코드는 

df["Pregnancies_high"] = df["Pregnancies_high"].astype(int)
h = df.hist(figsize=(10,10))

다음과 같은데 막대를 좀 더 추가해보고 싶으면 

df["Pregnancies_high"] = df["Pregnancies_high"].astype(int)
h = df.hist(figsize=(10,10), bins=20)

bins를 추가하여 막대를 여러 개로 나타낼 수도 있다.

 

13.2 반복문을 통한 서브플롯 구하기

 

13.2.1 displot

 

이번에는 반복문을 통해 서브플롯을 구해보자

# 컬럼의 수 만큼 for 문을 만들어서 서브플롯으로 시각화 합니다.
col_num = df.columns.shape
col_num

col_num이라는 변수에 df.columns.shape를 할당해주어서 컬럼의 갯수가 10개인 것을 확인할 수 있다.

이번에는 distplot으로 서브플롯을 그려보고자 한다.

# distplot 으로 서브플롯을 그립니다.

fig, axis = plt.subplots(nrows=3, ncols=3, figsize=(10,10))

sns.distplot(df["Outcome"], ax=axis[1][1])

이렇게 코드를 짜주게 되면

다음과 같이 9의 빈 그래프 공간이 나오고 Outcome이라는 그래프를 axis를 통해 자유로운 좌표 설정으로 어느 위치에 해당 그래프가 위치할 지 정해줄 수 있다.

 

따라서 서브플롯을 저 9개에 각각 채워주기 전에 

이 부분을 

# 컬럼의 수 만큼 for 문을 만들어서 서브플롯으로 시각화 합니다.
cols=df.columns[:-1].tolist()
cols

이렇게 바꿔주어 cols 안에 하나의 리스트로 만들어준후 

# distplot 으로 서브플롯을 그립니다.

fig, axis = plt.subplots(nrows=3, ncols=3, figsize=(10,10))

for i, col_name in enumerate(cols):
    print(i, col_name)
# sns.distplot(df["Outcome"], ax=axis[1][1])

for문에서 enumerate를 활용해서 서브플롯을 채워넣어주고자 한다.

 

그런데 여기서 9개의 플롯을 채워넣어주기전에 우리가 직접 어느 변수에 대한 그래프가 어느 행과열에 그려지게 될 지를 먼저 설정해주고 서브플롯을 채워줄 수 있다.

# distplot 으로 서브플롯을 그립니다.

fig, axis = plt.subplots(nrows=3, ncols=3, figsize=(10,10))

for i, col_name in enumerate(cols):
    row = i // 3
    col = i % 3
    print(i, col_name, row)
sns.distplot(df[col_name], ax=axis[row][col])

위 코드에서 row와 col 변수를 만들어주어서

변수명 오른쪽에 몇 번째 컬럼에 해당 변수들이 그려질 지를 정해주었고 ax를 통해 직접 row와 col을 설정해주어서 그래프를 삽입해주는 것이다.

(오류로 인해 결괏값을 강의 영상 이미지로 대체)

 

이번에는 당뇨병 발병 여부를 기준으로 시각화를 따로 해보고자 한다.

df[df["Outcome"] == 0]

먼저 다음과 같이 "Outcome"의 표를 가져와주고 

# distplot 으로 서브플롯을 그립니다.

fig, axis = plt.subplots(nrows=4, ncols=2, figsize=(15,15))

for i, col_name in enumerate(cols[:-1]):
    row = i // 2
    col = i % 2
sns.distplot(df_0[col_name], ax=axis[row][col])    
sns.distplot(df_1[col_name], ax=axis[row][col])

전에 만들어 놓았던 df_0과 df_1을 띄어 그래프를 겹쳐보아 시각화 분석을 수행할 것이다.

위의 그래프를 보면 첫 번째 pregnancies를 보면 특정 수치를 기준으로 당뇨병 발병 비율이 나뉘고, glucose의 경우에도 특정 값을 기준으로 발병율이 바뀌는 것을 확인할 수 있다. Bloodpressure 부분을 보면 그래프가 비슷한 것으로 보아 큰 차이는 없어보이며, BMI 지수도 보면 지수가 높을수록 발병율이 더높아질 것이라 예측할 수 있다(특히 20후반에서 30중반까지). 그리고 AGE를 살펴보면 20-30사이에 그래프가 집중적으로 치솟고 있는 것을 확인할 수 있다.

2.1.7 시각화를 통한 변수간의 차이 이해하기

이전의 displot소스에서 violinplot으로 변한다. violinplot은 박스 플롯과 커널밀도추정 함수 그래프를 합쳐놓은 그래프라고 볼 수 있다. 따라서 박스플롯이 제공하는 정보를 모두 포함하며, 모양은 커널밀도추정 함수 그래프 형태이다.

해당 코드
해당 코드의 결과 -> 박스 플롯과 마찬가지로 여러 기술 통계값을 볼 수 있음

- > 0으로 몰려있는 데이터는 결측치입니다. 이전에 알고 있는 도메인을 바탕으로 포도당 수치와 인슐린 수치는 당뇨병과 관련이 있을 것이란 가설을 제시해본다, 이를 시각화해보기 위해 regplot을 사용한다

해당 코드

그런데 regplot은 hue 속성이 존재하지 않아, 회귀선만 그릴 뿐, 색상을 다르게 지정하지 못한다. 색상을 다르게 하고 싶다면 Implot을 사용한다.

해당 코드

인슐린이 0 인 값들이 많아 회귀선이 아래쪽으로 치우치는 것을 볼 수 있다 -> 따라서 인슐린이 0보다 큰 값들을 모아 다시 한번 그려본다

해당 코드

해당 값들이 더욱 회귀선에 밀접하게 분포함을 알 수 있다.

pairplot은 모든 수치들에 대해 분포를 그려준다

pairplot을 이용 ->개인적으로는 &nbsp;분포를 알아보기가 쉽지 않음

보다 다양한 형태의 그림을 보기 위해 pairgrid 함수를 이용하고, hue값을 지정해준다.

해당 코드
대각선에 있는 그림은 자기 자신과 상관이기에 직선의 형태를 가진다

 

2.1.8 피처엔지니어링을 위한 상관 계수 분석하기

상관분석 및 상관계수(r)의 정의 -

예측하고자 하는 값인 당뇨병 여부가 다른 변수들과 어느 정도 상관관계가 있는지 분석해 보겠습니다.

상관 분석의 wikipedia 페이지에서 보면 다음과 같이 나와있습니다.

상관 분석은 두 변수 간에 어떤 선형적 관계를 갖고 있는 지를 분석하는 방법이다.
두 변수는 서로 독립적인 관계이거나 상관된 관계일 수 있으며
이때 두 변수간의 관계의 강도를 상관관계(correlation)라 한다.

상관 계수는 두 변수간의 연관된 정도를 나타낼 뿐 인과관계를 설명하는 것은 아니다.
두 변수간에 원인과 결과의 인과관계가 있는지에 대한 것은
회귀분석을 통해 인과관계의 방향, 정도와 수학적 모델을 확인해 볼 수 있다.

 

또한 상관계수를 다음과 같이 나눠서 구분합니다:

상관계수(r)의 범위: 관계 
-1.0 < r < -0.7 강한 음적 선형관계
-0.7 < r < -0.3 뚜렷한 음적 선형관계
-0.3 < r < -0.1 약한 음적 선형관계
-0.1 < r < +0.1 거의 무시될 수 있는 선형관계
+0.1 < r < +0.3 약한 양적 선형관계
+0.3 < r < +0.7 뚜렷한 양적 선형관계
+0.7 < r < +1.0 강한 양적 선형관계

 

- 상관계수 구하기 -

pandas에서는 함수 .corr 을 이용하여 각 항목끼리 상관계수를 구해보겠습니다.

- 결측치 처리 전 -

df_corr = df.corr()
df_corr.style.background_gradient()  # 상관계수가 높을 수록 진하게 표시

pandas의 상관계수 dataframe; 파란색이 진할 수록 상관계수가 더 높습니다

 

이렇게 보는 것도 좋지만, 상관계수의 양적/음적 선형관계를 다 표시하기 위해서 seaborn의 heatmap 플롯을 사용해 시각화를 하겠습니다.

# 위에서 구한 상관계수를 heatmap으로 시각화합니다.
plt.figure(figsize=(13,8))
sns.heatmap(df_corr, annot=True, vmax=1, vmin=-1, cmap='coolwarm')

결측치를 포함한 dataframe으로 계산한 상관계수 heatmap

 

대각선으로 1의 상관계수가 쭉 나열된 것은 같은 변수끼리 비교를 한 상관계수들이 대각선에 위치해 있기 때문입니다.

1에 가까운 상관계수는 붉은색, -1에 가까운 상관계수는 푸른색으로 나타내었습니다.

  • 그래프에 파란색이 많이 없다는 것은 강력한 음적 선형관계를 가진 변수들이 별로 없다는 것을 뜻합니다.

그래프를 보았을 때, 1값을 제외하고 가장 높은 상관계수를 가진것이 Pregnacies와 Pregnacies_high인데,
이는 우리가 Pregnancies 데이터를 이용해 범주형 변수인 Pregnancies_high를 만들어 준 것이기 때문에 높은 상관계수가 나오는 것이 당연합니다.

그 다음으로 높은 상관계수들을 보겠습니다:

  • Age와 Pregnancies(0.54)
    • 이는 임신횟수가 많을 수록 나이가 많아지는 선형관계가 꽤 뚜렷하게 보인다는 것을 의미합니다.
      나이가 많을 수록 임신할 기회가 많았을 것을 생각하면 놀라운 관계는 아닙니다.
  • Glucose와 Outcome(0.47)
    • 당뇨병에 대해서 우리가 아는 기본 지식을 인용했을 때, 당연히 포도당 수치가 당뇨병 여부와 관련이 있을 거라 생각했고, 이를 상관계수를 통해 다시 한번 확인할 수 있습니다.

반면에 여기서 본 Insulin과 Outcome의 상관관계는 그다지 높지 않습니다. 과연 인슐린 수치가 당뇨병 여부와 정말 큰 관계가 없는 것일 까요? 아니면 우리가 위에서 본 결측치들이 상관계수를 낮추는 걸까요?

 

- 결측치 처리 후 -

이번엔 결측치를 처리하고 상관계수를 구해보도록 하겠습니다.

  • Glucose
  • BloodPressure
  • SkinThickness
  • Insulin
  • BMI

위 항목들은 사람에서 계측했을 때, 최소값이 0이 나오기 어려운 항목들입니다.

그러므로 해당 항목들에서 0값을 NaN으로 치환해주겠습니다.

df.corr() 함수가 NaN은 무시하고 처리하기 때문에 계측치가 상관계수 계산에 영향을 미치지 않을 것입니다.

df_copy = df.copy()
cols = ['Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI']

# 0인 결측치 값을 NaN으로 치환합니다.
df_copy[cols] = df[cols].replace(0, np.nan)

# 직접 만든 범주형 변수인 Pregnancies_high도 배제하겠습니다
df_copy.drop(columns=["Pregnancies_high"], inplace=True)
df_copy.head()

NaN 값이 들어간 것을 볼 수 있습니다

위와 동일하게 상관계수를 계산한 후, seaborn의 heatmap 플롯을 이용해서 시각화 하겠습니다.

# 상관계수를 구합니다.
df_copy_corr = df_copy.corr()

# 상관계수를 heatmap으로 시각화합니다.
plt.figure(figsize=(13,8))
sns.heatmap(df_copy_corr, annot=True, vmax=1, vmin=-1, cmap='coolwarm')

결측치를 배제하고 계산한 상관계수의 heatmap

그래프를 분석하면 다음 사실들을 알 수 있습니다:

  1. Glucose와 Outcome의 상관관계는 여전히 높은 것을 확인할 수 있습니다.
  2. Insulin과 Glucose의 상관계수가 확연히 증가한 것을 볼 수 있습니다.
    • 0.33에서 0.58로 증가
  3. Outcome과 Insulin의 상관계수도 증가하였습니다.
    • 0.13에서 0.3으로 증가
  4. 음적 선형관계를 가진 변수들도 있으나, 강한 관계는 아닌 것 같습니다.

- 상관계수가 높은 변수끼리 시각화 하기 -

- Insulin VS Glucose -

우리가 관심있는 Insulin과 Glucose의 선형관계를 regplot을 이용하여 시각화 하겠습니다.

# Insulin과 Glucose로 regplot을 그립니다.
sns.regplot(data=df_copy, x='Insulin', y='Glucose')

이렇게 Insulin과 Glucose를 시각화 했을 때, 상관관계가 꽤 높아보이기는 하나, 이상치(outlier)들도 꽤 있다는 것을 볼 수 있습니다.

이 이상치들을 전처리 해줄 경우 예측에 좀 더 도움이 되지 않을까라는 생각을 해볼 수 있습니다.

- Age VS Pregnancy -

다음으론 0.54의 상관계수를 가졌던 Age 와 Pregnancy의 선형관계를 regplot을 이용하여 시각화 하겠습니다.

# Age과 Pregnancies로 regplot을 그립니다.
sns.regplot(data=df_copy, x='Age', y='Pregnancies')

여기서도 마찬가지로 이상치들을 확인할 수 있는데, 예측 시에 이상치들을 포함 시킬건지 아니면 따로 전처리를 할 것인지를 데이터를 보고 판단해 볼 수 있습니다.

위 그래프를 당뇨병 여부에 따라 색깔을 달리하여 보려면 hue 옵션을 지원하는 lmplot을 사용해 Outcome에 따라 시각화 할 수 있습니다.

# Age와 Pregnancies를 Outcome으로 구분하여 시각화 합니다.
sns.lmplot(data=df_copy, x="Age", y="Pregnancies", hue="Outcome")

마무리하며

이렇게 Outcome 값을 예측하기 위해서 어떤 변수들을 어떤 방식으로 사용하면 좋을지 EDA (Exploratory Data Analysis)를 통해서 단서를 얻을 수 있습니다.

보고 싶은 내용에 따라 시각화 할 수 있는 방법은 정말 여러가지가 있습니다

EDA를 한 것을 바탕으로 데이터를 파악하여 결측치 처리, 이상치 처리, 수치형/범주형 데이터 변환 등을 고려할 수 있습니다.

 

1조(trillion)_2주차_과제.ipynb
2.84MB