본문 바로가기
의학 연구

[비전공자의 머신러닝 의학연구 #11] 머신러닝을 위한 데이터 전처리

by Dr CK 2026. 7. 3.

의학연구를 하다 보면 SAS·SPSS·Excel로 정리된 데이터를 쓰는 게 익숙한데, 머신러닝 모델은 전혀 다른 형식을 요구한다. 결측치는 어떻게 처리할지, 범주형 변수는 어떻게 숫자로 바꿀지, 나이와 검사 수치처럼 스케일이 다른 변수들을 어떻게 맞출지, 그리고 PyTorch나 TensorFlow에 넣으려면 또 어떤 형태로 변환해야 하는지, 이것에 전처리(preprocessing) 가 필요하다.

이번 글에서는 의학연구자의 입장에서 머신러닝을 시작할 때 반드시 알아야 할 데이터 전처리 과정을 파일 불러오기부터 텐서 변환까지 순서대로 정리한다.

 

1. 데이터 파일 불러오기

의학연구에서 다루는 데이터 파일은 주로 세 가지다 — CSV, Excel, SAS. 각각 읽는 방법을 정리한다.

1-1. CSV 파일 — pd.read_csv()

가장 흔하고 가장 가벼운 형식. 쉼표로 구분된 텍스트 파일이다.

import pandas as pd

df = pd.read_csv('cancer_data.csv')                  # 기본
df = pd.read_csv('cancer_data.csv', encoding='cp949') # 한글 인코딩 문제 시
df = pd.read_csv('cancer_data.csv', na_values=['.', 'NA', 'unknown'])  # 결측 표시 지정

자주 발생하는 문제 — 한글이 깨질 때는 encoding='cp949' 또는 encoding='euc-kr'을 시도한다. SAS·SPSS에서 내보낸 CSV는 결측치를 . 으로 표시하는 경우가 많아서 na_values 지정이 필요하다.

1-2. Excel 파일 — pd.read_excel()

df = pd.read_excel('cancer_data.xlsx')               # 첫 번째 시트
df = pd.read_excel('cancer_data.xlsx', sheet_name='Patients')  # 시트 이름 지정
df = pd.read_excel('cancer_data.xlsx', sheet_name=0)  # 시트 번호

엑셀 파일을 읽으려면 openpyxl 라이브러리가 필요하다. 없으면 설치한다.

pip install openpyxl

1-3. SAS 파일(sas7bdat) — pd.read_sas()

df = pd.read_sas('cancer_data.sas7bdat', encoding='utf-8')

 

2. 예시 데이터

이 글 전반에서 사용할 가상의 위암 환자 데이터를 만든다. 실제로는 위의 방법으로 파일을 불러오겠지만, 학습 목적이므로 코드로 직접 생성한다.

import pandas as pd
import numpy as np

np.random.seed(42)
n = 200

df = pd.DataFrame({
    'ID': [f'P{i:04d}' for i in range(1, n+1)],
    'age': np.random.randint(30, 85, n),
    'sex': np.random.choice(['Male', 'Female'], n),
    'T_stage': np.random.choice(['T1', 'T2', 'T3', 'T4'], n, p=[0.3, 0.3, 0.25, 0.15]),
    'N_stage': np.random.choice(['N0', 'N1', 'N2', 'N3'], n, p=[0.4, 0.3, 0.2, 0.1]),
    'M_stage': np.random.choice(['M0', 'M1'], n, p=[0.85, 0.15]),
    'treatment': np.random.choice(['A', 'B', 'C'], n),
    'follow_up_days': np.random.randint(30, 2000, n),
    'death': np.random.choice([0, 1], n, p=[0.7, 0.3]),
})

print(df.head())

 

출력:

      ID  age     sex T_stage N_stage M_stage treatment  follow_up_days  death
0  P0001   68    Male      T2      N0      M0         A            1245      0
1  P0002   52  Female      T1      N0      M0         A             820      0
2  P0003   71    Male      T3      N1      M0         B            1567      0
3  P0004   44  Female      T2      N0      M0         B             295      1
4  P0005   72    Male      T4      N2      M1         C             180      1

 

변수 유형 설명
ID 식별자(문자) 환자번호. 분석에서 제외
age 연속형 나이 (30~85)
sex 범주형(이진) Male / Female
T_stage 순서형 T1 < T2 < T3 < T4
N_stage 순서형 N0 < N1 < N2 < N3
M_stage 이진형 M0 / M1 (원격전이 여부)
treatment 범주형(다중) A / B / C (순서 없음)
follow_up_days 시간 추적관찰 일수
death 이진(target) 사망 여부 (0/1)

 

3. 데이터가 제대로 읽혔는지 확인

데이터가 제대로 읽혔는지 확인한다.

3-1. 기본 확인 — 모양과 미리보기

print(df.shape)        # (200, 9)
print(df.head())       # 첫 5행
print(df.tail())       # 마지막 5행

행과 열의 개수가 예상한 값과 같은가 부터 확인한다.

3-2. dtype 확인 — .info()와 .dtypes

각 변수가 어떤 자료형으로 저장되었는지 확인한다. 잘못된 dtype은 오류를 일으킬 수 있다.

df.info()
RangeIndex: 200 entries, 0 to 199
Data columns (total 9 columns):
 #   Column          Non-Null Count  Dtype 
---  ------          --------------  ----- 
 0   ID              200 non-null    object   ← 문자 (예상대로)
 1   age             200 non-null    int64    ← 정수 (예상대로)
 2   sex             200 non-null    object   ← 문자 (Male/Female)
 3   T_stage         200 non-null    object   ← 문자 (T1~T4)
 4   N_stage         200 non-null    object   ← 문자 (N0~N3)
 5   M_stage         200 non-null    object   ← 문자 (M0/M1)
 6   treatment       200 non-null    object   ← 문자 (A/B/C)
 7   follow_up_days  200 non-null    int64    ← 정수
 8   death           200 non-null    int64    ← 정수 (0/1)

dtype의 의미 — pandas가 변수를 저장하는 형식이다.

dtype 의미 예시
int64 정수 age, follow_up_days, death
float64 실수 BMI, Hb, CEA
object 문자열 ID, sex, T_stage, treatment
bool True/False (변환 후)
category pandas 전용 범주형 (변환 후)
datetime64 날짜·시간 진단일, 수술일
  • sex, T_stage, treatment 같은 범주형 변수는 object로 저장되는 게 정상이다.
  • age, death가 object로 나오면 문제다. 숫자여야 할 변수에 문자가 섞여 있다는 뜻 (예: 결측치가 .이나 unknown으로 들어 있을 때).
  • follow_up_days가 float64로 나오면 보통 결측치가 있다는 신호다.

3-3. 범주형 변수의 값 확인 — .value_counts()

각 범주가 예상한 값과 빈도로 들어 있는지 확인한다.

print(df['sex'].value_counts())
sex
Male      102
Female     98
Name: count, dtype: int64
print(df['T_stage'].value_counts())
T_stage
T1    62
T2    61
T3    52
T4    25
Name: count, dtype: int64
  • 오타가 있는지 — Male과 male이 섞여 있으면 다른 범주로 인식된다.
  • 예상치 못한 값이 있는지 — T_stage에 Tx나 unknown이 섞여 있을 수 있다.
  • 극도로 불균형인지 — 한 범주가 1~2명뿐이면 그 범주는 무의미할 수 있다.

3-4. ID 같은 식별자 변수는 빈도표시가 무의미하다

print(df['ID'].value_counts())
ID
P0001    1
P0002    1
P0003    1
P0004    1
P0005    1
        ..
Name: count, dtype: int64

ID는 모든 값이 고유해야 정상이다. 따라서 모두 빈도 1로 나오는데, 이건 빈도표시가 의미 없다는 뜻이다.

ID 변수는 다음과 같이 확인하는 게 맞다.

print(df['ID'].nunique())        # 고유값 개수 (= 환자 수)
print(df['ID'].duplicated().sum()) # 중복된 ID 개수 (0이어야 정상)
print(len(df) == df['ID'].nunique())  # True여야 정상
200
0
True

ID에 중복이 있다면 같은 환자가 여러 행에 기록되어 있다는 뜻이고, 분석 전에 원인 파악이 필요하다.

3-5. 수치형 변수의 분포 — .describe()

print(df.describe())
              age  follow_up_days       death
count  200.000000      200.000000  200.000000
mean    57.030000      988.660000    0.305000
std     16.044120      568.451341    0.461521
min     30.000000       37.000000    0.000000
25%     43.000000      521.500000    0.000000
50%     57.000000      976.500000    0.000000
75%     71.000000     1462.500000    1.000000
max     84.000000     1997.000000    1.000000
  • age의 min이 음수이거나 max가 비현실적이면 입력 오류.
  • follow_up_days의 min이 0이거나 음수면 시간 변수 정의 오류.
  • death의 평균(=사건 발생 비율)이 0이나 1이면 모델 학습 불가.

3-6. 결측치 확인

print(df.isnull().sum())
ID                0
age               0
sex               0
T_stage           0
N_stage           0
M_stage           0
treatment         0
follow_up_days    0
death             0
dtype: int64

각 변수에서 결측치가 몇 개인지 한눈에 본다.

 

3-7. dtype 변환

PyTorch의 기본 dtype은 float32다. numpy의 기본인 float64로 넣으면 type mismatch 에러가 난다. 그 외에도 학습을 위해서는 자료형 dtype의 변환이 필요할때가 있는데, 이때 아래와 같이 수정할 수 있다.

df['age'] = df['age'].astype('int32')          # 정수형으로
df['death'] = df['death'].astype('int64')      # 정수형으로
df['sex'] = df['sex'].astype('category')       # 범주형으로

tensor = torch.tensor(arr, dtype=torch.float32)

 

4. 파이썬 데이터 구조의 이해

4-1. pandas DataFrame

엑셀 표 같은 형태다. 행과 열에 이름이 있고, 각 열마다 다른 자료형을 가질 수 있다.

import pandas as pd

print(type(df))         # <class 'pandas.core.frame.DataFrame'>
print(df.dtypes)        # 각 열의 자료형 확인
print(df.shape)         # (200, 9)
print(df.columns)       # 열 이름 목록

데이터를 읽고, 보고, 정리하고, 전처리하는 단계. 의학연구자에게는 가장 익숙한 형태로, R의 data.frame과 거의 같다.

4-2. numpy array

행렬·벡터 형태의 수치 배열이다. 모든 원소가 같은 자료형이어야 한다는 게 가장 큰 특징이다. 그래서 범주형 변수가 모두 숫자로 변환된 후에야 numpy로 변환 가능하다.

import numpy as np

arr = df[['age', 'follow_up_days']].to_numpy()
print(type(arr))        # <class 'numpy.ndarray'>
print(arr.shape)        # (200, 2)
print(arr.dtype)        # int64

머신러닝 모델에 입력으로 넣기 직전. scikit-learn은 numpy array를 입력으로 받는다. 또한 수학 계산에 최적화되어 있어 pandas보다 훨씬 빠르다.

4-3. PyTorch tensor

numpy array와 거의 비슷한데, GPU에서 돌릴 수 있고 자동 미분(autograd)이 가능하다는 차이가 있다.

import torch

tensor = torch.tensor(arr, dtype=torch.float32)
print(type(tensor))     # <class 'torch.Tensor'>
print(tensor.shape)     # torch.Size([200, 2])
print(tensor.dtype)     # torch.float32

딥러닝 모델(PyTorch)에 입력. 학습 과정에서 gradient를 계산하려면 반드시 tensor 형태여야 한다.

4-4. 변환 흐름 정리

머신러닝(RF, XGBoost 등)만 쓴다면 DataFrame → numpy 까지만 가면 되고, 딥러닝까지 간다면 numpy → tensor 변환이 추가된다.

원본 파일                  pandas             numpy             PyTorch
─────────────             DataFrame          array             tensor
csv, xlsx, sas7bdat   →   전처리 단계    →   머신러닝 입력  →   딥러닝 입력

4-5. axis 

numpy와 pandas 코드를 보면 axis=0 또는 axis=1 이라는 표현이 나온다. 2차원 데이터(표)에는 두 개의 축이 있다.

  • axis=0 — 행 방향 (위에서 아래). 환자 P001 → P002 → P003 → ...
  • axis=1 — 열 방향 (왼쪽에서 오른쪽). age → T_stage → death → ...

연산을 할 때 axis=0 을 지정하면 그 축을 따라 진행하면서 값을 모은다. 즉 axis=0이면 세로로 내려가며 연산하고, axis=1이면 가로로 진행하며 연산한다.

axis=0 — df.mean(axis=0) 은 각 열을 따라 세로로 평균을 낸다. 결과는 변수별 평균 (변수 개수만큼). (eg. age의 평균)

axis=1 — df.mean(axis=1) 은 각 행을 따라 가로로 평균을 낸다. 결과는 환자별 평균 (환자 수만큼). (eg. P001의 평균, P002의 평균, ...)

 

행·열 제거 — df.drop() 의 axis는 무엇을 지울지를 결정.

df.drop('age', axis=1)        # 'age' 열을 제거 (axis=1 = 열)
df.drop(0, axis=0)            # 0번 행을 제거 (axis=0 = 행)

# 여러 개 동시 제거
df.drop(['ID', 'death'], axis=1)

합치기 — pd.concat() 의 axis는 어느 방향으로 붙일지.

pd.concat([df1, df2], axis=0)   # 세로로 쌓기 (행 추가, 환자 추가)
pd.concat([df1, df2], axis=1)   # 가로로 붙이기 (열 추가, 변수 추가)

 

PyTorch에서는 같은 개념을 axis 대신 dim 이라고 부른다. 의미는 동일하다.

import torch

t = torch.tensor([[65., 2., 0.],
                  [58., 3., 1.],
                  [72., 1., 0.],
                  [45., 4., 1.]])

t.mean(dim=0)    # tensor([60.0, 2.5, 0.5])   ← 열별
t.mean(dim=1)    # tensor([22.3, 20.7, 24.3, 16.7])  ← 행별

PyTorch도 axis 키워드를 받아주긴 하지만, 공식 문서와 예제는 모두 dim을 쓴다.

 

3차원 이상에서의 axis

영상(이미지) 데이터나 시계열 데이터를 다루면 3차원, 4차원 배열도 만난다. 예를 들어 한 환자의 시계열 검사 데이터를 모은 텐서의 shape이 (환자, 시점, 검사항목) 일 때:

# shape: (200 환자, 12 시점, 5 검사항목)
data.shape                  # (200, 12, 5)
data.mean(axis=0).shape     # (12, 5)   ← 모든 환자의 평균 패턴
data.mean(axis=1).shape     # (200, 5)  ← 환자별 검사 평균
data.mean(axis=2).shape     # (200, 12) ← 시점별 검사 평균

 

5. 결측치 처리

대부분의 머신러닝 모델은 결측치를 입력으로 받지 못한다. 학습 전에 반드시 다음 중 하나를 선택해야 한다.

  • 제거 — 결측이 있는 행을 빼고 학습 (df.dropna())
  • 대체(imputation) — 평균·중앙값·최빈값 등으로 채움 (SimpleImputer)
  • 모델이 직접 처리 — XGBoost, LightGBM 등은 결측치를 그대로 받을 수 있다
모델 결측치 직접 처리 비고
선형 회귀, 로지스틱 불가 반드시 채워야 함
Random Forest (sklearn) 불가 반드시 채워야 함
XGBoost, LightGBM 가능 결측 자체를 학습 신호로 사용
신경망 (PyTorch 등) 불가 NaN이 있으면 학습 망가짐

 

6. 범주형 변수 처리 — One-Hot Encoding

머신러닝 모델은 숫자만 입력으로 받는다. "Male", "T1" 같은 문자열을 숫자로 변환하는 게 핵심 작업이고, 이는 통계모형에서도 프로그램에서 자동으로 실행하는 작업이로 더미변수라고도 한다.

 

One-Hot Encoding은 각 범주를 별도의 0/1 변수로 분리한다.

각 환자가 어떤 치료를 받았는지를 세 개의 0/1 변수로 표현한다. A를 받은 환자는 treatment_A=1, treatment_B=0, treatment_C=0, B를 받은 환자는 treatment_A=0, treatment_B=1, treatment_C=0 같은 식이다.

이렇게 하면 각 범주를 독립적인 변수로 만들기 때문에 임의의 순서가 부여되지 않는다.

 

pandas로 One-Hot — get_dummies

df_encoded = pd.get_dummies(df, columns=['sex', 'treatment'])
print(df_encoded.columns.tolist())
['ID', 'age', 'T_stage', 'N_stage', 'M_stage', 'follow_up_days', 'death',
 'sex_Female', 'sex_Male',
 'treatment_A', 'treatment_B', 'treatment_C']

 

reference 처리 — 통계 모델에서는 기준 범주(reference) 를 하나 제거하는 게 표준이다. reference를 설정한다면 drop_first=True 옵션을 쓴다. (머신러닝 모델에서는 필요하지 않을 수 있다)

df_encoded = pd.get_dummies(df, columns=['sex'], drop_first=True)
# sex_Female만 남고 sex_Male은 빠짐 (Female이 reference)

 

scikit-learn으로 One-Hot — OneHotEncoder

학습 데이터와 검증 데이터에 일관된 변환을 적용해야 할 때 sklearn 방식이 안전하다.

from sklearn.preprocessing import OneHotEncoder

encoder = OneHotEncoder(sparse_output=False, drop='first', handle_unknown='ignore')
encoded = encoder.fit_transform(df[['sex', 'treatment']])

# 새 데이터에는 transform만
encoded_new = encoder.transform(df_test[['sex', 'treatment']])

fit은 훈련 데이터에만, transform은 훈련·검증 모두에 적용하는 게 원칙이다. 검증 데이터에서 fit하면 데이터 누수가 발생한다.

 

get_dummies 와 OneHotEncoder의 차이

반환 타입 DataFrame numpy array / sparse
dtype bool (또는 uint8) float64
fit/transform 분리 ❌ 불가 ✅ 가능
새 범주 처리 자동으로 새 컬럼 생성 handle_unknown으로 명시적
컬럼 일관성 보장 ❌ 데이터에 따라 다름 ✅ fit 시점 기준 유지
Pipeline 통합 ✅ 표준
변환 규칙 저장 ✅ joblib
drop 옵션 drop_first만 first, if_binary, 특정 범주
NaN 처리 dummy_na=True 별도 카테고리 또는 imputer
속도 (대용량) 빠름 조금 느림
코드 간결성 ✅ 한 줄 더 길지만 명시적

get_dummies의 결과는 dataframe으로 컬럼명이 포함되지만,

age, sex_Female, sex_Male, T_stage_T1, T_stage_T2, T_stage_T3, T_stage_T4,
treatment_A, treatment_B, treatment_C

 

OneHotEncoder의 결과는 numpy array로 컬럼명이 포함되어 있지 않다.

Feature 0, Feature 1, Feature 2, ..., Feature 9

 

이 때문에 학습 후에 모델 해석을 위해서 컬럼명에 접근할때 OneHotEncoder으로 변환하면 중요하다고 판명된 그 피쳐가 어떤것인지 알수가 없다. 따라서 OneHotEncoder의 경우 get_feature_names_out() 을 통해서 명시적으로 가져와야 한다.

encoder = OneHotEncoder(sparse_output=False)
encoded = encoder.fit_transform(df[['sex', 'T_stage', 'treatment']])

# 변수 이름 복원
feature_names = encoder.get_feature_names_out(['sex', 'T_stage', 'treatment'])
print(feature_names)
# ['sex_Female' 'sex_Male' 'T_stage_T1' 'T_stage_T2' 'T_stage_T3' 'T_stage_T4'
#  'treatment_A' 'treatment_B' 'treatment_C']
 

이 이름들을 SHAP 등에 직접 전달하면 된다.

shap.summary_plot(shap_values, X_encoded, feature_names=feature_names)
 

또는 array를 다시 DataFrame으로 감싼다.

X_df = pd.DataFrame(X_encoded, columns=feature_names)
shap.summary_plot(shap_values, X_df)

 

7. 순서형 변수 — Ordinal Encoding

T stage, N stage 같은 순서가 있는 변수는 One-Hot이 아니라 순서를 살린 인코딩이 적절할 수 있다.

stage_map = {'T1': 1, 'T2': 2, 'T3': 3, 'T4': 4}
df['T_stage_ord'] = df['T_stage'].map(stage_map)

또는 sklearn:

from sklearn.preprocessing import OrdinalEncoder

encoder = OrdinalEncoder(categories=[['T1', 'T2', 'T3', 'T4']])
df['T_stage_ord'] = encoder.fit_transform(df[['T_stage']])

 

8. 연속형 변수 처리 — 스케일링

의학데이터의 변수들은 스케일이 천차만별이다.

  • 나이: 30 ~ 85
  • BMI: 18 ~ 35
  • CEA: 0.5 ~ 100+
  • Hb: 8 ~ 17

이 상태에서 그대로 학습시키면 값의 절댓값이 큰 변수가 모델을 지배한다. 특히 거리 기반 모델(KNN, SVM, PCA)이나 경사하강법 기반 모델(딥러닝, 로지스틱) 에서 문제가 크다. 스케일링은 모든 변수를 비슷한 범위로 맞춰주는 작업이다.

 

StandardScaler (Z-score 정규화)

가장 흔히 쓰인다. 평균 0, 표준편차 1 로 변환한다.

from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
num_cols = ['age', 'follow_up_days']

df[num_cols] = scaler.fit_transform(df[num_cols])
print(df[num_cols].describe())
# mean ≈ 0, std ≈ 1

언제 쓰나  가장 일반적. 정규분포에 가까운 변수에 적합. 딥러닝의 표준 전처리.

 

MinMaxScaler

0 ~ 1 사이로 변환한다.

from sklearn.preprocessing import MinMaxScaler

scaler = MinMaxScaler()
df[num_cols] = scaler.fit_transform(df[num_cols])
# 모든 값이 [0, 1] 범위

언제 쓰나 — 이미지(픽셀값 0-255), 또는 경계가 명확해야 하는 경우. 이상치에 민감하다는 단점.

 

RobustScaler

중앙값과 IQR 을 이용한다. 이상치에 강하다.

from sklearn.preprocessing import RobustScaler

scaler = RobustScaler()
df[num_cols] = scaler.fit_transform(df[num_cols])

언제 쓰나 — CEA 같이 왜도가 큰 의학 검사 수치 에 특히 유용하다.

 

스케일링의 핵심 원칙 : fit은 훈련 데이터에만, transform은 양쪽 모두에 적용한다.

# 올바른 흐름
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)    # 훈련 데이터로 평균/표준편차 학습
X_test_scaled = scaler.transform(X_test)          # 동일한 평균/표준편차로 변환

