빅데이터분석기사 실기 학습자료 1과목 파트2 - 집계 분석
2026. 4. 28. 20:13ㆍ빅데이터분석기사
11111
2222

| [샘플데이터] 실습 데이터 (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 ← 중복 제거 후 여성 급여의 표준편차 |
'빅데이터분석기사' 카테고리의 다른 글
| 빅데이터분석기사 실기 학습자료 2과목 파트1 - 모델링 기초 · 데이터 분할 · 전처리 · 평가지표 (0) | 2026.04.28 |
|---|---|
| 빅데이터분석기사 실기 학습자료 1과목 파트3 - 실전 기출 패턴 (0) | 2026.04.28 |
| 빅데이터분석기사 실기 학습자료 1과목 파트1 - Python · NumPy · Pandas (0) | 2026.04.28 |
| 빅데이터분석기사 실기 - 판다스 정리 (0) | 2026.03.12 |
| 빅데이터분석기사 실기 - 2유형 코드 공부 (0) | 2026.03.12 |