12.1 forcats로하는팩터형 들어가기

R에서 팩터형은 범주형 변수에 사용되는데, 범주형변수란 가질 수 있는 값이 미리 고정되고 또 알려진 변수를 말한다.팩터형은 문자형 벡터를 알파벳순이 아닌 순서로 표시하고 싶을 때도 이용할 수 있다.

12.1.1 준비하기

팩터형을 다루기 위해 forcats 패키지를 사용하려는데,이 패키지에는 범주형 변수(팩터형의 다른말)에 적용하는 도구들이 있다 forcats는 tidyverse 핵심 구성원에 포함되어 있지 않기 때문에 명시적으로 로드해야 한다. #아래와 같이 따로 실행

library(tidyverse)
## -- Attaching packages --------------------------------------- tidyverse 1.3.0 --
## √ ggplot2 3.3.2     √ purrr   0.3.4
## √ tibble  3.0.4     √ dplyr   1.0.2
## √ tidyr   1.1.2     √ stringr 1.4.0
## √ readr   1.4.0     √ forcats 0.5.0
## Warning: package 'tibble' was built under R version 4.0.3
## -- Conflicts ------------------------------------------ tidyverse_conflicts() --
## x dplyr::filter() masks stats::filter()
## x dplyr::lag()    masks stats::lag()
library(forcats)

12.2 팩터형 생성하기

월을 기록한 변수가 있다고 가정하자

x1 <- c("Dec","Apr","Jan","Mar")
  1. 12개의 달 외의 오타를 입력했을 때,경고가 발생되지 않아 실수를 알아채기 어렵다.
x2 <- c("Dec","Apr","Jam","Mar")
  1. 유용한 순서로 정렬되지 않는다.
sort(x1)
## [1] "Apr" "Dec" "Jan" "Mar"

팩터형을 이용하면 이러한 문제를 모두 해결할 수 있다.팩터형을 생성하기 위해서는 유효한 레벨들의 리스트를 생성하는 것부터 시작해야 한다.

month_levels <- c("Jan","Fed","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec")

이제 팩터형을 사용할 수 있다. 그리고 이 레벨 집합에 포함되지 않는 값은 조용히 NA로 변환된다.

y2 <- factor(x2, levels = month_levels) 
y2
## [1] Dec  Apr  <NA> Mar 
## Levels: Jan Fed Mar Apr May Jun Jul Aug Sep Oct Nov Dec

경고가 발생되길 원하는 경우에는 readr::parse_factor()를 사용하면 된다.

y2 <- parse_factor(x2, levels = month_levels)
## Warning: 1 parsing failure.
## row col           expected actual
##   3  -- value in level set    Jam

앞의 levels를 생략하면 데이터로부터 알파벳 순서로 취할 것 이다.

factor(x1)
## [1] Dec Apr Jan Mar
## Levels: Apr Dec Jan Mar

종종 레벨의 순서가 데이터에서 처음으로 등장하는 순서와 일치되길 원할 수 있다. 팩터형 생성 시 레벨을 unique(x)로 설정하거나 사후적으로 fct_inoder()를 사용하면 된다.

f1 <- factor(x1, levels= unique(x1))
f1
## [1] Dec Apr Jan Mar
## Levels: Dec Apr Jan Mar
f2 <- x1 %>% factor() %>% fct_inorder()
f2
## [1] Dec Apr Jan Mar
## Levels: Dec Apr Jan Mar

12.3 종합사회조사

시카고 대학 독립연구기관인 NORC 에서 장기간 수행한 미국내 설문조사(General Social Survey)의 샘플 데이터이다.수천개의 문항이 있는데, 이중에서도 팩터형과 작업할 때 자주 발생하는 문제를 보여주는 것들을 gss_cat 으로 선택했다.

gss_cat
## # A tibble: 21,483 x 9
##     year marital     age race  rincome    partyid     relig     denom    tvhours
##    <int> <fct>     <int> <fct> <fct>      <fct>       <fct>     <fct>      <int>
##  1  2000 Never ma~    26 White $8000 to ~ Ind,near r~ Protesta~ Souther~      12
##  2  2000 Divorced     48 White $8000 to ~ Not str re~ Protesta~ Baptist~      NA
##  3  2000 Widowed      67 White Not appli~ Independent Protesta~ No deno~       2
##  4  2000 Never ma~    39 White Not appli~ Ind,near r~ Orthodox~ Not app~       4
##  5  2000 Divorced     25 White Not appli~ Not str de~ None      Not app~       1
##  6  2000 Married      25 White $20000 - ~ Strong dem~ Protesta~ Souther~      NA
##  7  2000 Never ma~    36 White $25000 or~ Not str re~ Christian Not app~       3
##  8  2000 Divorced     44 White $7000 to ~ Ind,near d~ Protesta~ Luthera~      NA
##  9  2000 Married      44 White $25000 or~ Not str de~ Protesta~ Other          0
## 10  2000 Married      47 White $25000 or~ Strong rep~ Protesta~ Souther~       3
## # ... with 21,473 more rows