검증 데이터의 평균까지 알면 data leakage 가 된다. 이건 모든 전처리(스케일러, imputer, encoder)에 공통 원칙이다.

 

9. 데이터 분할 — Train/Test Split

전처리가 끝나면 데이터를 학습용과 검증용으로 나눈다.

from sklearn.model_selection import train_test_split

X = df.drop(['ID', 'death', 'follow_up_days'], axis=1)
y = df['death']

X_train, X_test, y_train, y_test = train_test_split(
    X, y, 
    test_size=0.2,        # 20%를 테스트로
    random_state=42,      # 재현성 확보
    stratify=y            # 클래스 비율 유지 (불균형 데이터에 필수)
)

print(X_train.shape, X_test.shape)  # (160, n_features) (40, n_features)

stratify=y는 의학연구에서 반드시 써야 한다. 사망(소수 클래스) 환자가 한쪽에 몰리면 모델이 제대로 학습되지 않는다.

 

10. PyTorch Tensor 변환

딥러닝 모델까지 가려면 numpy를 tensor로 바꿔야 한다.

import torch

X_train_tensor = torch.tensor(X_train.to_numpy(), dtype=torch.float32)
y_train_tensor = torch.tensor(y_train.to_numpy(), dtype=torch.float32)

print(X_train_tensor.shape)   # torch.Size([160, n_features])
print(X_train_tensor.dtype)   # torch.float32

