美文网首页R
R 数据处理(十四)—— tidyr

R 数据处理(十四)—— tidyr

作者: 名本无名 | 来源:发表于2021-01-25 19:46 被阅读0次

    R 数据处理(十四)—— tidyr

    4. 分隔和合并

    到目前为止,你已经学会了如何整理 table2table4

    但是对于 table3,有一个不一样的问题:我们有一列(rate)包含两个变量(casespopulation)。

    要解决这个问题,可以使用 separate() 函数。而如果单个变量在不同的列上,则可以使用 unite()

    4.1 separate

    separate() 通过在分隔符的位置上拆分数据,将一列拆分为多列。对于 table3

    > table3
    # A tibble: 6 x 3
      country      year rate             
    * <chr>       <int> <chr>            
    1 Afghanistan  1999 745/19987071     
    2 Afghanistan  2000 2666/20595360    
    3 Brazil       1999 37737/172006362  
    4 Brazil       2000 80488/174504898  
    5 China        1999 212258/1272915272
    6 China        2000 213766/1280428583
    

    rate 列同时包含了 casespopulation,我们需要将其拆分为两个变量

    separate() 获取要分离的列的名称,以及要分离为列的名称

    > table3 %>% 
    +     separate(rate, into = c("cases", "population"))
    # A tibble: 6 x 4
      country      year cases  population
      <chr>       <int> <chr>  <chr>     
    1 Afghanistan  1999 745    19987071  
    2 Afghanistan  2000 2666   20595360  
    3 Brazil       1999 37737  172006362 
    4 Brazil       2000 80488  174504898 
    5 China        1999 212258 1272915272
    6 China        2000 213766 1280428583
    
    image.png

    在默认情况下,separate() 会在看到非字母数字字符(即不是数字或字母的字符)的任何位置对值进行拆分。

    例如,在上面的代码中,separate()rate 列的值用 / 分隔。如果要指定特定的分隔字符,可以将该字符传递给 separate()sep 参数。例如,我们可以将上面的代码重写

    table3 %>% 
      separate(rate, into = c("cases", "population"), sep = "/")
    

    注意sep 支持正则表达式

    你可能也注意到 casespopulation 列都是字符类型,这是 separate() 函数的默认行为,保持列的类型不变。

    然而,在这里它并不适用,因为这些实际上是数字。我们可以使用 convert=TRUEseparate() 尝试将值转换为更好的类型

    > table3 %>% 
    +     separate(rate, into = c("cases", "population"), convert = TRUE)
    # A tibble: 6 x 4
      country      year  cases population
      <chr>       <int>  <int>      <int>
    1 Afghanistan  1999    745   19987071
    2 Afghanistan  2000   2666   20595360
    3 Brazil       1999  37737  172006362
    4 Brazil       2000  80488  174504898
    5 China        1999 212258 1272915272
    6 China        2000 213766 1280428583
    

    也可以为 sep 传递整数向量,separate() 会将整数解释为需要拆分的位置,正数值表示从字符串最左侧的位置 1 开始;负数值表示从字符串最右侧的位置 -1 开始。

    当使用整数分隔字符串时,sep 的长度应该比 into 中的名称数少一个。

    我们可以使用这种方式来分隔年份,虽然会使数据不那么整洁,但是有时会很有用

    > table3 %>% 
    +     separate(year, into = c("century", "year"), sep = 2)
    # A tibble: 6 x 4
      country     century year  rate             
      <chr>       <chr>   <chr> <chr>            
    1 Afghanistan 19      99    745/19987071     
    2 Afghanistan 20      00    2666/20595360    
    3 Brazil      19      99    37737/172006362  
    4 Brazil      20      00    80488/174504898  
    5 China       19      99    212258/1272915272
    6 China       20      00    213766/1280428583
    

    4.2 unite

    unite()separate() 的逆函数:它将多列合并为一列。与 separate() 相比,你用到它的频率要低得多,但它仍然是一个很有用的工具。

    image

    我们可以使用 unite() 重新合并上一个示例中创建的 centuryyear 列。该数据被保存为 tidyr::table5

    unite() 接受一个数据框,需要创建的新的列名以及要组合的一组列,接受 select 样式。

    > table5 %>% 
    +     unite(new, century, year)
    # A tibble: 6 x 3
      country     new   rate             
      <chr>       <chr> <chr>            
    1 Afghanistan 19_99 745/19987071     
    2 Afghanistan 20_00 2666/20595360    
    3 Brazil      19_99 37737/172006362  
    4 Brazil      20_00 80488/174504898  
    5 China       19_99 212258/1272915272
    6 China       20_00 213766/1280428583
    

    在这种情况下,我们还需要使用 sep 参数。默认值将在不同列的值之间放置下划线(_)。在这里我们不需要任何分隔符,所以使用 ""

    > table5 %>% 
    +     unite(new, century, year, sep = "")
    # A tibble: 6 x 3
      country     new   rate             
      <chr>       <chr> <chr>            
    1 Afghanistan 1999  745/19987071     
    2 Afghanistan 2000  2666/20595360    
    3 Brazil      1999  37737/172006362  
    4 Brazil      2000  80488/174504898  
    5 China       1999  212258/1272915272
    6 China       2000  213766/1280428583
    

    4.3 思考练习

    1. extrafill 参数在 separate() 中的作用是什么?可以对以下两个数据集进行实验
    tibble(x = c("a,b,c", "d,e,f,g", "h,i,j")) %>% 
      separate(x, c("one", "two", "three"))
    
    tibble(x = c("a,b,c", "d,e", "f,g,i")) %>% 
      separate(x, c("one", "two", "three"))
    
    1. unite()separate() 都有一个 remove 参数,它有什么作用?为什么要把它设置为 FALSE

    2. 比较 separate()extract(),为什么分隔有三种变体(通过位置,分隔符和分组),但是 unite 只有一个?

    5. 缺失值

    更改数据的表现形式,可能会引入缺失值。以下两种方式之一都会引入缺失值

    • 显式:例如,存在 NA
    • 隐式:例如,数据中不存在的值

    让我们用一个简单的数据来解释

    stocks <- tibble(
      year   = c(2015, 2015, 2015, 2015, 2016, 2016, 2016),
      qtr    = c(   1,    2,    3,    4,    2,    3,    4),
      return = c(1.88, 0.59, 0.35,   NA, 0.92, 0.17, 2.66)
    )
    

    这个数据中存在两个缺失值:

    • 2015 年第四季度的回报率是缺失的,其对应的值为 NA
    • 2016 年第一季度的回报隐式地丢失了,它根本没有出现在数据中

    数据集的表示方式可以使隐式的值显式化,例如,我们可以通过将年份放在列中来显示隐式的缺失值。

    > stocks %>% 
    +     pivot_wider(names_from = year, values_from = return)
    # A tibble: 4 x 3
        qtr `2015` `2016`
      <dbl>  <dbl>  <dbl>
    1     1   1.88  NA   
    2     2   0.59   0.92
    3     3   0.35   0.17
    4     4  NA      2.66
    

    由于这些显式缺失值在数据的其他表示形式中可能并不重要,因此可以在 pivot_longer() 中设置 values_drop_na = TRUE 将显式缺失值变为隐式

    > stocks %>% 
    +     pivot_wider(names_from = year, values_from = return) %>% 
    +     pivot_longer(
    +         cols = c(`2015`, `2016`), 
    +         names_to = "year", 
    +         values_to = "return", 
    +         values_drop_na = TRUE
    +     )
    # A tibble: 6 x 3
        qtr year  return
      <dbl> <chr>  <dbl>
    1     1 2015    1.88
    2     2 2015    0.59
    3     2 2016    0.92
    4     3 2015    0.35
    5     3 2016    0.17
    6     4 2016    2.66
    

    另一个显式标记缺失值的工具就是 complete()

    > stocks %>% 
    +     complete(year, qtr)
    # A tibble: 8 x 3
       year   qtr return
      <dbl> <dbl>  <dbl>
    1  2015     1   1.88
    2  2015     2   0.59
    3  2015     3   0.35
    4  2015     4  NA   
    5  2016     1  NA   
    6  2016     2   0.92
    7  2016     3   0.17
    8  2016     4   2.66
    

    complete() 接受一组列,并搜索所有唯一的组合。然后确保原始数据集包含所有这些值,并在必要时显式的填充 NA

    还有另一个处理缺失值的工具,例如

    treatment <- tribble(
      ~ person,           ~ treatment, ~response,
      "Derrick Whitmore", 1,           7,
      NA,                 2,           10,
      NA,                 3,           9,
      "Katherine Burke",  1,           4
    )
    

    你可以使用 fill() 来填充缺失值。它接收一组列,并在这组列中指定用于填补缺失值的值

    > treatment %>% 
    +     fill(person)
    # A tibble: 4 x 3
      person           treatment response
      <chr>                <dbl>    <dbl>
    1 Derrick Whitmore         1        7
    2 Derrick Whitmore         2       10
    3 Derrick Whitmore         3        9
    4 Katherine Burke          1        4
    

    5.1 思考练习

    1. fill 参数与 pivot()complete() 进行比较。

    2. fill()direction 参数有什么作用?

    6. 案例分析

    下面我们将上面介绍的知识结合起来,来解决一个数据整理的问题。

    我们使用 tidyr::who 数据,其中包含按年,国家,年龄,性别和诊断方法的结核病(TB)病例

    在这个数据集中有保存了丰富的流行病学信息,但直接使用它所提供的数据形式是不太好的。

    > who
    # A tibble: 7,240 x 60
       country iso2  iso3   year new_sp_m014 new_sp_m1524 new_sp_m2534 new_sp_m3544 new_sp_m4554
       <chr>   <chr> <chr> <int>       <int>        <int>        <int>        <int>        <int>
     1 Afghan… AF    AFG    1980          NA           NA           NA           NA           NA
     2 Afghan… AF    AFG    1981          NA           NA           NA           NA           NA
     3 Afghan… AF    AFG    1982          NA           NA           NA           NA           NA
     4 Afghan… AF    AFG    1983          NA           NA           NA           NA           NA
     5 Afghan… AF    AFG    1984          NA           NA           NA           NA           NA
     6 Afghan… AF    AFG    1985          NA           NA           NA           NA           NA
     7 Afghan… AF    AFG    1986          NA           NA           NA           NA           NA
     8 Afghan… AF    AFG    1987          NA           NA           NA           NA           NA
     9 Afghan… AF    AFG    1988          NA           NA           NA           NA           NA
    10 Afghan… AF    AFG    1989          NA           NA           NA           NA           NA
    # … with 7,230 more rows, and 51 more variables: new_sp_m5564 <int>, new_sp_m65 <int>,
    #   new_sp_f014 <int>, new_sp_f1524 <int>, new_sp_f2534 <int>, new_sp_f3544 <int>, new_sp_f4554 <int>,
    #   new_sp_f5564 <int>, new_sp_f65 <int>, new_sn_m014 <int>, new_sn_m1524 <int>, new_sn_m2534 <int>,
    #   new_sn_m3544 <int>, new_sn_m4554 <int>, new_sn_m5564 <int>, new_sn_m65 <int>, new_sn_f014 <int>,
    #   new_sn_f1524 <int>, new_sn_f2534 <int>, new_sn_f3544 <int>, new_sn_f4554 <int>,
    #   new_sn_f5564 <int>, new_sn_f65 <int>, new_ep_m014 <int>, new_ep_m1524 <int>, new_ep_m2534 <int>,
    #   new_ep_m3544 <int>, new_ep_m4554 <int>, new_ep_m5564 <int>, new_ep_m65 <int>, new_ep_f014 <int>,
    #   new_ep_f1524 <int>, new_ep_f2534 <int>, new_ep_f3544 <int>, new_ep_f4554 <int>,
    #   new_ep_f5564 <int>, new_ep_f65 <int>, newrel_m014 <int>, newrel_m1524 <int>, newrel_m2534 <int>,
    #   newrel_m3544 <int>, newrel_m4554 <int>, newrel_m5564 <int>, newrel_m65 <int>, newrel_f014 <int>,
    #   newrel_f1524 <int>, newrel_f2534 <int>, newrel_f3544 <int>, newrel_f4554 <int>,
    #   newrel_f5564 <int>, newrel_f65 <int>
    

    这是非常典型的真实数据,它包含冗余的列和许多缺失值,总之乱七八糟的。

    通常处理这种数据需要用管道来组合 dplyrtidyr 中的操作函数。

    通常,最好的着手点就是将非变量的列集合在一起,让我们来分析一下

    • 似乎 country, iso2iso3 这三列是冗余的国家信息
    • year 显然是一个变量
    • 我们还不知道其他所有列是什么,但是给定了变量名的结构(例如 new_sp_m014, new_ep_m014, new_ep_f014),这些很可能是值而是变量

    因此,我们需要收集从 new_sp_m014newrel_f65 所有列。

    我们还不知道这些值代表什么,所以我们给它们取一个通用的名称 。我们知道单元格代表病例数,因此我们将使用 cases 作为变量。

    同时,当前的表示形式中有很多缺失值,所以我们使用 values_drop_na 只专注于已经存在的值

    > who1 <- who %>% 
    +     pivot_longer(
    +         cols = new_sp_m014:newrel_f65, 
    +         names_to = "key", 
    +         values_to = "cases", 
    +         values_drop_na = TRUE
    +     )
    > who1
    # A tibble: 76,046 x 6
       country     iso2  iso3   year key          cases
       <chr>       <chr> <chr> <int> <chr>        <int>
     1 Afghanistan AF    AFG    1997 new_sp_m014      0
     2 Afghanistan AF    AFG    1997 new_sp_m1524    10
     3 Afghanistan AF    AFG    1997 new_sp_m2534     6
     4 Afghanistan AF    AFG    1997 new_sp_m3544     3
     5 Afghanistan AF    AFG    1997 new_sp_m4554     5
     6 Afghanistan AF    AFG    1997 new_sp_m5564     2
     7 Afghanistan AF    AFG    1997 new_sp_m65       0
     8 Afghanistan AF    AFG    1997 new_sp_f014      5
     9 Afghanistan AF    AFG    1997 new_sp_f1524    38
    10 Afghanistan AF    AFG    1997 new_sp_f2534    36
    # … with 76,036 more rows
    

    然后,我们对新的列 key 进行统计

    > who1 %>% 
    +     count(key)
    # A tibble: 56 x 2
       key              n
       <chr>        <int>
     1 new_ep_f014   1032
     2 new_ep_f1524  1021
     3 new_ep_f2534  1021
     4 new_ep_f3544  1021
     5 new_ep_f4554  1017
     6 new_ep_f5564  1017
     7 new_ep_f65    1014
     8 new_ep_m014   1038
     9 new_ep_m1524  1026
    10 new_ep_m2534  1020
    # … with 46 more rows
    

    你可以用自己的方式来解析这列数据,但是幸运的是,我们手里有这一数据的一些信息:

    1. 每列的前三个字母表示该列包含的是新的还是旧的 TB 病例,在这个数据中,每列包含的都是新的病例

    2. 后面两个字符代表 TB 的类型

    • rel 代表复发病例
    • ep 代表肺外结核病例
    • sn 代表无法通过肺部涂片诊断的肺结核病例(涂片阴性)
    • sp 代表可以通过肺部涂片诊断的肺结核病例(涂片阳性)
    1. 第六个字符表示结核病患者的性别,男性(m)和女性(f

    2. 剩下的数字表示年龄分组,数据将年龄分为七个年龄组。

    • 014 = 0 – 14
    • 1524 = 15 – 24
    • 2534 = 25 – 34
    • 3544 = 35 – 44
    • 4554 = 45 – 54
    • 5564 = 55 – 64
    • 65 = 65 or older

    我们先对列名的格式进行一个小小的修改,用 new_rel 替换 newrel,而其他都是以 _ 分隔的

    > who2 <- who1 %>% 
    +     mutate(key = stringr::str_replace(key, "newrel", "new_rel"))
    > who2
    # A tibble: 76,046 x 6
       country     iso2  iso3   year key          cases
       <chr>       <chr> <chr> <int> <chr>        <int>
     1 Afghanistan AF    AFG    1997 new_sp_m014      0
     2 Afghanistan AF    AFG    1997 new_sp_m1524    10
     3 Afghanistan AF    AFG    1997 new_sp_m2534     6
     4 Afghanistan AF    AFG    1997 new_sp_m3544     3
     5 Afghanistan AF    AFG    1997 new_sp_m4554     5
     6 Afghanistan AF    AFG    1997 new_sp_m5564     2
     7 Afghanistan AF    AFG    1997 new_sp_m65       0
     8 Afghanistan AF    AFG    1997 new_sp_f014      5
     9 Afghanistan AF    AFG    1997 new_sp_f1524    38
    10 Afghanistan AF    AFG    1997 new_sp_f2534    36
    # … with 76,036 more rows
    

    我们可以通过两次 separate() 来分隔每个代码的值。

    第一次在下划线处拆分代码

    > who3 <- who2 %>% 
    +     separate(key, c("new", "type", "sexage"), sep = "_")
    > who3
    # A tibble: 76,046 x 8
       country     iso2  iso3   year new   type  sexage cases
       <chr>       <chr> <chr> <int> <chr> <chr> <chr>  <int>
     1 Afghanistan AF    AFG    1997 new   sp    m014       0
     2 Afghanistan AF    AFG    1997 new   sp    m1524     10
     3 Afghanistan AF    AFG    1997 new   sp    m2534      6
     4 Afghanistan AF    AFG    1997 new   sp    m3544      3
     5 Afghanistan AF    AFG    1997 new   sp    m4554      5
     6 Afghanistan AF    AFG    1997 new   sp    m5564      2
     7 Afghanistan AF    AFG    1997 new   sp    m65        0
     8 Afghanistan AF    AFG    1997 new   sp    f014       5
     9 Afghanistan AF    AFG    1997 new   sp    f1524     38
    10 Afghanistan AF    AFG    1997 new   sp    f2534     36
    # … with 76,036 more rows
    

    然后我们删除 new 这一列,这列值是一个常量。同时,我们也删除 iso2iso3 列,它们是多余的 country 信息。

    > who3 %>% 
    +     count(new)
    # A tibble: 1 x 2
      new       n
      <chr> <int>
    1 new   76046
    > who4 <- who3 %>% 
    +     select(-new, -iso2, -iso3)
    

    接下来,我们将 sexage 列分割为 sexage

    > who5 <- who4 %>% 
    +     separate(sexage, c("sex", "age"), sep = 1)
    > who5
    # A tibble: 76,046 x 6
       country      year type  sex   age   cases
       <chr>       <int> <chr> <chr> <chr> <int>
     1 Afghanistan  1997 sp    m     014       0
     2 Afghanistan  1997 sp    m     1524     10
     3 Afghanistan  1997 sp    m     2534      6
     4 Afghanistan  1997 sp    m     3544      3
     5 Afghanistan  1997 sp    m     4554      5
     6 Afghanistan  1997 sp    m     5564      2
     7 Afghanistan  1997 sp    m     65        0
     8 Afghanistan  1997 sp    f     014       5
     9 Afghanistan  1997 sp    f     1524     38
    10 Afghanistan  1997 sp    f     2534     36
    # … with 76,036 more rows
    

    现在,数据已经变得非常整洁了。

    最后,我们使用管道操作符将上面的代码进行简化

    who %>%
      pivot_longer(
        cols = new_sp_m014:newrel_f65, 
        names_to = "key", 
        values_to = "cases", 
        values_drop_na = TRUE
      ) %>% 
      mutate(
        key = stringr::str_replace(key, "newrel", "new_rel")
      ) %>%
      separate(key, c("new", "var", "sexage")) %>% 
      select(-new, -iso2, -iso3) %>% 
      separate(sexage, c("sex", "age"), sep = 1)
    

    6.1 思考练习

    1. 在本例中,我们通过设置 values_drop_na = TRUE,方便检查数据是否具有正确的值。这样做合理吗?考虑一下该数据集中的缺失值代表的含义,是否存在隐式缺失值?NA0 有什么区别?

    2. 上面的示例中如果忽略 mutate() 这一步骤会发生什么?

    3. 计算每个国家、每年和每种性别的结核病例总数,并对数据进行可视化。

    相关文章

      网友评论

        本文标题:R 数据处理(十四)—— tidyr

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