패키지 import 및 데이터 전처리

- 한글 깨짐 참고 : https://mirae-kim.tistory.com/14

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
import seaborn as sns
import scipy.stats as stats
from scipy.stats import norm
import statsmodels.formula.api as smf

fig_dims = (10, 6)  
sns.set(rc = {'figure.figsize':fig_dims}) # plot 사이즈 및 스타일 통일
sns.set_theme() # 테마 설정
matplotlib.rcParams['font.family'] = 'Malgun Gothic' # 한글이 깨지지 않도록 설정
matplotlib.rcParams['axes.unicode_minus'] = False    # 한글이 깨지지 않도록 설정
df = pd.read_csv('https://raw.githubusercontent.com/gkswotn9999/data/main/blood_data.csv', header = 0) ## 2013~2014년에 실시된 백만개의 국가건강검진_혈압혈당데이터
df.shape ## matrix는 1000000 × 7 크기
(1000000, 7)
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1000000 entries, 0 to 999999
Data columns (total 7 columns):
 #   Column  Non-Null Count    Dtype  
---  ------  --------------    -----  
 0   SEX     1000000 non-null  int64  
 1   BTH_G   1000000 non-null  int64  
 2   SBP     1000000 non-null  int64  
 3   DBP     1000000 non-null  int64  
 4   FBS     1000000 non-null  int64  
 5   DIS     1000000 non-null  int64  
 6   BMI     1000000 non-null  float64
dtypes: float64(1), int64(6)
memory usage: 53.4 MB

- 결측치는 없다

- 우선 열 이름을 한글로 변경한다

df.columns = ['성별', '연령대', '수축기혈압', '이완기혈압', '공복혈당', '고혈압_당뇨_진료내역', 'BMI'] 
df.head(6)
성별 연령대 수축기혈압 이완기혈압 공복혈당 고혈압_당뇨_진료내역 BMI
0 1 1 116 78 94 4 16.6
1 1 1 100 60 79 4 22.3
2 1 1 100 60 87 4 21.9
3 1 1 111 70 72 4 20.2
4 1 1 120 80 98 4 20.0
5 1 1 115 79 95 4 23.1

- 변수별 요약통계량은 아래와 같다

df.describe().round(2)
성별 연령대 수축기혈압 이완기혈압 공복혈당 고혈압_당뇨_진료내역 BMI
count 1000000.00 1000000.00 1000000.00 1000000.00 1000000.00 1000000.00 1000000.0
mean 1.49 13.91 121.87 75.79 98.86 3.47 23.8
std 0.50 7.01 14.56 9.79 22.98 0.95 3.3
min 1.00 1.00 82.00 50.00 60.00 1.00 14.8
25% 1.00 9.00 110.00 70.00 87.00 3.00 21.5
50% 1.00 14.00 120.00 76.00 94.00 4.00 23.6
75% 2.00 19.00 130.00 80.00 104.00 4.00 25.8
max 2.00 27.00 190.00 120.00 358.00 4.00 40.3

- 수축기혈압은 82~190의 범위를 가지고 중앙값은 120, 평균은 121.87이다

- 이완기혈압은 50~120의 범위를 가지고 중앙값은 76, 평균은 75.79이다

- 공복혈당은 60~358의 범위를 가지고 중앙값은 94, 평균은 98.86이다

- BMI은 14.8~40.3의 범위를 가지고 중앙값은 23.6, 평균은 23.8이다

- 양적변수들을 보면 중앙값과 평균이 거의 비슷한데 대칭인 분포인 것 같다

- 각 변수에 대한 설명은 아래와 같다

변수명 범위 비고
수축기혈압 82-190 mmHg 상·하위 극단값 0.05% 제거
이완기혈압 50-120 mmHg 〃(위와 동일함)
공복혈당 60-358 mg/dL
BMI 14.8-40.3 kg/m2 $\cfrac{w}{t^2}$키가 t미터, 몸무게가 w킬로그램〃(위와 동일함)
성별 숫자
남자 1
여자 2
고혈압/당뇨병 진료내역 숫자
고혈압/당뇨병 진료내역 있음 1
고혈압 진료내역 있음 2
당뇨병 진료내역 있음 3
고혈압/당뇨병 진료내역 없음 4

- 고혈압, 당뇨 진료내역의 의미는 다음과 같다

- 현재 고혈압을 앓고있을 수 도 있고 아니면 과거에 고혈압에 걸렸었던 것일 수 도 있다

- 즉 현재 고혈압인 경우와 과거에 고혈압이었던 경우로 나뉘며 당뇨도 마찬가지이다

연령대 범주 변경

- 현재 연령대 column 이 가지는 값은 1부터 27 까지인데 이들에는 1 => 20~24, 2 부터는 24살부터 2세 간격으로 끊어진 연령대가 할당되고 마지막은 27 => 75+ 이다

ft = df['연령대'].value_counts() 
rft = df['연령대'].value_counts() / len(df['연령대']) 
age_group_table = pd.DataFrame({'Freq': ft, 'Relative freq': rft})
age_group_table.sort_index()
Freq Relative freq
1 26699 0.026699
2 22398 0.022398
3 26925 0.026925
4 30595 0.030595
5 34984 0.034984
6 33797 0.033797
7 29666 0.029666
8 30074 0.030074
9 53811 0.053811
10 50787 0.050787
11 50881 0.050881
12 49544 0.049544
13 46303 0.046303
14 46373 0.046373
15 52352 0.052352
16 53382 0.053382
17 47760 0.047760
18 45048 0.045048
19 37174 0.037174
20 33846 0.033846
21 30824 0.030824
22 32253 0.032253
23 22906 0.022906
24 22720 0.022720
25 24530 0.024530
26 18463 0.018463
27 45905 0.045905

- 변수가 가지는 범주가 너무 많고 범주가 가지는 나이의 범위가 현재 2세인데 조금 더 늘려도 크게 차이가 있을 것 같지는 않다

- 표본수가 많으므로 10세 간격으로 끊지 않고 5세 간격으로 끊어 비슷한 연령끼리 그룹화 하겠다

- 애매한 것은 연령대 데이터가 31-32, 33-34, 35-36, 37-38, 39-40 이런식으로 되어있어 5개씩 나눌 수 가 없는 점이다

- 0~4 과 5~9으로 나누면 균등해지고 좋을 것 같다

- 혈압혈당데이터는 건강검진을 받은 사람들 중 백만명을 무작위 층화추출했다

- 그렇기에 주기성이 없을 것이니 하나의 난수를 가지고 20~70세의 초반, 후반을 나누겠다

- 5세 간격으로 끊어 편의상 0~4는 '초반', 5~9는 '후반'으로 표기했다

- 위의 범주를 무작위로 50%씩 나눠서 반절은 '초반'에 나머지 반절은 '후반'에 할당하겠다

np.random.seed(2021)
rs = np.random.binomial(n = 1, p = 0.5, size = 60000) # 최대가 53000이라 넉넉히 60000개 뽑았다
c = 2
age = 2
cnt = 3

df.loc[df['연령대'] == 1, '연령대'] = '20대초반' 

for idx in [4, 9, 14, 19, 24]:  # 변수에 대한 설명을 보면 9~10 에 해당하는 범주의 값은 4, 9, 14, 19, 24이다                   
    df_ = df.loc[df['연령대'] == idx, '연령대']
    df.loc[df['연령대'] == idx, '연령대'] = rs[: len(df_)]
    df.loc[df['연령대'] == 0, '연령대'] = str(c) + '0대후반'
    
    c += 1
    df.loc[df['연령대'] == 1, '연령대'] = str(c) + '0대초반'
        
    
for i in range(2, 27):  
    if cnt < 3:
        str_ = '초반'
        
    else:
        str_ = '후반'
        
    df.loc[df['연령대'] == i, '연령대'] = str(age) + '0대' + str_
    
    if cnt == 5:
        age += 1
        cnt = 0
        
    cnt += 1
    
df.loc[df['연령대'] == 27, '연령대'] = '75세이상' 

- 연령대의 도수분포표를 그려보겠다

ft = df['연령대'].value_counts() 
rft = df['연령대'].value_counts() / len(df['연령대']) 
age_group_table = pd.DataFrame({'Freq': ft, 'Relative freq': rft})
age_group_table.sort_index()
Freq Relative freq
20대초반 26699 0.026699
20대후반 64616 0.064616
30대초반 84083 0.084083
30대후반 86639 0.086639
40대초반 128580 0.128580
40대후반 118978 0.118978
50대초반 128976 0.128976
50대후반 111352 0.111352
60대초반 83300 0.083300
60대후반 66487 0.066487
70대초반 54385 0.054385
75세이상 45905 0.045905

데이터 EDA

- 기본적으로 고혈압_당뇨 진료내역에 따른 특성들을 확인할 것이다

범주형변수 탐색

고혈압, 당뇨 진료내역 빈도

- 우선 고혈압, 당뇨 진료내역 범주의 비율을 확인했다

ft = df['고혈압_당뇨_진료내역'].value_counts() 
rft = df['고혈압_당뇨_진료내역'].value_counts() / len(df['고혈압_당뇨_진료내역']) 
DIS_table = pd.DataFrame({'Freq': ft, 'Relative freq': rft})
DIS_table
Freq Relative freq
4 740662 0.740662
2 162826 0.162826
1 53398 0.053398
3 43114 0.043114
count_by_cut = df.groupby('고혈압_당뇨_진료내역').size()
count_by_cut
고혈압_당뇨_진료내역
1     53398
2    162826
3     43114
4    740662
dtype: int64
plt.pie(x = count_by_cut, labels = ['고혈압,당뇨', '고혈압', '당뇨', '없음'], autopct = '%.2f%%') 
plt.show()

- 고혈압/당뇨 둘 다 진료내역 없음이 74%로 가장 많이 차지한다

- 당뇨 진료내역만 있는 경우는 4%, 고혈압 진료내역만 있는 경우는 16%, 둘다 있는 경우는 5%이다

- 둘 다 진료내역이 있는 경우가 당뇨 진료내역만 있는 경우보다 많은 것으로 보아 당뇨가 있는 경우 고혈압도 있는 경우가 빈번한 것 같다

- 고혈압/당뇨 진료내역에 따른 사람들의 연령대와 성별을 확인하겠다

- 우선 고혈압/당뇨 진료내역에 따른 연령대를 확인하겠다

고혈압, 당뇨 진료내역과 연령대 분할표

# df['연령대'] = pd.Categorical(df['연령대'], ['20대초반', '20대후반', '30대초반', '30대후반', '40대초반', '40대후반', '50대초반', '50대후반', '60대초반', '60대후반', '70대초반', '75세이상'])
DIS_AGE_table = df.groupby(['연령대', '고혈압_당뇨_진료내역']).size().reset_index(name = 'cnt').pivot(index = '고혈압_당뇨_진료내역', columns = '연령대', values = 'cnt')
# DIS_AGE_table = pd.crosstab(df['고혈압_당뇨'], df['연령대'], margins = True)
DIS_AGE_table
연령대 20대초반 20대후반 30대초반 30대후반 40대초반 40대후반 50대초반 50대후반 60대초반 60대후반 70대초반 75세이상
고혈압_당뇨_진료내역
1 9 35 137 431 1358 2763 5362 7861 8818 9232 9300 8092
2 80 359 1104 2936 7607 13091 21954 26180 24447 23256 21241 20571
3 79 253 543 1233 2865 4400 6555 7371 6648 5524 4479 3164
4 26531 63969 82299 82039 116750 98724 95105 69940 43387 28475 19365 14078

- 공통적으로 확인되는것은 4번의 경우가 가장 많은 것과 2번의 경우가 3번의 경우보다 더 많다는 것이다

- 당뇨병보다는 고혈압이 흔한것 같다

- 그리고 젊은 연령대에서는 1의 경우가 2, 3인 경우보다 더 적은데 50대 초반부터는 늙을수록 1의 경우가 3의 경우보다 더 많아진다

- 50대 부터는 당뇨가 있는 경우 고혈압도 있는 경우가 흔한것같고 이는 연령대가 높아질수록 더 심해진다

- 그런데 연령마다 총인원수가 다르기에 사람수로 판단하기 보다는 비율로 판단하는게 좋아보인다

- 예로 70대초반에서 고혈압_당뇨 범주값이 1인 경우 count는 9300이고 75세이상의 경우는 8092여서 감소한것 같지만 70대초반 인구수와 75세이상 인구수가 다르기에 비율로 따져야 정확하다

