2026년 Python 데이터 사이언스: NumPy, Pandas, Scikit-Learn 완벽 가이드

2026년 기준 Python 데이터 사이언스 핵심 라이브러리인 NumPy, Pandas, Scikit-Learn의 실전 활용법을 코드 예제와 함께 상세히 다룹니다.

Python 데이터 사이언스 튜토리얼: NumPy, Pandas, Scikit-Learn 라이브러리 활용 가이드

2026년 Python 데이터 사이언스 워크플로우는 전체 작업의 90%를 처리하는 세 가지 핵심 라이브러리에 의존합니다. 수치 연산을 위한 NumPy, 테이블 데이터 조작을 위한 Pandas, 그리고 머신러닝을 위한 Scikit-Learn이 그 주인공입니다. 이 튜토리얼에서는 각 라이브러리의 실전 코드를 단계별로 살펴보며, 원시 데이터를 불러오고 정제하고 피처를 생성하여 모델을 학습시키는 완전한 파이프라인을 구축하는 과정을 안내합니다.

이 튜토리얼에서 사용하는 버전

모든 코드는 Python 3.12+, NumPy 2.1, Pandas 2.2, Scikit-Learn 1.6 환경에서 실행됩니다. API 인터페이스는 마이너 버전 간에 안정적이지만, NumPy 2.x의 성능 개선 사항은 대규모 배열 연산에서 업그레이드할 가치가 충분합니다.

NumPy 배열 연산을 활용한 고효율 계산

NumPy는 Python 리스트를 연속 메모리 블록으로 구성된 N차원 배열로 대체합니다. 성능 차이는 상당합니다. 100만 개 요소에 대한 벡터화된 NumPy 연산은 동일한 Python 반복문보다 50~100배 빠르게 수행됩니다. 모든 Pandas DataFrame과 Scikit-Learn 모델은 내부적으로 NumPy 배열을 사용합니다.

python
# numpy_basics.py
import numpy as np

# Create arrays from different sources
prices = np.array([29.99, 49.99, 19.99, 99.99, 39.99])
quantities = np.arange(1, 6)  # [1, 2, 3, 4, 5]

# Vectorized arithmetic — no loops needed
revenue = prices * quantities
print(revenue)  # [29.99, 99.98, 59.97, 399.96, 199.95]

# Statistical aggregations
print(f"Total revenue: ${revenue.sum():.2f}")   # $789.85
print(f"Mean price: ${prices.mean():.2f}")       # $47.99
print(f"Std deviation: ${prices.std():.2f}")     # $27.64

# Boolean indexing — filter without loops
premium_mask = prices > 40
premium_items = prices[premium_mask]  # [49.99, 99.99]

불리언 인덱싱은 실제 데이터 사이언스 코드에서 가장 빈번하게 등장하는 패턴입니다. 반복문 안에서 if 문을 작성하는 대신, 불리언 마스크가 조건을 만족하는 요소를 단일 연산으로 선택합니다.

NumPy의 Reshape와 Broadcasting

2차원 연산을 수행하려면 NumPy가 서로 다른 형상(shape)의 배열을 브로드캐스팅하는 방식을 이해해야 합니다. 브로드캐스팅 규칙을 통해 명시적인 타일링 없이도 행렬과 벡터 간 연산이 가능합니다.

python
# numpy_reshape.py
import numpy as np

# Monthly sales data: 4 products x 3 months
sales = np.array([
    [120, 150, 130],  # Product A
    [200, 180, 220],  # Product B
    [90, 110, 95],    # Product C
    [300, 280, 310],  # Product D
])

# Column-wise mean (average per month)
monthly_avg = sales.mean(axis=0)  # [177.5, 180.0, 188.75]

# Row-wise sum (total per product)
product_totals = sales.sum(axis=1)  # [400, 600, 295, 890]

# Normalize each product relative to its own max
normalized = sales / sales.max(axis=1, keepdims=True)
# keepdims=True preserves the shape for broadcasting
print(normalized[0])  # [0.8, 1.0, 0.867] — Product A relative to its peak

# Reshape for Scikit-Learn (requires 2D input)
flat_sales = sales.flatten()  # 1D array of 12 values
reshaped = flat_sales.reshape(-1, 1)  # 12x1 column vector

axis 매개변수는 집계 방향을 제어합니다. axis=0은 행을 축소하여 열 방향으로 연산하고, axis=1은 열을 축소하여 행 방향으로 연산합니다. keepdims=True 인자는 브로드캐스팅 시 형상 불일치를 방지합니다.

Pandas DataFrame 조작과 데이터 정제

Pandas는 NumPy 배열을 레이블이 부여된 축과 SQL 유사 연산으로 감싸는 라이브러리입니다. DataFrame은 핵심 데이터 구조로, 각 열이 서로 다른 데이터 타입을 가질 수 있는 테이블 형태입니다. Pandas 2.2는 기본적으로 PyArrow 기반 문자열 컬럼을 사용하며, object dtype 문자열에 비해 메모리 사용량이 현저히 적습니다.

