Cleaning

R for Data Science (2e) by Wickham, Çetinkaya-Rundel & Grolemund

Published

September 4, 2026

유용한 함수들

  • select(), mutate(), filter(), rename(): 기본 tidyverse verbs
  • rowSums(), rowMeans(): composite 변수들의 합 또는 평균을 구함
  • factor(): 카테고리 변수의 변환
  • if_else(), case_when(): 조건에 따라 값을 바꿈

앞서 다운받은 데이터: altruism.csv 파일 링크

library(tidyverse)

# import data
helping <- read_csv("data/altruism.csv")
helping
# A tibble: 120 × 12
     id pho_1 pho_2 pho_3   sex   age emp_q20 emp_q22 emp_q23 emp_q24 emp_q25
  <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>
1     1    95    95    95     1  2004      80      NA      80      80      70
2     2    58    62    NA     0  2003      62      58      59      57      56
3     3   100    50    50    NA  2003      90      51      51      51      52
4     4    77    77    64     1  2004      66      72      88      82      67
5     5    NA    NA    NA    NA    NA      NA      NA      NA      NA      NA
6     6   100    75   100     0  2004     100      60      70      55      70
# ℹ 114 more rows
# ℹ 1 more variable: emp_q26 <dbl>

변수명 수정

rename()

helping |>
  rename(ph1 = pho_1, ph2 = pho_2, ph3 = pho_3)
# A tibble: 120 × 12
     id   ph1   ph2   ph3   sex   age emp_q20 emp_q22 emp_q23 emp_q24 emp_q25
  <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>
1     1    95    95    95     1  2004      80      NA      80      80      70
2     2    58    62    NA     0  2003      62      58      59      57      56
3     3   100    50    50    NA  2003      90      51      51      51      52
4     4    77    77    64     1  2004      66      72      88      82      67
5     5    NA    NA    NA    NA    NA      NA      NA      NA      NA      NA
6     6   100    75   100     0  2004     100      60      70      55      70
# ℹ 114 more rows
# ℹ 1 more variable: emp_q26 <dbl>

변형 후에는 꼭 변수에 assign!

helping <- # 원래 데이터에 overwrite
  helping |>
  rename(ph1 = pho_1, ph2 = pho_2, ph3 = pho_3)

helping
# A tibble: 120 × 12
     id   ph1   ph2   ph3   sex   age emp_q20 emp_q22 emp_q23 emp_q24 emp_q25
  <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>
1     1    95    95    95     1  2004      80      NA      80      80      70
2     2    58    62    NA     0  2003      62      58      59      57      56
3     3   100    50    50    NA  2003      90      51      51      51      52
4     4    77    77    64     1  2004      66      72      88      82      67
5     5    NA    NA    NA    NA    NA      NA      NA      NA      NA      NA
6     6   100    75   100     0  2004     100      60      70      55      70
# ℹ 114 more rows
# ℹ 1 more variable: emp_q26 <dbl>

문항을 더할 때

rowSums(na.rm = TRUE) 함수를 이용하는 것이 직접 덧셈보다 더 적절함

ph1, ph2, ph3 세 문항을 더하려면,

# 먼저 문항을 선택/확인
helping |>
  select(ph1:ph3) # position!
# A tibble: 120 × 3
    ph1   ph2   ph3
  <dbl> <dbl> <dbl>
1    95    95    95
2    58    62    NA
3   100    50    50
4    77    77    64
5    NA    NA    NA
6   100    75   100
# ℹ 114 more rows

mutate() 안에서 pick()으로 열을 골라 rowSums()에 넘김 (dplyr 1.1부터)

helping <- helping |>
  mutate(phone = rowSums(pick(ph1:ph3), na.rm = TRUE))

helping |> select(id, ph1:ph3, phone)
# A tibble: 120 × 5
     id   ph1   ph2   ph3 phone
  <dbl> <dbl> <dbl> <dbl> <dbl>
