빅데이터 분석기사 실기 1유형 (2) — 결측치·이상치·스케일링·날짜

1유형 단골 변환 기법 모음. 결측치 대체, IQR 이상치, 스케일링, 인코딩, 날짜 처리까지 한 번에 정리.

1유형 (1)에서 정렬·필터링·그룹화를 다뤘다면, 이번 글은 변환 영역이다. 결측치, 이상치, 스케일링, 인코딩, 날짜. 1유형 후반 단골들.


결측치 처리

결측치 확인

df.isnull().sum()           # 컬럼별 결측치 개수
df.isnull().sum().sum()     # 전체 결측치 개수
df.isnull().mean()          # 결측 비율

isna()도 똑같이 동작한다. 둘 중 어떤 걸 써도 된다.

결측치 제거

df.dropna()                       # 결측치가 하나라도 있는 행 삭제
df.dropna(subset=["age"])         # age 컬럼만 기준
df.dropna(axis=1)                 # 결측치 있는 컬럼 삭제
df.dropna(thresh=3)               # 유효값 3개 이상인 행만 유지

결측치 대체 (fillna)

# 평균/중앙값/최빈값으로
df["age"] = df["age"].fillna(df["age"].mean())
df["age"] = df["age"].fillna(df["age"].median())
df["city"] = df["city"].fillna(df["city"].mode()[0])  # 최빈값은 Series로 옴

# 그룹별 평균으로 (자주 출제)
df["income"] = df.groupby("job")["income"].transform(
    lambda x: x.fillna(x.mean())
)

# 앞/뒤 값으로
df["price"] = df["price"].fillna(method="ffill")   # 앞 값
df["price"] = df["price"].fillna(method="bfill")   # 뒤 값

시험에서 "결측치를 중앙값으로 대체하라"고 명시하면 무조건 그대로 따라야 한다. 평균이 더 좋아 보여도 점수는 0점이다.


이상치 탐지 — IQR 방식

1유형 단골 중 단골. 공식만 외우면 된다.

Q1 = 25% 분위수
Q3 = 75% 분위수
IQR = Q3 - Q1

이상치 = (Q1 - 1.5*IQR) 미만  OR  (Q3 + 1.5*IQR) 초과

코드

Q1 = df["price"].quantile(0.25)
Q3 = df["price"].quantile(0.75)
IQR = Q3 - Q1

lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR

# 이상치 행
outliers = df[(df["price"] < lower) | (df["price"] > upper)]

# 이상치 개수
print(len(outliers))

# 이상치를 제외한 데이터의 평균
clean = df[(df["price"] >= lower) & (df["price"] <= upper)]
print(round(clean["price"].mean(), 2))

Z-score 방식 (가끔 출제)

from scipy import stats
import numpy as np

z = np.abs(stats.zscore(df["price"]))
outliers = df[z > 3]   # |z| > 3을 이상치로 본다

스케일링 (정규화·표준화)

Min-Max 정규화

값을 0과 1 사이로 압축한다. 공식: (x - min) / (max - min)

from sklearn.preprocessing import MinMaxScaler

scaler = MinMaxScaler()
df["age_scaled"] = scaler.fit_transform(df[["age"]])
# 또는 수동
df["age_scaled"] = (df["age"] - df["age"].min()) / (df["age"].max() - df["age"].min())

표준화 (Z-score)

평균 0, 표준편차 1로 변환. 공식: (x - mean) / std

from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
df["age_z"] = scaler.fit_transform(df[["age"]])
# 또는 수동
df["age_z"] = (df["age"] - df["age"].mean()) / df["age"].std()

df["age"].std()는 기본이 표본 표준편차(ddof=1) 다. StandardScaler모표준편차(ddof=0). 결과가 미세하게 다르니 문제에서 어떤 식을 요구하는지 확인할 것.

로그 변환

값의 분포가 한쪽으로 치우쳐 있을 때.

import numpy as np
df["price_log"] = np.log(df["price"] + 1)   # 0 방지용 +1

범주형 인코딩

Label Encoding (순서가 의미 있을 때)