DIS_AGE_table_prob = DIS_AGE_table.apply(lambda x: x*100 / sum(x), axis = 0) # 상대도수를 구함
DIS_AGE_table_prob
연령대 20대초반 20대후반 30대초반 30대후반 40대초반 40대후반 50대초반 50대후반 60대초반 60대후반 70대초반 75세이상
고혈압_당뇨_진료내역
1 0.033709 0.054166 0.162934 0.497466 1.056152 2.322278 4.157363 7.059595 10.585834 13.885421 17.100303 17.627709
2 0.299637 0.555590 1.312988 3.388774 5.916161 11.002874 17.021771 23.511028 29.348139 34.978266 39.056725 44.812112
3 0.295891 0.391544 0.645790 1.423147 2.228185 3.698163 5.082341 6.619549 7.980792 8.308391 8.235727 6.892495
4 99.370763 98.998700 97.878287 94.690613 90.799502 82.976685 73.738525 62.809828 52.085234 42.827921 35.607245 30.667683
DIS_AGE_table_prob.T.plot.bar(stacked = True, rot = 0, figsize=(12 , 6))
plt.legend(['고혈압, 당뇨', '고혈압', '당뇨', '없음'])
<matplotlib.legend.Legend at 0x19789d29880>

- 일단 4(고혈압,당뇨 둘 다 없음)인 경우를 확인하겠다

- 20대초반의 99%는 4인데 나이가 들어감에 따라 비율이 감소하여 75세이상에서는 4의 경우가 30%이다

- 1과 2의 경우는 4의 경우와 반대로 나이가 들어가면서 비율이 높아진다

- 3의 경우는 60대후반까지는 증가하다가 그 이후부터 줄어든다

- 요약하면 나이를 먹을수록 4는 증가하고 1,2,3은 감소한다(건강한 사람보다는 병을 앓던 사람이 많다는 의미이다)

- 고혈압_당뇨 범주마다 연령대의 비율은 어느정도인지 시각화하겠다

sequential_colors = sns.color_palette('RdPu', 12)
DIS_AGE_table_prob2 = DIS_AGE_table.apply(lambda x: x*100 / sum(x), axis = 1) # 상대도수를 구함
DIS_AGE_table_prob2.plot.bar(stacked = True, rot = 0, color = sequential_colors)
plt.legend(loc = 'center left', bbox_to_anchor = (1, 0.5))
plt.show()

- 막대그래프의 밑에서부터 20대초반, 20대후반, $\cdots$, 70대 초반, 75세이상이다(색이 연하면 나이가 적고 진하면 많도록 설정했다)

- 고혈압_당뇨가 1, 2, 3인 경우는 비슷해보인다

- 고혈압/당뇨 둘 다 진료내역이 없는 경우(4)에는 확실히 다르다(색이 전체적으로 연하다)

- 고혈압/당뇨가 1, 2, 3인 경우 30대 후반까지 차지하는 비중이 낮다(연한색의 비율이 작다)

- 하지만 4인 경우에는 30대 후반까지 차지하는 비중이 높아졌다(연한색의 비율이 크다)

고혈압, 당뇨 진료내역과 성별 분할표

- 이제 고혈압/당뇨 진료내역에 따른 성별을 확인하겠다

count = df.groupby('성별').size()
plt.pie(x = count, labels = ['남자', '여자'], autopct = '%.2f%%') 
plt.show()

- 우선 전체 백만명중 남자가 51% 여자가 49%로 둘이 비슷하다

DIS_SEX_table = df.groupby(['성별', '고혈압_당뇨_진료내역']).size().reset_index(name = 'cnt').pivot(index = '고혈압_당뇨_진료내역', columns = '성별', values = 'cnt')
DIS_SEX_table
성별 1 2
고혈압_당뇨_진료내역
1 27979 25419
2 79892 82934
3 23900 19214
4 378456 362206
DIS_SEX_table_prob = DIS_SEX_table.apply(lambda x: x*100 / sum(x), axis = 1) # 상대도수를 구함
DIS_SEX_table_prob
성별 1 2
고혈압_당뇨_진료내역
1 52.397094 47.602906
2 49.065874 50.934126
3 55.434430 44.565570
4 51.096992 48.903008
DIS_SEX_table_prob.plot.bar(stacked = True, rot = 0)
plt.legend(['남자', '여자'])
<matplotlib.legend.Legend at 0x1978b011df0>

- 당뇨진료내역이 있는 경우 남자의 비율이 여자보다 살짝 높고 나머지는 비슷하다

- 표본크기가 매우커서 55%, 45%(당뇨 진료내역 있음) 정도면 차이가 있다고 할 수 있다

- 그리고 52.4%, 47.6%(고혈압, 당뇨 진료내역 있음) 도 차이가 있다고 할 수 있다

- 마찬가지로 고혈압 진료내역이 있는 경우도 차이가 있다고 할 수 있다

- 비율 차이 검정을 통해 정확히 확인하겠다

df.groupby(['고혈압_당뇨_진료내역', '성별']).size().reset_index().\
rename(columns = {0:'count'}).\
merge(df.groupby(['고혈압_당뇨_진료내역']).agg({'성별':'size'}).\
rename(columns = {'성별':'total'}).reset_index()).eval('prop = count / total').\
query('성별 == 1').eval('z = (prop - 0.5102) / sqrt(prop * (1-prop) / total)').\
assign(right_z_0 = norm.ppf(0.975), left_z_0 = norm.ppf(0.275))
고혈압_당뇨_진료내역 성별 count total prop z right_z_0 left_z_0
0 1 1 27979 53398 0.523971 6.371704 1.959964 -0.59776
2 2 1 79892 162826 0.490659 -15.773216 1.959964 -0.59776
4 3 1 23900 43114 0.554344 18.441415 1.959964 -0.59776
6 4 1 378456 740662 0.510970 1.325525 1.959964 -0.59776

- 고혈압, 당뇨 진료내역이 없는 경우를 제외하면

- 고혈압, 당뇨 진료내역에 따라 남자와 여자의 비율에 차이가 있음을 알 수 있다

- 고혈압 진료내역만 있는 경우는 여자가 남자보다 비율이 높고

- 고혈압, 당뇨 진료내역과 당뇨 진료내역만 있는 경우는 남자가 여자보다 비율이 높다

양적변수 탐색

개별 변수의 시각화

수축기혈압

- 양적 변수인 수축기혈압, 이완기혈압, 공복혈당, BMI의 분포를 확인하겠다

sns.histplot(data = df, x = '수축기혈압', binwidth = 5)
<AxesSubplot:xlabel='수축기혈압', ylabel='Count'>

- 분포의 모양을 크게 눈에 띄는 봉우리가 4개(100, 110, 120, 130) 존재한다

- 즉 몇 군데(봉우리)에 데이터가 많이 몰려있다는 의미이다

sns.boxplot(x = df['수축기혈압'])
<AxesSubplot:xlabel='수축기혈압'>

- boxplot을 보니 수축기혈압의 이상점은 160을 넘는 경우인 듯 하다

- 성별에 따라 수축기혈압이 다른지 궁금하다

sns.violinplot(x = '성별', y = '수축기혈압', data = df)
<AxesSubplot:xlabel='성별', ylabel='수축기혈압'>
df.groupby('성별')['수축기혈압'].describe()
count mean std min 25% 50% 75% max
성별
1 510227.0 124.279954 13.547532 82.0 115.0 123.0 132.0 190.0
2 489773.0 119.363001 15.146169 82.0 110.0 119.0 130.0 190.0

- 남자인 경우 여자인 경우보다 평균적으로 수축기혈압이 5정도 높다

- 신기한건 남자가 여자보다 이완기 혈압이 평균적으로 4정도 높지만 두 봉우리의 위치는 남자와 여자가 동일하다

- 그리고 표준편차는 여자가 1.5정도 더 높다(변동성이 더 크다) ---> 남자쪽 봉우리가 더 뾰족해서인듯 하다(데이터가 몰려있다)

- 그런데 고혈압, 당뇨 진료내역에 따라 수축기혈압의 분포를 살펴봤는데 이 때도 봉우리의 위치는 4개다 동일하다

- 남자가 여자보다 혈압이 평균적으로 더 높지만 봉우리의 위치가 같은건 어떤 의미인지 깊게 생각할 필요가 있다

- 수축기혈압 분포에서 봉우리가 연령대에 의한건지 궁금하다

b = sns.FacetGrid(data = df, row = '연령대', height = 7)
b.map(sns.histplot, '수축기혈압', kde = False, binwidth = 5, color = 'gray')
<seaborn.axisgrid.FacetGrid at 0x2117500c760>

- 수축기혈압 분포에서 봉우리가 여러개인것이 연령대 때문은 아니다

- 그런데 사실 고혈압/당뇨인 사람들은 어떤 특성을 가진 사람들인지가 궁금하다

- 고혈압/당뇨에 따라 plot을 그려보겠다

sns.boxplot(x = '고혈압_당뇨_진료내역', y = '수축기혈압', data = df)
<AxesSubplot:xlabel='고혈압_당뇨_진료내역', ylabel='수축기혈압'>

- 분포는 모두 대칭이고 종모양으로 보이며 봉우리가 많다

- 고혈압_당뇨가 1,2인 경우는 130이 중심으로 보인다

- 고혈압_당뇨가 3,4인 경우는 120이 중심으로 보인다

- 고혈압/당뇨 둘다 진료내역이 없는 그룹은 수축기혈압이 낮은쪽에서는 이상점이 없다

- 바이올린플랏을 그려 분포의 모양도 같이 확인하겠다

sns.violinplot(x = '고혈압_당뇨_진료내역', y = '수축기혈압', data = df)
<AxesSubplot:xlabel='고혈압_당뇨_진료내역', ylabel='수축기혈압'>

- 봉우리가 많은 이유가 고혈압_당뇨 진료내역에 따라 분포가 상이하여 그럴것 같았지만 아니었다

- 고혈압/당뇨 진료내역이 없는 사람들의 수축기 혈압은 다른 경우 비해 몇군데에 더욱 많이 몰렸있다

- 봉우리가 많은 분포임은 넷다 동일하다

- 데이터를 보면 1은 고혈압 당뇨 둘 다 진료내역이 있고 2는 고혈압만 3은 당뇨만 있고 4는 둘 다 진료 내역이 없는 경우이다

- 2와 3을 보면 2가 수축기 혈압이 평균적으로 더 높다

- 그런데 1과 2를 보면 거의 차이가 없어보인다

- 3과 4를 보면 당뇨 진료내역이 있는 사람들이 그렇지 않은 사람보다 평균이 조금 더 크고 분포도 더 넓게 퍼져있다

- 고협압/당뇨 둘 다 진료내역이 없는 상황에서 당뇨 진료내역이 추가되면 수축기 혈압이 높아지고 더 넓게 분포한다

- 하지만 고혈압 진료내역이 있는 상태라면 당뇨 진료내역의 유무는 수축기 혈압에 거의 영향을 끼치지 못하는 것으로 보인다

- 수치로 정확히 확인하겠다

df.groupby('고혈압_당뇨_진료내역')['수축기혈압'].describe()
count mean std min 25% 50% 75% max
고혈압_당뇨_진료내역
1 53398.0 130.563972 14.981472 82.0 120.0 130.0 140.0 190.0
2 162826.0 130.551300 14.851658 82.0 120.0 130.0 140.0 190.0
3 43114.0 123.322146 13.618274 83.0 114.0 122.0 130.0 190.0
4 740662.0 119.252575 13.484496 82.0 110.0 120.0 130.0 190.0

- 고혈압 진료내역만 있는 경우와 둘 다 진료내역이 있는 경우에 둘의 수축기 혈압 분포는 거의 똑같다

- 표준편차는 약 15이고 평균은 약 130이다

- 둘 다 진료내역이 없는 경우가 당뇨 진료내역만 있는 경우보다 수축기 혈압이 평균 4정도 낮다

- 표준편차는 두 분포 모두 약 13.5이다

- 고혈압이 있는 경우 그렇지 않은 경우보다 수축기 혈압이 평균 10정도 높다

- 위에서 말했듯이 고혈압 진료내역이 있는 상태라면 당뇨병 진료내역과 수축기혈압은 상관이 없다

- 하지만 고혈압이 없고 당뇨만 있는 상태라면 없는 경우보다 수축기혈압이 높다

- 고혈압, 당뇨 진료내역 말고도 연령대별 수축기혈압에 차이가 있을 수 있다

- 일반적으로 연령대가 높아질수록 수축기혈압이 높아진다고 한다

- 이를 확인해볼 필요가 있다

- 이상점에 영향을 덜받고자 중앙값을 선택했다