1     1    95    95    95   285
2     2    58    62    NA   120
3     3   100    50    50   200
4     4    77    77    64   218
5     5    NA    NA    NA     0
6     6   100    75   100   275
# ℹ 114 more rows
왜 그냥 더하면 안 되는가
helping |>
  mutate(phone_sum = ph1 + ph2 + ph3) |>
  select(id, ph1:ph3, phone, phone_sum)
# A tibble: 120 × 6
     id   ph1   ph2   ph3 phone phone_sum
  <dbl> <dbl> <dbl> <dbl> <dbl>     <dbl>
1     1    95    95    95   285       285
2     2    58    62    NA   120        NA
3     3   100    50    50   200       200
4     4    77    77    64   218       218
5     5    NA    NA    NA     0        NA
6     6   100    75   100   275       275
# ℹ 114 more rows

+로 더하면 문항 하나만 결측이어도 합계 전체가 NA가 되어버림 (2행).
rowSums(na.rm = TRUE)는 응답한 문항만으로 합을 구함.
단, 모두 결측인 5행이 NA가 아니라 0이 되는 점은 주의해야 함

문항을 평균낼 때

rowMeans(na.rm = TRUE) 함수를 이용하는 것이 적절함

# 먼저, 평균을 낼 문항을 선택/확인
helping |>
  select(emp_q20, emp_q22:emp_q26) # ":" operator와 "," 섞어써도 무방
# A tibble: 120 × 6
  emp_q20 emp_q22 emp_q23 emp_q24 emp_q25 emp_q26
    <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>
1      80      NA      80      80      70      70
2      62      58      59      57      56      59
3      90      51      51      51      52     100
4      66      72      88      82      67      69
5      NA      NA      NA      NA      NA      NA
6     100      60      70      55      70      90
# ℹ 114 more rows
helping <- helping |>
  mutate(persp = rowMeans(pick(emp_q20, emp_q22:emp_q26), na.rm = TRUE))

helping |> select(id, emp_q20, emp_q22:emp_q26, persp)
# A tibble: 120 × 8
     id emp_q20 emp_q22 emp_q23 emp_q24 emp_q25 emp_q26 persp
  <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl> <dbl>
1     1      80      NA      80      80      70      70  76  
2     2      62      58      59      57      56      59  58.5
3     3      90      51      51      51      52     100  65.8
4     4      66      72      88      82      67      69  74  
5     5      NA      NA      NA      NA      NA      NA NaN  
6     6     100      60      70      55      70      90  74.2
# ℹ 114 more rows

예전에는 pick()이 없어서 다음과 같이 썼음

helping["persp"] <- helping |>
  select(emp_q20, emp_q22:emp_q26) |>
  rowMeans(na.rm = TRUE)

한 행씩 처리하는 row-wise operation을 쓸 수도 있으나 느림

helping |>
  rowwise() |>
  mutate(persp = mean(c_across(c(emp_q20, emp_q22:emp_q26)), na.rm = TRUE)) |>
  ungroup()

참고: column-wise operation

Important

문항 몇 개에만 응답한 사람의 평균을 그대로 쓰는 것은 위험함.
보통 절반 이상 응답한 경우에만 평균을 인정하는 식의 기준을 세움

helping |>
  mutate(
    n_answered = rowSums(!is.na(pick(emp_q20, emp_q22:emp_q26))),
    persp = if_else(n_answered >= 3, persp, NA)
  ) |>
  count(n_answered)
# A tibble: 3 × 2
  n_answered     n
       <dbl> <int>
1          0     1
2          5     1
3          6   118

표준화 및 중심화

  • 표준화(standardize): scale(x) 함수를 이용
  • 중심화(center): scale(x, scale = FALSE) 함수를 이용
helping |>
  mutate(
    phone_z = as.vector(scale(phone)), # scale()은 matrix로 반환; vector 변환 필요
    persp_z = as.vector(scale(persp))
  ) |>
  select(id, phone, persp, phone_z, persp_z)
# A tibble: 120 × 5
     id phone persp  phone_z   persp_z
  <dbl> <dbl> <dbl>    <dbl>     <dbl>
