iBetter Books
수정

데이터 불러오기와 정리

분석은 데이터를 불러오는 것부터 시작합니다. CSV 파일, 엑셀 파일, 내장 데이터셋을 R로 가져오는 방법을 익히고, tidyverse로 분석하기 좋은 형태로 정리합니다.

작업 디렉토리 설정

R은 파일을 읽고 쓸 때 작업 디렉토리를 기준으로 경로를 해석합니다. 현재 작업 디렉토리를 먼저 확인합니다.

getwd()           # 현재 작업 디렉토리 확인
setwd("경로")     # 작업 디렉토리 변경

# RStudio를 사용한다면 프로젝트(.Rproj)를 만들면
# 자동으로 프로젝트 폴더가 작업 디렉토리가 됩니다.
# 프로젝트를 쓰는 것을 권장합니다.

CSV 파일 불러오기

가장 많이 쓰는 형식입니다. readr 패키지의 read_csv()는 base R의 read.csv()보다 빠르고, 결과가 tibble로 반환됩니다.

library(readr)

# CSV 불러오기
df <- read_csv("data/students.csv")

# 인코딩 지정 — 한글이 깨질 때
df <- read_csv("data/students.csv", locale = locale(encoding = "EUC-KR"))

# 특정 열 자료형 지정
df <- read_csv("data/students.csv",
               col_types = cols(
                 id    = col_character(),
                 score = col_double(),
                 date  = col_date(format = "%Y-%m-%d")
               ))

# 확인
glimpse(df)   # 열 이름, 자료형, 미리보기
head(df)      # 처음 6행

read_csv()read.csv()의 차이를 정리하면 이렇습니다.

비교 항목 read.csv() (base R) read_csv() (readr)
반환 형식 data.frame tibble
속도 보통 빠름
문자열 처리 factor 변환 문자 유지
열 이름 공백을 . 으로 변환 그대로 유지

엑셀 파일 불러오기

library(readxl)

# 기본 — 첫 번째 시트를 읽습니다
df <- read_excel("data/sales.xlsx")

# 시트 지정
df <- read_excel("data/sales.xlsx", sheet = "2024년")
df <- read_excel("data/sales.xlsx", sheet = 2)  # 두 번째 시트

# 범위 지정
df <- read_excel("data/sales.xlsx", range = "A1:D50")

# 헤더가 없는 경우
df <- read_excel("data/sales.xlsx", col_names = FALSE)

# 시트 목록 확인
excel_sheets("data/sales.xlsx")

R 내장 데이터셋

R에는 연습용 데이터셋이 기본으로 포함되어 있습니다. 파일 없이 바로 쓸 수 있어서 이 책에서도 자주 활용합니다.

# 내장 데이터셋 목록
data()

# 자주 쓰는 데이터셋
data(iris)      # 붓꽃 데이터 (150행, 5열)
data(mtcars)    # 자동차 데이터 (32행, 11열)
data(airquality) # 대기질 데이터 (153행, 6열)

head(iris)
str(mtcars)

데이터 탐색

데이터를 불러왔다면 분석 전에 반드시 데이터 구조를 파악합니다.

library(tidyverse)

df <- iris

# 기본 탐색
dim(df)          # 행과 열 수: 150 5
nrow(df)         # 행 수
ncol(df)         # 열 수
names(df)        # 열 이름
str(df)          # 구조 요약
glimpse(df)      # tidyverse 스타일 구조 요약
summary(df)      # 각 열의 기술통계량

# 결측값 확인
sum(is.na(df))                    # 전체 결측값 수
colSums(is.na(df))                # 열별 결측값 수
df %>% summarise(across(everything(), ~sum(is.na(.))))

filter — 행 조건 필터링

library(dplyr)

df <- mtcars

# 단일 조건
df %>% filter(cyl == 6)           # 실린더가 6인 차
df %>% filter(mpg > 20)           # 연비가 20 초과

# 복합 조건 — & (AND), | (OR)
df %>% filter(cyl == 4 & mpg > 25)
df %>% filter(cyl == 4, mpg > 25) # 쉼표도 AND로 동작합니다
df %>% filter(cyl == 4 | cyl == 6)
df %>% filter(cyl %in% c(4, 6))   # %in%으로 여러 값 비교

# 결측값 필터
df %>% filter(!is.na(hp))         # hp가 결측이 아닌 행
df %>% filter(is.na(hp))          # hp가 결측인 행