df_mid = df.groupby(['연령대']).agg({'수축기혈압':np.median})
df_mid
수축기혈압
연령대
20대초반 113
20대후반 115
30대초반 118
30대후반 119
40대초반 120
40대후반 120
50대초반 120
50대후반 122
60대초반 125
60대후반 128
70대초반 130
75세이상 130
plt.figure(figsize = (12, 6))
sns.lineplot(x = '연령대', y = '수축기혈압', data = df_mid)
sns.scatterplot(x = '연령대', y = '수축기혈압', data = df_mid, legend = False)  
<AxesSubplot:xlabel='연령대', ylabel='수축기혈압'>

- 위 plot을 보면 연령별 수축기혈압의 평균은 증가함을 알 수 있다

- 그런데 40대초반~50대초반에서 수축기혈압의 중앙값은 120으로 동일하다

- 이는 70대초반과 75세이상의 경우에도 마찬가지이다(130)

- 중간에 정체하는 구간을 기준으로 나눠봤을 때 구간 전보다 후가 증가폭이 더 크다($7.5 \to 10$)

- 그런데 연령대 말고도 고혈압, 당뇨 진료내역마다 수축기혈압에 차이가 존재한다

- 연령대별 고혈압, 당뇨 진료내역마다 수축기혈압의 중앙값들을 구하고 연령대에 따른 변화를 살펴보겠다

df_median = df.groupby(['연령대', '고혈압_당뇨_진료내역'])['수축기혈압'].median().reset_index(name = '수축기혈압') 
tab10_colors = sns.color_palette('tab10', 4)
plt.figure(figsize = (12, 6))
sns.lineplot(x = '연령대', y = '수축기혈압', hue = '고혈압_당뇨_진료내역', palette = tab10_colors, data = df_median)
sns.scatterplot(x = '연령대', y = '수축기혈압', hue = '고혈압_당뇨_진료내역', palette = tab10_colors, data = df_median, legend = False)  
plt.legend(['고혈압, 당뇨', '고혈압', '당뇨', '없음'])
<matplotlib.legend.Legend at 0x21101cffb80>

- 고혈압, 당뇨 둘 다 진료내역이 있는 경우와 고혈압만 진료내역이 있는 경우는 거의 동일한 양상을 보인다

- 위의 경우 30대후반부터 수축기혈압의 중앙값은 130으로 동일하다

- 당뇨 진료내역만 있는 경우 50대후반까진 수축기혈압의 중앙값이 120이다가 증가한다

- 고혈압, 당뇨 진료내역이 둘 다 없는 경우도 증가하는 양상을 보이는데 40대초반에 살짝 감소한다

- 그리고 40대후반부터 60대초반까지 수축기혈압 중앙값은 120으로 동일하다가 증가한다

- 이완기 혈압도 수축기 혈압과 비슷한 양상을 보이는지 확인하겠다

이완기혈압

sns.histplot(data = df, x = '이완기혈압', binwidth = 2)
<AxesSubplot:xlabel='이완기혈압', ylabel='Count'>

- 이완기 혈압도 수축기 혈압의 분포처럼 몇 군에데 데이터가 많이 몰려있는 분포(봉우리)이다

- 짜잘한 봉우리가 많이 있긴한데 눈에 띄는 봉우리는 2개가 존재한다(쌍봉분포)

- 왜 봉우리가 2개(70, 80)인지 궁금하다

- 연령대 때문인지가 궁금하다

b = sns.FacetGrid(data = df, row = '연령대', height = 7)
b.map(sns.histplot, '이완기혈압', kde = False, binwidth = 2, color = 'gray')
<seaborn.axisgrid.FacetGrid at 0x2ba411253a0>

- 그렇지는 않다

- 30대 초반까진 70부근이 최빈값이고 그 이후부터는 80부근이 최빈값이다

- 나이가 많은 사람은 이완기혈압이 크다고 해석할 수 있다

sns.violinplot(x = '성별', y = '이완기혈압', data = df)
<AxesSubplot:xlabel='성별', ylabel='이완기혈압'>
df.groupby('성별')['이완기혈압'].describe()
count mean std min 25% 50% 75% max
성별
1 510227.0 77.614977 9.504481 50.0 70.0 79.0 83.0 120.0
2 489773.0 73.884467 9.727230 50.0 68.0 73.0 80.0 120.0

- 남자인 경우 여자인 경우보다 평균적으로 이완기혈압이 4정도 높다

- 신기한건 남자가 여자보다 이완기 혈압이 평균적으로 4정도 높지만 두 봉우리의 위치는 남자와 여자가 동일하다

- 수축기혈압의 경우 여성이 편차가 더 컸는데 이완기혈압의 경우는 비슷하다

- 수축기 혈압 분포와 이완기 혈압 분포를 같이 놓고 비교하겠다

fig, (ax1, ax2) = plt.subplots(1,2)
fig.set_figwidth(12)

sns.histplot(data = df, x = '수축기혈압', binwidth = 5, ax = ax1)
sns.histplot(data = df, x = '이완기혈압', binwidth = 2, ax = ax2)

fig.tight_layout()

- 수축기 혈압 분포의 봉우리가 이완기 혈압 분포의 봉우리 개수보다 많다

- 변동계수를 계산하면 비슷하다 ---> 수축기혈압과 이완기혈압의 변동성은 비슷하다

- 고혈압/당뇨에 따른 이완기 혈압 분포를 확인하겠다

b = sns.FacetGrid(data = df, row = '고혈압_당뇨_진료내역', height = 7)
b.map(sns.histplot, '이완기혈압', kde = False, binwidth = 2, color = 'gray')
<seaborn.axisgrid.FacetGrid at 0x2ba452396d0>

- 고혈압, 당뇨 둘 다 없는 경우 이완기혈압은 50~105에 분포하는 것으로 보이고 나머지는 60~105에 분포하는 것 같다

sns.violinplot(x = '고혈압_당뇨_진료내역', y = '이완기혈압', data = df)
<AxesSubplot:xlabel='고혈압_당뇨_진료내역', ylabel='이완기혈압'>

- 이완기 혈압도 수축기혈압과 동일한 양상을 보인다

- 수치를 통해 정확히 확인해보면 다음과 같다

df.groupby('고혈압_당뇨_진료내역')['이완기혈압'].describe()
count mean std min 25% 50% 75% max
고혈압_당뇨_진료내역
1 53398.0 78.366193 9.874400 50.0 70.0 80.0 84.0 120.0
2 162826.0 80.040417 10.050473 50.0 72.0 80.0 86.0 120.0
3 43114.0 75.679478 9.137214 50.0 70.0 76.0 80.0 120.0
4 740662.0 74.673427 9.471040 50.0 70.0 75.0 80.0 120.0

- 둘 다 없는 경우보다 당뇨만 있는 경우 이완기혈압의 평균이 1정도 높다

- 고혈압이 있으면 위의 경우보다 혈압이 평균적으로 4~5정도 높다

- 신기한건 고혈압만 있는 경우가 고혈압, 당뇨 둘 다 있는 경우보다 사분위수와 평균이 약 2정도 더 크다

- 당뇨 진료내역 여부는 혈압에 영향을 별로 끼치지 못하는 것처럼 보인다

- 정말로 그런지 진료내역이 없는 경우와 당뇨 진료내역만 있는 경우의 평균 차이에 대해 검정하도록 하겠다

- 특정 값에 데이터가 많이 몰려있어 정규분포는 아닌것처럼 보이지만 표본크기가 매우 크고 종모양이기에 t검정을 실시하겠다

- 우선 등분산인지 검정하겠다

x_B = df.query('고혈압_당뇨_진료내역 == 3')['이완기혈압']
x_NB = df.query('고혈압_당뇨_진료내역 == 4')['이완기혈압']
# print(stats.bartlett(x_B, x_NB))
print(stats.levene(x_B, x_NB))
LeveneResult(statistic=108.15121732780896, pvalue=2.4998221943218063e-25)

- p-value가 0이다

- 그러니 이분산 가정하에 t검정을 실시하겠다

t_stat, pvalue = stats.ttest_ind(x_B, x_NB, equal_var = False) 
print(t_stat, pvalue)
22.179011908568423 1.892237004554513e-108

- t통계량에 근거한 p-value가 0이다

- 당뇨 진료내역만 있는 경우와 진료내역이 없는 경우의 이완기혈압 평균의 차이는 존재한다

- 하지만 그 차이가 1 뿐이기에 통계적으로는 유의한 차이지만 실질적인 차이는 거의 없다

- 고혈압 진료내역이 있는 경우 수축기혈압과 이완기 혈압이 높은데 이는 당연하다(고혈압은 혈압이 높은것)

- 그리고 고혈압이 있는 경우가 그렇지 않은 경우보다 혈압이 높은데 수축기혈압의 증가폭(10)이 이완기혈압의 증가폭(5)보다 더 크다

- 고혈압, 당뇨 진료내역 말고도 연령대별 이완기혈압에 차이가 있을 수 있다

- 일반적으로 연령대가 높아질수록 이완기혈압이 높아진다고 한다

- 이를 확인해볼 필요가 있다

- 수축기혈압때처럼 연령대별 이완기혈압의 중앙값들을 구하고 연령대에 따른 변화를 살펴보겠다

df_mid2 = df.groupby(['연령대']).agg({'이완기혈압':np.median})
df_mid2
이완기혈압
연령대
20대초반 70
20대후반 70
30대초반 74
30대후반 75
40대초반 75
40대후반 77
50대초반 78
50대후반 78
60대초반 78
60대후반 78
70대초반 78
75세이상 78
plt.figure(figsize = (12, 6))
sns.lineplot(x = '연령대', y = '이완기혈압', data = df_mid2)
sns.scatterplot(x = '연령대', y = '이완기혈압', data = df_mid2, legend = False)  
<AxesSubplot:xlabel='연령대', ylabel='이완기혈압'>

- 위 plot을 보면 이완기혈압의 중앙값은 증가함을 알 수 있다

- 그런데 50대초반부터는 이완기혈압의 중앙값은 78로 동일하다

- 그리고 30대후반~40대초반에 이완기혈압이 75로 동일하다

- 중간에 정체하는 구간을 기준으로 나눠봤을 때 구간 전보다 후가 증가폭이 더 크다($5 \to 3$)

- 그런데 연령대 말고도 고혈압, 당뇨 진료내역마다 이완기혈압에 차이가 존재한다

- 연령대별 고혈압, 당뇨 진료내역마다 이완기혈압의 중앙값들을 구하고 연령대에 따른 변화를 살펴보겠다

df_median2 = df.groupby(['연령대', '고혈압_당뇨_진료내역'])['이완기혈압'].median().reset_index(name = '이완기혈압') 
plt.figure(figsize = (12, 6))
sns.lineplot(x = '연령대', y = '이완기혈압', hue = '고혈압_당뇨_진료내역',  palette = tab10_colors, data = df_median2)
sns.scatterplot(x = '연령대', y = '이완기혈압', hue = '고혈압_당뇨_진료내역',  palette = tab10_colors, data = df_median2, legend = False)  
plt.legend(['고혈압, 당뇨', '고혈압', '당뇨', '없음'])
<matplotlib.legend.Legend at 0x1978bc6f280>

- 이완기혈압을 연령대별로 라인플랏을 그려봤을땐 감소하는 구간이 없었다

- 또한 히스토그램을 확인했을 때도 마찬가지였다

- 그런데 고혈압, 당뇨 진료내역에 따라 그래프를 나눠그리니 감소하는 구간이 생겼다

- 위에서 나이가 들수록 이완기혈압이 높아진다고 했는데 고혈압, 당뇨 진료내역이 없는 사람을 제외하면 그렇지 않다

- 공통적으로 30대 후반까지는 이완기혈압 중앙값이 증가한다

- 그런데 그 이후부터는 고혈압, 당뇨 둘다 없는 경우를 제외하면 이완기혈압 중앙값이 감소한다

- 중앙값이 문제일 수 있어서 평균으로 바꿔 그려봤으나 문제가 없었다

- 수축기혈압 중앙값은 나이가 들수록 증가하는 반면 이완기혈압 중앙값은 진료내역이 없는 경우를 제외하면 증가하다가 감소한다

수축기혈압과 이완기혈압 고찰

- 위에서 수축기혈압과 이완기혈압의 분포를 살펴봤는데 고혈압 진료내역이 있지만 혈압이 낮은 사람도 있고 고혈압 진료내역이 없지만 혈압이 높은사람도 있었다

- 고혈압 진료내역이 있지만 혈압이 정상범주안에 있는 사람은 혈압관리가 잘되고 있는것으로 간주할 수 있을 것 같다

- 이들의 비율을 확인하겠다

- 고혈압은 우리나라 기준 수축기 혈압 140mmHg 이상이거나 이완기 혈압 90mmHg 이상인 경우라고 한다

