美文网首页R小技巧R
R 数据处理(十五)—— forcat

R 数据处理(十五)—— forcat

作者: 名本无名 | 来源:发表于2021-01-26 22:17 被阅读0次

    1. 前言

    R 中,因子用于处理类别变量,即具有固定且已知所有可能值的变量。当您希望以非字母顺序显示字符向量时,它们也很有用

    以经验上来看,因子通常比字符串更容易处理,因此,R 中提供的许多基础函数都会自动将字符串转换为因子。

    但是这些转换通常是没有意思的,所以,在 tidyverse 中并不会出现这种问题。

    1.1 导入

    为了处理因子,我们需要用到 tidyverse 中的 forcat 包。

    它提供了处理分类变量的工具,包含了许多的辅助函数用于处理因子。

    library(tidyverse)
    

    2. 构建因子

    假设您有一个记录月份的变量

    x1 <- c("Dec", "Apr", "Jan", "Mar")
    

    我们使用字符串来记录这个变量会有两个问题:

    1. 月份只有 12 个可能的值,但是拼写错误的问题应该是无法避免的,例如
    x2 <- c("Dec", "Apr", "Jam", "Mar")
    
    1. 无法对其进行排序
    > sort(x1)
    [1] "Apr" "Dec" "Jan" "Mar"
    

    你可以用一个因子来解决这两个问题。

    要创建因子,必须先创建有效的 levels

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

    然后可以创建因子了

    > (y1 <- factor(x1, levels = month_levels))
    [1] Dec Apr Jan Mar
    Levels: Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
    
    # 排序
    > sort(y1)
    [1] Jan Mar Apr Dec
    Levels: Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
    

    不在 levels 中的任何值都将自动转换为 NA

    > (y2 <- factor(x2, levels = month_levels))
    [1] Dec  Apr  <NA> Mar 
    Levels: Jan Feb 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
    

    有时您可能希望 levels 的顺序与数据第一次出现的顺序相匹配

    在创建因子时,可以通过将 levels 设置为 unique(x) ,或者在创建之后使用 fct_inorder() 来做到这一点

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

    如果你想要直接访问 levels,可以使用 levels() 进行访问

    > levels(f2)
    [1] "Dec" "Apr" "Jan" "Mar"
    

    3. 社会调查

    在本节的后续部分,我们将重点讨论 forcats::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 marri…    26 White $8000 to 9999 Ind,near rep     Protestant      Southern bapt…      12
     2  2000 Divorced        48 White $8000 to 9999 Not str republi… Protestant      Baptist-dk wh…      NA
     3  2000 Widowed         67 White Not applicab… Independent      Protestant      No denominati…       2
     4  2000 Never marri…    39 White Not applicab… Ind,near rep     Orthodox-chris… Not applicable       4
     5  2000 Divorced        25 White Not applicab… Not str democrat None            Not applicable       1
     6  2000 Married         25 White $20000 - 249… Strong democrat  Protestant      Southern bapt…      NA
     7  2000 Never marri…    36 White $25000 or mo… Not str republi… Christian       Not applicable       3
     8  2000 Divorced        44 White $7000 to 7999 Ind,near dem     Protestant      Lutheran-mo s…      NA
     9  2000 Married         44 White $25000 or mo… Not str democrat Protestant      Other                0
    10  2000 Married         47 White $25000 or mo… Strong republic… Protestant      Southern bapt…       3
    # … with 21,473 more rows
    

    可以使用 ?gss_cat 查看数据变量的含义

    当因子存储在一个 tible 表中时,你可能没那么容易能看到它们的 levels。一种方法是使用 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()
    
    image.png

    默认情况下,ggplot2 将删除没有任何值的 levels,您可以强制它们显示

    > ggplot(gss_cat, aes(race)) +
    +     geom_bar() +
    +     scale_x_discrete(drop = FALSE)
    
    image.png

    使用因子时,最常见的两种操作是更改 levels 的顺序以及对应的值,这些操作将在下面展开介绍。

    3.1 思考练习

    1. 在这项调查中最常见的 relig 是哪个?最常见的 partyid 是什么?

    4. 修改因子顺序

    在可视化应用中改变因子的 levels 的顺序通常是有用的。

    例如,您想了解不同宗教每天看电视的平均时间

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

    这张图看起来不是很直观,我们可以使用 fct_reorder() 来重新排列宗教的 levels 来呈现更好的效果。

    fct_reorder() 接受三个参数:

    1. f - 要修改的 levels 的因子
    2. x - 要用于重新排列 levels 的数字向量
    3. fun - 可选参数,汇总函数,默认 median
    > ggplot(relig_summary, aes(tvhours, fct_reorder(relig, tvhours))) +
    +     geom_point()
    
    image.png

    通过对宗教进行重新排序,我们可以更容易地看到 Don’t know 类别的人看电视的时间更长,而印度教和其他东方宗教看得时间更少。

    当您开始进行更复杂的转换时,我建议将它们从 aes() 移到单独的 mutate() 步骤中。

    例如,您可以将上面的绘图写成

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

    如果我们想创建一个类似的图,来研究平均年龄和收入水平之间是如何变化的,该怎么做

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

    您可以使用 fct_relevel() 来重新排列 levels,它传入一个因子 f,然后是你想要移动到前面的任意数量的 levels

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

    如果你绘图使用的是彩色的线条,另一种类型的重排将会很有用的。

    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")
    
    image.png image.png

    最后,对于条形图,可以使用 fct_infreq() 以递增的频率对 levels 进行排序。

    这是最简单的重新排序方式,它不需要任何额外的变量。您可以与 fct_rev() 结合使用。

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

    4.1 思考练习

    1. tvhours 列中有一些特别高的值,使用均值来衡量是否准确?

    2. 对于 gss_cst 中的每个因子,确定 levels 的顺序是任意的还是有规律的。

    5. 修改 levels

    比改变 levels 顺序更强大的是改变他们的值,你可以使用 fct_recode() 函数来重新编码或更改每个 levels 的值。

    例如,对于 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
    

    它的 levels 非常简单且不太一致,让我们把它调整得更长一些,并使用并行结构

    > 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() 将会保留未明确提及的 level,并在引用不存在的 level 时发出警告

    要合并组,可以将多个旧的 levels 分配给同一个新的 levels

    > 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
    

    您必须谨慎使用此技术:如果确实要将不同的类别组合在一起,则会导致误导性的结果

    如果要折叠 levels,则可以使用 fct_recode() 的变体 fct_collapse()。对于每个新变量,您可以提供旧 levels 的向量

    > 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 来指定需要保留多少组(不包括其他组)

    > 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
    

    5.1 思考练习

    1. 如何将 rincome 折叠为更小的分类集合?

    2. Democrat, Republican, 和 Independent 的人数占比是如何随时间变化的?

    相关文章

      网友评论

        本文标题:R 数据处理(十五)—— forcat

        本文链接:https://www.haomeiwen.com/subject/reinzktx.html