빅데이터분석기사 실기 학습자료 1과목 파트2 - 집계 분석

2026. 4. 28. 20:13ㆍ빅데이터분석기사

11111

 

 

 
[샘플데이터] 실습 데이터 (df) — 결측값·이상치 포함 상태:
   id    age     dept  salary  gender   score
   1    32.0      IT  4500.0       M    85.0
   2     NaN      HR  3800.0       F    72.0   ← age
결측
   3    28.0  Finance  5200.0      M     NaN   ← score
결측
   4    55.0   Sales   3100.0      F    68.0
   5    38.0      IT  15000.0      M    88.0   ← salary
이상치(극단값)

 

1. 결측값(Missing Value) 완전 정복

1.1 결측값 탐지

import pandas as pd
import numpy as np
df = pd.read_csv("/data/train.csv")
 
# isnull(): 결측이면 True, 아니면 False → sum()으로 개수 집계
print(df.isnull().sum())              # 열별 결측값 개수
 
# 비율 = 결측수 / 전체행수 * 100
print(df.isnull().sum() / len(df) * 100)
 
# 결측값 있는 행이 몇 개인지 (any(axis=1): 행 중 하나라도 True)
print(df.isnull().any(axis=1).sum())
 
# 특정 열 결측인 행만 보기
print(df[df['age'].isnull()])
────────────────────────────────────────────────────  실행 결과
age       22     ← 22개 결측
salary     5
score      8
dtype: int64
 
age       2.2    ← 2.2% 결측
salary    0.5
score     0.8
 
25         ← 한 개 이상 결측값 있는 행 수: 25개
 
   id   age  dept  salary  score
4   5   NaN    IT  4200.0   85.0
11  12  NaN    HR  3900.0   72.0  ← age가 결측인 행들

 

1.2 결측값 제거 (dropna)

import pandas as pd
df = pd.read_csv("/data/train.csv")
 
# 결측값 있는 행 전부 제거 (가장 강력, 데이터 손실 큼)
df_clean = df.dropna()
print(f"원본: {len(df)}행, 제거 후: {len(df_clean)}행")
 
# 특정 열에 결측값 있는 행만 제거 (subset 사용)
df2 = df.dropna(subset=['age', 'salary'])   # age 또는 salary가 결측인 행 제거
print(f"subset 제거 후: {len(df2)}행")
 
# 결측 비율 50% 이상인 열 제거 (thresh: 최소 유효 데이터 수)
threshold = len(df) * 0.5          # 전체의 50%
df3 = df.dropna(axis=1, thresh=int(threshold))  # thresh 미만이면 열 삭제
print(df3.columns.tolist())
────────────────────────────────────────────────────  실행 결과
원본: 1000행, 제거 후: 975행    ← 25개 행 제거
 
subset 제거 후: 993행            ← age 또는 salary 결측인 7행만 제거
 
['id', 'age', 'dept', 'salary', 'score', 'hire_date']  ← 결측 적은 열만 유지

 

1.3 결측값 대체 (fillna) — 핵심

import pandas as pd
df = pd.read_csv("/data/train.csv")
 
# 수치형: 평균(mean) 또는 중앙값(median)으로 대체
# 이상치가 있으면 median 권장, 없으면 mean 사용
df['age'].fillna(df['age'].mean(), inplace=True)       # inplace=True: 원본 수정
df['salary'].fillna(df['salary'].median(), inplace=True)
 
# 범주형: 최빈값(mode)으로 대체 — ★ mode()[0] 반드시 [0] 붙이기!
df['dept'].fillna(df['dept'].mode()[0], inplace=True)
 
# 그룹별 중앙값으로 대체 (더 정교한 방법)
# 부서별 급여 중앙값을 해당 부서의 결측값에 채움
df['salary'] = df.groupby('dept')['salary'].transform(
    lambda x: x.fillna(x.median())
)
 
# 시계열: 앞/뒤 값으로 채우기 (순서 있는 데이터에서 사용)
df['sales'].fillna(method='ffill', inplace=True)  # 앞 값으로 채움(forward fill)
df['sales'].fillna(method='bfill', inplace=True)  # 뒤 값으로 채움(backward fill)
 