- 참고 : http://hqcenter.snu.ac.kr/archives/jiphyunjeon/%EA%B3%A0%ED%98%88%EC%95%95

- 위에 고혈압 기준을 넘어가는 혈압을 가진 사람들은 고혈압으로 그렇지 않는 사람은 정상혈압으로 간주하겠다

- 그런데 수축기혈압, 이완기혈압의 분포를 보면 혈압이 매우 낮은 사람도 존재함을 알 수 있다

- 이들은 저혈압으로 간주할 수 있을 것 같다

- 저혈압은 일반적으로 수축기 혈압 90mmHg 미만이거나 이완기 혈압 60mmHg 미만인 경우라고한다

- 참고 : https://health.kdca.go.kr/healthinfo/biz/health/gnrlzHealthInfo/gnrlzHealthInfo/gnrlzHealthInfoView.do?cntnts_sn=4616

def f(x, y):
    if x >= 140 or y >= 90:
        z = '고혈압'
    elif x < 90 or y < 60:
        z = '저혈압'
    else:
        z = '정상혈압'
    return z
df['혈압범주'] = list(map(f, df['수축기혈압'], df['이완기혈압']))
df.head()
성별 연령대 수축기혈압 이완기혈압 공복혈당 고혈압_당뇨_진료내역 BMI 혈압범주
0 1 20대초반 116 78 94 4 16.6 정상혈압
1 1 20대초반 100 60 79 4 22.3 정상혈압
2 1 20대초반 100 60 87 4 21.9 정상혈압
3 1 20대초반 111 70 72 4 20.2 정상혈압
4 1 20대초반 120 80 98 4 20.0 정상혈압
count = df.groupby('혈압범주').size()
plt.pie(x = count, labels = ['고혈압', '저혈압', '정상혈압'], autopct = '%.2f%%') 
plt.show()

- 정상혈압은 약 84%, 고혈압은 약 14%, 저혈압은 약 3% 이다

- 저혈압보다는 고혈압이 흔한것같다

- 고혈압 진료내역이 있는 사람이 약 22%정도였는데 7%p정도 차이가 있다

tab = pd.crosstab(df['혈압범주'], df['고혈압_당뇨_진료내역'])
tab
고혈압_당뇨_진료내역 1 2 3 4
혈압범주
고혈압 16318 51261 5696 62049
저혈압 773 1518 1056 23435
정상혈압 36307 110047 36362 655178
tab.apply(lambda x: x*100 / sum(x), axis = 0) 
고혈압_당뇨_진료내역 1 2 3 4
혈압범주
고혈압 30.559197 31.482073 13.211486 8.377506
저혈압 1.447620 0.932284 2.449320 3.164061
정상혈압 67.993183 67.585644 84.339194 88.458433

- 위를 보면 고혈압_당뇨 둘 다 없더라도 12%정도는 혈압에 문제가 있는것을 알 수 있다

- 신기한건 고혈압 진료내역이 있는 경우인데 고혈압 진료내역이 있지만 실제 고혈압인 경우는 약 31%이고 68%는 정상혈압 나머지 1%는 저혈압이다

tab2 = pd.crosstab(df['혈압범주'], df['성별'])
tab2.apply(lambda x: x*100 / sum(x), axis = 0) 
성별 1 2
혈압범주
고혈압 15.345719 11.643353
저혈압 1.360375 4.051060
정상혈압 83.293906 84.305586

- 남자가 여자보다 혈압이 더 높은건 고혈압 환자가 많아서였다

- 참고로 백만명중 남자는 51% 여자는 49%이다

혈압범주 세분화

- 같은 고혈압이더라도 수축기혈압만 높은지 이완기혈압만 높은지 아니면 둘 다 높은지가 다를것이다

- 그런데 위와 같이 나누면 이를 구분할 수 없다

- 게다가 수축기혈압은 고혈압인데 이완기혈압은 저혈압인 경우도 있다고 한다

- 혈압범주를 세분화시키면 정확도는 올라가지만 편의성이 떨어진다

- 세분화시킬 필요가 있을지 확인하겠다

- 일단 혈압범주를 세분화시킨 새로운 컬럼을 만들고 시각화하겠다

def blood_pressure(x, y):
    if x >= 140 and y >= 90:
        z = 'HH'
    elif x < 90 and y < 60:
        z = 'LL'
    elif x >= 140 and 60 <= y < 90:
        z = 'HN'
    elif x >= 140 and 60 > y:
        z = 'HL'
    elif 90 <= x < 140 and 60 <= y < 90:
        z = 'NN'
    elif 90 <= x < 140 and 60 > y:
        z = 'NL'
    elif 90 <= x < 140 and y >= 90:
        z = 'NH'
    elif 90 > x and 90 <= y:
        z = 'LH'
    elif 90 > x and 60 <= y < 90:
        z = 'LN' 
    return z

- 혈압세부범주를 혈압범주로 치환하면 아래와 같다

- HH, HN, HL, NH, LH ---> 고혈압

- LL, NL, LN ---> 저혈압

- NN ---> 정상혈압

- 혈압범주에서는 수축기고혈압 + 이완기저혈압이면 고혈압으로 표기했다(하지만 저혈압이기도 함)

- 혈압범주를 나누는 함수내부를 보면 if else문에서 고혈압을 먼저 판단하고 저혈압을 판단해서 그렇다

- 하지만 혈압세부범주에서는 그럴일은 없다

- 진료내역별 혈압세부범주를 확인하겠다

df['혈압세부범주'] = list(map(blood_pressure, df['수축기혈압'], df['이완기혈압']))
df.head()
성별 연령대 수축기혈압 이완기혈압 공복혈당 고혈압_당뇨_진료내역 BMI 맥압 혈압범주 혈압세부범주
0 1 20대초반 116 78 94 4 16.6 38 정상혈압 NN
1 1 20대초반 100 60 79 4 22.3 40 정상혈압 NN
2 1 20대초반 100 60 87 4 21.9 40 정상혈압 NN
3 1 20대초반 111 70 72 4 20.2 41 정상혈압 NN
4 1 20대초반 120 80 98 4 20.0 40 정상혈압 NN
tab = pd.crosstab(df['혈압세부범주'], df['고혈압_당뇨_진료내역'])
tab
고혈압_당뇨_진료내역 1 2 3 4
혈압세부범주
HH 6348 23684 2141 28800
HL 25 44 7 17
HN 8059 19684 2503 16395
LL 12 37 42 1443
LN 5 33 24 952
NH 1886 7849 1045 16837
NL 756 1448 990 21040
NN 36307 110047 36362 655178
tab.apply(lambda x: x*100 / sum(x), axis = 0) 
고혈압_당뇨_진료내역 1 2 3 4
혈압세부범주
HH 11.888086 14.545589 4.965904 3.888413
HL 0.046818 0.027023 0.016236 0.002295
HN 15.092326 12.088978 5.805539 2.213560
LL 0.022473 0.022724 0.097416 0.194826
LN 0.009364 0.020267 0.055666 0.128534
NH 3.531967 4.820483 2.423807 2.273237
NL 1.415783 0.889293 2.296238 2.840702
NN 67.993183 67.585644 84.339194 88.458433

- 그런데 위에서 확인했듯이 고혈압 진료내역이 있다고 현재도 고혈압인것은 아니다

- 그러니 혈압범주와 혈압세부범주를 비교하겠다

- 또한 혈압범주를 혈압세부범주로 나눈것이 얼마나 유용할지도 판단하겠다

tab2 = pd.crosstab(df['혈압세부범주'], df['혈압범주'])
tab2
혈압범주 고혈압 저혈압 정상혈압
혈압세부범주
HH 60973 0 0
HL 93 0 0
HN 46641 0 0
LL 0 1534 0
LN 0 1014 0
NH 27617 0 0
NL 0 24234 0
NN 0 0 837894

- 우선 수축기고혈압이면서 이완기저혈압인사람은 93명이다 ---> 백만명중에 93명이니 무시할만한 수준이다

- 그리고 수축기저혈압이면서 이완기고혈압인 사람은 없다!

- 또한 혈압범주에서 정상이었으면 혈압세부범주에서도 정상이다

- 저혈압인 사람중에서 둘다 저혈압인 사람은 적은데 고혈압인 사람중에서 둘다 고혈압인 사람은 50%정도이다

- 그래서 혈압범주와 혈압세부범주는 얼마나 차이가 있는가?

- 간단히 plot을 통해 확인하겠다

sns.violinplot(x = '혈압세부범주', y = '공복혈당', data = df)
<AxesSubplot:xlabel='혈압세부범주', ylabel='공복혈당'>
sns.violinplot(x = '혈압세부범주', y = 'BMI', data = df)
<AxesSubplot:xlabel='혈압세부범주', ylabel='BMI'>

- 일단 혈압범주나 혈압세부범주를 가지고 수축기혈압이나 이완기혈압의 boxplot등을 그려보는것은 별로 유용하지 않다

- 왜냐하면 애초애 수축기, 이완기혈압을 통해 혈압범주들을 구했기 때문이다(상관관계 매우큼)

- 또한 수축기혈압과 이완기혈압의 상관계수는 0.7이 넘는다

- 당연히 고혈압인 사람은 혈압이 높게나오고 저혈압인 사람은 혈압이 낮게 나올것이다

sns.violinplot(x = '혈압범주', y = '수축기혈압', data = df)
<AxesSubplot:xlabel='혈압범주', ylabel='수축기혈압'>
sns.violinplot(x = '혈압세부범주', y = '수축기혈압', data = df)
<AxesSubplot:xlabel='혈압세부범주', ylabel='수축기혈압'>
sns.violinplot(x = '혈압범주', y = '이완기혈압', data = df)
<AxesSubplot:xlabel='혈압범주', ylabel='이완기혈압'>
sns.violinplot(x = '혈압세부범주', y = '이완기혈압', data = df)
<AxesSubplot:xlabel='혈압세부범주', ylabel='이완기혈압'>

- 그래서 결론은 혈압세부범주는 사용안하고 혈압범주만 사용할것이다

- 문제가 되었던 고혈압과 저혈압을 둘다 가지고 있지만 혈압범주를 나누는 함수에서 고혈압을 먼저 처리하여 고혈압이 된 사람들은 문제가 없다

- 왜냐하면 HL인 사람은 93명밖에 없으면 LH인 사람은 0명이기 때문이다

- 애초애 저혈압인 사람은 3만명도 안되는데 고혈압인 사람은 12만명이 넘는다

- 정상혈압인 사람은 혈압세부범주에서 NN인 사람으로 동일하다

- 그리고 저혈압인 경우 HL, NL, LL 총 3가지가 있는데 대부분 HL(90%)이어서 문제 없다

- 제일 차이가 나는건 고혈압인 경우이다(HH, HN, NH)

- HH인경우가 50%이고 HN은 30%, NH는 20%정도이다

- 만약 둘다 고혈압인 경우와 하나만 고혈압인 경우가 많이 다르다면 고려할만 하지만 차이가 별로 없다

- 하지만 위에서 그린 공복혈당, BMI 바이올린플랏을 보면 알수있듯이 거의 차이가 없다

- 혈압범주를 세분화시킨건 여러개의 봉우리가 생긴원인이 이때문인지 확인하려고 한 것이다

- 그런데 세분화시켰음에도 이는 동일하다 ---> 그러니 혈압은 고혈압, 저혈압, 정상혈압으로만 나누겠다

- 결론 : 혈압을 세분화시켜도 차이가 거의 없으니 시각화하기 편하게 3가지로만 나눈다

df = df.drop('혈압세부범주', axis = 1)

맥압

- 수축기혈압과 이완기혈압의 차이를 맥압이라고 하는데 수축기혈압과 이완기혈압 둘다 정상 범주에 속하더라도 맥압이 높다면 건강상에 문제가 있을 수 있다고 한다

- 성인의 경우 35~45mmHg가 정상범주라고 한다

- 참고 : http://assinmun.kr/m/page/view.php?no=4162&code=20140925141337_5787&d_code=20140925150830_7887&ds_code=

- 맥압의 분포는 어떻게 되는지 살펴보도록 하겠다

df['맥압'] = df['수축기혈압'] - df['이완기혈압']
df.head()
성별 연령대 수축기혈압 이완기혈압 공복혈당 고혈압_당뇨_진료내역 BMI 혈압범주 맥압
0 1 20대초반 116 78 94 4 16.6 정상혈압 38
1 1 20대초반 100 60 79 4 22.3 정상혈압 40
2 1 20대초반 100 60 87 4 21.9 정상혈압 40
3 1 20대초반 111 70 72 4 20.2 정상혈압 41
4 1 20대초반 120 80 98 4 20.0 정상혈압 40
sns.histplot(data = df, x = '맥압', binwidth = 5)
<AxesSubplot:xlabel='맥압', ylabel='Count'>

