library(forcats)
library(dplyr)
library(ggplot2)

Розглянемо пакет для маніпуляції із факторами forcats від Hadley Wickham.

Створимо фактор для маніпуляції над ним. Окрім введення тексту, одним із основних найважливіших аргументів являється список доступних рівнів.

x <- c("pear", "apple", "banana", "apple", "pear", "apple")
factor(x, levels = c("banana", "apple", "pear"))
## [1] pear   apple  banana apple  pear   apple 
## Levels: banana apple pear

Всі значення, які не вказані в списку рівнів, приймають значення NA:

factor(x, levels = c("apple", "banana"))
## [1] <NA>   apple  banana apple  <NA>   apple 
## Levels: apple banana

Якщо не вказати рівні, то вони будуть взяті із даних в алфавітному порядку:

factor(x)
## [1] pear   apple  banana apple  pear   apple 
## Levels: apple banana pear

1 Зміна порядку рівнів фактора

1.1 fct_inorder()

Інколи потрібно, щоб порядок рівнів співпадав із порядком першої появи значення в даних. Для цього можна використати функції unique(), задавши її в аргументі levels=. А можна використати функцію fct_inorder():

f1 <- factor(x, levels = unique(x))
f1
## [1] pear   apple  banana apple  pear   apple 
## Levels: pear apple banana
f2 <- x %>% factor() %>% fct_inorder()
f2
## [1] pear   apple  banana apple  pear   apple 
## Levels: pear apple banana

Якщо потрібно отримати доступ до рівнів фактора, то можна використати функцію levels()

levels(f2)
## [1] "pear"   "apple"  "banana"

1.2 fct_reorder()

В подільшому будемо використовувати дані forcats::gss_cat - вибірка даних із загального соціального дослідження.

# data("gss_cat")
# ?gss_cat 
gss_cat
## # A tibble: 21,483 <U+00D7> 9
##     year       marital   age   race        rincome            partyid
##    <int>        <fctr> <int> <fctr>         <fctr>             <fctr>
## 1   2000 Never married    26  White  $8000 to 9999       Ind,near rep
## 2   2000      Divorced    48  White  $8000 to 9999 Not str republican
## 3   2000       Widowed    67  White Not applicable        Independent
## 4   2000 Never married    39  White Not applicable       Ind,near rep
## 5   2000      Divorced    25  White Not applicable   Not str democrat
## 6   2000       Married    25  White $20000 - 24999    Strong democrat
## 7   2000 Never married    36  White $25000 or more Not str republican
## 8   2000      Divorced    44  White  $7000 to 7999       Ind,near dem
## 9   2000       Married    44  White $25000 or more   Not str democrat
## 10  2000       Married    47  White $25000 or more  Strong republican
## # ... with 21,473 more rows, and 3 more variables: relig <fctr>,
## #   denom <fctr>, tvhours <int>

Коли фактори зберігаються в даних формату tibble, не можна просто побачити його рівні. Для цього можна скористатися функцією count()

gss_cat %>% 
  count(race)
## # A tibble: 3 <U+00D7> 2
##     race     n
##   <fctr> <int>
## 1  Other  1959
## 2  Black  3129
## 3  White 16395

Або за допомогою стовбчастої діаграми:

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

Інколи корисно буває змінити порядок факторів (особливо при візуалізації). Наприклад, представте, що потрібно дослідити середню кількість годин, проведених дивлячись ТВ у розрізі релігії:

relig <- gss_cat %>% 
  group_by(relig) %>% 
  summarise(
    age = mean(age, na.rm = TRUE),
    tvhours = mean(tvhours, na.rm = TRUE)
  )

ggplot(relig, aes(x = tvhours, y = relig)) +
  geom_point()

Дуже складно інтерпритувати даний графік, тому що не видно загальної картини. Це можна виправити, змінивши порядок рівнів у фактору relig за допомогою фукнції fct_reorder(). Функція fct_reorder() приймає три аргументи:

  • f - сам фактор;
  • x - числовий вектор, який потрібно використати для зміни порядку фактора;
  • fun - необов’язковий аргумент, використовується при наявності декількох значень x для одного значення f (значення за замовчуванням - медіана).
ggplot(relig, aes(x = tvhours, y = fct_reorder(relig, tvhours))) +
  geom_point()

Зміна порядку фактору привело до того, що ми відразу бачимо, що ті, хто не знає своєї релігії, дивляться більше ТВ, а ті, хто сповідує Іудаїзм та інші східні релігії - найменше.

Для кращого сприйняття коду (і щоб осі нормально підписані були), буде правильніше винести функцію fct_reorder() за межі аргументу aes():

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

1.3 fct_relevel()

Розглянемо середнє значення віку у розрізі доходу:

rincome <- gss_cat %>%
  group_by(rincome) %>%
  summarise(
    age = mean(age, na.rm = TRUE),
    tvhours = mean(tvhours, na.rm = TRUE),
    n = n()
  )

ggplot(rincome, aes(age, rincome))+geom_point()

Але напевне значення "Not applicable" якось не вписується у верхню частину графіку, по причині того, що дані і так впорядковані, а дане значення виглядало більш коректно у нижній частині графіку. Для цього можна використати іншу функцію зміни порядку фактора fct_relevel() - переміщує рівень “у початок списку”:

levels(rincome$rincome)
##  [1] "No answer"      "Don't know"     "Refused"        "$25000 or more"
##  [5] "$20000 - 24999" "$15000 - 19999" "$10000 - 14999" "$8000 to 9999" 
##  [9] "$7000 to 7999"  "$6000 to 6999"  "$5000 to 5999"  "$4000 to 4999" 
## [13] "$3000 to 3999"  "$1000 to 2999"  "Lt $1000"       "Not applicable"
levels(fct_relevel(rincome$rincome, "Not applicable"))
##  [1] "Not applicable" "No answer"      "Don't know"     "Refused"       
##  [5] "$25000 or more" "$20000 - 24999" "$15000 - 19999" "$10000 - 14999"
##  [9] "$8000 to 9999"  "$7000 to 7999"  "$6000 to 6999"  "$5000 to 5999" 
## [13] "$4000 to 4999"  "$3000 to 3999"  "$1000 to 2999"  "Lt $1000"
rincome %>% 
  mutate(rincome = fct_relevel(rincome, "Not applicable")) %>% 
  ggplot(aes(age, rincome))+geom_point()

1.4 fct_reorder2()

Інший вид зміни порядку фактора - функція fct_reorder2(). Використовується, коли задається колір для ліній на груфіку. Це робить графік більш читабельним.

Її аргументи:

  • f - фактор;
  • x, y - змінні, по яким відбувається зміна порядку фактора. Сортує фактор по значенням y зв“язаних із найбільшими значеннями x;
  • fun - необов’язковий аргумент, використовується при наявності декількох значень x для одного значення f (значення за замовчуванням - медіана).
by_age <- gss_cat %>%
  filter(!is.na(age)) %>%
  group_by(age, marital) %>%
  count() %>%
  mutate(prop = n / sum(n))

levels(by_age$marital)
## [1] "No answer"     "Never married" "Separated"     "Divorced"     
## [5] "Widowed"       "Married"
levels(fct_reorder2(by_age$marital, by_age$age, by_age$prop))
## [1] "Widowed"       "Married"       "Divorced"      "Never married"
## [5] "No answer"     "Separated"
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")

1.5 fct_infreq() (+ fct_rev())

Для bar plot зручно виростивувати фактори, які впрорядковані по частоті. Це можна зробити за допомогою функції fct_infreq(). Також дана функція може бути використана разом із функцією fct_rev - обернений порядок рівнів фактора.

factor(x)
## [1] pear   apple  banana apple  pear   apple 
## Levels: apple banana pear
fct_infreq(x)
## [1] pear   apple  banana apple  pear   apple 
## Levels: apple pear banana
gss_cat %>%
  mutate(marital = marital %>% fct_infreq()) %>%
  ggplot(aes(marital)) +
    geom_bar()

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

2 Зміна значення рівнів фактора

2.1 fct_recode()

Більш потужим механізмом, аніж зміна порядку фактора, є зміна його значення. Це дозволить більш ясніше вказати мітки рівнів фактору для публікацій. Найбільш загальний і потужний інструмент - це fct_recode(). Дана функція дозволяє перекодувати або змінити значення кожного рівня. Наприклад, використаємо gss_cat$partyid (партійну приналежність):

gss_cat %>% 
  count(partyid)
## # A tibble: 10 <U+00D7> 2
##               partyid     n
##                <fctr> <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 <U+00D7> 2
##                  partyid     n
##                   <fctr> <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 <U+00D7> 2
##                 partyid     n
##                  <fctr> <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

2.2 fct_collapse()

Якщо потрібно “схлопнути”" багато рівнів, то краще використовувати функцію fct_collapse() замість fct_recode():

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 <U+00D7> 2
##   partyid     n
##    <fctr> <int>
## 1   other   548
## 2     rep  5346
## 3     ind  8409
## 4     dem  7180

2.3 fct_lump()

gss_cat %>%
  mutate(relig = relig) %>%
  count(relig)
## # A tibble: 15 <U+00D7> 2
##                      relig     n
##                     <fctr> <int>
## 1                No answer    93
## 2               Don't know    15
## 3  Inter-nondenominational   109
## 4          Native american    23
## 5                Christian   689
## 6       Orthodox-christian    95
## 7             Moslem/islam   104
## 8            Other eastern    32
## 9                 Hinduism    71
## 10                Buddhism   147
## 11                   Other   224
## 12                    None  3523
## 13                  Jewish   388
## 14                Catholic  5124
## 15              Protestant 10846

Якщо потрібно об“єднати всі найменші рівні в один, можна використати функцію fct_lump, яка залишить найбільший рівень, а всі інші об”єдна“є:

gss_cat %>% 
  mutate(relig = fct_lump(relig)) %>% 
  count(relig)
## # A tibble: 2 <U+00D7> 2
##        relig     n
##       <fctr> <int>
## 1 Protestant 10846
## 2      Other 10637

Також можна використати параметр n, щоб вказати скільки груп (за виключенням Other) залишити:

gss_cat %>% 
  mutate(relig = fct_lump(relig, n = 5)) %>% 
  count(relig, sort = TRUE)
## # A tibble: 6 <U+00D7> 2
##        relig     n
##       <fctr> <int>
## 1 Protestant 10846
## 2   Catholic  5124
## 3       None  3523
## 4      Other   913
## 5  Christian   689
## 6     Jewish   388