빅데이터 분석기사 실기 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유형 (머신러닝 모델링) 으로 넘어간다.