- 눈에 띄는 봉우리가 2개 보인다(쌍봉 분포)

- 이완기혈압의 히스토그램을 그려봤을 때 봉우리가 2개(70, 80)였는데 10차이이다

- 맥압의 경우 40과 50부근에 봉우리가 있는데 이역시 10차이이다

- 맥압은 수축기혈압과 이완기혈압의 차이이므로 이완기혈압에 영향을 당연히 받는다

- 중요한건 이완기혈압의 분포가 왜 쌍봉 분포인지를 파악하는것이다

- 고혈압, 당뇨 진료내역에 따른 박스플랏을 그려보겠다

sns.violinplot(x = '고혈압_당뇨_진료내역', y = '맥압', data = df)
<AxesSubplot:xlabel='고혈압_당뇨_진료내역', ylabel='맥압'>

- 고혈압, 당뇨 진료내역에 따른 수축기혈압, 이완기혈압 분포를 확인했을땐 당뇨병만 진료내역이 있는 경우와 둘 다 없는 경우의 분포가 비슷했는데 맥압의 경우는 다르다

- 고혈압 진료내역이 있지만 현재는 혈압이 정상범주에 속해있을 수 도 있다

- 그러니 고혈압, 당뇨 진료내역의 따른 맥압의 분포말고 건강검진을 받았을 당시의 혈압으로 구분하겠다

- 혈압이 정상범주인 경우의 맥압의 분포와 그렇지 않은 경우 맥압의 분포를 비교하겠다

sns.violinplot(x = '혈압범주', y = '맥압', data = df)
<AxesSubplot:xlabel='혈압범주', ylabel='맥압'>

- 저혈압의 경우 종모양을 띄고있다

- 고혈압의 경우 눈에띄는 봉우리가 4개정도 보인다

- 바이올린플랏을 보면 정상혈압에서 유달리 눈에띄는 2개의 봉우리를 볼 수 있는데 이 때문에 맥압이 쌍봉분포의 형태를 띄는것으로 보인다

- 고혈압인 경우 맥압이 정상혈압, 저혈압인 경우보다 높다

- 혈압이 오르면 수축기혈압의 상승폭이 이완기혈압의 상승폭보다 높다고 해석할 수 있다

- 수축기혈압이 이완기혈압보다 기본적으로 높기에 어찌보면 당연하다

- 한편 위의 출처에 나와있는 설명을 보면 나이가 들어감에따라 맥압이 높아진다고 한다

- 연령대에 따른 박스플랏을 그려보겠다

plt.figure(figsize = (12, 6))
sns.violinplot(x = '연령대', y = '맥압', data = df)
<AxesSubplot:xlabel='연령대', ylabel='맥압'>

- 연령대가 높아질수록 평균 맥압은 증가하는것으로 보인다

sns.violinplot(x = '성별', y = '맥압', data = df)
<AxesSubplot:xlabel='성별', ylabel='맥압'>
df.groupby('성별')['맥압'].describe()
count mean std min 25% 50% 75% max
성별
1 510227.0 46.664977 9.35818 8.0 40.0 46.0 51.0 130.0
2 489773.0 45.478534 10.20447 4.0 40.0 44.0 50.0 140.0

- 남자인 경우 여자인 경우보다 평균적으로 수축기혈압이 5정도 높았고 이완기혈압은 4정도 높았는데 맥압의 경우는 1정도 차이가 난다

- 수축기혈압(5) - 이완기혈압(4) = 맥압(1)

- 하지만 통계적으로 유의한 차이가 아닐 수 도 있기에 평균차이검정을 실시하겠다

- 특정값에 집중적으로 몰려있어 정규분포라 하기 어려울 수 있지만 종모양이고 표본 크기가 매우 크므로 t검정을 실행할 것이다

x_B = df.query('성별 == 1')['맥압']  ## 남자 
x_NB = df.query('성별 == 2')['맥압'] ## 여자
# print(stats.bartlett(x_B, x_NB))
print(stats.levene(x_B, x_NB))
LeveneResult(statistic=1875.218691424608, pvalue=0.0)

- 분산의 동일성 검정에서 p-value가 각각 0.0, 0.0으로 전체적으로 판단했을 때

- 두 그룹의 분산이 동일하지 않으므로 이분산 가정 하에서 t검정을 실시한다

t_stat, pvalue = stats.ttest_ind(x_B, x_NB, equal_var = False) 
print(t_stat, pvalue)
60.525582091193876 0.0

- t통계량에 근거한 p-value가 0.0이므로 남자와 여자의 맥압은 다르다고 할 수 있다

혈압의 끝자리수 편향

- 혈압의 끝자리수 편향에 대해 얘기하기전에 수축기혈압과 이완기혈압의 분포를 다시한번 시각화하겠다

fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2,2)
sns.histplot(data = df, x = '수축기혈압', hue = '고혈압_당뇨_진료내역', palette = tab10_colors[::-1], binwidth = 5, ax = ax1)
sns.histplot(data = df, x = '이완기혈압', hue = '고혈압_당뇨_진료내역', palette = tab10_colors[::-1], binwidth = 2, ax = ax2)
sns.histplot(data = df, x = '수축기혈압', hue = '혈압범주', binwidth = 5, ax = ax3)
sns.histplot(data = df, x = '이완기혈압', hue = '혈압범주', binwidth = 2, ax = ax4)
ax1.legend(['없음', '당뇨', '고혈압', '고혈압, 당뇨'])
ax2.legend(['없음', '당뇨', '고혈압', '고혈압, 당뇨'])
fig.tight_layout()

- 일단 고혈압, 당뇨 진료내역이 둘 다 없는 경우가 대부분이다

- 진료내역이 아닌 고혈압, 정상혈압, 저혈압으로 나눠도 정상혈압이 대부분이다(혈당의 경우도 정상혈당이 대부분)

- 처음에 수축기혈압과 이완기혈압의 히스토그램을 그려봤을 때 봉우리가 많은것이 신기했다

- 혈당이나 BMI의 히스토그램같이 종모양일 줄 알았기 때문이었다

- 왜 혈압분포의 경우 봉우리가 많은지에 대해 성별, 연령대, 혈압범주로 나누어 살펴봤지만 알 수 없었다

- 위의 분포를 보면 알겠지만 혈압범주로 나누는건 의미가 없고 성별, 연령대의 경우도 마찬가지었다

- 봉우리가 너무 특정구간에만 몰려있는것이 이상했다

- 그래서 혈압을 측정함에 있어 문제가 있는것이 아닐까 생각했다

- 혈압을 측정하여 소수점이 나오면 이를 자연수로 만듦으로써 소수점 이하를 없애는데 측정자의 주관이 개입한것이다

- 예컨대 수축기혈압이 138.7mmHg가 나왔다면 반올림하면 139mmHg이지만 숫자를 깔끔하게 하기 위해 140mmHg로 처리하는 것이다

- 정말 그런건지 확인하겠다

df['수축기_혈압_끝자리수'] = list(map(lambda x: int(str(x)[-1]), df['수축기혈압']))
df['이완기_혈압_끝자리수'] = list(map(lambda x: int(str(x)[-1]), df['이완기혈압']))
df.head()
성별 연령대 수축기혈압 이완기혈압 공복혈당 고혈압_당뇨_진료내역 BMI 혈압범주 맥압 수축기_혈압_끝자리수 이완기_혈압_끝자리수
0 1 20대초반 116 78 94 4 16.6 정상혈압 38 6 8
1 1 20대초반 100 60 79 4 22.3 정상혈압 40 0 0
2 1 20대초반 100 60 87 4 21.9 정상혈압 40 0 0
3 1 20대초반 111 70 72 4 20.2 정상혈압 41 1 0
4 1 20대초반 120 80 98 4 20.0 정상혈압 40 0 0

- 특정 숫자가 혈압의 끝자리수로 많이 등장할 이유는 없을 것이다

- 그렇다면 끝자리수로 숫자마다 대략 10만개씩 등장할 것이다

df.groupby(by = ['수축기_혈압_끝자리수']).agg('size').reset_index().rename(columns = {0:'count'}).\
plot.bar(x = '수축기_혈압_끝자리수', y = 'count', rot = 0, legend = False)
<AxesSubplot:xlabel='수축기_혈압_끝자리수'>

- 수축기혈압의 전체 count수가 100만인데 약 40만개의 끝자리수가 $0$이다!

- $0$ 다음으로 많이 등장하는 숫자는 $5$이다

- $0$과 $5$를 제외하고보면 홀수보다는 짝수가 더 많이 등장한다

- 이완기혈압도 수축기혈압과 같은 양상을 보이는지 확인하겠다

df.groupby(by = ['이완기_혈압_끝자리수']).agg('size').reset_index().rename(columns = {0:'count'}).\
plot.bar(x = '이완기_혈압_끝자리수', y = 'count', rot = 0, legend = False)
<AxesSubplot:xlabel='이완기_혈압_끝자리수'>

- 이완기혈압도 수축기혈압과 마찬가지다

- 수축기혈압과 이완기혈압의 분포에서 봉우리가 많이보인것은 끝자리수 편향 때문이었다

- 눈에 띄게 높은 봉우리는 편향에 의한 끝자리수 $0$에 의한것이었고 자잘자잘한 봉우리는 $5$와 홀수보다 짝수가 더 선호되기 때문이었다

- 수축기혈압의 봉우리가 이완기혈압의 봉우리보다 많은것은 수축기혈압이 이완기혈압보다 더 넓게 분포되었기 때문이다(ex) 끝자리가 $0$인 숫자의 개수가 더 많다)

df = df.drop(['수축기_혈압_끝자리수', '이완기_혈압_끝자리수'], axis = 1)

- 이제 공복혈당의 분포를 확인하겠다

공복혈당

sns.histplot(data = df, x = '공복혈당', binwidth = 5)
<AxesSubplot:xlabel='공복혈당', ylabel='Count'>

- 오른쪽으로 꼬리가 긴 분포이다

- 성별에 따라 공복혈당이 다른지 확인하겠다

sns.violinplot(x = '성별', y = '공복혈당', data = df)
<AxesSubplot:xlabel='성별', ylabel='공복혈당'>

- 성별에 따라 공복혈당은 비슷해보인다

- 수치로 확인하겠다

df.groupby('성별')['공복혈당'].describe()
count mean std min 25% 50% 75% max
성별
1 510227.0 101.141925 24.891629 60.0 88.0 96.0 106.0 358.0
2 489773.0 96.491818 20.538645 60.0 86.0 93.0 101.0 358.0

- 남자가 여자보다 공복혈당이 평균 5정도 높고 표준편차가 4정도 높다

- 혈압과 혈당을 보면 남자가 여자보다 살짝 높은정도를 제외하면 차이가 없다

- $\text{남자} = \text{여자} + \alpha$ 와 같이 어느 한쪽으로 나머지를 나타낼 수 있어 보인다

- 이제껏 분석한 내용을 보면 성별은 그다지 중요한 정보가 아닌것 같다

- 고혈압_당뇨 진료내역에 따른 공복혈당의 분포를 확인하겠다

sns.violinplot(x = '고혈압_당뇨_진료내역', y = '공복혈당', data = df)
<AxesSubplot:xlabel='고혈압_당뇨_진료내역', ylabel='공복혈당'>
df.groupby('고혈압_당뇨_진료내역')['공복혈당'].describe()
count mean std min 25% 50% 75% max
고혈압_당뇨_진료내역
1 53398.0 130.447114 40.524698 60.0 104.0 122.0 145.0 358.0
2 162826.0 99.671023 16.816511 60.0 90.0 97.0 106.0 351.0
3 43114.0 134.759892 45.923753 60.0 104.0 124.0 151.0 358.0
4 740662.0 94.320677 15.557456 60.0 86.0 93.0 100.0 358.0

- 4개의 분포 모두 공복혈당이 큰 쪽에 이상점이 매우 많다

- 고혈압, 당뇨 둘 다 진료내역이 있는 경우와 당뇨 진료내역이 있는 경우의 분포는 당뇨만 있는 경우가 조금 더 넓게 퍼진것을 빼면 동일하다

- 분포의 중심은 125인것으로 보인다

- 고혈압, 당뇨 둘 다 진료내역이 없는 경우와 고혈압만 있는 경우의 분포는 고혈압만 있는 경우가 조금 더 넓게 퍼진것을 빼면 동일하다

- 분포의 중심은 95인것으로 보인다

- 당뇨가 있다면 공복혈당이 평균적으로 30정도 높다

