콘텐츠로 이동

P2-12.3 학습용 데이터셋(dataset) 준비의 직관

Section ID: P2-12.3 Version: v2026.07.23

P2-12.1에서는 DataFrame을 표 형식 데이터 구조로 읽었습니다. P2-12.2에서는 그 표에서 필요한 열을 고르고, 조건으로 행을 걸러 내고, 요약값을 확인했습니다. 이제 질문은 이 표를 모델이 읽을 수 있는 학습용 데이터셋으로 바꾸려면 무엇을 준비해야 하는가?로 한 단계 더 앞으로 갑니다.

여기서 중요한 점은 Pandas를 잘 다루는 것학습용 데이터셋을 잘 준비하는 것이 같지 않다는 점입니다. 전자는 표를 조작하는 기술이고, 후자는 모델이 어떤 입력을 받고 어떤 정답을 배우게 할지 결정하는 일입니다.

여기서는 데이터셋(dataset), 특징(feature), 타깃(target), 검증(validation), 데이터 누수(data leakage)의 기본 구분을 설명합니다. DataFrame과 표 선택의 대표 설명은 P2-12.1, P2-12.2와 개념사전에 두고, 여기서는 그 표를 학습용 입력과 정답으로 어떻게 다시 구성할지 설명합니다.

Chapter 11이 계산 가능한 배열 모양을 만들었다면, 지금 Chapter 12는 표에서 어떤 열을 남기고 어떤 열을 뺄지 결정하는 단계입니다. 이렇게 정리한 입력과 정답 후보는 다음 Chapter 13의 시각화와 Chapter 14의 기록 정리로 이어집니다.

핵심 기준: 학습용 데이터셋(dataset) 준비의 직관

  • 학습용 데이터셋 준비를 원본 표를 모델 입력과 정답으로 재구성하는 일로 설명할 수 있습니다.
  • 한 행(row)은 한 샘플(sample), 한 열(column)은 하나의 특징(feature) 또는 타깃(target) 후보로 읽을 수 있습니다.
  • 입력 X와 정답 y를 분리하는 이유를 설명할 수 있습니다.
  • 학습, 검증, 테스트 분리를 왜 하는지 설명할 수 있습니다.
  • 데이터 누수(data leakage)가 왜 평가를 왜곡하는지 설명할 수 있습니다.
  • Pandas가 데이터 읽기, 선택, 필터링, 열 생성, 기초 점검에 쓰이고, 학습 분리는 별도 도구와 함께 다뤄질 수 있음을 설명할 수 있습니다.

먼저 붙잡을 한 장면

이 절에서 가장 먼저 붙잡아야 할 장면은, 한 행(row)은 보통 한 샘플(sample)이고 한 열(column)은 특징(feature) 또는 타깃(target) 후보이며, X는 입력 열 묶음이고 y는 맞혀야 할 정답 열이라는 점입니다.

작은 학생 표를 다시 보면:

표에서 보는 것 학습용 데이터셋 언어로 다시 읽을 때
한 행 학생 한 명 샘플
region, absences, score 입력 특징(feature) 후보
passed 정답 타깃(target) 후보
X.shape = (4, 3) 샘플 4개, 특징 3개
y.shape = (4,) 샘플 4개의 정답 묶음

이 표가 먼저 잡히면 뒤의 train_test_split, fit, predict도 덜 막연하게 읽힙니다.

세 가지 기준

기준 왜 중요한가 이 절에서 필요한 이해 수준
표를 그대로 학습시키면 안 되는 이유 표 구조와 모델 입력 구조를 구분하게 해 줍니다. 질문과 무관한 열, 정답 열, 식별자 열이 섞여 있을 수 있음을 이해합니다.
Xy를 왜 나누는가 입력과 정답의 경계를 분명하게 해 줍니다. 입력과 맞히려는 대상을 분리해야 학습 구조가 선명해진다는 점을 이해합니다.
왜 먼저 데이터를 나누는가 데이터 누수와 부풀려진 평가를 막는 기준이 됩니다. 나중 평가에 쓸 정보를 미리 학습에 새어 넣지 않기 위해서라는 점을 이해합니다.
용어 이 절에서 먼저 잡을 뜻
데이터셋(dataset) 학습이나 평가를 위해 정리한 샘플과 변수의 묶음입니다.
특징(feature) 모델 입력으로 사용할 열 또는 값입니다.
타깃(target) 모델이 맞혀야 하는 정답 열입니다.
검증(validation) 설정과 선택을 비교 점검하는 중간 평가용 데이터입니다.
데이터 누수(data leakage) 예측 시점에 알 수 없는 정보가 학습 과정에 미리 섞여 들어가는 문제입니다.