from sklearn.preprocessing import LabelEncoder

le = LabelEncoder()
df["grade_le"] = le.fit_transform(df["grade"])
# A,B,C → 0,1,2

One-Hot Encoding (순서 없는 범주)

# pandas 한 줄
df_oh = pd.get_dummies(df, columns=["city"])

# drop_first로 다중공선성 피하기
df_oh = pd.get_dummies(df, columns=["city"], drop_first=True)

날짜 처리 (datetime)

문자열 → datetime

df["date"] = pd.to_datetime(df["date"])
df["date"] = pd.to_datetime(df["date"], format="%Y-%m-%d")  # 형식 지정

날짜에서 정보 뽑기

df["year"]     = df["date"].dt.year
df["month"]    = df["date"].dt.month
df["day"]      = df["date"].dt.day
df["weekday"]  = df["date"].dt.weekday      # 월=0, 일=6
df["dayname"]  = df["date"].dt.day_name()   # 'Monday'
df["quarter"]  = df["date"].dt.quarter

기간 필터링

mask = (df["date"] >= "2024-01-01") & (df["date"] <= "2024-12-31")
df_2024 = df[mask]

# 또는 연도로 직접
df[df["date"].dt.year == 2024]

날짜 간 차이

df["diff_days"] = (df["end"] - df["start"]).dt.days
df["diff_hours"] = (df["end"] - df["start"]).dt.total_seconds() / 3600

자주 나오는 1유형 변환 패턴

패턴 1. 결측치 대체 후 통계량

df["age"] = df["age"].fillna(df["age"].median())
answer = round(df["age"].mean(), 2)
print(answer)

패턴 2. 이상치 제거 후 표준편차

Q1, Q3 = df["price"].quantile([0.25, 0.75])
IQR = Q3 - Q1
clean = df[(df["price"] >= Q1 - 1.5*IQR) & (df["price"] <= Q3 + 1.5*IQR)]
answer = round(clean["price"].std(), 2)
print(answer)

패턴 3. 표준화 후 특정 값 개수

df["z"] = (df["score"] - df["score"].mean()) / df["score"].std()
answer = (df["z"].abs() > 2).sum()    # 절대값 2 초과 개수
print(answer)

패턴 4. 월별 집계 (날짜 + groupby)

df["date"] = pd.to_datetime(df["date"])
df["month"] = df["date"].dt.month
monthly = df.groupby("month")["sales"].sum()
answer = monthly.idxmax()             # 매출이 가장 큰 달
print(answer)

패턴 5. 요일별 평균 비교

df["date"] = pd.to_datetime(df["date"])
df["weekday"] = df["date"].dt.weekday
weekend = df[df["weekday"].isin([5, 6])]["sales"].mean()
weekday = df[df["weekday"] < 5]["sales"].mean()
answer = round(weekend - weekday, 2)
print(answer)

출력값 가공 — 마지막 체크

문제마다 요구하는 형식이 다르다. 항상 마지막에 가공해서 출력한다.

# 정수
print(int(answer))

# 반올림 소수점 둘째 자리
print(round(answer, 2))

# 문자열로 가공해야 할 때
print(str(answer))

print() 안 하고 변수만 두고 끝내는 실수가 의외로 많다. 시험은 출력값으로 채점되니 반드시 print()로 마무리한다.


정리

영역 핵심
결측치 isnull().sum(), fillna(mean/median/mode), dropna
이상치 Q1 - 1.5*IQR ~ Q3 + 1.5*IQR 범위, Z-score
스케일링 MinMaxScaler, StandardScaler, 수동 공식
인코딩 LabelEncoder, pd.get_dummies
날짜 pd.to_datetime, dt.year/month/weekday, 기간 필터

1유형은 결국 **"문제가 시킨 그대로 변환 → 집계 → 출력"**의 반복이다. 패턴을 5~6개만 머릿속에 넣어두면 시험장에서 문제 보자마자 손이 움직인다.

다음 글부터는 배점이 가장 큰 2유형 (머신러닝 모델링) 으로 넘어간다.