- 위의 그림을 통해 공복혈당의 분포는 당뇨병의 진료내역이 좌지우지하는것으로 보인다(고혈압 진료내역은 거의 영향을 끼치지 못한다)

sns.violinplot(x = '혈압범주', y = '공복혈당', data = df)
<AxesSubplot:xlabel='혈압범주', ylabel='공복혈당'>

- 고혈압인 경우 공복혈당이 고혈압이 아닌경우보다 더 높은것으로 보인다

- 고혈압이 있는 사람들중 공복혈당이 높은 사람이 많다

공복혈당 고찰

- 위에서 공복혈당 분포를 살펴봤는데 당뇨 진료내역이 있지만 공복혈당이 낮은 사람도 있고 당뇨 진료내역이 없지만 공복혈당이 높은사람도 있었다

- 당뇨 진료내역이 있지만 공복혈당이 정상범주안에 있는 사람은 혈당조절을 잘하고 있는것으로 간주할 수 있을 것 같다

- 이들의 비율을 확인하겠다

- 당뇨병의 진단에 있어 혈당치의 기준은 공복 혈당치 126 mg/dL 이상(고혈당)이라고 한다

- 참고 : https://www.diabetes.or.kr/general/class/index.php?idx=5

- 위에서 기준을 넘어가는 혈당을 가진 사람들은 고혈당으로 그렇지 않는 사람은 정상혈당으로 간주하겠다

- 공복혈당이 너무 낮으면(60 mg/dL이하) 저혈당으로 간주하나 거의 최소값이 60 mg/dL임으로 무시하겠다

def g(x):
    if x >= 126:
        y = '고혈당'
    else:
        y = '정상혈당'
    return y    
df['혈당범주'] = list(map(g, df['공복혈당']))
df.head()
성별 연령대 수축기혈압 이완기혈압 공복혈당 고혈압_당뇨_진료내역 BMI 혈압범주 혈당범주
0 1 20대초반 116 78 94 4 16.6 정상혈압 정상혈당
1 1 20대초반 100 60 79 4 22.3 정상혈압 정상혈당
2 1 20대초반 100 60 87 4 21.9 정상혈압 정상혈당
3 1 20대초반 111 70 72 4 20.2 정상혈압 정상혈당
4 1 20대초반 120 80 98 4 20.0 정상혈압 정상혈당
count = df.groupby('혈당범주').size()
plt.pie(x = count, labels = ['고혈당', '정상혈당'], autopct = '%.2f%%') 
plt.show()

- 정상혈당은 약 93%, 고혈당은 약 7% 이다

tab = pd.crosstab(df['혈당범주'], df['고혈압_당뇨_진료내역'])
tab.apply(lambda x: x*100 / sum(x), axis = 0) 
고혈압_당뇨_진료내역 1 2 3 4
혈당범주
고혈당 44.911794 5.178534 47.938025 2.412976
정상혈당 55.088206 94.821466 52.061975 97.587024

- 위를 보면 고혈압_당뇨 둘 다 없는경우 2.5%정도는 혈당에 문제가 있는것을 알 수 있다

- 앞서 봤던 고혈압(12%)에 비하면 매우 적은 수치이다

- 고혈압만 있는 경우 고혈당인 경우는 5%정도로 고혈압 당뇨 둘다 없는경우와 비슷한 수치이다

- 당뇨 진료내역이 있는 경우 고혈당인 경우는 47%정도이다

- 고혈압 진료내역이 있는 경우 고혈압인 경우는 31%정도였다

- 이와 비교하면 고혈압에서 정상혈압에 속하는 것보다 고혈당에서 정상혈당에 속하는것이 흔치않음을 알 수 있다

- 전체 인구를 혈압, 혈당에 따라 나타내 확인하겠다

tab = pd.crosstab(df['혈압범주'], df['혈당범주'])
tab*100 / 1000000
혈당범주 고혈당 정상혈당
혈압범주
고혈압 1.7877 11.7447
저혈압 0.0918 2.5864
정상혈압 5.2159 78.5735

- 전체 중 고혈당은 8%, 고혈압은 12%이다

- 혈압, 혈당 모두 정상인 사람은 78%이다

- 결론 : 당뇨가 고혈압보다 흔하며 당뇨인 사람중 고혈압인 비율보다 고혈압인 사람중 당뇨인 비율이 더 낮다

혈압, 혈당 범주 vs 고혈압, 당뇨 진료내역

- 한가지 의문점이 든다

- 위에서 봤듯이 고혈압, 당뇨 진료내역이 있다고 현재도 고혈압, 당뇨인것은 아니다

- 그러면 고혈압, 당뇨 진료내역은 쓸모있는지가 의문이다

- 그냥 혈압, 혈당 범주로 대체하면 될것같다

- 고혈압, 당뇨 진료내역에 따라 수축기혈압에 차이가 있었다

- 그런데 이 차이가 그냥 고혈압에 의한것이라면??

- 고혈압 진료내역이 있으면 혈압이 정상범주더라도 높은쪽에 위치할것이라 생각했다

- 이것이 아니라면 굳이 애매모호한 고혈압 진료내역 대신에 혈압범주를 사용하는것이 좋을것이다(당뇨도 마찬가지)

- 이를 평균 차이 검정을 통해 확인하겠다

- 혈압(수축기, 이완기)과 혈당에 대해 특정 값에 데이터가 많이 몰려있어 정규분포는 아닌것처럼 보이지만

- 표본크기가 매우 크고 종모양이기에 t검정을 실시하겠다

- 우선 등분산인지 검정하겠다

수축기 혈압
sns.boxplot(data = df.query('혈압범주 == "정상혈압"'), y = '수축기혈압', x = '고혈압_당뇨_진료내역') 
<AxesSubplot:xlabel='고혈압_당뇨_진료내역', ylabel='수축기혈압'>
x_B = df.query('(혈압범주 == "정상혈압" and 고혈압_당뇨_진료내역 == 1) or (혈압범주 == "정상혈압" and 고혈압_당뇨_진료내역 == 2)')['수축기혈압']
x_NB = df.query('(혈압범주 == "정상혈압" and 고혈압_당뇨_진료내역 == 3) or (혈압범주 == "정상혈압" and 고혈압_당뇨_진료내역 == 4)')['수축기혈압']
# print(stats.bartlett(x_B, x_NB))
print(stats.levene(x_B, x_NB))
LeveneResult(statistic=3281.337451885476, pvalue=0.0)

- p-value가 0이다

- 그러니 이분산 가정하에 t검정을 실시하겠다

np.mean(x_B),np.mean(x_NB)
(123.56359921833364, 117.8503658501316)
t_stat, pvalue = stats.ttest_ind(x_B, x_NB, equal_var = True, alternative = 'greater') 
print(t_stat, pvalue)
183.60168146108893 0.0

- t통계량에 근거한 p-value가 0이다

- 정상혈압 범주에 속하더라도 고혈압 진료내역이 있는 경우 그렇지 않은 경우보다

- 평균 수축기혈압이 높다고 할 수 있다

sns.boxplot(data = df.query('혈압범주 == "고혈압"'), y = '수축기혈압', x = '고혈압_당뇨_진료내역') 
<AxesSubplot:xlabel='고혈압_당뇨_진료내역', ylabel='수축기혈압'>
x_B = df.query('(혈압범주 == "고혈압" and 고혈압_당뇨_진료내역 == 1) or (혈압범주 == "고혈압" and 고혈압_당뇨_진료내역 == 2)')['수축기혈압']
x_NB = df.query('(혈압범주 == "고혈압" and 고혈압_당뇨_진료내역 == 3) or (혈압범주 == "고혈압" and 고혈압_당뇨_진료내역 == 4)')['수축기혈압']
# print(stats.bartlett(x_B, x_NB))
print(stats.levene(x_B, x_NB))
LeveneResult(statistic=5.967971491616882, pvalue=0.014569299214451329)

- p-value가 0.015이다

- 그러니 이분산 가정하에 t검정을 실시하겠다

np.mean(x_B),np.mean(x_NB)
(146.45712425457612, 143.02040002952248)
t_stat, pvalue = stats.ttest_ind(x_B, x_NB, equal_var = False, alternative = 'greater') 
print(t_stat, pvalue)
56.10982828533957 0.0

- t통계량에 근거한 p-value가 0이다

- 고혈압 범주에 속하더라도 고혈압 진료내역이 있는 경우 그렇지 않은 경우보다

- 평균 수축기혈압이 높다고 할 수 있다

sns.boxplot(data = df.query('혈압범주 == "저혈압"'), y = '수축기혈압', x = '고혈압_당뇨_진료내역') 
<AxesSubplot:xlabel='고혈압_당뇨_진료내역', ylabel='수축기혈압'>
x_B = df.query('(혈압범주 == "저혈압" and 고혈압_당뇨_진료내역 == 1) or (혈압범주 == "저혈압" and 고혈압_당뇨_진료내역 == 2)')['수축기혈압']
x_NB = df.query('(혈압범주 == "저혈압" and 고혈압_당뇨_진료내역 == 3) or (혈압범주 == "저혈압" and 고혈압_당뇨_진료내역 == 4)')['수축기혈압']
# print(stats.bartlett(x_B, x_NB))
print(stats.levene(x_B, x_NB))
LeveneResult(statistic=294.86340294030697, pvalue=9.754371916406274e-66)

- p-value가 0이다

- 그러니 이분산 가정하에 t검정을 실시하겠다

np.mean(x_B),np.mean(x_NB)
(108.05237887385421, 100.26552611163285)
t_stat, pvalue = stats.ttest_ind(x_B, x_NB, equal_var = False, alternative = 'greater') 
print(t_stat, pvalue)
32.08308134495256 1.7389384139189746e-190

- t통계량에 근거한 p-value가 0이다

- 저혈압 범주에 속하더라도 고혈압 진료내역이 있는 경우 그렇지 않은 경우보다

- 평균 수축기혈압이 높다고 할 수 있다

이완기 혈압
sns.boxplot(data = df.query('혈압범주 == "정상혈압"'), y = '이완기혈압', x = '고혈압_당뇨_진료내역') 
<AxesSubplot:xlabel='고혈압_당뇨_진료내역', ylabel='이완기혈압'>
x_B = df.query('(혈압범주 == "정상혈압" and 고혈압_당뇨_진료내역 == 1) or (혈압범주 == "정상혈압" and 고혈압_당뇨_진료내역 == 2)')['이완기혈압']
x_NB = df.query('(혈압범주 == "정상혈압" and 고혈압_당뇨_진료내역 == 3) or (혈압범주 == "정상혈압" and 고혈압_당뇨_진료내역 == 4)')['이완기혈압']
# print(stats.bartlett(x_B, x_NB))
print(stats.levene(x_B, x_NB))
LeveneResult(statistic=2356.370080988216, pvalue=0.0)
np.mean(x_B),np.mean(x_NB)
(75.8151400030064, 73.81038696243168)
t_stat, pvalue = stats.ttest_ind(x_B, x_NB, equal_var = False, alternative = 'greater') 
print(t_stat, pvalue)
98.5367189947476 0.0
sns.boxplot(data = df.query('혈압범주 == "고혈압"'), y = '이완기혈압', x = '고혈압_당뇨_진료내역') 
<AxesSubplot:xlabel='고혈압_당뇨_진료내역', ylabel='이완기혈압'>
x_B = df.query('(혈압범주 == "고혈압" and 고혈압_당뇨_진료내역 == 1) or (혈압범주 == "고혈압" and 고혈압_당뇨_진료내역 == 2)')['이완기혈압']
x_NB = df.query('(혈압범주 == "고혈압" and 고혈압_당뇨_진료내역 == 3) or (혈압범주 == "고혈압" and 고혈압_당뇨_진료내역 == 4)')['이완기혈압']
# print(stats.bartlett(x_B, x_NB))
print(stats.levene(x_B, x_NB))
LeveneResult(statistic=886.1776754493117, pvalue=4.221002676847139e-194)
np.mean(x_B),np.mean(x_NB)
(88.67230944524186, 90.75784190715181)
t_stat, pvalue = stats.ttest_ind(x_B, x_NB, equal_var = False, alternative = 'greater') 
print(t_stat, pvalue)
-43.85799935873388 1.0
sns.boxplot(data = df.query('혈압범주 == "저혈압"'), y = '이완기혈압', x = '고혈압_당뇨_진료내역') 
<AxesSubplot:xlabel='고혈압_당뇨_진료내역', ylabel='이완기혈압'>
x_B = df.query('(혈압범주 == "저혈압" and 고혈압_당뇨_진료내역 == 1) or (혈압범주 == "저혈압" and 고혈압_당뇨_진료내역 == 2)')['이완기혈압']
x_NB = df.query('(혈압범주 == "저혈압" and 고혈압_당뇨_진료내역 == 3) or (혈압범주 == "저혈압" and 고혈압_당뇨_진료내역 == 4)')['이완기혈압']
# print(stats.bartlett(x_B, x_NB))
print(stats.levene(x_B, x_NB))
LeveneResult(statistic=15.370482832670643, pvalue=8.85777173868185e-05)
np.mean(x_B),np.mean(x_NB)
(56.317765168048886, 56.322322485811114)
t_stat, pvalue = stats.ttest_ind(x_B, x_NB, equal_var = False, alternative = 'greater') 
print(t_stat, pvalue)
-0.07423781188396941 0.5295867869938078

