[학습 일기#19] 데이터 분석 및 시각화 3일차

Ariel_Jeong·2026년 1월 14일

[학습 일기 시리즈]

목록 보기
19/44

데이터 분석 및 시각화 마지막 수업...
어떻게 지나갔는지 모를 정도로 많은 정보들이 몰아쳤다.
까먹지 않게 복습!!!
계속 작성해서 몸에 익히자...


* 예시 코드 및 과제 내용의 출처는 오즈코딩스쿨로부터...



1. 데이터 전처리란?

원시 데이터를 분석/모델링이 가능한 형태로 바꾸는 과정.
특히 의료 데이터는 “결측·중복·이상치·타입 오류”가 흔해서, 전처리가 곧 데이터 품질을 결정한다.

1.1. 전처리 로드맵

  • 1단계: 데이터 정제(Data Cleaning) - 데이터를 깨끗하게 만들기
    • 결측치, 중복, 이상치 처리, 데이터 타입 변환
  • 2단계: 기본 변환(Basic Transformation) - 데이터를 AI모델이 학습하기 좋은 형태로 변환
    • 스케일링, 인코딩(원핫 인코딩/라벨인코딩)



1.2. 결측치(Missing Value) 탐지와 처리

1.2.1. 결측치란?

값이 비어 있거나(NA), 기록이 누락된 상태. NaN, None, NaT 등으로 표현.


1.2.2. 결측치 탐지

  • 컬럼별 결측치 개수: df.isnull().sum()
  • 전체 결측치 총합: df.isnull().sum().sum()

1.2.3. 결측치 처리 전략

  • 삭제: df.dropna() (데이터가 충분하고 결측이 무작위일 때)
    • 주요 파라미터:
      axis=0(행 삭제, 기본값), axis=1(열 삭제)
      subset=['컬럼명'](특정 컬럼 기준으로 결측치 있는 행만 삭제)
      thresh=숫자(최소 유효값 개수)
      inplace=False(기본값. 원본 유지), inplace=True(원본 수정)
  • 대체(Imputation): df['컬럼'].fillna(값)
    • method='ffill' or 'bfill': 앞/뒤 값으로 채우기
      (앞/뒤 값이 나올 때까지 찾는데, 없으면 NaN이 됨..)
    • inplace=True(기본. 원본 직접 수정)
    • 평균(mean): 연속형이고 분포가 비교적 안정적일 때
    • 중앙값(median): 이상치에 강함(분포가 치우치거나 이상치가 있을 때)
    • 최빈값(mode): 범주형일 때

1.2.4. 중복 데이터(Duplicate) 탐지와 처리

1) 탐지

df.duplicated(subset=['PatientID']).sum()

2) 제거(최신 데이터 유지)

drop_duplicates(subset=['PatientID'], keep='last')
→ 같은 PatientID가 여러 개면 마지막(최신)만 남긴다.


1.2.5. 이상치(Outlier) 탐지와 처리 — IQR

1) 이상치란?

대부분 값의 범위에서 크게 벗어난 값.
평균을 무너뜨리고 모델을 흔들 수 있지만 때에 따라서는 필요한 경우도 있다.
(ex. 희귀병 환자의 데이터)

2) IQR 방식 핵심

Q1 = 25% 지점, Q3 = 75% 지점

IQR = Q3 − Q1

경계값

  • lower = Q1 − 1.5×IQR
  • upper = Q3 + 1.5×IQR
    * 1.5 는 경험적으로 얻어낸 값으로 위의 계산에 따라 얻게 된 범위는
    대체로 데이터의 약 99.7%가 이 범위 내에 들어오게 된다.

3) 처리 전략

  • 제거(drop): 표본이 많고 이상치가 명백히 오류일 때
  • 대체(replace): 의료 데이터처럼 표본이 적거나, 이상치가 “측정 오류/입력 오류” 가능성이 있을 때

1.2.6. 데이터 타입(Type) 오류와 변환

1) 왜 중요한가?

문자열로 들어온 날짜/숫자는 분석/정렬/시계열 처리에서 에러의 원인이 된다.

2) 변환 포인트

  • 문자열 → 숫자: pd.to_numeric(errors='coerce')
  • 문자열 → 날짜: pd.to_datetime()




2. 과제 해결 과정 정리


[캡쳐 1-1. 과제 요구 사항]


[캡쳐 1-2. 과제 기본 데이터]


2.1. 미션 1) 결측치 탐지 및 처리


[캡쳐 1-3. 1번 미션 코드 비교]

차이점 — 결측치 현황 출력 방식

  • 정답 코드:
missing_count = df_processed.isnull().sum()
print(missing_count[missing_count > 0])
  • 내 코드:
    missing_count[missing_count > 0]에 해당하는 부분을 df_null에 할당하고,
    df_null로 결과 출력(그리고 “처리 후 결측치 현황”도 추가로 출력)

✅ 내 코드 장점

  • “처리 전/후 검증”을 코드로 남겨서 한번 더 확실히 확인.

⚠️ 단점(개선 포인트)

  • 불필요하게 변수명 또 생성

2.2. 미션 2) 중복 데이터 처리


[캡쳐 1-4. 2번 미션 코드 비교]

정답 코드와 동일한 방식으로 작성이 됨


2.3. 미션 3, 4) 이상치 탐지 및 처리, 데이터 타입 오류 수정


[캡쳐 1-5. 3, 4번 미션 코드 비교]

차이점 — “정상 범위 마스크(inlier)” vs “이상치 조건(outlier)” 정의 방식

  • 정답 코드:
    outliers_condition = (BP < lower) | (BP > upper) ← 이상치(True)
    이상치 처리 시 조건을 그대로 사용

  • 내 코드:
    iqr_range = (BP >= lower) & (BP <= upper) ← 정상 범위(True)
    ~iqr_range를 써서 이상치 처리

✅ 내 코드 장점

  • 정상군을 판단하기에는 쉬움.
  • ~iqr_range로 이상치가 직관적으로 뒤집히는 구조라 논리 구조가 보임.

⚠️ 내 코드 단점(실수 가능 지점)

  • 팀 협업에서는 정답 코드처럼 outliers_condition이 더 읽기 쉬운 편.

2.4. 트러블슈팅: 왜 ExamDate에서 에러가 터졌나?


[캡쳐 1-5. 에러 발생 및 해결 과정]

에러 메시지 핵심

ValueError: time data '145.0' doesn't match format ...

즉, ExamDate 컬럼에 날짜가 아닌 145.0 같은 숫자가 섞여 들어갔기 때문

진짜 원인: 이상치 처리에서 “행 전체를 덮어쓴 대입”

df_processed[~iqr_range] = df_processed['BloodPressure'].median()

여기서 iqr_range는 BloodPressure 기준 boolean 마스크인데,
df_processed[~iqr_range]는 “조건에 맞는 행 전체(모든 컬럼)”를 뜻한다.

그래서 Age, BMI, Cholesterol, BloodSugar, ExamDate까지 전부 145.0으로 여기서 덮인거였다...

결과적으로 ExamDate에 145.0이 들어가서 datetime 변환이 폭발

올바른 해결: “컬럼을 지정해서” 이상치 행의 BloodPressure만 대체

df_processed.loc[조건, 'BloodPressure'] = 값

결국 아직 문법이 몸에 안익어서 난 실수였다...
반복 작성만이 답..!



3. 시각화 문법 정리

3.1. 기본 세팅: matplotlib + seaborn

시각화는 보통 아래 3단계로 생각하면 깔끔하다.

  • 캔버스 생성(figure)
  • 그래프 그리기(plot)
  • 꾸미기(title/label/ticks/grid/layout) + 출력(show)

먼저 필요한 라이브러리를 import 해준다.

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

# (선택) 한글 폰트/마이너스 깨짐 방지 (ipynb에 있던 설정)
plt.rc('font', family='NanumBarunGothic')
plt.rcParams['axes.unicode_minus'] = False

캔버스 생성 및 출력(기본)

plt.figure(figsize=(가로, 세로)) - “새 도화지 만들기”

figsize=(8,5)는 인치 단위.

그래프가 겹치거나 글씨가 잘리면 먼저 figsize 키우는 게 가장 빠른 해결책.

plt.show() - “출력”

주피터는 자동 출력처럼 보이기도 하지만, 습관적으로 show()를 넣는 게 안전.


3.2. 그래프 그리기!

3.2.1. 선 그래프(Line plot)

“치료 일수(days) - 혈압(blood_pressure)”처럼 시간 흐름/순서가 있는 변화를 보여줄 때 쓰는 형태.

  • plt.plot(x, y): 가장 기본
  • 자주 쓰는 옵션 및 꾸미기 세트
plt.plot(
    x, y,
    marker='o',     # 점 표시 (예: 'o', '^', 's')
    linestyle='-',  # 선 스타일 ('-', '--', ':')
    linewidth=2     # 선 두께
)

plt.title('제목')
plt.xlabel('x축 라벨')
plt.ylabel('y축 라벨')
plt.grid(True, alpha=0.3)   # 격자, alpha는 투명도
plt.tight_layout()          # 글씨 잘림 방지
plt.show()