표를 그대로 학습시키는 것이 아니라, 질문에 맞게 다시 구성한다

원본 표는 종종 사람이 읽기 좋게 정리되어 있지만, 모델이 바로 학습하기 좋은 모양은 아닙니다.

예를 들어 다음 표를 봅니다.

student_id name region absences score passed
S001 Kim Seoul 1 82 yes
S002 Park Busan 5 45 no
S003 Lee Seoul 0 90 yes
S004 Choi Busan 2 73 yes

사람은 이 표를 보고 여러 질문을 할 수 있습니다.

  • 점수(score)를 예측하고 싶은가?
  • 합격 여부(passed)를 분류하고 싶은가?
  • 지역(region)별 경향을 보고 싶은가?

같은 표라도 질문이 바뀌면 학습용 데이터셋의 구성도 바뀝니다.

질문 y 후보 X 후보
합격 여부를 맞히는가 passed region, absences, score
점수를 예측하는가 score region, absences, passed 제외 여부 재검토
학생 식별을 하는가 별도 문제 정의 필요 student_id, name은 보통 식별자 역할

즉, 데이터셋 준비는 “표를 정리한다”보다 더 정확히는 “질문에 맞게 입력과 정답의 경계를 다시 긋는다”에 가깝습니다.

Xy를 분리한다

scikit-learn 문서는 보통 X를 입력 특징(feature matrix), y를 타깃(target)으로 사용합니다. glossary에서는 특징(feature)을 샘플을 수치 또는 범주 값으로 나타내는 양으로 설명하고, 데이터 행렬에서는 특징이 열(columns)로 표현된다고 설명합니다. 또한 sample은 보통 하나의 feature vector로, target은 지도학습(supervised learning)에서의 종속 변수(dependent variable)로 설명합니다.

여기서는 다음 차이를 먼저 기억해 두면 됩니다.

  • X: 모델이 보고 판단에 사용할 입력 열 묶음
  • y: 모델이 맞혀야 할 정답 열

작은 예를 보면:

문제 상황: 학생 표에서 합격 여부를 맞히는 분류 문제로 바꿀 때 입력 X와 정답 y를 먼저 나누어야 합니다. 입력(input): 학생별 지역, 결석 수, 점수, 합격 여부가 들어 있는 작은 DataFrame. 기대 출력(output): 입력 열 묶음 X와 정답 열 y가 분리됩니다. 확인할 개념: 학습용 데이터셋 준비는 원본 표를 그대로 쓰는 일이 아니라 문제 정의에 맞게 입력과 타깃을 나누는 일입니다.

# DataFrame에서 특성 X와 정답 y를 나누고 학습용 데이터셋을 준비하는 예제입니다.
import pandas as pd

df = pd.DataFrame(
    {
        "student_id": ["S001", "S002", "S003", "S004"],
        "region": ["Seoul", "Busan", "Seoul", "Busan"],
        "absences": [1, 5, 0, 2],
        "score": [82, 45, 90, 73],
        "passed": ["yes", "no", "yes", "yes"],
    }
)

X = df[["region", "absences", "score"]]
y = df["passed"]

이 코드는 passed를 맞히는 분류(classification) 문제로 읽을 수 있습니다.

  • 각 행은 한 학생 샘플(sample)입니다.
  • region, absences, score는 입력 특징(feature)입니다.
  • passed는 정답 타깃(target)입니다.

도식으로 보면 다음과 같습니다.

flowchart TD
    raw["원본 DataFrame<br/>student_id, region, absences, score, passed"]
    choose["타깃 선택<br/>무엇을 맞히려는가"]
    split["열 분리<br/>X와 y 나누기"]
    x["X<br/>region, absences, score"]
    y["y<br/>passed"]

    raw --> choose --> split
    split --> x
    split --> y