팩터형이 티블로 저장되면 해당하는 레벨들을 쉽게 볼 수 없게 된다.볼 수 있는 한가지 방법은 count()이다.

gss_cat %>%
  count(race)
## # A tibble: 3 x 2
##   race      n
##   <fct> <int>
## 1 Other  1959
## 2 Black  3129
## 3 White 16395

또는 그래프로도 볼 수 있다.

ggplot(gss_cat, aes(race)) +
  geom_bar()

기본적으로 ggplot2는 값이 없는 레벨을 제거 한다.다음과 같이 강제적으로 표시하도록 할 수 있다.

ggplot(gss_cat, aes(race)) +
  geom_bar() +
  scale_x_discrete(drop = FALSE)

이 레벨들은 유효하지만 이 데이터셋에서 나타나지 않는 값을 나타낸다. dplyr 에는 drop 옵션이 아직 없지만 향후 제공될 예정이다. 펙터형으로 작업할 때 자주하는 작업 두가지는 레벨의 순서와 값을 변경하는 것이다.

12.4 팩터 순서 수정하기

시각화에서 팩터 레벨의 순서를 변경하는 것이 유용할 때가 종종 있다. 예를 들어 종교에 따른 하루 tv 시청시간의 평균을 탐색하고 싶다고 해보자.

relig_summary <- gss_cat %>%
  group_by(relig) %>%
  summarize(
    age = mean(age, na.rm = TRUE),
    tvhours = mean(tvhours, na.rm = TRUE),
  )
## `summarise()` ungrouping output (override with `.groups` argument)
ggplot(relig_summary, aes(tvhours, relig)) + geom_point()

전반적인 패턴이 없기 때문에 이 플롯을 해석하기는 어렵다. fct_reorder()를 사용하여 relig의 레벨을 재정렬 해서 개선할 수 있다. fct_reorder()에는 세개의 인수가 있다.

*f:레벨을 수정할 팩터.

*x:레벨을 재정렬하기 위해 사용할 수치형 벡터.

*선택적으로 fun:f의 각 값에 대해 x값이 여러개가 있을때 사용 할 함수. 기본값은 median 이다.

ggplot(relig_summary, aes(tvhours, fct_reorder(relig, tvhours))) +
  geom_point()

종교를 재배열하면 ’모름(Don’t know)’ 범주의 사람들이 TV를 훨씬 많이 보고, 힌두교와 다른 동양 종교 사람들이 훨씬 덜 본다는 것을 쉽게 알 수 있다. 좀 더 복잡한 변환을 해야 된다면 aes() 내부보다는 별도의 mutate() 단계에서 변환할 것을 추천한다. 예를 들어 앞의 플롯을 다음과 같이 다시 작성할 수 있다.

relig_summary %>%
  mutate(relig = fct_reorder(relig, tvhours)) %>%
  ggplot(aes(tvhours, relig)) +
    geom_point()

보고된 소긋 레벨에 따라 평균 나이가 어떻게 변화하는지를 보여주는 플롯을 유사하게 만긓어보면 어떨까?

rincome_summary <- gss_cat %>%
  group_by(rincome) %>%
  summarize(
    age = mean(age, na.rm = TRUE),
    tvhours = mean(tvhours, na.rm = TRUE),
  )
## `summarise()` ungrouping output (override with `.groups` argument)
ggplot(rincome_summary, aes(age, fct_reorder(rincome, age))) + geom_point()

여기에서 레벨을 임의로 재정렬하는 것은 좋은 생각이 아니다. rincome은 이미 원칙 있게 정렬되어 있어서 건드리지 말아야 하기 때문이다. fct_reorder()는 레벨이 임의적으로 정렬된 팩터의 경우에만 사용해야 한다. ’해당없음(Not applicable)’을 다른 특별한 레벨들과 함께 앞으로 가져오는 것이 좋다. fct_relevel()을 사용하면 된다. 팩터형 f와 앞으로 옮기고자 하는 레벨을 입력하면 된다.

ggplot(rincome_summary, aes(age, fct_relevel(rincome, "Not applicable"))) +
  geom_point()

재정렬이 유용한 경우가 있는데, 플롯의 선에 색상을 입힐 때이다. fct_reorder2()는 가장 큰x값과 연관된 y값으로 팩터형을 재정렬한다. 선 색상은 범례와 정렬되므로 이렇게 하면 플롯 읽기가 쉬워진다.

by_age <- gss_cat %>%
  filter(!is.na(age)) %>%
  count(age, marital) %>%
  group_by(age) %>%
  mutate(prop = n / sum(n))

ggplot(by_age, aes(age, prop, colour = marital)) +
  geom_line(na.rm = TRUE)

ggplot(by_age, aes(age, prop, colour = fct_reorder2(marital, age, prop))) +
  geom_line() +
  labs(colour = "marital")

fct_infreq()를 사용해 빈도 오름차순으로 레벨을 정렬할 수 있다. 추가 변수가 필요 없어서 재정렬 방법중 가장 간단한 유형이다. fct_rev()와 조합하여 사용할 수 있다.

