내용으로 건너뛰기
Dream of E&C - Wiki
사용자 도구
로그인
사이트 도구
검색
도구
문서 보기
이전 판
역링크
최근 바뀜
미디어 관리자
로그인
>
최근 바뀜
미디어 관리자
추적:
•
📂 Models
wiki:hr:statistics:hr_data_preprocessing
이 문서는 읽기 전용입니다. 원본을 볼 수는 있지만 바꿀 수는 없습니다. 문제가 있다고 생각하면 관리자에게 문의하세요.
====== HR 데이터 전처리 실무 가이드 (원리부터 실전까지) ====== **"Garbage in, Garbage out (쓰레기를 넣으면, 쓰레기가 나온다)" - 데이터 분석의 제1원칙입니다. 이 문서는 성공적인 HR 분석을 위해 가장 중요하지만 가장 많은 시간이 소요되는 '데이터 전처리'의 모든 것을 다루는 실무 가이드입니다.** {{wiki:hr:statistics:hr_data_preprocessing_1.png?400}} ---- ===== 🎯 왜 HR 데이터 전처리가 중요한가? (요리의 재료 손질과 같이) ===== 훌륭한 요리가 신선하고 깨끗하게 손질된 재료에서 시작되듯, 신뢰할 수 있는 분석 결과는 잘 정제된 데이터에서 나옵니다. 데이터 전처리는 단순히 데이터를 '깨끗하게' 만드는 것을 넘어, 분석의 목적에 맞게 데이터를 '요리하기 좋은 상태'로 만드는 과정입니다. HR 데이터는 특히나 사람과 조직의 복잡한 특성이 얽혀 있어, 다른 어떤 데이터보다 세심한 '재료 손질(전처리)'이 필요합니다. * **📝 수동 입력의 한계**: 사람이 직접 데이터를 입력하는 과정에서 발생하는 자연스러운 문제입니다. * ''문제 예시'': '인사팀', '인사 팀'(띄어쓰기), 'HR' 등 담당자마다 다른 표기법 사용, 연봉이나 나이의 오타(0을 하나 더 붙이는 등) * **🔄 잦은 조직 변화의 영향**: 조직은 살아있는 생물처럼 계속 변화하며, 이는 데이터에 그대로 흔적을 남깁니다. * ''문제 예시'': 과거 '전략기획팀'이 현재 '미래전략실'로 변경된 경우, 두 부서를 동일한 부서로 인식하지 못하면 연도별 분석에 오류 발생 * **👥 개인정보 보호의 필요성**: 직원의 민감 정보를 다루므로, 분석 전 반드시 비식별화 조치를 거쳐야 합니다. * ''문제 예시'': 직원 이름을 익명의 ID로 대체하거나, 상세 주소를 '수도권'/'비수도권' 등으로 그룹화하는 과정에서 데이터 구조가 변경됨 * **⏰ 시간의 흐름에 따른 데이터 변화**: 오래전에 구축된 시스템과 현재 시스템의 데이터 저장 방식이 다를 수 있습니다. * ''문제 예시'': 과거에는 '850115' 형식으로 저장하던 생년월일을 현재는 '1985-01-15' 형식으로 저장하여, 두 데이터를 합쳤을 때 형식이 불일치함 > **💡 실무자의 교훈:** > "지저분한 데이터는 단순히 작업 시간을 늘리는 것을 넘어, 분석 결과 전체를 왜곡시킬 수 있습니다. 예를 들어, 부서명을 통일하지 않고 이직률을 계산하면 특정 부서의 이직률이 실제보다 낮게 계산되어 잘못된 의사결정으로 이어질 수 있습니다. 전처리에 투자하는 시간은 분석의 신뢰성을 담보하는 가장 확실한 투자입니다." ---- ===== 🔍 HR 데이터의 주요 문제 유형 및 해결 방안 ===== **① 범주형 데이터 불일치 (부서명/직급명 등)** * **근본 원인**: 표준화된 입력 규칙의 부재, 담당자의 자의적 입력, 시스템 변경 이력 관리 미흡 * **방치 시 문제점**: 그룹별 집계(부서별 만족도, 직급별 이직률 등) 시 누락되거나 왜곡된 결과 도출. '인사팀'의 데이터가 '인사부', 'HR팀' 등으로 분산되어 정확한 현황 파악 불가. * **해결 전략**: 데이터 표준화 규칙(Standardization Rule)을 정의하고, 이를 기반으로 데이터를 통일시킵니다. <code> # === 실무 예시: 부서명 표준화 === # 문제가 있는 원본 데이터 dept_names <- c("인사팀", "인사부", "HR팀", "HR부", "재무팀", "회계팀") # 해결 방법: dplyr 패키지의 case_when 함수로 규칙 기반 표준화 library(dplyr) dept_standardized <- case_when( dept_names %in% c("인사팀", "인사부", "HR팀", "HR부") ~ "인사팀", // 여러 표기를 '인사팀'으로 통일 dept_names %in% c("재무팀", "회계팀") ~ "재무회계팀", // 유사 부서를 '재무회계팀'으로 통합 TRUE ~ dept_names // 규칙에 해당하지 않는 나머지는 그대로 유지 ) # 코드 해설: # case_when은 여러 개의 if-else 구문을 체계적으로 처리해주는 매우 유용한 함수입니다. # 'A이면 B로 바꾸고, C이면 D로 바꾸고, 그 외에는 그대로 둬라' 와 같은 복잡한 규칙을 쉽게 구현할 수 있습니다. </code> **② 날짜 형식 불일치** * **근본 원인**: 데이터를 입력하는 시스템이나 사람마다 다른 날짜 표기법 사용 * **방치 시 문제점**: 근속연수, 퇴사까지 걸린 시간, 연령 등 '기간' 계산이 불가능해짐. 날짜를 기준으로 정렬하거나 필터링할 수 없음. * **해결 전략**: 모든 날짜 표기를 R이 인식할 수 있는 표준 날짜(Date) 객체로 변환합니다. <code> # === 실무 예시: 다양한 날짜 형식 통일 === # lubridate 패키지는 날짜 처리를 매우 쉽게 만들어주는 필수 패키지입니다. library(lubridate) # 문제가 있는 원본 데이터 dates_chr <- c("2024-01-15", "2024/02/20", "24.03.10", "2024년 4월 5일") # 해결 방법: lubridate 패키지의 파싱(Parsing) 함수 사용 clean_dates <- ymd(dates_chr) // '연-월-일' 순서로 된 대부분의 형식을 자동으로 인식하여 변환 # 코드 해설: # ymd() 함수는 'yyyy-mm-dd', 'yyyy/mm/dd', 'yy.mm.dd' 등 다양한 구분자를 가진 # '연-월-일' 순서의 문자열을 똑똑하게 인식하여 표준 날짜 객체로 바꿔줍니다. # 만약 '월-일-연' 순서라면 mdy()를, '일-월-연' 순서라면 dmy()를 사용하면 됩니다. </code> **③ 결측치 (Missing Values, NA)** * **근본 원인**: 설문조사의 선택적 응답, 시스템 오류, 아직 발생하지 않은 데이터(예: 신입사원의 첫 성과평가 점수) 등 * **방치 시 문제점**: 평균, 합계 등 대부분의 통계 계산 시 오류가 발생하거나 결과가 NA로 나옴. 분석 모델의 성능을 저하시키고 예측을 불가능하게 만듦. * **해결 전략**: 결측치의 발생 원인과 데이터의 특성을 고려하여 '삭제', '대체', 또는 '결측 자체를 정보로 활용'하는 방법 중 신중하게 선택해야 합니다. <code> # === 실무 예시: 결측치 처리 === # 1. 결측치 확인: 어떤 변수에 얼마나 많은 결측치가 있는지 파악하는 것이 우선입니다. # summarise_all(~sum(is.na(.))) : 모든 컬럼에 대해 is.na() (결측치인가?)의 합계(sum)를 구함 hr_data %>% summarise_all(~sum(is.na(.))) # 2. 처리 방법 선택 # 방법 A: 삭제 (Listwise Deletion) - 결측치가 있는 행 전체를 제거. # 주의! 정보 손실이 크므로, 결측치 비율이 매우 낮고(보통 5% 미만) 데이터가 충분히 많을 때만 고려 clean_data_A <- hr_data %>% drop_na() # 방법 B: 평균/중앙값 대체 (Imputation) - 수치형 변수의 결측치를 해당 변수의 평균이나 중앙값으로 채움. # 주의! 데이터의 분포를 왜곡시킬 수 있음. 이상치가 많을 때는 평균보다 중앙값이 더 안정적. # hr_data$salary의 결측치(NA)를, 결측치를 제외한(na.rm=T) salary의 평균(mean)으로 대체 hr_data$salary[is.na(hr_data$salary)] <- mean(hr_data$salary, na.rm = TRUE) # 방법 C: 최빈값 대체 (Mode Imputation) - 범주형 변수의 결측치를 가장 자주 등장하는 값으로 채움. get_mode <- function(v) { names(which.max(table(v))) } hr_data$department[is.na(hr_data$department)] <- get_mode(hr_data$department) </code> **④ 이상치 (Outliers)** * **근본 원인**: 데이터 입력 오류(예: 나이 20세 -> 200세), 시스템 오류, 또는 실제로 존재하는 극단적인 값(예: CEO의 연봉) * **방치 시 문제점**: 평균, 표준편차 등 통계량을 심하게 왜곡함. 회귀분석 등 예측 모델의 성능을 크게 떨어뜨림. * **해결 전략**: 이상치를 무조건 '오류'로 단정하고 삭제해서는 안 됩니다. 통계적 방법으로 탐지하되, 반드시 해당 값이 비즈니스 맥락상 가능한 값인지 검토해야 합니다. <code> # === 실무 예시: IQR 기법을 이용한 이상치 처리 === # 1. 시각적 탐지: 박스플롯(Boxplot)은 이상치를 한눈에 보여주는 훌륭한 도구입니다. # 박스(상자)의 위/아래 수염을 벗어나는 점들이 이상치 후보입니다. library(ggplot2) ggplot(hr_data, aes(y = salary)) + geom_boxplot() # 2. 통계적 처리: IQR(Interquartile Range) 규칙 # Q1(25% 지점)과 Q3(75% 지점)을 찾고, 그 사이의 거리(IQR)를 계산 Q1 <- quantile(hr_data$salary, 0.25, na.rm = TRUE) Q3 <- quantile(hr_data$salary, 0.75, na.rm = TRUE) IQR_value <- Q3 - Q1 # 정상 범위를 정의 (Q1 - 1.5*IQR ~ Q3 + 1.5*IQR) lower_bound <- Q1 - 1.5 * IQR_value upper_bound <- Q3 + 1.5 * IQR_value # 정상 범위 내의 값만 남기고 필터링 clean_data <- hr_data %>% filter(salary >= lower_bound & salary <= upper_bound) # 코드 해설: # 이 방법은 데이터 분포의 중앙 50%를 기준으로, 거기서 너무 멀리 떨어진 값들을 # 이상치로 간주하는 통계적으로 널리 쓰이는 방법입니다. # 하지만 CEO의 연봉처럼 '실제 이상치'일 수 있으므로, 제거 전 반드시 현업 담당자와 확인해야 합니다. </code> ---- ===== 🛠️ HR 데이터 전처리 실전 워크플로우 ===== **Step 1: 데이터 탐색 및 구조 파악 (EDA - Exploratory Data Analysis)** ''목표'': 데이터의 전체적인 모습과 건강 상태를 진단합니다. <code> # 데이터의 구조(데이터 타입 등) 확인 str(hr_data) # >> 'str'은 structure의 약자. 각 컬럼의 이름, 타입(예: num, chr, Date), 일부 값을 보여줌 # >> 여기서 숫자여야 할 컬럼이 chr(문자)로 되어 있지는 않은지 등을 1차로 확인 # 기술 통계량 요약 summary(hr_data) # >> 숫자형 컬럼에 대해서는 최소, 최대, 평균, 중앙값 등을 보여줌 # >> 여기서 나이가 200이라거나, 만족도 점수가 5점 만점에 6점으로 나오는 등의 오류를 발견할 수 있음 # 결측치 패턴 시각화 (VIM 패키지) library(VIM) aggr(hr_data, prop = FALSE, numbers = TRUE) # >> 어떤 컬럼에 결측치가 집중되어 있는지, 컬럼 간 결측치 발생이 연관성이 있는지 등을 시각적으로 파악 </code> **Step 2: 데이터 타입 변환** ''목표'': 각 변수를 분석 목적에 맞는 올바른 타입으로 바꿔줍니다. 컴퓨터가 '숫자'와 '문자', '날짜'를 구분하게 만들어야 합니다. <code> # 문자형 -> 숫자형 (계산을 위해) hr_data$tenure_str <- "3.5" # "3.5"는 문자 hr_data$tenure_num <- as.numeric(hr_data$tenure_str) # 3.5는 숫자 # 문자형 -> 날짜형 (기간 계산을 위해) hr_data$hire_date <- as.Date(hr_data$hire_date, format = "%Y-%m-%d") # 문자형 -> 범주형(Factor) (그룹 분석을 위해) # Factor로 변환하면 R은 이 변수를 그룹화 가능한 범주로 인식하고, 분석 시 편리하게 처리해 줍니다. hr_data$department <- as.factor(hr_data$department) </code> **Step 3: 중복 데이터 처리** ''목표'': 동일한 정보가 중복으로 집계되는 것을 막아 분석의 정확성을 높입니다. <code> # 완전히 동일한 행(Row) 확인 및 제거 duplicated_rows <- hr_data[duplicated(hr_data), ] # 중복된 행을 찾아냄 hr_data_clean <- hr_data[!duplicated(hr_data), ] # 중복되지 않은 행만 남김 # 특정 ID 기준 중복 제거 (더 자주 사용됨) # 예를 들어, 한 직원의 정보가 실수로 두 번 입력된 경우, 직원 ID 기준으로 하나만 남겨야 합니다. hr_data_clean <- hr_data %>% distinct(employee_id, .keep_all = TRUE) # employee_id가 같은 행 중 첫 번째만 남기고 모두 제거 </code> **Step 4: 파생 변수 생성 (Feature Engineering)** ''목표'': 기존 변수를 조합하여 분석에 더 유용한 새로운 변수를 만듭니다. 이는 분석의 깊이를 더하는 창의적인 과정입니다. <code> # 입사일과 기준일로부터 '근속연수' 계산 hr_data <- hr_data %>% mutate( tenure_years = as.numeric(difftime(Sys.Date(), hire_date, units = "days")) / 365.25 ) # 생년월일로부터 '연령대' 생성 hr_data <- hr_data %>% mutate( age_group = case_when( age < 30 ~ "20대", age >= 30 & age < 40 ~ "30대", age >= 40 & age < 50 ~ "40대", TRUE ~ "50대 이상" ) ) </code> ---- ===== 📊 HR 데이터 전처리 체크리스트 ===== **데이터 검토 (Review) 단계** * [ ] 분석에 필요한 모든 변수(컬럼)가 포함되었는가? * [ ] 각 변수의 데이터 타입(숫자, 문자, 날짜)이 올바르게 인식되었는가? (''str()'' 확인) * [ ] 통계 요약 정보에 비상식적인 값(최소/최대값 오류 등)은 없는가? (''summary()'' 확인) **데이터 정제 (Clean) 단계** * [ ] 결측치 발생 현황을 파악하고, 처리 전략(삭제/대체 등)을 수립했는가? * [ ] 이상치 탐지 및 비즈니스적 의미를 검토하고, 처리 전략을 수립했는가? * [ ] 중복된 데이터(행 전체 또는 핵심 ID 기준)를 식별하고 제거했는가? **데이터 변환 (Transform) 단계** * [ ] 부서명, 직급명 등 텍스트로 된 범주형 변수의 표기를 통일했는가? * [ ] 다양한 형식의 날짜를 단일 표준 형식(YYYY-MM-DD)으로 변환했는가? * [ ] 분석에 필요한 파생 변수(근속연수, 연령대 등)를 생성했는가? * [ ] (필요시) 수치형 변수의 단위를 통일하거나 스케일링을 진행했는가? (예: 연봉 단위를 '원'으로 통일) **데이터 검증 (Validate) 단계** * [ ] 전처리 전/후 데이터의 행/열 개수를 비교하고, 의도치 않은 데이터 손실은 없었는가? * [ ] 전처리 후 각 변수의 결측치가 의도대로 처리되었는지 재확인했는가? * [ ] 전처리 과정을 다른 사람이 재현할 수 있도록 코드를 저장하고 주석을 달았는가? ---- ===== 💡 실무에서 자주 하는 실수와 해결책 ===== **❌ 자주 하는 실수들:** * **결측치를 무조건 평균값으로 대체하기**: 데이터의 원래 분포를 왜곡하고, 모든 결측치에 인위적으로 동일한 특성을 부여하게 됩니다. * **이상치를 무조건 '오류'로 간주하고 삭제하기**: CEO의 연봉이나 S급 인재의 성과 점수처럼, 비즈니스적으로 매우 중요하고 의미 있는 정보일 수 있습니다. * **전처리 과정을 기록하지 않기**: "그때 왜 이 값을 이렇게 바꿨더라?" 나중에 본인도 기억하지 못하며, 분석의 신뢰도와 재현성이 떨어집니다. * **원본 데이터를 덮어쓰기**: 전처리 중 실수했을 때 되돌릴 수 없게 됩니다. 항상 원본 데이터는 복사본을 만들어 사용해야 합니다. **✅ 올바른 접근법:** * **결측치**: 발생 원인을 먼저 추론합니다. (예: '퇴사일'의 결측치는 '재직자'를 의미하는 중요한 정보). 원인에 따라 처리 방법을 다르게 적용합니다. * **이상치**: 통계적으로 탐지한 후, 반드시 현업 담당자(해당 데이터의 맥락을 아는 사람)와 함께 이 값이 실제 가능한 값인지 논의하고 처리 방향을 결정합니다. * **문서화**: 어떤 논리로, 어떤 코드를 사용해 데이터를 변경했는지 주석이나 별도 문서로 상세히 기록하여 '분석의 투명성'을 확보합니다. * **버전 관리**: 원본 데이터(raw_data) -> 1차 정제 데이터(clean_data) -> 최종 분석용 데이터(analysis_data) 와 같이 단계별로 데이터를 저장하여 과정을 추적할 수 있게 합니다. ---- ===== 🔧 R을 활용한 HR 데이터 전처리 핵심 패키지 ===== **'tidyverse' 생태계 하나면 대부분 해결됩니다.** 'tidyverse'는 데이터 과학을 위해 설계된 R 패키지들의 모음이며, 아래 패키지들을 포함합니다. 일관된 문법으로 데이터 전처리를 매우 효율적으로 만들어줍니다. * **dplyr**: 데이터 조작의 맥가이버 칼. ''filter()''(필터링), ''mutate()''(변수생성), ''group_by()'' & ''summarise()''(그룹별요약) 등 데이터 전처리의 80%를 담당합니다. * **tidyr**: 지저분한 데이터를 깔끔하게 정리. ''pivot_longer()'', ''pivot_wider()''(데이터 형태 변환), ''drop_na()''(결측치제거) 등의 함수를 제공합니다. * **lubridate**: 날짜와 시간 데이터 처리의 제왕. 어떤 복잡한 날짜 형식도 쉽게 다룰 수 있게 해줍니다. * **forcats**: 범주형 변수(Factor) 처리 전문가. 범주 순서 변경, 그룹화 등 까다로운 작업을 쉽게 처리합니다. * **stringr**: 문자열 처리의 달인. ''str_replace()''(문자열 교체), ''str_detect()''(문자열 탐지) 등 텍스트 데이터 정제에 필수적입니다. ---- ===== 📈 전처리 효과 측정: 나의 노력을 증명하는 법 ===== 전처리는 눈에 잘 띄지 않는 고된 작업이지만, 그 효과를 정량적으로 보여줌으로써 나의 기여를 증명할 수 있습니다. **전처리 전후 비교 지표:** * 데이터 완성도: 전체 데이터 중 결측치가 아닌 값의 비율 * 데이터 일관성: 표준화된 범주의 수 (예: 부서명의 고유값 개수가 50개 -> 15개로 감소) * 데이터 유효성: 분석에 사용할 수 있는 유효한 행(row)의 비율 증가 <code> # 전처리 효과를 보여주는 간단한 리포트 함수 preprocessing_report <- function(before_df, after_df) { cat("====== 데이터 전처리 효과 보고서 ======\n\n") cat("1. 데이터 볼륨 변화:\n") cat(" - 전처리 전 행 수:", nrow(before_df), "개\n") cat(" - 전처리 후 행 수:", nrow(after_df), "개\n") cat(" - 유효 데이터 변화율:", round(nrow(after_df) / nrow(before_df) * 100, 1), "%\n\n") cat("2. 데이터 완성도 개선:\n") before_na <- sum(is.na(before_df)) after_na <- sum(is.na(after_df)) cat(" - 전처리 전 총 결측치:", before_na, "개\n") cat(" - 전처리 후 총 결측치:", after_na, "개\n") cat(" - 결측치 감소율:", round((before_na - after_na) / before_na * 100, 1), "%\n\n") cat("3. 데이터 일관성 향상 (부서명 예시):\n") before_dept_count <- length(unique(before_df$department)) after_dept_count <- length(unique(after_df$department)) cat(" - 전처리 전 부서명 종류:", before_dept_count, "개\n") cat(" - 전처리 후 부서명 종류:", after_dept_count, "개\n") cat("=======================================\n") } # 사용 예시 # preprocessing_report(raw_hr_data, final_hr_data) </code> ---- ===== 🔗 관련 문서 ===== * [[wiki:hr:statistics:hr_statistics_guide|HR 통계 이론 및 실무 가이드]] * [[wiki:hr:statistics:r_programming_guide|R 프로그래밍 실무 가이드]] * [[wiki:hr:statistics:start|HR 통계 전체 문서 목록]] ---- > **💡 최종 조언:** > 완벽한 전처리를 위해 너무 많은 시간을 쓰기보다, '이 분석의 목적을 달성하기 위해 최소한 어떤 정제가 필요한가?'를 먼저 생각하세요. 분석의 목적에 따라 전처리의 깊이와 범위는 달라질 수 있습니다. 작게 시작하고, 분석을 진행하며 필요한 전처리를 추가해 나가는 애자일(Agile)한 접근 방식이 더 효과적일 수 있습니다.
wiki/hr/statistics/hr_data_preprocessing.txt
· 마지막으로 수정됨:
2025/07/31 06:31
저자
syjang0803
문서 도구
문서 보기
이전 판
역링크
맨 위로