실수 포인트

  • x와 y 길이가 다르면 바로 에러.
  • 숫자 대신 문자열이 섞이면 축이 이상하게 나오거나 정렬이 꼬일 수 있음.

3.2.2.막대 그래프 (Bar chart)

“진료과별 환자 수”처럼 범주형(카테고리) vs 개수/합계를 보여줄 때.

  • plt.bar(): 가장 기본
  • 아래와 같이 사용 가능
plt.figure(figsize=(8, 5))
plt.bar(categories, values)
plt.title('진료과별 환자 수')
plt.ylabel('환자 수(명)')
plt.xticks(rotation=45)      # 라벨 겹치면 회전(글자 겹침 방지)
plt.tight_layout()
plt.show()

3.2.3. 히스토그램 (Histogram)

연속형 데이터 분포 확인할 때.
Age, BMI, BloodPressure처럼 “값이 어떤 구간에 몰려 있나?”를 보는 용도.

matplotlib 버전

  • plt.hist()
plt.figure(figsize=(8, 5))
plt.hist(data, bins=10, alpha=0.7) # bins는 구간 갯수(적으면 뭉개지고, 많으면 너무 잘게 쪼개짐)
plt.title('분포 확인')
plt.xlabel('값')
plt.ylabel('빈도')
plt.show()

seaborn 버전

  • sns.histplot()
plt.figure(figsize=(8, 5))
sns.histplot(data, bins=10, kde=True)  # kde=True면 밀도곡선 같이
plt.show()

실수 포인트

  • 결측치가 섞이면 경고/그래프 왜곡 가능
    → 미리 dropna() 또는 결측 처리 후 그리는 게 안전.

3.2.4. 박스플롯 (Box plot)

이상치(outlier) + 중앙값 + 사분위수를 한 번에 보는 그래프.

matplotlib 버전

  • plt.boxplot()
plt.figure(figsize=(8, 5))
plt.boxplot(data)
plt.show()

seaborn 버전

  • sns.boxplot()
plt.figure(figsize=(8, 5))
sns.boxplot(x=category, y=value, data=df)
plt.title('범주별 분포 비교')
plt.show()

실수 포인트

  • seaborn에서 data=df를 쓰는데 x, y에 컬럼명을 문자열로 넣어야 함
    (ex. x='Sex', y='BloodPressure')

3.2.5. 카운트 플롯 (Count plot)

“범주별 개수”를 빠르게 그리는 seaborn 전용 치트키.
HeartDisease(0/1), Sex(0/1) 같은 컬럼 분포 확인에 매우 좋음.

  • sns.countplot()
plt.figure(figsize=(6, 4))
sns.countplot(x='HeartDisease', data=df)
plt.title('HeartDisease 분포')
plt.show()

# 순서 지정 필요할 때(범주가 뒤죽박죽이면?)
sns.countplot(x='col', data=df, order=sorted(df['col'].unique()))

실수 포인트

  • df['col'] 안에 문자열/숫자 혼재하면 예상과 다른 정렬이 나올 수 있음.

3.2.6. 상관관계 히트맵 (Correlation Heatmap)

수치형 변수들 간의 상관계수 행렬(corr)을 색으로 보여줘서, 어떤 변수가 같이 움직이는지 한눈에 파악 가능.

1) 상관계수 행렬 만들기
2) 히트맵 그리기

  • sns.heatmap()
mask = np.triu(np.ones_like(corr, dtype=bool))    # 중복 영역 가리기

plt.figure(figsize=(10, 6))
sns.heatmap(
    corr,
    mask=mask,
    annot=True,    # 숫자 표시
    fmt='.2f',    # 소수점 2자리
    center=0,    # 0을 기준으로 색 잡기(양수/음수)
    square=True,    # 칸을 정사각형으로
    cbar_kws={'label': '상관계수', 'shrink': 0.8}    # label: 색상 막대의 이름(축 라벨), shrink: colorbar 길이 조정(지금은 히트맵 높이 대비 80% 길이로 축소)
)
plt.title('변수 간 상관관계')
plt.xticks(rotation=45, ha='right')
plt.yticks(rotation=0)
plt.tight_layout()
plt.show()

실수 포인트

  • 문자열 컬럼이 섞인 채로 corr() 하면 결과가 비거나 경고가 날 수 있음
    numeric_only=True로 안전하게 수치형만.



3.3. “그래프가 이상할 때” 디버깅 순서

1) 타입 확인

2) 결측치 확인

3) 값이 이상하게 섞였는지 확인(특히 문자열/숫자 혼합)

4) 범주형 개수 확인(너무 많으면 countplot이 지옥 됨)

profile
R&D 분야의 경험을 토대로 커리어 확장에 도전중인 개발꿈나무입니다.

0개의 댓글