1     1   285  76    1.05      0.104  
2     2   120  58.5 -1.00     -0.981  
3     3   200  65.8 -0.00643  -0.527  
4     4   218  74    0.218    -0.0200 
5     5     0 NaN   -2.50    NaN      
6     6   275  74.2  0.928    -0.00964
# ℹ 114 more rows

across() 함수를 이용하면 여러 변수를 한 번에 변환할 수 있음

helping |>
  mutate(across(
    .cols = c(phone, persp),
    .fns = \(x) as.vector(scale(x)), # \(x): 즉석에서 정의하는 함수 (R 4.1부터)
    .names = "{.col}_z" # 변수명을 일괄 지정: 변수명 + "_z"
  )) |>
  select(id, phone, persp, phone_z, persp_z)
# A tibble: 120 × 5
     id phone persp  phone_z   persp_z
  <dbl> <dbl> <dbl>    <dbl>     <dbl>
1     1   285  76    1.05      0.104  
2     2   120  58.5 -1.00     -0.981  
3     3   200  65.8 -0.00643  -0.527  
4     4   218  74    0.218    -0.0200 
5     5     0 NaN   -2.50    NaN      
6     6   275  74.2  0.928    -0.00964
# ℹ 114 more rows

카테고리 변수 및 연산

카테고리 변수는 R의 factor 타입으로 바꾸어 분석하는 것이 유리함

간단한 연산은 직접 계산

survey_year <- 2023 # 이 자료를 수집한 해

helping |>
  mutate(
    # factor 타입의 변수로 변환
    sex = factor(sex, levels = c(0, 1), labels = c("male", "female")),
    age = survey_year - age # 출생년도로부터 나이 계산
  ) |>
  select(id, sex, age)
# A tibble: 120 × 3
     id sex      age
  <dbl> <fct>  <dbl>
1     1 female    19
2     2 male      20
3     3 <NA>      20
4     4 female    19
5     5 <NA>      NA
6     6 male      19
# ℹ 114 more rows
Tip

factor의 레벨 순서를 다루는 일이 잦다면 tidyverse의 forcats 패키지가 편리함

  • fct_relevel(): 레벨 순서를 직접 지정 (회귀분석의 기준 집단을 정할 때)
  • fct_infreq(): 빈도순으로 정렬 (그래프를 그릴 때)
  • fct_lump_min(): 빈도가 낮은 레벨을 “Other”로 묶음
  • fct_recode(): 레벨의 이름을 바꿈

행의 삭제

filter()를 활용

예를 들어, 5번째 행을 지우려면

helping |>
  filter(id != 5) # !=는 not equal, !(id == 5)와 같음
# A tibble: 119 × 14
     id   ph1   ph2   ph3   sex   age emp_q20 emp_q22 emp_q23 emp_q24 emp_q25
  <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>
1     1    95    95    95     1  2004      80      NA      80      80      70
2     2    58    62    NA     0  2003      62      58      59      57      56
3     3   100    50    50    NA  2003      90      51      51      51      52
4     4    77    77    64     1  2004      66      72      88      82      67
5     6   100    75   100     0  2004     100      60      70      55      70
6     7    77    94    86     1  2004      91      93      85      91      73
# ℹ 113 more rows
# ℹ 3 more variables: emp_q26 <dbl>, phone <dbl>, persp <dbl>
# 다시 helping에 assign 해야 수정됨!

여러 행을 지우려면?
%in% 응용

helping |>
  filter(!id %in% c(1, 3, 5)) # !는 not의 의미
# A tibble: 117 × 14
     id   ph1   ph2   ph3   sex   age emp_q20 emp_q22 emp_q23 emp_q24 emp_q25
  <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>