python
# pandas_cleaning.py
import pandas as pd
import numpy as np

# Load and inspect raw data
df = pd.read_csv("candidates.csv")
print(df.shape)       # (1500, 8)
print(df.dtypes)      # Check column types
print(df.isna().sum()) # Count missing values per column

# Clean in a reproducible chain
df_clean = (
    df
    .dropna(subset=["salary", "experience_years"])      # Drop rows missing critical fields
    .assign(
        salary=lambda x: x["salary"].clip(lower=20000, upper=500000),  # Cap outliers
        experience_years=lambda x: x["experience_years"].astype(int),
        hired_date=lambda x: pd.to_datetime(x["hired_date"], errors="coerce"),
    )
    .drop_duplicates(subset=["email"])                  # Remove duplicate candidates
    .query("experience_years >= 0")                     # Filter invalid entries
    .reset_index(drop=True)
)

print(f"Cleaned: {len(df)} -> {len(df_clean)} rows")

.assign()을 활용한 메서드 체이닝은 변환 과정을 읽기 쉽고 재현 가능하게 만듭니다. 각 단계가 무엇이 변경되고 왜 변경되는지를 명확하게 기록합니다. .assign() 내부의 lambda x: 패턴은 체인의 해당 시점에서의 DataFrame을 참조하므로, 오래된 데이터를 참조하는 실수를 방지합니다.

Pandas 2.2의 Copy-on-Write

Pandas 2.2는 Copy-on-Write를 기본으로 활성화합니다. 이를 통해 SettingWithCopyWarning이 제거되고 체이닝 연산의 안전성이 향상됩니다. 슬라이스에 대한 수정이 더 이상 원본 DataFrame을 무음으로 변경하지 않으며, 실제 쓰기가 발생할 때만 복사본이 생성됩니다.

Pandas의 GroupBy 집계와 피처 엔지니어링

GroupBy는 데이터를 하위 집합으로 분할하고, 각 하위 집합에 함수를 적용한 뒤, 결과를 결합하는 패턴입니다. 이 패턴은 테이블 기반 머신러닝에서 대부분의 피처 엔지니어링 워크플로우를 주도합니다.

python
# pandas_groupby.py
import pandas as pd

# Aggregate candidate stats by department
dept_stats = (
    df_clean
    .groupby("department")
    .agg(
        avg_salary=("salary", "mean"),
        median_experience=("experience_years", "median"),
        headcount=("email", "count"),
        max_salary=("salary", "max"),
    )
    .sort_values("avg_salary", ascending=False)
)
print(dept_stats.head())

# Create features for ML: encode categorical + add aggregated stats
df_features = (
    df_clean
    .assign(
        # Ratio of individual salary to department average
        salary_ratio=lambda x: x["salary"] / x.groupby("department")["salary"].transform("mean"),
        # Time since hire in days
        tenure_days=lambda x: (pd.Timestamp.now() - x["hired_date"]).dt.days,
        # Binary encoding
        is_senior=lambda x: (x["experience_years"] >= 5).astype(int),
    )
)

.transform() 메서드는 원본 인덱스에 정렬된 Series를 반환하므로 .assign() 내부에서 안전하게 사용할 수 있습니다. 집계된 결과를 원본 DataFrame에 수동으로 조인해야 하는 흔한 실수를 방지할 수 있습니다.

Data Science & ML 면접 준비가 되셨나요?

인터랙티브 시뮬레이터, flashcards, 기술 테스트로 연습하세요.

Scikit-Learn 파이프라인 처음부터 구축하기

Scikit-Learn 파이프라인은 전처리 단계와 모델을 하나의 객체로 연결합니다. 파이프라인은 변환기를 학습 데이터에서만 적합(fit)시킨 후 동일한 변환을 테스트 데이터에 적용하므로, 데이터 누수를 방지합니다.

python
# sklearn_pipeline.py
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.metrics import classification_report
import pandas as pd

# Define column groups
numeric_features = ["salary", "experience_years", "salary_ratio", "tenure_days"]
categorical_features = ["department", "role_level"]
target = "promoted"

# Split before any preprocessing
X = df_features[numeric_features + categorical_features]
y = df_features[target]
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)

# Build the preprocessing + model pipeline
preprocessor = ColumnTransformer(
    transformers=[
        ("num", StandardScaler(), numeric_features),
        ("cat", OneHotEncoder(handle_unknown="ignore"), categorical_features),
    ]
)

pipeline = Pipeline([
    ("preprocessor", preprocessor),
    ("classifier", GradientBoostingClassifier(
        n_estimators=200,
        learning_rate=0.1,
        max_depth=4,
        random_state=42,
    )),
])

# Train and evaluate
pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)
print(classification_report(y_test, y_pred))

ColumnTransformer는 서로 다른 열 유형에 대해 서로 다른 전처리를 단일 단계에서 적용합니다. StandardScaler는 수치 피처를 평균 0, 분산 1로 정규화합니다. OneHotEncoder는 범주형 문자열을 이진 열로 변환합니다. handle_unknown="ignore" 매개변수는 테스트 데이터에 학습 데이터에 없는 카테고리가 포함되어 있어도 파이프라인이 오류 없이 작동하도록 보장합니다.