mutate — 열 추가와 변환

df <- mtcars

# 새 열 추가
df <- df %>%
  mutate(
    kpl = mpg * 0.4251,           # 마일/갤런 → 킬로미터/리터 변환
    weight_kg = wt * 453.6,       # 파운드 → kg
    high_power = hp > 150         # 고출력 여부 (논리값)
  )

# 기존 열 변환
df <- df %>%
  mutate(cyl = as.factor(cyl))    # 숫자를 범주형으로 변환

# case_when — 여러 조건 분기 (ifelse의 확장판)
df <- df %>%
  mutate(
    mpg_grade = case_when(
      mpg >= 25 ~ "상",
      mpg >= 18 ~ "중",
      TRUE      ~ "하"            # 나머지 모두
    )
  )

select — 열 선택

df <- iris

# 열 이름으로 선택
df %>% select(Sepal.Length, Species)

# 열 제외
df %>% select(-Species)

# 범위 선택
df %>% select(Sepal.Length:Petal.Length)

# 헬퍼 함수
df %>% select(starts_with("Sepal"))   # "Sepal"로 시작하는 열
df %>% select(ends_with("Width"))     # "Width"로 끝나는 열
df %>% select(contains("Length"))     # "Length"를 포함하는 열
df %>% select(where(is.numeric))      # 숫자형 열만

arrange — 정렬

df <- mtcars

df %>% arrange(mpg)              # 오름차순
df %>% arrange(desc(mpg))        # 내림차순
df %>% arrange(cyl, desc(mpg))   # cyl 오름차순, 같은 cyl 안에서 mpg 내림차순

group_by + summarise — 그룹별 집계

분석에서 가장 자주 쓰는 조합입니다.

df <- iris

df %>%
  group_by(Species) %>%
  summarise(
    n           = n(),
    mean_length = mean(Sepal.Length),
    sd_length   = sd(Sepal.Length),
    min_length  = min(Sepal.Length),
    max_length  = max(Sepal.Length)
  )

결과를 보면 종별로 꽃받침 길이가 다르다는 것을 바로 알 수 있습니다.

결측값 처리

현실 데이터에는 결측값이 거의 항상 있습니다.

# 예제 데이터 생성
df <- data.frame(
  name  = c("A", "B", "C", "D", "E"),
  score = c(85, NA, 78, 95, NA),
  age   = c(20, 22, NA, 20, 23)
)

# 결측값 제거 — 결측값이 있는 행 전체 삭제
df_clean <- df %>% drop_na()         # 모든 열 기준
df_clean <- df %>% drop_na(score)    # score 열만 기준

# 결측값 대체
df_filled <- df %>%
  mutate(
    score = replace_na(score, mean(score, na.rm = TRUE)),  # 평균으로 대체
    age   = replace_na(age, 21)                            # 특정값으로 대체
  )

데이터 형 변환

df <- data.frame(
  id    = c("001", "002", "003"),
  score = c("85", "92", "78"),
  date  = c("2024-01-15", "2024-01-16", "2024-01-17")
)

df <- df %>%
  mutate(
    score = as.numeric(score),          # 문자 → 숫자
    date  = as.Date(date),              # 문자 → 날짜
    id    = as.factor(id)               # 문자 → 범주형
  )

str(df)

전체 흐름 예시

데이터를 불러오고 정리하는 전형적인 흐름을 한눈에 봅니다.

library(tidyverse)

# 1. 데이터 불러오기
raw <- read_csv("data/exam_results.csv")

# 2. 탐색
glimpse(raw)
summary(raw)
colSums(is.na(raw))

# 3. 정리
clean <- raw %>%
  filter(!is.na(score)) %>%              # 점수 없는 행 제거
  mutate(
    score   = as.numeric(score),
    grade   = case_when(
      score >= 90 ~ "A",
      score >= 80 ~ "B",
      score >= 70 ~ "C",
      TRUE        ~ "D"
    )
  ) %>%
  select(id, name, score, grade) %>%    # 필요한 열만 선택
  arrange(desc(score))                  # 점수 높은 순 정렬

# 4. 결과 저장
write_csv(clean, "data/exam_results_clean.csv")

데이터 정리가 끝났다면 이제 숫자로 데이터를 요약하는 기술통계 단계로 넘어갑니다.