1     2    58    62    NA     0  2003      62      58      59      57      56
2     4    77    77    64     1  2004      66      72      88      82      67
3     6   100    75   100     0  2004     100      60      70      55      70
4     7    77    94    86     1  2004      91      93      85      91      73
5     8    90    68    20     0  2004      67      66      31      67      63
6     9   100    79    77     0  2003      61      51      30      51      51
# ℹ 111 more rows
# ℹ 3 more variables: emp_q26 <dbl>, phone <dbl>, persp <dbl>

열의 삭제

select() 활용

emp_q23, emp_q25 두 열을 삭제

helping |>
  select(-emp_q23, -emp_q25)
# A tibble: 120 × 12
     id   ph1   ph2   ph3   sex   age emp_q20 emp_q22 emp_q24 emp_q26 phone
  <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>   <dbl>   <dbl>   <dbl>   <dbl> <dbl>
1     1    95    95    95     1  2004      80      NA      80      70   285
2     2    58    62    NA     0  2003      62      58      57      59   120
3     3   100    50    50    NA  2003      90      51      51     100   200
4     4    77    77    64     1  2004      66      72      82      69   218
5     5    NA    NA    NA    NA    NA      NA      NA      NA      NA     0
6     6   100    75   100     0  2004     100      60      55      90   275
# ℹ 114 more rows
# ℹ 1 more variable: persp <dbl>

emp_q23부터 emp_q26 열을 삭제 (위치의 의미로)

helping |>
  select(-(emp_q23:emp_q26)) # () 꼭 필요
# A tibble: 120 × 10
     id   ph1   ph2   ph3   sex   age emp_q20 emp_q22 phone persp
  <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>   <dbl>   <dbl> <dbl> <dbl>
1     1    95    95    95     1  2004      80      NA   285  76  
2     2    58    62    NA     0  2003      62      58   120  58.5
3     3   100    50    50    NA  2003      90      51   200  65.8
4     4    77    77    64     1  2004      66      72   218  74  
5     5    NA    NA    NA    NA    NA      NA      NA     0 NaN  
6     6   100    75   100     0  2004     100      60   275  74.2
# ℹ 114 more rows

이상치 발견

Outliers을 찾는 방법은 다양하고 복잡한 테크닉을 요하기도 하는데, 앞으로 점차 익히게 될 것임

가장 먼저 할 일은 변수별 범위를 훑어보는 것

helping <- read_csv("data/altruism.csv")

helping |>
  select(sex, age, emp_q20) |>
  summary()
      sex             age           emp_q20      
 Min.   :0.000   Min.   :  203   Min.   :  0.00  
 1st Qu.:0.000   1st Qu.: 2003   1st Qu.: 62.50  
 Median :1.000   Median : 2003   Median : 80.00  
 Mean   :0.678   Mean   : 2100   Mean   : 78.24  
 3rd Qu.:1.000   3rd Qu.: 2004   3rd Qu.: 91.50  
 Max.   :1.000   Max.   :20004   Max.   :100.00  
 NAs    :2       NAs    :3       NAs    :1       

예를 들어, age에 잘못 기입한 경우가 있는데

helping |>
  ggplot(aes(x = age)) +
  geom_histogram(binwidth = 100)

age는 출생년도를 물어봤으나 다른 답을 한 경우들이 있음
값은 2002 ~ 2004 사이가 정상이므로 filter()를 써서 확인해 볼 수 있음

helping |>
  filter(age < 2002 | age > 2004)
# A tibble: 7 × 12
     id pho_1 pho_2 pho_3   sex   age emp_q20 emp_q22 emp_q23 emp_q24 emp_q25
  <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>
1    11    65    94    56     1   203      62      86      58      47      62
2    21    17    10    17     1 20004       0       6       1       0       4
3    43    90    88    30     1   507     100      78      62     100      78
4    52   100    82    85     1   723      87      83      89     100      88
5    59    76    86    64     0   709     100      93      67      94      79
6   108    75   100    85     1  2005     100     100     100     100      97
7   118    92    76    94     0  1108      55      51      51      60      53
# ℹ 1 more variable: emp_q26 <dbl>

값을 수정