- 이완기혈압은 수축기혈압과 다른 양상을 보였다

- 정상혈압에 속하는 경우를 제외하면 고혈압 진료내역이 있는 경우 그렇지 않은 경우보다

- 이완기혈압이 더 높다고 할 수 없다

공복혈당
sns.boxplot(data = df.query('혈당범주 == "고혈당"'), y = '공복혈당', x = '고혈압_당뇨_진료내역') 
<AxesSubplot:xlabel='고혈압_당뇨_진료내역', ylabel='공복혈당'>
x_B = df.query('(혈당범주 == "고혈당" and 고혈압_당뇨_진료내역 == 1) or (혈당범주 == "고혈당" and 고혈압_당뇨_진료내역 == 3)')['공복혈당']
x_NB = df.query('(혈당범주 == "고혈당" and 고혈압_당뇨_진료내역 == 2) or (혈당범주 == "고혈당" and 고혈압_당뇨_진료내역 == 4)')['공복혈당']
# print(stats.bartlett(x_B, x_NB))
print(stats.levene(x_B, x_NB))
LeveneResult(statistic=723.8340005554786, pvalue=1.234539994480611e-158)

- p-value가 0.015이다

- 그러니 이분산 가정하에 t검정을 실시하겠다

np.mean(x_B),np.mean(x_NB)
(165.37926091825307, 152.71555656934308)
t_stat, pvalue = stats.ttest_ind(x_B, x_NB, equal_var = False, alternative = 'greater') 
print(t_stat, pvalue)
41.82511929437937 0.0

- t통계량에 근거한 p-value가 0이다

- 고혈당 범주에 속하더라도 당뇨 진료내역이 있는 경우 그렇지 않은 경우보다

- 평균 공복혈당이 높다고 할 수 있다

sns.boxplot(data = df.query('혈당범주 == "정상혈당"'), y = '공복혈당', x = '고혈압_당뇨_진료내역') 
<AxesSubplot:xlabel='고혈압_당뇨_진료내역', ylabel='공복혈당'>
x_B = df.query('(혈당범주 == "정상혈당" and 고혈압_당뇨_진료내역 == 1) or (혈당범주 == "정상혈당" and 고혈압_당뇨_진료내역 == 3)')['공복혈당']
x_NB = df.query('(혈당범주 == "정상혈당" and 고혈압_당뇨_진료내역 == 2) or (혈당범주 == "정상혈당" and 고혈압_당뇨_진료내역 == 4)')['공복혈당']
# print(stats.bartlett(x_B, x_NB))
print(stats.levene(x_B, x_NB))
LeveneResult(statistic=7492.881978979407, pvalue=0.0)

- p-value가 0.015이다

- 그러니 이분산 가정하에 t검정을 실시하겠다

np.mean(x_B),np.mean(x_NB)
(103.95798465157533, 93.5627473825332)
t_stat, pvalue = stats.ttest_ind(x_B, x_NB, equal_var = False, alternative = 'greater') 
print(t_stat, pvalue)
171.357714380074 0.0

- t통계량에 근거한 p-value가 0이다

- 정상혈당 범주에 속하더라도 당뇨 진료내역이 있는 경우 그렇지 않은 경우보다

- 평균 공복혈당이 높다고 할 수 있다

- plot을 그려보고 가설검정을 통해 진료내역이 영향을 끼치는 것을 알 수 있었다

- 이제 BMI의 분포를 확인하겠다

BMI

sns.histplot(data = df, x = 'BMI', hue = '성별', binwidth = 0.5)
<AxesSubplot:xlabel='BMI', ylabel='Count'>

- 종모양인 것 같지만 오른쪽으로 꼬리가 조금 길다

- BMI도 다른 양적변수와 마찬가지로 남자가 여자보다 조금 더 높은것을 제외하면 동일하다

- BMI의 분포를 혈압범주, 혈당범주에 따라 시각화해보자

sns.boxplot(x = '혈압범주', y = 'BMI', data = df)
<AxesSubplot:xlabel='혈압범주', ylabel='BMI'>
df.groupby('혈압범주')['BMI'].describe()
count mean std min 25% 50% 75% max
혈압범주
고혈압 135324.0 25.242038 3.448873 14.8 22.9 25.0 27.3 40.3
저혈압 26782.0 21.835046 2.736182 14.8 19.9 21.6 23.5 38.0
정상혈압 837894.0 23.634720 3.213817 14.8 21.4 23.4 25.6 40.3

- 고혈압은 정상혈압보다 BMI가 평균적으로 1.6 크다

- 정상혈압은 저혈압보다 BMI가 평균적으로 1.8 크다

sns.boxplot(x = '혈당범주', y = 'BMI', data = df)
<AxesSubplot:xlabel='혈당범주', ylabel='BMI'>
df.groupby('혈당범주')['BMI'].describe()
count mean std min 25% 50% 75% max
혈당범주
고혈당 70954.0 25.168695 3.396526 14.8 22.9 24.9 27.1 40.3
정상혈당 929046.0 23.699806 3.266234 14.8 21.4 23.5 25.7 40.3

- 고혈당인 경우 정상혈당보다 BMI가 평균적으로 1.5정도 크다

- 고혈압과 당뇨가 끼치는 영향을 같이 확인하겠다

sns.boxplot(x = '고혈압_당뇨_진료내역', y = 'BMI', data = df)
<AxesSubplot:xlabel='고혈압_당뇨_진료내역', ylabel='BMI'>

- 고혈압 진료내역만 있는 경우가 당뇨 진료내역만 있는 경우보다 BMI가 크다

- 여기까지 개별 양적변수에 대한 분포를 확인했다

- 그런데 수축기혈압과 이완기혈압같이 두 변수사이에 관계가 있을 수 있다

- 그렇기에 산점도를 그려 변수사이에 관계를 확인하겠다

두 변수의 시각화

상관관계 행렬

- 우선 양적변수간의 상관관계 행렬을 그려보겠다

corr_df = df.loc[:, ('수축기혈압', '이완기혈압', '맥압', '공복혈당', 'BMI')]
corr_matrix = corr_df.corr(method = 'pearson') # 상관관계 행렬
corr_matrix
수축기혈압 이완기혈압 맥압 공복혈당 BMI
수축기혈압 1.000000 0.743006 0.743398 0.186501 0.304383
이완기혈압 0.743006 1.000000 0.104699 0.138717 0.275492
맥압 0.743398 0.104699 1.000000 0.138498 0.176977
공복혈당 0.186501 0.138717 0.138498 1.000000 0.173688
BMI 0.304383 0.275492 0.176977 0.173688 1.000000
sns.heatmap(corr_matrix, annot = True, cbar = False)
<AxesSubplot:>

- 수축기혈압과 이완기혈압은 상관계수가 0.74로 높다

- 혈압과 혈당끼리는 상관관계가 강하지 않다

- BMI와 혈압과는 약한 양의 상관관계가 있다

- BMI는 혈당보다는 혈압에 영향을 더 받는다

- 신기한게 맥압은 수축기혈압과 이완기혈압의 차이여서 이완기혈압과의 상관관계가 당연히 크다고 생각했는데 아니었다

- 맥압과 이완기혈압의 상관계수는 0.1이다(서로 상관이 없는 수준이다)

- 이제 두 변수 사이의 관계를 시각화하겠다

def jitter(values, i):
    return values + np.random.normal(i, 0.5, len(values))
sns.scatterplot(x = jitter(df.수축기혈압, 1), y = jitter(df.이완기혈압, 1), alpha = 0.01, s = 20)
<AxesSubplot:xlabel='수축기혈압', ylabel='이완기혈압'>

- 문제가 있는데 관측치(점의 개수)가 너무 많아 시각화가 제대로 되지 않는다

- 전체의 1%(10000개) 정도만 무작위 추출하여 산점도를 그려보겠다

np.random.seed(2021)
df_s = df.sample(frac = 0.01)
ft = df_s['고혈압_당뇨_진료내역'].value_counts() 
rft = df_s['고혈압_당뇨_진료내역'].value_counts() / len(df_s['고혈압_당뇨_진료내역']) 
DIS_table2 = pd.DataFrame({'Freq': ft, 'Relative freq': rft})
DIS_table2
Freq Relative freq
4 7336 0.7336
2 1664 0.1664
1 560 0.0560
3 440 0.0440

- 원본과 비율이 거의 동일하다

- 새로운 데이터프레임을 사용해 시각화하겠다

수축기혈압과 이완기혈압

sns.lmplot(x = '수축기혈압', y = '이완기혈압', scatter_kws = {'alpha':0.4, 's':20}, height = 7, data = df_s)
<seaborn.axisgrid.FacetGrid at 0x21109aacac0>

- 수축기혈압은 90~140, 이완기 혈압은 60~90사이에 데이터가 많이 몰려있다

model = smf.ols(formula = '이완기혈압 ~ 수축기혈압', data = df)
result = model.fit()
result.params   # 추정된 직선의 기울기 및 절편
Intercept    14.887860
수축기혈압         0.499706
dtype: float64

- 임의로 이완기혈압을 종속변수, 수축기혈압을 독립변수로 설정하고 회귀선을 추정하면 다음과 같다

- 추정된 회귀선 : $\widehat{\text{이완기혈압}}=14.887860+0.499706\times\text{수축기혈압}$

- 이를 통해 수축기혈압이 1단위 올라갈 때 이완기혈압은 0.5단위 올라간다는 것을 알 수 있다

- 위에서 수축기혈압에 대해 얘기할 때 수축기혈압의 증가폭(10)이 이완기혈압의 증가폭(5)보다 더 크다고 했었다

- 이를 추정된 회귀선을 통해 해석하면 수축기혈압이 10mmHg 상승했으니 이완기혈압이 5mmHg 상승했다고 할 수 있다

- 근데 사실 시각화의 목적은 진료내역에 따른 차이를 확인하는 것이다

- 고혈압_당뇨 진료내역을 색깔변수로 하여 산점도를 그려보겠다

lmplot = sns.lmplot(x = '수축기혈압', y = '이완기혈압', hue = '고혈압_당뇨_진료내역', scatter_kws = {'alpha':0.4, 's':20}, height = 7, data = df_s)
for lh in lmplot._legend.legendHandles: 
    lh.set_alpha(1)    
    lh._sizes = [50] 
str_ = ['고혈압, 당뇨', '고혈압', '당뇨', '없음']

for i in [1, 2, 3, 4]:
    model = smf.ols(formula = '이완기혈압 ~ 수축기혈압', data = df.query('고혈압_당뇨_진료내역 == @i'))
    result = model.fit()
    print(str_[i-1])
    print(result.params)   # 추정된 직선의 기울기 및 절편
    print('----------------------------')
고혈압, 당뇨
Intercept    24.020584
수축기혈압         0.416237
dtype: float64
----------------------------
고혈압
Intercept    20.632209
수축기혈압         0.455056
dtype: float64
----------------------------
당뇨
Intercept    20.112536
수축기혈압         0.450584
dtype: float64
----------------------------
없음
Intercept    11.217272
수축기혈압         0.532116
dtype: float64
----------------------------

- 진료내역이 없는 경우 회귀선의 기울기가 고혈압 진료내역이 있는 경우 회귀선의 기울기보다 더 크다

- 즉 고혈압 진료내역이 있는 경우 진료내역이 없는 경우보다 수축기혈압이 상승했을 때 이완기혈압의 상승폭이 작다는 의미이다

- 고혈압, 당뇨병 진료내역 둘다 없는 사람들의 경우 수축기혈압은 80~140, 이완기 혈압은 50~90사이에 데이터가 많이 몰려있다

- 우선 고혈압_당뇨가 진료내역이 2(고혈압만)인 경우와 1(둘 다 있음)인 경우 비슷한 산점도를 보인다

- 그런데 고혈압_당뇨가 진료내역이 3(당뇨만)인 경우 고혈압_당뇨 진료내역이 1,2인 경우보다 이완기혈압과 수축기혈압이 낮은쪽에 점이 위치하고 있음을 알 수 있다

수축기혈압과 공복혈당