자주 발생하는 에러:

  • dtype mismatch — torch.tensor()만 쓰면 float64로 변환된다. 반드시 dtype=torch.float32 명시.
  • object 자료형 섞임 — DataFrame에 문자열이 남아 있으면 변환 안 된다. 전처리 후 모든 열이 숫자여야 한다.
  • target dtype — 분류 문제는 torch.long, 회귀·생존분석은 torch.float32.

 

11. 전체 흐름 예시

지금까지의 내용을 처음부터 끝까지 하나의 파이프라인으로 정리한다.

import numpy as np
import pandas as pd
import torch
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder, OrdinalEncoder
from sklearn.model_selection import train_test_split
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline

# 1. 파일 불러오기
# df = pd.read_csv('cancer_data.csv')
# df = pd.read_excel('cancer_data.xlsx')
# df = pd.read_sas('cancer_data.sas7bdat', encoding='utf-8')

# 2. 변수 그룹 정의
num_cols = ['age']
ord_cols = ['T_stage', 'N_stage']       # 순서형
nom_cols = ['sex', 'M_stage', 'treatment']  # 명목형
target = 'death'

# 3. X, y 분리 (ID는 제외)
X = df[num_cols + ord_cols + nom_cols]
y = df[target]

# 4. Train/Test 분할 (전처리 전!)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)

