끄적끄적
CH 4. 실전 프로젝트 - 데이터 전처리 본문
📊 온라인 강의 플랫폼 데이터 전처리 과정
이번 포스트에서는 온라인 강의 플랫폼 데이터를 불러와, 분석이 가능한 형태로 전처리 및 파생변수 생성까지 진행한 과정을 정리했습니다.
데이터 출처: Kaggle – Online Course Student Engagement Metrics
1️⃣ 데이터 로드 및 기본 확인
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
plt.rcParams['font.family'] ='Malgun Gothic'
plt.rcParams['axes.unicode_minus'] =False
pd.set_option('display.max_columns', None)
df = pd.read_csv('./Courses.csv')
df.info()
df.head()
- 총 16개 컬럼이 존재하며, 학생들의 등록/수강 여부, 성적, 국가, 학력, 나이, 이벤트 활동 수 등을 포함하고 있습니다.
- incomplete_flag를 확인해보니 일부 레코드에 결측치가 포함되어 있음도 알 수 있었습니다.
2️⃣ 성적(grade) 결측치 처리
- grade 컬럼에는 ' ' (공백) 값이 존재 → 이를 NaN으로 변환했습니다.
df['grade'] = df['grade'].replace(' ', np.nan)
3️⃣ 이상치 처리
전처리 과정에서 여러 논리적/통계적 이상치를 정의하고 제거했습니다.
(1) 수강 이력과 이벤트 불일치
- 조건 컬럼: nplay_video, nchapters, nevents, ndays_act
- 규칙
- viewed == False 이면서 위 조건 값이 모두 존재 → 삭제
- viewed == False 이면서 일부 값만 존재 → 해당 값들을 0으로 치환
conditions = ['nplay_video', 'nchapters', 'nevents', 'ndays_act']
condition_not = (~df['nevents'].isna()) & (~df['ndays_act'].isna()) & (~df['nplay_video'].isna()) & (~df['nchapters'].isna())
drop_index = df[(df['viewed']==False) & condition_not].index
df = df.drop(index=list(drop_index)).reset_index(drop=True)
mask = (df['viewed']==False) & (df[conditions].notna().any(axis=1))
df.loc[mask, conditions] = 0
(2) 출생연도(YoB) 이상치
- IQR을 사용하여 이상치 탐지
- 상한값(upper) = 2004.5
- 2005년 이후 출생 데이터는 잘못 입력된 값으로 판단하여 삭제
q1, q3 = df['YoB'].quantile([0.25, 0.75])
iqr = q3 - q1
upper = q3 + 1.5*iqr
drop_index = df[df['YoB'] > upper].index
df = df.drop(index=list(drop_index)).reset_index(drop=True)
(3) 불필요/결측 처리
- roles 컬럼: 분석에 필요 없으므로 삭제
- LoE_DI, gender: 결측치는 "Unknown"으로 대체
df.drop(columns='roles', inplace=True)
df = df.fillna({'LoE_DI': 'Unknown', 'gender': 'Unknown'})
(4) 성적(grade) 특수값
- 값이 1.01인 경우 실제 100% 성적 의미 → 1.0으로 변환
df['grade'] = df['grade'].replace('1.01', '1')
(5) 논리적 오류 데이터 삭제
- viewed == False인데 explored 또는 certified == True → 불가능한 케이스 → 삭제
- nevents가 197,757회인 데이터(이상치) → 삭제
- last_event_DI < start_time_DI (시간 역전) → 삭제
drop_index = df[(df['viewed']==False)&((df['explored']==True)|(df['certified']==True))].index
df = df.drop(index=list(drop_index)).reset_index(drop=True)
drop_index = df[df['nevents']==197757].index
df = df.drop(index=list(drop_index)).reset_index(drop=True)
drop_index = df[(df['last_event_DI'].notna()) & (df['start_time_DI'].notna()) &(df['last_event_DI'] < df['start_time_DI'])].index
df = df.drop(index=list(drop_index)).reset_index(drop=True)
4️⃣ 파생변수 생성
데이터를 더 분석하기 쉽게 새로운 컬럼들을 추가했습니다.
(1) 대학, 강의 코드, 학기, 연도
uni_course_seme = df['course_id'].str.split('/', expand=True)
uni_course_seme.columns = ['university', 'course_code', 'semester']
date = uni_course_seme['semester'].str.split('_', expand=True)
date.columns = ['year', 'semester']
new_col = pd.concat([uni_course_seme[['university','course_code']], date], axis=1)
df_f = pd.concat([df, new_col], axis=1)
(2) 강의명 매핑
course_dict = {
"HarvardX/CB22x/2013_Spring": "The Ancient Greek Hero",
"HarvardX/CS50x/2012": "Introduction to Computer Science",
"HarvardX/ER22x/2013_Spring": "Justice",
"HarvardX/PH207x/2012_Fall": "Health in Numbers: Quantitative Methods in Clinical & Public Health Research",
"HarvardX/PH278x/2013_Spring": "Human Health and Global Environmental Change",
"MITx/6.002x/2012_Fall": "Circuits and Electronics",
"MITx/6.002x/2013_Spring": "Circuits and Electronics",
"MITx/14.73x/2013_Spring": "The Challenges of Global Poverty",
"MITx/2.01x/2013_Spring": "Elements of Structures",
"MITx/3.091x/2012_Fall": "Introduction to Solid State Chemistry",
"MITx/3.091x/2013_Spring": "Introduction to Solid State Chemistry",
"MITx/6.00x/2012_Fall": "Introduction to Computer Science and Programming",
"MITx/6.00x/2013_Spring": "Introduction to Computer Science and Programming",
"MITx/7.00x/2013_Spring": "Introduction to Biology - The Secret of Life",
"MITx/8.02x/2013_Spring": "Physics II: Electricity and Magnetism",
"MITx/8.MReV/2013_Summer": "Mechanics ReView"
}
df_f['course_title'] = df_f['course_id'].map(course_dict)
(3) 학습일수(study_days)
df_f['study_days'] = (pd.to_datetime(df_f['last_event_DI']) - pd.to_datetime(df_f['start_time_DI'])).dt.days
df_f['study_days'] = df_f['study_days'].fillna(0).astype(int)
(4) 나이(age)
- 기준 연도 = 2013년
df_f['age'] = 2013 - df_f['YoB']
(5) 지역(region)
- 국가(final_cc_cname_DI)를 대륙 단위로 매핑
region_map = {
'United States': 'North America',
'Canada': 'North America',
'Mexico': 'North America',
'Other North & Central Amer., Caribbean': 'North America',
'Brazil': 'South America',
'Colombia': 'South America',
'Other South America': 'South America',
'France': 'Europe',
'United Kingdom': 'Europe',
'Germany': 'Europe',
'Spain': 'Europe',
'Greece': 'Europe',
'Portugal': 'Europe',
'Poland': 'Europe',
'Ukraine': 'Europe',
'Russian Federation': 'Europe',
'Other Europe': 'Europe',
'India': 'South Asia',
'Pakistan': 'South Asia',
'Bangladesh': 'South Asia',
'Other South Asia': 'South Asia',
'China': 'East Asia',
'Japan': 'East Asia',
'Other East Asia': 'East Asia',
'Philippines': 'Southeast Asia',
'Indonesia': 'Southeast Asia',
'Egypt': 'Middle East & North Africa',
'Morocco': 'Middle East & North Africa',
'Other Middle East/Central Asia': 'Middle East & Central Asia',
'Nigeria': 'Sub-Saharan Africa',
'Other Africa': 'Sub-Saharan Africa',
'Australia': 'Oceania',
'Other Oceania': 'Oceania',
'Unknown/Other': 'Unknown'
}
df_f['region'] = df_f['final_cc_cname_DI'].map(region_map)
(6) 연령대(age_group)
bins = [0, 19, 29, 39, 49, 59, 100]
labels = ['10대', '20대', '30대', '40대', '50대', '60대 이상']
df_f['age_group'] = pd.cut(df_f['age'], bins=bins, labels=labels, right=True)
5️⃣ 최종 저장
마지막으로 전처리 및 파생변수 생성이 완료된 데이터를 CSV로 저장했습니다.
✅ 정리
이번 전처리 과정에서는
- 결측치 및 이상치 처리
- 논리적 오류 데이터 제거
- 분석을 위한 파생변수 생성
까지 수행하여, 학습자 데이터를 분석 가능한 형태로 변환했습니다.
이제 이 데이터를 활용해 수강 패턴 분석, 학습 성과 예측, 학습자 세분화(클러스터링) 등을 진행할 수 있습니다 🚀
'[스파르타]내일배움캠프 데이터 분석 트랙 > Project' 카테고리의 다른 글
| CH 4. 실전 프로젝트 - 가설 검증 및 대시보드 생성 (4) | 2025.08.18 |
|---|---|
| CH 3. 심화 프로젝트 - 데이터전처리 (4) | 2025.07.17 |
| CH 2. 기초 프로젝트 - 최종 정리 (1) | 2025.06.23 |
| CH 2. 기초 프로젝트 - 데이터 분석 (2) (2) | 2025.06.19 |
| CH 2. 기초 프로젝트 - 데이터 전처리 (2) (5) | 2025.06.18 |