핵심은 Xy가 원래부터 정해져 있지 않다는 점입니다. 문제 정의가 먼저이고, 그다음에 열을 나눕니다.

여기서 P2-11.2와 바로 연결되는 관점도 함께 남깁니다.

표현 지금 이 절에서 읽는 방식
X.shape[0] 샘플(sample) 수
X.shape[1] 특징(feature) 수
y.shape[0] 정답 개수, 보통 샘플 수와 같음

Xy를 나눈다는 말은 열 이름만 나누는 것이 아니라, 모델이 읽을 배열의 모양(shape)도 함께 정한다는 뜻입니다.

조금 더 많은 행을 가진 입력으로도 같은 분리를 확인할 수 있습니다. 입력 파일은 P2-12.2와 같은 student-progress-samples.csv입니다. 한 행은 학생 한 명의 학습 기록이고, 여기서는 passed를 타깃으로 두고 나머지 일부 열을 입력 후보로 봅니다.

문제 상황: 원본 표에서 입력 열 묶음 X, 정답 열 y, 간단한 train/test 후보가 어떤 모양으로 나뉘는지 확인하고 싶습니다. 입력(input): 36행 학생 진행도 CSV, feature_columns, target_column. 기대 출력(output): X, y, 인코딩된 입력 열, train/test 분할 후보의 shape. 확인할 개념: 데이터셋 준비는 모델 학습 전에 표에서 입력과 정답의 경계를 정하고, 평가용 행을 따로 떼어 두는 일입니다.

# DataFrame에서 특성 X와 정답 y를 나누고 학습용 데이터셋을 준비하는 예제입니다.
from pathlib import Path
import pandas as pd

csv_path = Path("docs/assets/part-02/chapter-12/student-progress-samples.csv")
df = pd.read_csv(csv_path)

feature_columns = ["region", "study_hours", "absences", "practice_quizzes", "score"]
target_column = "passed"

X = df[feature_columns]
y = df[target_column]
X_encoded = pd.get_dummies(X, columns=["region"], dtype=int)

test_index = df.sample(frac=0.25, random_state=42).index
train_index = df.index.difference(test_index)

print("X shape:", X.shape)
print("y shape:", y.shape)
print("encoded columns:", list(X_encoded.columns))
print("train/test rows:", len(train_index), len(test_index))

같은 코드는 p2_12_3_dataset_split_preview.py로 실행할 수 있습니다. 여기서는 scikit-learn 모델을 학습하지 않습니다. Part 2의 목표는 먼저 어떤 열이 입력이고 어떤 열이 정답이며, 어떤 행을 나중 확인용으로 떼어 둘 것인가를 Pandas 수준에서 확인하는 것입니다.

모든 열을 그대로 넣으면 안 된다

표에 있는 열이 모두 모델 입력으로 적합한 것은 아닙니다. 보통 다음 세 부류를 구분합니다.

  1. 예측에 직접 사용할 수 있는 열
  2. 변환이 필요한 열
  3. 입력에서 빼야 하는 열

예를 들어:

바로 사용 여부 이유
absences 비교적 바로 사용 가능 수치 열이기 때문
region 변환 필요 문자열 범주이기 때문
student_id 보통 제외 식별자이지 일반 패턴 자체는 아닐 수 있기 때문
passed 타깃이면 입력에서 제외 정답 열을 입력에 넣으면 누수 위험이 커지기 때문

여기서 region 같은 범주형(categorical) 열은 모델에 따라 숫자 형태로 바꾸는 단계가 필요할 수 있습니다. pandas의 get_dummies()는 범주형 변수를 더미/지시 변수(dummy/indicator variables)로 바꾸는 함수로 소개됩니다.

예를 들면:

문제 상황: 문자열 범주인 region은 많은 모델이 그대로 읽기 어렵기 때문에 표현을 바꾸어야 합니다. 입력(input): region, absences, score 열로 이루어진 입력 X. 기대 출력(output): region이 여러 개의 0/1 열로 바뀐 X_encoded. 확인할 개념: 범주형 열은 학습 전에 숫자 형태로 다시 표현해야 할 수 있습니다.

