유용한 함수들
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
문항 몇 개에만 응답한 사람의 평균을 그대로 쓰는 것은 위험함.
보통 절반 이상 응답한 경우에만 평균을 인정하는 식의 기준을 세움
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
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)
정리된 파일로 분석 시작!
# 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
정리한 데이터는 타입이 보존되는 형식으로 저장해두면 다음에 스크립트를 다시 돌리지 않아도 됨
write_rds (helping, "data/helping_clean.rds" )
다만 원본 파일은 절대 덮어쓰지 말 것 . 가공 과정은 언제나 스크립트로 재현되어야 함