print("결측값 처리 후:", df.isnull().sum().sum(), "개 남음")
────────────────────────────────────────────────────  실행 결과
결측값 처리 후: 0 개 남음    ← 모든 결측값 처리 완료

 

 
[주의] mode()[0]에서 [0] 빠뜨리면 Series 반환 → 오답 처리. 반드시 [0] 붙일 것!

 

2. 이상치(Outlier) 완전 정복

2.1 IQR 방법 (시험 표준)

import pandas as pd
df = pd.read_csv("/data/train.csv")
 
# IQR(사분위범위) = Q3 - Q1
# 이상치 기준: Q1-1.5*IQR 미만 또는 Q3+1.5*IQR 초과
Q1  = df['salary'].quantile(0.25)    # 1사분위수 (하위 25%)
Q3  = df['salary'].quantile(0.75)    # 3사분위수 (상위 25%)
IQR = Q3 - Q1
 
lower = Q1 - 1.5 * IQR    # 이상치 하한선
upper = Q3 + 1.5 * IQR    # 이상치 상한선
 
print(f"Q1={Q1}, Q3={Q3}, IQR={IQR}")
print(f"이상치 기준: {lower:.0f} 미만 또는 {upper:.0f} 초과")
 
# 이상치 제거 (기준 범위 안의 데이터만 유지)
df_clean = df[(df['salary'] >= lower) & (df['salary'] <= upper)]
print(f"원본: {len(df)}행 → 제거 후: {len(df_clean)}행")
print(f"이상치 {len(df)-len(df_clean)}개 제거")
print(f"평균: {df['salary'].mean():.2f} → {df_clean['salary'].mean():.2f}")
────────────────────────────────────────────────────  실행 결과
Q1=3750.0, Q3=5050.0, IQR=1300.0
이상치 기준: 1800 미만 또는 7000 초과
 
원본: 1000행 → 제거 후: 977행
이상치 23개 제거
평균: 4423.82 → 4301.45    ← 이상치 제거 후 평균이 낮아짐

 

2.2 Z-score 방법

import pandas as pd, numpy as np
from scipy import stats
df = pd.read_csv("/data/train.csv")
 
# Z-score = (값 - 평균) / 표준편차
# |Z| > 3 이면 이상치 (평균에서 표준편차 3개 이상 떨어진 값)
z_scores = (df['salary'] - df['salary'].mean()) / df['salary'].std()
df_clean  = df[np.abs(z_scores) < 3]   # |z| < 3인 것만 유지
print(f"numpy Z-score: {len(df)}행 → {len(df_clean)}행")
 
# scipy 사용 방법 (같은 결과, 더 간결)
z = np.abs(stats.zscore(df['salary'].dropna()))
df_clean2 = df.loc[df['salary'].notna()][z < 3]
print(f"scipy Z-score: {len(df_clean2)}행")
────────────────────────────────────────────────────  실행 결과
numpy Z-score: 1000행 → 991행   ← IQR보다 기준이 더 관대 (적게 제거)
scipy Z-score: 991행

 

3. 인코딩

3.1 레이블 인코딩

from sklearn.preprocessing import LabelEncoder
import pandas as pd
df = pd.read_csv("/data/train.csv")
 
le = LabelEncoder()
# fit_transform: 고유값 학습(fit) + 변환(transform) 동시 수행
# 알파벳 순서로 0,1,2,3 부여됨
df['dept_enc'] = le.fit_transform(df['dept'].astype(str))
 
print(le.classes_)             # 인코딩 클래스 순서 확인
print(df[['dept','dept_enc']].drop_duplicates().sort_values('dept_enc'))
────────────────────────────────────────────────────  실행 결과
['Finance' 'HR' 'IT' 'Sales']    ← 알파벳 순: Finance=0, HR=1, IT=2, Sales=3
 
      dept  dept_enc
2  Finance         0
1       HR         1
0       IT         2
3    Sales         3

 