1
2
3
4
5
# DataFrame에서 특성 X와 정답 y를 나누고 학습용 데이터셋을 준비하는 예제입니다.
X = df[["region", "absences", "score"]]
X_encoded = pd.get_dummies(X, columns=["region"])

print(X_encoded)

이 단계의 핵심은 인코딩 방법을 외우는 것이 아닙니다. 더 중요한 질문은 이 열은 숫자처럼 바로 읽히는가, 아니면 먼저 표현을 바꿔야 하는가?입니다.

식별자와 정답 열을 구분하지 않으면 쉽게 잘못 읽는다

자주 겪는 혼동은 눈에 보이는 모든 열이 특징(feature)인 것처럼 느껴지는 것입니다. 하지만 식별자(identifier)와 정답(target)은 특징과 역할이 다릅니다.

예를 들어:

  • student_id는 각 행을 구분하는 표식입니다.
  • name은 사람에게 읽기 좋은 이름입니다.
  • passed는 맞혀야 할 정답일 수 있습니다.

이들을 그대로 입력에 포함하면 두 가지 문제가 생길 수 있습니다.

  1. 모델이 일반 패턴보다 우연한 식별 정보에 끌릴 수 있습니다.
  2. 정답과 너무 가까운 정보를 입력에 넣어 평가가 부풀려질 수 있습니다.

이 감각은 앞으로 계속 중요합니다. 좋은 데이터셋은 열이 많다고 좋은 것이 아니라, 문제 정의에 맞는 열만 남아 있는 데이터셋에 가깝습니다.

학습(train), 검증(validation), 테스트(test)를 나눈다

데이터셋 준비에서 다음으로 중요한 단계는 분리(split)입니다. scikit-learn의 train_test_split 문서는 배열이나 행렬을 무작위로 학습(train)과 테스트(test) 부분집합으로 나누는 도구로 설명합니다.

여기서는 다음처럼 이해하면 됩니다.

  • train: 모델이 실제로 배우는 데이터
  • validation: 설정을 점검하고 선택을 비교하는 데이터
  • test: 마지막에 처음 보는 것처럼 확인하는 데이터

도식으로 보면:

flowchart TD
    full["전체 데이터셋"]
    train["train set<br/>학습에 사용하는 데이터"]
    val["validation set<br/>설정 비교에 쓰는 데이터"]
    test["test set<br/>마지막 확인에 쓰는 데이터"]

    full --> train
    full --> val
    full --> test

왜 나누어야 할까요? 모델이 이미 본 데이터에서 잘하는 것은 충분하지 않기 때문입니다. 우리가 알고 싶은 것은 처음 보는 데이터에서도 비슷하게 작동하는가입니다.

이 관점은 Part 3의 과적합(overfitting), 일반화(generalization), 평가(metric)로 직접 이어집니다.

분리를 먼저 하고, 학습에서 배울 변환은 train에서만 배운다

scikit-learn의 common pitfalls 문서는 두 가지 실수를 강하게 경고합니다.

  • inconsistent preprocessing: 학습 데이터와 테스트 데이터에 서로 다른 전처리를 적용하는 실수
  • data leakage: 예측 시점에 알 수 없는 정보가 학습 과정에 섞여 들어가는 실수

특히 문서는 “test data should never be used to make choices about the model”이라고 설명하고, 일반 규칙으로 fit 또는 fit_transform을 테스트 데이터에 호출하지 말라고 안내합니다.

여기서는 다음 순서를 기준으로 두면 됩니다.

  1. 전체 표에서 입력 후보와 정답 후보를 고른다.
  2. train / validation / test로 먼저 나눈다.
  3. 평균, 표준화, 인코딩, 특징 선택처럼 배워야 하는 변환은 train에서만 기준을 잡는다.
  4. 같은 변환을 validation, test에는 적용만 한다.

이 순서를 어기면 모델이 미리 답을 훔쳐본 것과 비슷한 상황이 됩니다.

잘못된 흐름과 더 안전한 흐름을 나란히 보면:

흐름 문제
전체 데이터로 기준을 만든 뒤 나눈다 test 정보가 미리 섞일 수 있음
먼저 나눈 뒤 train으로만 기준을 만든다 평가가 더 공정해짐