gss_cat %>%
  mutate(marital = marital %>% fct_infreq() %>% fct_rev()) %>% ggplot(aes(marital)) +
  geom_bar()

12.5 팩터 레벨 수정하기

레벨의 순서 변경보다 값을 변경하는 게 강력한 방법이다. 화면출력시 라벨을 명확히 할 수 있고, 레벨을 병합하여 상위 레벨 시각화를 할 수 있다. fct_recode가 일반적이며 각레벨값을 변경할 수 있다. 예를 들어 gss_cat$partyid를보자.

gss_cat %>% count(partyid)
## # A tibble: 10 x 2
##    partyid                n
##    <fct>              <int>
##  1 No answer            154
##  2 Don't know             1
##  3 Other party          393
##  4 Strong republican   2314
##  5 Not str republican  3032
##  6 Ind,near rep        1791
##  7 Independent         4119
##  8 Ind,near dem        2499
##  9 Not str democrat    3690
## 10 Strong democrat     3490

이레벨들을 풀어쓰고 병렬구조를 사용해보자

gss_cat %>%
  mutate(partyid = fct_recode(partyid,
    "Republican, strong"    = "Strong republican",
    "Republican, weak"      = "Not str republican",
    "Independent, near rep" = "Ind,near rep",
    "Independent, near dem" = "Ind,near dem",
    "Democrat, weak"        = "Not str democrat",
    "Democrat, strong"      = "Strong democrat"
  )) %>%
  count(partyid)
## # A tibble: 10 x 2
##    partyid                   n
##    <fct>                 <int>
##  1 No answer               154
##  2 Don't know                1
##  3 Other party             393
##  4 Republican, strong     2314
##  5 Republican, weak       3032
##  6 Independent, near rep  1791
##  7 Independent            4119
##  8 Independent, near dem  2499
##  9 Democrat, weak         3690
## 10 Democrat, strong       3490

fct_recode()명시적으로 언급되지 안은 레벨은 그대로둔다. 존재하지 않은 레벨을 참조하면 경고가 발생한다. 그룹을 결합하려면 레벨들을 같은 새로운 레벨로 할당하면된다

gss_cat %>%
  mutate(partyid = fct_recode(partyid,
    "Republican, strong"    = "Strong republican",
    "Republican, weak"      = "Not str republican",
    "Independent, near rep" = "Ind,near rep",
    "Independent, near dem" = "Ind,near dem",
    "Democrat, weak"        = "Not str democrat",
    "Democrat, strong"      = "Strong democrat",
    "Other"                 = "No answer",
    "Other"                 = "Don't know",
    "Other"                 = "Other party"
  )) %>%
  count(partyid)
## # A tibble: 8 x 2
##   partyid                   n
##   <fct>                 <int>
## 1 Other                   548
## 2 Republican, strong     2314
## 3 Republican, weak       3032
## 4 Independent, near rep  1791
## 5 Independent            4119
## 6 Independent, near dem  2499
## 7 Democrat, weak         3690
## 8 Democrat, strong       3490

서로 같지 않은 범주들을 함께 묶는다면 잘 못된 결과를 도출하게 될 것이다. 다수의 레벨을 병합하고자 하면 fct_recofe()의 변형 함수인 fct_collapse()가 편리하다. 각각의 새로운 변수에 대해 이전 레벨로 이루어진 벡터를 제공해야 한다.

gss_cat %>%
  mutate(partyid = fct_collapse(partyid,
    other = c("No answer", "Don't know", "Other party"),
    rep = c("Strong republican", "Not str republican"),
    ind = c("Ind,near rep", "Independent", "Ind,near dem"),
    dem = c("Not str democrat", "Strong democrat")
  )) %>%
  count(partyid)
## # A tibble: 4 x 2
##   partyid     n
##   <fct>   <int>
## 1 other     548
## 2 rep      5346
## 3 ind      8409
## 4 dem      7180

가끔은 플롯이나 테이블을 간단히 만들기 위해 소규모 그룹 모두를 묶고 싶을 수도 있다. 이때 fct_lump()가 이작업을 한다.

gss_cat %>%
  mutate(relig = fct_lump(relig)) %>%
  count(relig)
## # A tibble: 2 x 2
##   relig          n
##   <fct>      <int>
## 1 Protestant 10846
## 2 Other      10637

기본동작은 묶은 그룹이 가장 작은 그룹이 되는 조건을 유지하면서 작은 그룹 들을 점진적으로 묶는다. N 인수를 사용하여 유지하고 싶은 그룹 개수(other 제외)를 지정할 수 있다.

gss_cat %>%
  mutate(relig = fct_lump(relig, n = 10)) %>%
  count(relig, sort = TRUE) %>%
  print(n = Inf)
## # A tibble: 10 x 2
##    relig                       n
##    <fct>                   <int>
##  1 Protestant              10846
##  2 Catholic                 5124
##  3 None                     3523
##  4 Christian                 689
##  5 Other                     458
##  6 Jewish                    388
##  7 Buddhism                  147
##  8 Inter-nondenominational   109
##  9 Moslem/islam              104
## 10 Orthodox-christian         95