3.2 원-핫 인코딩

import pandas as pd
df = pd.read_csv("/data/train.csv")
 
# get_dummies: 범주형 열 → 0/1 이진 열 여러 개로 변환
# prefix: 새 열 이름 앞에 붙일 접두어
dummies = pd.get_dummies(df['dept'], prefix='dept')
df = pd.concat([df, dummies], axis=1)  # 원본에 붙이기
df.drop(columns=['dept'], inplace=True) # 원본 열 삭제
 
print(df.filter(like='dept_').head(3))
 
# 다중 열 동시 인코딩 + 다중공선성 방지
# drop_first=True: 첫 번째 열 제거 (Finance 제거, HR/IT/Sales만 유지)
df2 = pd.get_dummies(df, columns=['dept', 'gender'], drop_first=True)
print(df2.shape)
────────────────────────────────────────────────────  실행 결과
   dept_Finance  dept_HR  dept_IT  dept_Sales
0           0        0        1           0    ← IT 부서 = dept_IT 만 1
1           0        1        0           0    ← HR 부서 = dept_HR 만 1
2           1        0        0           0    ← Finance = dept_Finance만 1
 
(1000, 14)    ← 열 수 증가

 

4. 스케일링

from sklearn.preprocessing import StandardScaler, MinMaxScaler
import pandas as pd
df = pd.read_csv("/data/train.csv")
num_cols = ['age', 'salary']
 
# 표준화(Standardization): 평균=0, 표준편차=1로 변환
# 공식: (x - mean) / std
ss = StandardScaler()
df_std = df.copy()
df_std[num_cols] = ss.fit_transform(df[num_cols])
print("표준화 후:")
print(df_std[num_cols].describe().round(3))
 