이 절에서는 구현 세부보다 이 판단 기준을 먼저 잡습니다.

아래 도식으로 다시 보면 더 분명합니다.

flowchart TD
    full["전체 표"]
    choose["X와 y 선택"]
    split["먼저 분리<br/>train / validation / test"]
    fit_train["변환 기준 학습<br/>train에서만 fit"]
    apply_other["같은 변환 적용<br/>validation, test에는 apply만"]
    bad["잘못된 순서<br/>전체 표에서 먼저 기준 학습"]

    full --> choose --> split --> fit_train --> apply_other
    full -.피해야 함.-> bad

잘못된 순서와 더 안전한 순서를 질문으로 다시 쓰면 다음과 같습니다.

질문 더 안전한 답
표를 다 정리한 뒤 나누면 안 되는가 나중 평가에 쓸 정보가 먼저 섞일 수 있으므로 먼저 나눈다
평균, 표준화, 인코딩 기준은 어디서 배우는가 train에서만 배운다
validation, test는 무엇을 하는가 train에서 배운 같은 기준을 적용만 한다

사례 및 예시

사례 1. 합격 여부를 맞히려는데 정답 열을 같이 넣어 버린 경우

학습자가 학생 표로 합격 여부(passed)를 예측하는 모델을 만들고 싶다고 해 보겠습니다. 표에는 student_id, region, absences, score, passed가 함께 들어 있습니다. 이때는 눈에 보이는 모든 열을 X에 넣고 싶어질 수 있습니다.

하지만 이렇게 하면 문제가 생깁니다. passed는 맞혀야 할 정답인데 입력에도 같이 들어가 버리고, student_id는 학생을 구분하는 표식일 뿐 일반 패턴과 직접 관련이 없을 수 있습니다. 모델은 진짜 규칙을 배우기보다 정답을 훔쳐보거나 우연한 식별자 정보에 끌릴 수 있습니다.

그래서 데이터셋 준비에서는 먼저 무엇을 맞히려는가를 정하고, 그다음에 y를 분리하고, 남길 특징과 뺄 열을 다시 고릅니다. 이어서 train, validation, test를 먼저 나눈 뒤, 인코딩이나 스케일링처럼 학습에서 기준을 배워야 하는 변환은 train 쪽에서만 잡아야 합니다.

이 사례는 데이터셋 준비가 단순 정리가 아니라는 점을 보여 줍니다. 같은 표라도 질문에 따라 Xy의 경계가 달라지고, 분리 순서를 잘못 잡으면 평가가 부풀려질 수 있습니다. Pandas는 이 과정의 앞부분에서 열을 고르고 구조를 점검하는 도구이고, 공정한 학습 평가는 그다음 분리와 변환 순서까지 함께 맞춰야 성립합니다.

독자가 여기서 특히 붙잡아야 할 문장은 다음입니다.

  • 먼저 X와 y를 나누고, 먼저 train / validation / test를 나눈 다음, train에서만 배울 변환 기준을 만든다.

Pandas는 준비 과정의 앞부분을 담당한다

Pandas는 보통 데이터셋 준비의 앞부분에서 강합니다.

  • CSV, Excel, JSON 같은 원본을 읽는다.
  • 필요한 열만 고른다.
  • 잘못된 값이나 이상한 형식을 점검한다.
  • 범주형 열과 수치형 열을 구분한다.
  • 기본적인 열 생성과 변환을 한다.

예를 들어:

문제 상황: 모델 학습 전에 입력 열과 정답 열이 실제로 어떤 모양인지 먼저 눈으로 점검하고 싶습니다. 입력(input): 분리된 X, y. 기대 출력(output): X의 앞부분 행과 y의 앞부분 값이 출력됩니다. 확인할 개념: Pandas는 학습 전에 표 구조를 확인하고 열 역할을 점검하는 데 강합니다.

1
2
3
4
5
6
# DataFrame에서 특성 X와 정답 y를 나누고 학습용 데이터셋을 준비하는 예제입니다.
X = df[["region", "absences", "score"]]
y = df["passed"]

print(X.head())
print(y.head())

또는:

문제 상황: 결측치가 있는지, 각 열이 어떤 타입으로 읽히는지 기본 상태를 확인해야 합니다. 입력(input): 원본 DataFrame df. 기대 출력(output): 열별 결측치 개수와 각 열의 데이터 타입 목록. 확인할 개념: 학습 전 점검은 값 자체뿐 아니라 결측치와 타입 구조를 보는 일입니다.

1
2
3
# DataFrame에서 특성 X와 정답 y를 나누고 학습용 데이터셋을 준비하는 예제입니다.
print(df.isna().sum())
print(df.dtypes)

이런 코드는 “모델을 학습시킨다”기보다 “학습 전에 표를 점검한다”에 가깝습니다.

반면 실제 분리와 학습 파이프라인은 scikit-learn 같은 도구와 이어서 다루는 경우가 많습니다.

예를 들어:

문제 상황: 점검이 끝난 뒤에는 학습과 평가를 분리하기 위해 데이터를 train/test로 나누어야 합니다. 입력(input): 입력 X, 정답 y, 분할 비율과 난수 시드. 기대 출력(output): 학습용과 테스트용으로 나뉜 X_train, X_test, y_train, y_test. 확인할 개념: 표 조작 단계와 학습용 데이터 분할 단계는 이어지지만 역할이 다른 작업입니다.

1
2
3
4
5
6
7
8
9
# DataFrame에서 특성 X와 정답 y를 나누고 학습용 데이터셋을 준비하는 예제입니다.
from sklearn.model_selection import train_test_split

X = df[["region", "absences", "score"]]
y = df["passed"]

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.25, random_state=42
)

여기서 중요한 것은 함수 사용법 전체를 외우는 것이 아니라, 표 조작학습 분리가 서로 이어지는 다른 단계라는 점입니다.

학습용 데이터셋 준비를 한 문장으로 요약하면

이 절의 핵심을 한 문장으로 줄이면, 학습용 데이터셋 준비는 원본 표에서 샘플(sample), 특징(feature), 타깃(target)의 역할을 다시 정하고, 평가가 왜곡되지 않도록 분리 순서를 지키는 일입니다.

이 관점이 없으면 Pandas 코드가 길어질수록 무엇을 위해 열을 고치고 나누는지 놓치기 쉽습니다. 반대로 이 관점이 잡히면, 아직 모든 전처리 기법을 몰라도 “지금 이 코드는 입력을 고르는가, 정답을 고르는가, 누수를 막는가”를 질문할 수 있습니다.

체크리스트

  • 현재 표에서 무엇을 예측하려는지 한 문장으로 말할 수 있는가?
  • 어떤 열이 y이고 어떤 열이 X 후보인지 구분할 수 있는가?
  • 식별자 열과 정답 열을 입력에 그대로 넣으면 왜 위험한지 설명할 수 있는가?
  • 왜 train / validation / test를 나누는지 설명할 수 있는가?
  • 왜 전처리 기준은 train에서만 배워야 하는지 설명할 수 있는가?
  • X.shape = (4, 3)y.shape = (4,)를 보면 무엇이 샘플 수이고 무엇이 특징 수인지 말할 수 있는가?
  • 데이터셋 준비는 표를 그대로 쓰는 일이 아니라 문제 정의에 맞게 Xy를 나누는 일이라는 점을 설명할 수 있는가?

출처와 참고 자료

  • pandas Developers, pandas.get_dummies, pandas 3.0.4 documentation, 확인 날짜: 2026-07-20. 범주형 변수를 dummy/indicator 변수로 바꾸는 예시 확인에 사용했다.
  • scikit-learn Developers, Glossary, scikit-learn 1.9.0 documentation, 확인 날짜: 2026-07-20. 1d/2d array, array-like, estimator 입력 관례와 X, y 용어 배경 확인에 사용했다.
  • scikit-learn Developers, train_test_split, scikit-learn 1.9.0 documentation, 확인 날짜: 2026-07-20. 배열과 행렬을 train/test subset으로 나누는 API와 test_size, random_state 예시 확인에 사용했다.
  • scikit-learn Developers, Common pitfalls and recommended practices, scikit-learn 1.9.0 documentation, 확인 날짜: 2026-07-20. train/test 분리 전후의 전처리 순서와 data leakage 주의 설명 확인에 사용했다.