교차 검증과 하이퍼파라미터 튜닝

단일 학습/테스트 분할은 어떤 샘플이 각 세트에 포함되는지에 따라 오해의 소지가 있는 결과를 만들어낼 수 있습니다. 교차 검증은 여러 번의 분할을 수행하고 점수의 평균을 계산하여 더 신뢰할 수 있는 성능 추정치를 제공합니다.

python
# sklearn_tuning.py
from sklearn.model_selection import cross_val_score, GridSearchCV
import numpy as np

# 5-fold cross-validation on the full pipeline
scores = cross_val_score(pipeline, X_train, y_train, cv=5, scoring="f1")
print(f"F1 scores: {scores}")
print(f"Mean F1: {scores.mean():.3f} (+/- {scores.std() * 2:.3f})")

# Grid search over hyperparameters
param_grid = {
    "classifier__n_estimators": [100, 200, 300],
    "classifier__max_depth": [3, 4, 5],
    "classifier__learning_rate": [0.05, 0.1, 0.2],
}

grid_search = GridSearchCV(
    pipeline,
    param_grid,
    cv=5,
    scoring="f1",
    n_jobs=-1,
    verbose=1,
)

grid_search.fit(X_train, y_train)
print(f"Best params: {grid_search.best_params_}")
print(f"Best F1: {grid_search.best_score_:.3f}")

# Evaluate the best model on held-out test set
best_model = grid_search.best_estimator_
print(classification_report(y_test, best_model.predict(X_test)))

이중 밑줄 구문(classifier__max_depth)은 파이프라인 내부의 중첩된 매개변수를 참조합니다. n_jobs=-1은 사용 가능한 모든 CPU 코어에 걸쳐 탐색을 병렬화합니다.

교차 검증에서의 데이터 누수

분할 전에 전체 데이터셋에 스케일러를 적합시키면, 검증 폴드의 정보가 학습 폴드로 누출됩니다. 스케일러를 Pipeline 내부에 배치하면 각 폴드의 학습 부분에서만 스케일링이 적합되도록 보장합니다. 이것은 머신러닝 면접 질문에서 가장 흔하게 출제되는 실수 패턴입니다.

프로덕션을 위한 모델 저장 및 불러오기

학습된 파이프라인은 배포를 위해 직렬화해야 합니다. joblib 라이브러리는 Python 내장 pickle보다 NumPy 배열을 더 효율적으로 처리합니다.

python
# sklearn_export.py
import joblib
from pathlib import Path

# Save the complete pipeline (preprocessor + model)
model_dir = Path("models")
model_dir.mkdir(exist_ok=True)
joblib.dump(best_model, model_dir / "promotion_model_v1.joblib")

# Load and predict in a different process
loaded_model = joblib.load(model_dir / "promotion_model_v1.joblib")
new_data = pd.DataFrame({
    "salary": [75000],
    "experience_years": [4],
    "salary_ratio": [1.05],
    "tenure_days": [730],
    "department": ["Engineering"],
    "role_level": ["Mid"],
})

prediction = loaded_model.predict(new_data)
probability = loaded_model.predict_proba(new_data)[:, 1]
print(f"Promoted: {bool(prediction[0])}, Confidence: {probability[0]:.2%}")

모델만 저장하는 대신 전체 파이프라인을 저장하면, 추론 시점에서도 전처리 단계가 동일하게 적용되는 것을 보장할 수 있습니다.

결론

  • NumPy 벡터화 연산은 Python 반복문을 50~100배 빠른 배열 계산으로 대체하며, 불리언 인덱싱과 브로드캐스팅이 대부분의 필터링 및 변환 작업을 처리합니다
  • Pandas의 .assign().transform()을 활용한 메서드 체이닝은 읽기 쉽고 재현 가능한 데이터 정제 파이프라인을 생성하며, Pandas 2.2의 Copy-on-Write는 무음 변이 버그를 제거합니다
  • Scikit-Learn Pipeline은 전처리와 모델링을 하나의 객체로 묶어 학습 세트와 테스트 세트 간의 데이터 누수를 방지합니다
  • GridSearchCV를 활용한 교차 검증은 신뢰할 수 있는 성능 추정치를 제공하고, 모든 CPU 코어에 걸쳐 하이퍼파라미터 튜닝을 자동화합니다
  • joblib으로 전체 파이프라인을 직렬화하면 학습 시점과 추론 시점에서 동일한 전처리가 보장됩니다
  • 이러한 패턴들을 데이터 사이언스 면접 문제로 연습하여 코딩 능력과 통계적 기초에 대한 이해를 모두 검증할 수 있습니다

연습을 시작하세요!

면접 시뮬레이터와 기술 테스트로 지식을 테스트하세요.

태그

#data-science
#python
#numpy
#pandas
#scikit-learn
#machine-learning
#tutorial

공유

관련 기사