# 5. 전처리 파이프라인 — ColumnTransformer 활용
num_pipeline = Pipeline([
    ('scaler', StandardScaler())
])

ord_pipeline = Pipeline([
    ('encoder', OrdinalEncoder(categories=[
        ['T1', 'T2', 'T3', 'T4'],
        ['N0', 'N1', 'N2', 'N3']
    ]))
])

nom_pipeline = Pipeline([
    ('encoder', OneHotEncoder(drop='first', sparse_output=False))
])

preprocessor = ColumnTransformer([
    ('num', num_pipeline, num_cols),
    ('ord', ord_pipeline, ord_cols),
    ('nom', nom_pipeline, nom_cols)
])

# 6. fit_transform — 훈련 데이터에서 변환 규칙 학습
X_train_processed = preprocessor.fit_transform(X_train)
X_test_processed = preprocessor.transform(X_test)

print(X_train_processed.shape)  # (160, n_features_processed)
print(X_train_processed.dtype)  # float64

# 7. PyTorch tensor 변환 (딥러닝용)
X_train_tensor = torch.tensor(X_train_processed, dtype=torch.float32)
X_test_tensor = torch.tensor(X_test_processed, dtype=torch.float32)
y_train_tensor = torch.tensor(y_train.to_numpy(), dtype=torch.float32)
y_test_tensor = torch.tensor(y_test.to_numpy(), dtype=torch.float32)

print(f"훈련: {X_train_tensor.shape}, 검증: {X_test_tensor.shape}")
print(f"dtype: {X_train_tensor.dtype}")

ColumnTransformer는 변수 유형별로 다른 전처리를 적용할 때 가장 깔끔한 방법이다. 코드 한 번에 인코딩 → 스케일링 까지 끝난다.

반응형