끄적끄적

CH 4. 실전 프로젝트 - 데이터 전처리 본문

[스파르타]내일배움캠프 데이터 분석 트랙/Project

CH 4. 실전 프로젝트 - 데이터 전처리

kminx 2025. 8. 18. 18:00

📊 온라인 강의 플랫폼 데이터 전처리 과정

이번 포스트에서는 온라인 강의 플랫폼 데이터를 불러와, 분석이 가능한 형태로 전처리 및 파생변수 생성까지 진행한 과정을 정리했습니다.
데이터 출처: 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
  • 규칙
    1. viewed == False 이면서 위 조건 값이 모두 존재 → 삭제
    2. 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로 저장했습니다.


✅ 정리

이번 전처리 과정에서는

  • 결측치 및 이상치 처리
  • 논리적 오류 데이터 제거
  • 분석을 위한 파생변수 생성

까지 수행하여, 학습자 데이터를 분석 가능한 형태로 변환했습니다.

이제 이 데이터를 활용해 수강 패턴 분석, 학습 성과 예측, 학습자 세분화(클러스터링) 등을 진행할 수 있습니다 🚀