lmplot = sns.lmplot(x = '수축기혈압', y = '공복혈당', hue = '고혈압_당뇨_진료내역', scatter_kws = {'alpha':0.4, 's':20}, height = 7, data = df_s)
for lh in lmplot._legend.legendHandles: 
    lh.set_alpha(1)    
    lh._sizes = [50] 

- 고혈압, 당뇨 진료내역 둘 다 없는 경우 산점도를 보면 공복혈당은 60~140 수축기혈압은 90~150사이에 대부분의 데이터가 존재한다

- 당뇨 진료내역은 없고 고혈압 진료내역만 있는 경우 확실히 당뇨 진료내익이 있는 경우보다 공복혈당이 낮은곳에 데이터가 분포함을 확인할 수 있다

- 고혈압 진료내역만 있는 경우는 고혈압, 당뇨 진료내역 둘 다 없는 경우의 산점도와 비슷하다

- 당뇨 진료내역이 있다면 추가로 고혈압 진료내역이 있다고해서 산점도가 달라지지는 않는것으로 보이며 둘이 비슷하다

- 추세선을 보면 당뇨 진료내역 유무에 따른 차이가 확실히 보인다

- 당뇨 진료내역 없다면 공복혈당은 확실히 낮다

- 당뇨병은 혈당으로 판단하기에 당연한 결과이긴 하다

수축기혈압과 BMI

lmplot = sns.lmplot(x = '수축기혈압', y = 'BMI', hue = '고혈압_당뇨_진료내역', scatter_kws = {'alpha':0.4, 's':20}, height = 7, data = df_s)
for lh in lmplot._legend.legendHandles: 
    lh.set_alpha(1)    
    lh._sizes = [50] 
str_ = ['고혈압, 당뇨', '고혈압', '당뇨', '없음']

for i in [1, 2, 3, 4]:
    model = smf.ols(formula = 'BMI ~ 수축기혈압', data = df.query('고혈압_당뇨_진료내역 == @i'))
    result = model.fit()
    print(str_[i-1])
    print(result.params)   # 추정된 직선의 기울기 및 절편
    print('----------------------------')
고혈압, 당뇨
Intercept    22.524843
수축기혈압         0.021394
dtype: float64
----------------------------
고혈압
Intercept    22.313435
수축기혈압         0.019930
dtype: float64
----------------------------
당뇨
Intercept    18.758148
수축기혈압         0.044647
dtype: float64
----------------------------
없음
Intercept    14.351341
수축기혈압         0.076078
dtype: float64
----------------------------

- 위의 회귀선을 보면 진료내역이 없는 경우의 기울기가 크고

- 고혈압 진료내역 있는 경우의 기울기는 작다

- 하지만 절편을 보면 고혈압 진료내역이 있는 경우가 진료내역이 없는 경우보다 더 크다

- 이는 진료내역이 없는 경우 변동성이 꽤 있어 기울기가 크고

- 고혈압 진료내역이 있는 경우 기본적으로 BMI가 높으므로 더 높아지기 어려우니 기울기가 작다고 볼 수 있다

- 한편 고혈압, 당뇨 진료내역 둘 다 없는 경우 산점도를 보면 BMI은 17~30 수축기혈압은 90~150사이에 대부분의 데이터가 존재하며

- 진료내역이 있는 경우보다 BMI와 수축기혈압이 낮은곳에 더 많은 데이터가 분포한다

- 산점도를 보면 당뇨 진료내역만 있는 경우 수축기혈압이 낮은 곳에 데이터가 분포하고 있으며

- BMI도 고혈압 진료내역이 있는 경우보다 낮은 곳에 분포함을 알 수 있다

- 당뇨 진료내역만 있는 경우 수축기혈압은 90~140, BMI는 17~30사이에 데이터가 몰려있다

- 고혈압 진료내역이 있는 경우에는 수축기혈압은 100~160, BMI는 17~33사이에 데이터가 몰려있다

- 고혈압 진료내역만 있는 경우와 둘 다 있는 경우의 분포는 서로 유사하다

- 추세선을 보면 약한 양의 상관관계가 있긴하다

- 당뇨 진료내역만 있는 경우 추세선의 기울기 조금더 가파르다

- 당뇨 진료내역만 있는 경우의 데이터가 고혈압 진료내역만 있는 경우의 데이터보다 덜 퍼져있어서 그런것으로 보인다

이완기혈압과 공복혈당

lmplot = sns.lmplot(x = '이완기혈압', y = '공복혈당', hue = '고혈압_당뇨_진료내역', scatter_kws = {'alpha':0.4, 's':20}, height = 7, data = df_s)
for lh in lmplot._legend.legendHandles: 
    lh.set_alpha(1)    
    lh._sizes = [50] 

- 고혈압, 당뇨 진료내역 둘 다 없는 경우 산점도를 보면 공복혈당은 60~140 이완기혈압은 50~90사이에 대부분의 데이터가 존재한다

- 당뇨 진료내역은 없고 고혈압 진료내역만 있는 경우 확실히 당뇨 진료내역이 있는 경우보다 공복혈당이 낮은곳에 데이터가 분포함을 확인할 수 있다

- 고혈압 진료내역만 있는 경우는 고혈압, 당뇨 진료내역 둘 다 없는 경우의 산점도와 비슷하다

- 당뇨 진료내역이 있다면 추가로 고혈압 진료내역이 있다고해서 산점도가 달라지지는 않는것으로 보이며 둘이 비슷하다

- 추세선을 보면 당뇨 진료내역 유무에 따른 차이가 확실히 보인다

- 당뇨 진료내역이 없다면 공복혈당은 확실히 낮다

- 이완기혈압은 혈압의 일종이며 상관계수도 높기에 수축기혈압과 거의 동일한 양상을 보였다

이완기혈압과 BMI

lmplot = sns.lmplot(x = '이완기혈압', y = 'BMI', hue = '고혈압_당뇨_진료내역', scatter_kws = {'alpha':0.4, 's':20}, height = 7, data = df_s)
for lh in lmplot._legend.legendHandles: 
    lh.set_alpha(1)    
    lh._sizes = [50] 
str_ = ['고혈압, 당뇨', '고혈압', '당뇨', '없음']

for i in [1, 2, 3, 4]:
    model = smf.ols(formula = 'BMI ~ 이완기혈압', data = df.query('고혈압_당뇨_진료내역 == @i'))
    result = model.fit()
    print(str_[i-1])
    print(result.params)   # 추정된 직선의 기울기 및 절편
    print('----------------------------')
고혈압, 당뇨
Intercept    21.618056
이완기혈압         0.047214
dtype: float64
----------------------------
고혈압
Intercept    22.023624
이완기혈압         0.036128
dtype: float64
----------------------------
당뇨
Intercept    19.093825
이완기혈압         0.068319
dtype: float64
----------------------------
없음
Intercept    16.095016
이완기혈압         0.098144
dtype: float64
----------------------------

- 위의 회귀선을 보면 수축기혈압의 경우와 마찬가지로 진료내역이 없는 경우의 기울기가 크고

- 고혈압 진료내역 있는 경우의 기울기는 작다

- 하지만 절편을 보면 고혈압 진료내역이 있는 경우가 진료내역이 없는 경우보다 더 크다

- 이는 진료내역이 없는 경우 변동성이 꽤 있어 기울기가 크고

- 고혈압 진료내역이 있는 경우 기본적으로 BMI가 높으므로 더 높아지기 어려우니 기울기가 작다고 볼 수 있다

- 한편 고혈압, 당뇨 진료내역 둘 다 없는 경우 산점도를 보면 BMI은 16~30 이완기혈압은 55~90사이에 대부분의 데이터가 존재하며

- 고혈압 또는 당뇨 진료내역이 있는 경우보다 BMI와 이완기혈압이 낮은곳에 더 많은 데이터가 분포한다

- 산점도를 보면 당뇨 진료내역만 있는 경우 이완기혈압이 낮은 곳에 데이터가 분포하고 있으며

- BMI도 고혈압 진료내역이 있는 경우보다 낮은 곳에 분포함을 알 수 있다

- 당뇨 진료내역만 있는 경우 이완기혈압은 60~90, BMI는 17~28사이에 데이터가 몰려있다

- 고혈압 진료내역이 있는 경우에는 이완기혈압은 60~100, BMI는 17~33사이에 데이터가 몰려있다

- 고혈압 진료내역만 있는 경우와 고혈압, 당뇨 진료내역이 둘다 있는 경우의 산점도는 서로 유사하다

- 추세선을 보면 약한 양의 상관관계가 있긴하다

- 당뇨 진료내역만 있는 경우 추세선의 기울기 조금더 가파르다

- 당뇨 진료내역만 있는 경우의 데이터가 고혈압 진료내역만 있는 경우의 데이터보다 덜 퍼져있어서 그런것으로 보인다

- 이완기혈압과 BMI의 산점도는 수축기혈압과 BMI의 산점도와 비슷한 양상을 보였다

공복혈당과 BMI

lmplot = sns.lmplot(x = '공복혈당', y = 'BMI', hue = '고혈압_당뇨_진료내역', scatter_kws = {'alpha':0.4, 's':20}, height = 7, data = df_s)
for lh in lmplot._legend.legendHandles: 
    lh.set_alpha(1)    
    lh._sizes = [50] 
str_ = ['고혈압, 당뇨', '고혈압', '당뇨', '없음']

for i in [1, 2, 3, 4]:
    model = smf.ols(formula = 'BMI ~ 공복혈당', data = df.query('고혈압_당뇨_진료내역 == @i'))
    result = model.fit()
    print(str_[i-1])
    print(result.params)   # 추정된 직선의 기울기 및 절편
    print('----------------------------')
고혈압, 당뇨
Intercept    24.775887
공복혈당          0.004156
dtype: float64
----------------------------
고혈압
Intercept    22.707251
공복혈당          0.022154
dtype: float64
----------------------------
당뇨
Intercept    23.707968
공복혈당          0.004127
dtype: float64
----------------------------
없음
Intercept    19.893091
공복혈당          0.037433
dtype: float64
----------------------------

- 위의 회귀선을 보면 진료내역이 없는 경우와 고혈압 진료내역 있는 경우 기울기가 크고

- 당뇨 진료내역 있는 경우의 기울기는 작다

- 절편도 진료내역이 없는 경우를 제외하면 큰 차이가 있지는 않다

- 이는 진료내역이 없는 경우 변동성이 꽤 있어 기울기가 크고

- 당뇨 진료내역이 있는 경우 기본적으로 공복혈당이 높으므로 더 높아지기 어려우니 기울기가 작다고 볼 수 있다

- 한편 고혈압, 당뇨 진료내역이 없는 산점도를 보면 BMI는 16~33, 공복혈당은 60~130인 구간에 대부분의 데이터가 존재함을 알 수 있다

- 당뇨 진료내역 유무에 따라 공복혈당에는 큰 차이가 있다

- 고혈압 진료내역만 있는 경우에 그렇지않은 경우보다 BMI가 더 넓게 퍼져있다

- 당뇨 진료내역만 있는 경우에는 BMI가 조금 더 좁게 퍼져있는 것으로 보인다

- 추세선을 보면 당뇨 진료내역만 있는 경우에는 약한 양의 상관관계가 있는 것으로 보여진다

- 그 외에 경우에는 BMI와 공복혈당은 관계가 없는 것으로 보인다

결론

- 고혈압/당뇨 둘 다 진료내역 없음이 74%로 가장 많이 차지한다

- 당뇨 진료내역만 있는 경우는 4%, 고혈압 진료내역만 있는 경우는 16%, 둘다 있는 경우는 5%이다

- 대체로 남자는 여자보다 수축기혈압과 이완기혈압, 맥압, 공복혈당, BMI가 높다

- 수축기혈압과 이완기혈압의 상관계수는 0.74이고 BMI와 혈압의 상관계수는 약 0.3이다

- 이들을 제외한 나머지 변수사이의 상관계수는 0.2보다 작은 수준이다

- 연령대가 높아질수록 수축기혈압, 이완기혈압은 높아지는 경향을 보인다

- 그러나 이완기혈압의 경우 고혈압, 당뇨 진료내역이 없는 경우를 제외하면 높아지다가 내려가는 경향을 보인다

- 고혈압 또는 당뇨 진료내역이 있는 사람은 진료내역이 없는 사람과 비교하여

- 이완기혈압을 제외하면 평균적으로 혈압 또는 혈당이 높다

- 정상혈압인 경우를 제외하면 오히려 이완기 혈압이 더 낮았다

- 수축기혈압과 이완기혈압의 분포에서 많은 봉우리를 관찰할 수 있는데 이는 혈압의 끝자리수 편향때문이다