ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [2월 3주차-2/17(1)]📊 심부전 데이터 분석: 데이터 전처리부터 시각화까지!
    Why Not SW CAMP 5기/수업 기록 2025. 2. 17. 13:58

    1. 의료 데이터 프로젝트 소개

    의료 데이터 분석은 심장병을 포함한 다양한 질병의 조기 진단과 예방에 중요한 역할을 합니다. 이번 프로젝트에서는 심부전(Heart Failure) 데이터를 분석하여 심장병 환자의 특성을 파악하고, 주요 변수 간의 관계를 이해하며, 시각화를 통해 인사이트를 도출하는 것을 목표로 합니다.

    2. 의료 데이터셋 파악

    분석할 데이터셋은 심부전 환자 데이터를 포함하며, 주요 변수는 다음과 같습니다.

    변수 설명

    • RestingBP : 혈압
    • Cholesterol : 콜레스테롤 농도
    • FastingBS : 공복상태 혈당 (120 이상이면 1)
    • RestingECG : 심전도 결과
    • MaxHR : 최대 심박수
    • ExerciseAngina : 운동 중 협심증 경험 여부
    • HeartDisease : 심장병 유무 (1이면 심장병 환자)

    데이터셋을 로드한 후, 기본적인 정보를 확인하면 다음과 같습니다.

    import pandas as pd
    import numpy as np
    
    import matplotlib.pyplot as plt
    import seaborn as sns
    
    heart = pd.read_csv('./data/heart.csv')
    heart.info()
    '''
    RangeIndex: 918 entries, 0 to 917
    Data columns (total 10 columns):
     #   Column          Non-Null Count  Dtype  
    ---  ------          --------------  -----  
     0   Age             918 non-null    int64  
     1   Sex             918 non-null    object 
     2   ChestPainType   918 non-null    object 
     3   RestingBP       891 non-null    float64   -> 결측치 존재
     4   Cholesterol     918 non-null    int64  
     5   FastingBS       827 non-null    float64   -> 결측치 존재
     6   RestingECG      918 non-null    object 
     7   MaxHR           918 non-null    int64  
     8   ExerciseAngina  918 non-null    object 
     9   HeartDisease    916 non-null    float64   -> 결측치 존재
    dtypes: float64(3), int64(3), object(4)
    '''
    • 데이터는 총 918개의 샘플과 10개의 컬럼으로 구성됨
    • 일부 컬럼에 결측치 존재 (RestingBP, FastingBS, HeartDisease)

    3. 심부전 데이터셋 필터링

    분석의 초점을 맞추기 위해 심장병이 있는 환자(HeartDisease == 1)만 필터링합니다.

    H = heart[heart['HeartDisease'] == 1]
    H.shape
    

    4. 심부전 데이터셋 결측치 처리

    결측치 비율을 계산한 후, 적절한 방법으로 처리합니다.

    for i in heart.columns:
        missingValueRate = heart[i].isna().sum() / len(H) * 100
        if missingValueRate > 0:
            print(f'{i} null rate: {round(missingValueRate,2)}')
    
    '''
    RestingBP null rate: 5.33
    FastingBS null rate: 17.95
    HeartDisease null rate: 0.39
    
    RestingBP: 혈압
    FastingBS: 공복상태 혈당 / 120 이상이면 1
    심장병 여부 (1/0)
    '''

    결측치 처리 방법

    • HeartDisease: 결측치 샘플 제거
    • FastingBS: 최빈값(0)으로 대체
    • RestingBP: 중앙값으로 대체
    heart = heart.dropna(subset=['HeartDisease'])
    heart['FastingBS'] = heart['FastingBS'].fillna(0)
    heart['RestingBP'] = heart['RestingBP'].fillna(heart['RestingBP'].median())

    5. 심부전 데이터셋 통계 처리

    5.1 주요 변수 요약 통계량

    heart[['Age', 'MaxHR', 'Cholesterol']].describe()
    '''
                  Age       MaxHR  Cholesterol
    count  916.000000  916.000000   916.000000
    mean    53.533843  136.830786   198.728166
    std      9.425923   25.447917   109.466452
    min     28.000000   60.000000     0.000000
    25%     47.000000  120.000000   173.000000
    50%     54.000000  138.000000   223.000000
    75%     60.000000  156.000000   267.000000
    max     77.000000  202.000000   603.000000
    
    Age mean : 53.533843 <- 대부분 50대 초반
    '''

    5.2 그룹별 통계 분석

    심장병 유무 및 흉통 유형에 따른 주요 변수의 평균값을 비교합니다.

    HD_CPT = heart.groupby(['HeartDisease', 'ChestPainType'])[['Age', 'MaxHR', 'Cholesterol']].mean()
    '''
                                      Age       MaxHR  Cholesterol
    HeartDisease ChestPainType                                    
    0.0          ASY            52.317308  138.548077   226.865385
                 ATA            48.236486  152.621622   232.668919
                 NAP            51.045802  150.641221   221.503817
                 TA             54.692308  150.500000   222.730769
    1.0          ASY            55.660714  125.806122   175.974490
                 ATA            55.958333  137.500000   233.291667
                 NAP            57.549296  129.394366   153.281690
                 TA             55.000000  144.500000   186.700000
    -> 심장병 없는 사람의 나이, 심박수, 콜레스테롤 수치가 
    각각 어떠한 흉통 유형에 따라 달라지는 지를 확인 할 수 있다
    '''

    6. 심부전 데이터셋 시각화

    # 심전부 색깔 시각화
    sns.palplot(['#003399', '#0099FF', '#00FFFF', '#CCFFFF'])
    plt.show()

    6.1 흉통 유형 시각화 (파이 차트)

    ratio = heart['ChestPainType'].value_counts()
    plt.figure(figsize=(5,5))
    plt.pie(x = ratio, labels=ratio.index, autopct='%0.f%%', startangle=90,
            explode=[0.05, 0.05, 0.05, 0.05], shadow=True,
            colors=['#003399', '#0099FF', '#00FFFF', '#CCFFFF'])
    plt.suptitle('Chest Pain Type', fontfamily='serif', fontsize=15)
    plt.title('ASY, NAP, ATA, TA', fontfamily='serif', fontsize=11)
    plt.show()

    6.2 흉통 유형과 심장병 관계 (막대 그래프)

    # 심부전증이 있을 때와 없을 때, 항상 ASY인 무증상이 압도적인지 궁금
    # 'HeartDisease' 여부에 따른 'ChestpainType' 막대 시각화
    
    # countplot(): 각 범주(심장병 여부)에 속하는 데이터(흉통 유형)의 개수를 막대 시각화
    #data: cuntplot에서 사용할 데이터 셋
    # x : HeartDisease
    # hue: 특정 열 데이터로 색상을 구분
    # hue_order: 색상 순서 수동 :'ASY, 'NAP', 'ATA', 'TA'
    
    # x축 눈금 설정
    #plt.xticks([0,1], ['',''])
    
    # plt.tight_layout(): 겹치지 않도록 최소한의 여백을 만듬
    
    plt.figure(figsize=(12,5))
    sns.countplot(data=heart,
                  x = 'HeartDisease',
                  hue = 'ChestPainType',
                  hue_order=['ASY', 'NAP', 'ATA', 'TA'],
                  palette = ['#003399', '#0099FF', '#00FFFF', '#CCFFFF'])
    
    plt.suptitle('Chest Pain Types / Heart Disease')
    plt.title('ASY, NAP, ATA, TA')
    plt.xticks([0,1], ['without heart disease', 'heart disease'])
    plt.tight_layout()
    plt.show()

    6.3 연령대별 심부전 환자 분포 (영역 그래프)

    # 심부전 데이터영역 그래프
    # 나이에 따른 심부전 여부 수치화
    Heart_Age = heart.groupby('Age')['HeartDisease'] \
                .value_counts().unstack(level='HeartDisease')
    
    Heart_Age.head(6)
    '''
    HeartDisease  0.0  1.0
    Age                   
    28            1.0  NaN
    29            3.0  NaN
    30            1.0  NaN
    31            1.0  1.0
    32            3.0  2.0
    33            1.0  1.0
    '''
    plt.figure(figsize=(15,5))
    
    # plt.fill_between(): x 축을 기준으로 그래프 영역 채움
    # x : 곡선을 정의하는 노드의 x좌표
    # y1: 첫 번째 곡선을 정의하는 노드의 y좌표
    # y2: 두 번째 곡선을 정의하는 노드의 y좌표
    # alpha: 투명도
    # label: 범례에 표시할 문자열
    
    # 심장병이 없는 환자들의 나이별 숫자 시각화
    plt.fill_between(x=Heart_Age[0].index, 
                     y1=0, # y좌표의 0부터 그래프 영역을 채워라
                     y2 = Heart_Age[0], # 0컬럼에 대응하는 값
                     color = '#003399',
                     alpha = 0.8,
                     label='Normal')
    
    # 심장병이 있는 환자들의 나이별 숫자 시각화
    plt.fill_between(x=Heart_Age[1].index, 
                     y1=0, # y좌표의 0부터 그래프 영역을 채워라
                     y2 = Heart_Age[1],
                     color = '#003399',
                     alpha = 0.4,
                     label='heart disease')
    
    plt.legend()
    plt.xlabel('Age')
    plt.ylabel('Count')
    plt.title('Heart_Age')
    plt.show()

    6.4 심부전 범주형 산점도 그래프

    # 심부전 범주형 산점도 그래프: sns.swarmplot()
    # H_0: 심장병이 없는 환자의 데이터 추출 HeartDisease == 0
    # H_1: 심장병이 있는 환자의 데이터 추출 HeartDisease == 1
    H_0 = heart[heart['HeartDisease'] == 0]
    H_1 = heart[heart['HeartDisease'] == 1]
    
    # 그래프 객체 생성(2개의 subplot 생성)
    fig = plt.figure(figsize = (15,5))
    ax1 = fig.add_subplot(1,2,1) # 1행 2열의 1
    ax2 = fig.add_subplot(1,2,2) # 1행 2열의 2
    
    # RestingECG / ExerciseAngina
    # H_0에서 Age별 RestingBP 수치에 따른 ExerciseAngina 여부
    sns.swarmplot(x = 'RestingECG',
                  y = 'Age',
                  data= H_0,
                  ax = ax1,
                  hue = 'ExerciseAngina',
                  palette=['#003399', '#0099FF'],
                  hue_order = ['Y', 'N']
                  )
    
    # H_1에서 Age별 RestingBP 수치에 따른 ExerciseAngina 여부
    sns.swarmplot(x = 'RestingECG',
                  y = 'Age',
                  data= H_1,
                  ax = ax2,
                  hue = 'ExerciseAngina',
                  palette=['#003399', '#0099FF'],
                  hue_order = ['Y', 'N']
                  )
    
    ax1.set_title('Without heart disease')
    ax2.set_title('With heart disease')
    plt.show()

    6.5 워드클라우드 (심부전 관련 논문 키워드 분석)

    # 심부전 워드 클라우드
    # 심부전관련 논문의 제목 데이터
    pubmed_title = pd.read_csv('./data/pubmed_title.csv')
    
    from wordcloud import WordCloud
    from PIL import Image
    
    plt.figure(figsize=(15,5))
    
    # pubmed_title['title']을 list로 변환시킨 후 str로 변환
    text = str(list(pubmed_title['Title']))
    
    # logo image 갖고오고 Image.open()
    # image를 넘파이 배열로 변환해줘야됨 np.array()
    mask = np.array(Image.open('./data/image.jpg'))
    # mask = : 단어를 그릴 위치 설정, 흰색 항목은 마스킹 된 것으로 간주
    
    # 색상맵
    cmap = plt.matplotlib.colors.LinearSegmentedColormap \
            .from_list('', ['#000066','#003399', '#00FFFF'])
    
    # 워드클라우드 생성
    wordcloud = WordCloud(background_color='white',
                          width = 2500, height = 1400,
                          max_words = 170,
                          mask = mask,
                          colormap=cmap).generate(text)
    
    plt.suptitle('Heart Disease WordCloud',
                 fontweight='bold', fontfamily='serif', fontsize=15)
    plt.title('Pubmed site: Heart Failure')
    # 워드클라우드 결과를 plots 창에 나타내기
    plt.imshow(wordcloud)
    plt.axis('off') # 축 감추기
    plt.show()

    결론

    이번 분석을 통해 심부전 환자의 특성과 주요 변수 간의 관계를 파악할 수 있었습니다. 특히, 무증상(ASY) 환자가 심부전 환자 중 가장 많았으며, 연령대별로 심박수 및 혈압과의 관계도 중요한 인사이트를 제공하였습니다.🔍

    추후 연구에서는 운동 여부, 생활 습관 데이터와 결합하여 추가적인 패턴을 분석하고, 머신러닝 모델을 활용한 예측 분석도 진행할 수 있을 것입니다.🚀

Designed by Tistory.