# 이상치에 대한 id를 우선 추출
ids_anomaly <- helping |>
  filter(age < 2002 | age > 2004) |>
  pull(id) # vector로 반환

ids_anomaly
[1]  11  21  43  52  59 108 118
# if_else(조건, 참일 때 값, 거짓일 때 값)
helping |>
  mutate(
    age = if_else(age > 2004, 2004, age),
    age = if_else(age < 2002, NA, age)
  ) |>
  filter(id %in% ids_anomaly) |>
  select(id, age)
# A tibble: 7 × 2
     id   age
  <dbl> <dbl>
1    11    NA
2    21  2004
3    43    NA
4    52    NA
5    59    NA
6   108  2004
7   118    NA

경우가 여럿이면 case_when()이 더 읽기 쉬움

helping |>
  mutate(
    age = case_when(
      age > 2004 & age < 2010 ~ 2004, # 2005, 2006 같은 사소한 오기입
      age < 2002 | age >= 2010 ~ NA, # 203, 20004처럼 명백한 오기입
      .default = age # 나머지는 그대로
    )
  ) |>
  filter(id %in% ids_anomaly) |>
  select(id, age)
# A tibble: 7 × 2
     id   age
  <dbl> <dbl>
1    11    NA
2    21    NA
3    43    NA
4    52    NA
5    59    NA
6   108  2004
7   118    NA
값을 하나씩 직접 입력하는 방식
helping[helping$id %in% ids_anomaly, "age"]
# A tibble: 7 × 1
    age
  <dbl>
1   203
2 20004
3   507
4   723
5   709
6  2005
7  1108
helping[helping$id %in% ids_anomaly, "age"] <- c(NA, 2004, NA, NA, NA, 2005, NA)

helping |>
  filter(id %in% ids_anomaly) |>
  select(id, age)
# A tibble: 7 × 2
     id   age
  <dbl> <dbl>
1    11    NA
2    21  2004
3    43    NA
4    52    NA
5    59    NA
6   108  2005
7   118    NA

if_else()가 조건을 코드에 남겨 재현 가능한 반면, 이 방식은 순서가 바뀌면 엉뚱한 값이 들어가므로 조심할 것

샘플 R script

지금까지의 작업을 하나의 스크립트로 정리하면

library(tidyverse)

survey_year <- 2023

# import data
helping <- read_csv("data/altruism.csv")

# rename
helping <- helping |>
  rename(ph1 = pho_1, ph2 = pho_2, ph3 = pho_3)

# delete responses
helping <- helping |>
  filter(id != 5)

# scoring
helping <- helping |>
  mutate(
    phone = rowMeans(pick(ph1:ph3), na.rm = TRUE),
    persp = rowMeans(pick(emp_q20, emp_q22, emp_q24:emp_q26), na.rm = TRUE)
  )

# substitute anomalies
helping <- helping |>
  mutate(
    age = case_when(
      age > 2004 & age < 2010 ~ 2004,
      age < 2002 | age >= 2010 ~ NA,
      .default = age
    )
  )

# factors and etc.
helping <- helping |>
  mutate(
    sex = factor(sex, levels = c(0, 1), labels = c("male", "female")),
    age = survey_year - age
  )

# select variables
helping <- helping |>
  select(id, sex, age, phone, persp)

정리된 파일로 분석 시작!

helping
# A tibble: 119 × 5
     id sex      age phone persp
  <dbl> <fct>  <dbl> <dbl> <dbl>
1     1 female    19  95    75  
2     2 male      20  60    58.4
3     3 <NA>      20  66.7  68.8
4     4 female    19  72.7  71.2
5     6 male      19  91.7  75  
6     7 female    19  85.7  82.6
# ℹ 113 more rows
Tip

정리한 데이터는 타입이 보존되는 형식으로 저장해두면 다음에 스크립트를 다시 돌리지 않아도 됨

write_rds(helping, "data/helping_clean.rds")

다만 원본 파일은 절대 덮어쓰지 말 것. 가공 과정은 언제나 스크립트로 재현되어야 함