# 정규화(Min-Max Scaling): 0~1 범위로 변환
# 공식: (x - min) / (max - min)
mm = MinMaxScaler()
df_norm = df.copy()
df_norm[num_cols] = mm.fit_transform(df[num_cols])
print("
정규화 후:")
print(df_norm[num_cols].describe().round(3))
────────────────────────────────────────────────────  실행 결과
표준화 후:
         age  salary
mean   0.000   0.000    ← 평균=0
std    1.000   1.000    ← 표준편차=1
min   -1.903  -2.107
max    2.285   3.401
 
정규화 후:
       age  salary
min  0.000   0.000    ← 최솟값=0
max  1.000   1.000    ← 최댓값=1
mean 0.397   0.413

 

5. 집계 分석 실전 패턴 20선

import pandas as pd
df = pd.read_csv("/data/train.csv")
df['hire_date'] = pd.to_datetime(df['hire_date'])
 
# [1] 부서별 평균 급여
print(df.groupby('dept')['salary'].mean().round(2))
 
# [2] 성별+부서별 중앙값 (다중 groupby → unstack으로 표 형태)
print(df.groupby(['gender','dept'])['salary'].median().unstack())
 
# [3] 부서별 최고-최저 급여 차이 (lambda로 커스텀 집계)
spread = df.groupby('dept')['salary'].agg(lambda x: x.max()-x.min())
print(spread)
 
# [4] 상위 25% 급여 직원 비율 (부서별)
p75   = df['salary'].quantile(0.75)
ratio = df[df['salary']>=p75].groupby('dept').size() / df.groupby('dept').size()
print(ratio.round(4))
 
# [5] 연도별 입사자 수
print(df.groupby(df['hire_date'].dt.year).size())
────────────────────────────────────────────────────  실행 결과
dept
Finance    5310.42
HR         3892.15
IT         5102.38
Sales      3650.00
 
gender  Finance      HR      IT   Sales
F        5420.0  3950.0  5200.0  3700.0
M        5210.0  3840.0  5010.0  3600.0
 
dept
Finance    4200.0    ← Finance 최고-최저 차이
HR         3100.0
IT         5200.0    ← IT가 가장 큰 편차
Sales      2800.0
 
dept
Finance    0.3208
HR         0.2500
IT         0.2969
Sales      0.2069    ← Sales는 상위25% 비율 낮음
 
hire_date
2015    142
2016    158
2017    175
2018    198
2019    180
2020    147

 

6. 실전 예제 풀이 5선

예제1: 이상치 제거 + 그룹 집계

 
[기출패턴] Q: 급여 이상치(IQR 기준) 제거 후 부서별 평균 급여 중 가장 높은 값 (소수점 2자리)

 

import pandas as pd
df = pd.read_csv("/data/train.csv")
 
# 1) IQR 이상치 제거
Q1,Q3 = df['salary'].quantile(0.25), df['salary'].quantile(0.75)
df_c  = df[(df['salary'] >= Q1-1.5*(Q3-Q1)) & (df['salary'] <= Q3+1.5*(Q3-Q1))]
 
# 2) 이상치 제거된 데이터로 부서별 평균 → 최대
result = df_c.groupby('dept')['salary'].mean().max()
print(round(result, 2))
────────────────────────────────────────────────────  실행 결과
5218.34    ← 이상치 제거 후 Finance 부서 평균이 가장 높음

 

예제2: 날짜 파생변수 + 조건 필터

 
[기출패턴] Q: 2020년 이후 입사한 IT 부서 직원의 평균 나이 (소수점 1자리)

 

import pandas as pd
df = pd.read_csv("/data/train.csv")
df['hire_date'] = pd.to_datetime(df['hire_date'])   # 문자열 → datetime
 
# 조건1: 입사연도 >= 2020 / 조건2: 부서 == IT
mask   = (df['hire_date'].dt.year >= 2020) & (df['dept'] == 'IT')
result = df[mask]['age'].mean()
print(round(result, 1))
────────────────────────────────────────────────────  실행 결과
31.4    ← 2020년 이후 IT 입사자 평균 나이 31.4세

 

예제3: 결측값 처리 + 순위

 
[기출패턴] Q: age 결측값을 중앙값으로 대체 후 나이 상위 10명의 평균 급여 (소수점 2자리)

 

import pandas as pd
df = pd.read_csv("/data/train.csv")
 
# 결측값 중앙값 대체
df['age'].fillna(df['age'].median(), inplace=True)
 
# nlargest: 특정 열 기준 상위 N개 행 추출
top10  = df.nlargest(10, 'age')   # age 상위 10명
result = top10['salary'].mean()
print(round(result, 2))
────────────────────────────────────────────────────  실행 결과
4832.10    ← 나이 상위 10명(50대 이상)의 평균 급여

 

예제4: 파생변수 + 구간 + 집계

 
[기출패턴] Q: 급여를 4분위로 나누고 Q4(상위 25%) 그룹에서 부서별 인원수 최대값 출력

 

import pandas as pd
df = pd.read_csv("/data/train.csv")
 
# qcut: 4분위수로 균등 분할 (각 구간 동일 인원수)
df['sal_q'] = pd.qcut(df['salary'], q=4, labels=['Q1','Q2','Q3','Q4'])
 
# Q4(상위 25%)만 필터 → 부서별 인원수 → 최댓값
q4     = df[df['sal_q'] == 'Q4']
result = q4.groupby('dept').size().max()
print(result)
────────────────────────────────────────────────────  실행 결과
98    ← Q4에서 가장 많은 부서의 인원 수 (주로 IT나 Finance)

 

예제5: 중복 제거 + 조건 + 집계

 
[기출패턴] Q: user_id 기준 중복 제거 후 여성(F) 직원의 salary 표준편차 (소수점 4자리)

 

import pandas as pd
df = pd.read_csv("/data/train.csv")
 
# drop_duplicates: 특정 열 기준 중복 행 제거 (첫 번째만 유지)
df_u   = df.drop_duplicates(subset=['user_id'])
 
# 여성만 필터 후 표준편차
result = df_u[df_u['gender'] == 'F']['salary'].std()
print(round(result, 4))
────────────────────────────────────────────────────  실행 결과
982.3417    ← 중복 제거 후 여성 급여의 표준편차

 

2222