-
[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) 환자가 심부전 환자 중 가장 많았으며, 연령대별로 심박수 및 혈압과의 관계도 중요한 인사이트를 제공하였습니다.🔍
추후 연구에서는 운동 여부, 생활 습관 데이터와 결합하여 추가적인 패턴을 분석하고, 머신러닝 모델을 활용한 예측 분석도 진행할 수 있을 것입니다.🚀
'Why Not SW CAMP 5기 > 수업 기록' 카테고리의 다른 글
[2월 3주차-2/18(1)]웹 스크래핑과 Selenium을 활용한 크롤링 가이드 🚀 (1) 2025.02.18 [2월 3주차-2/17(2)]📊 파이썬 웹 스크래핑으로 삼성전자 주가 데이터 분석하기 (1) 2025.02.17 [2월 2주차-2/14]🎬 넷플릭스 데이터 분석 프로젝트 (2) 2025.02.14 [2월 2주차-2/13(3)]🍻음주 빈도가 삶의 만족도와 건강 상태에 미치는 영향 분석 (0) 2025.02.13 [2월 2주차-2/13(2)]🌟 인터랙티브 시각화: HTML 파일로 저장하여 웹 브라우저에서 실행하기 (1) 2025.02.13