R数据科学(Tidy data)

本次整理内容参考 R For Data Science

一、准备工作

首先安装并加载R包

if(!require("tidyverse"))install.packages("tidyverse")
library(tidyverse)

二、什么是整洁的数据

加载内置的4个数据集,理解什么是整洁的数据。

每个数据集都展示了这4个变量 country, year, population, and cases, 但每个数据集对变量的数值的组织方式不同。

table1
#> # 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
table2
#> # A tibble: 12 x 4
#>   country      year type           count
#>   <chr>       <int> <chr>          <int>
#> 1 Afghanistan  1999 cases            745
#> 2 Afghanistan  1999 population  19987071
#> 3 Afghanistan  2000 cases           2666
#> 4 Afghanistan  2000 population  20595360
#> 5 Brazil       1999 cases          37737
#> 6 Brazil       1999 population 172006362
#> # … with 6 more rows
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

# Spread across two tibbles
table4a  # cases
#> # A tibble: 3 x 3
#>   country     `1999` `2000`
#> * <chr>        <int>  <int>
#> 1 Afghanistan    745   2666
#> 2 Brazil       37737  80488
#> 3 China       212258 213766
table4b  # population
#> # A tibble: 3 x 3
#>   country         `1999`     `2000`
#> * <chr>            <int>      <int>
#> 1 Afghanistan   19987071   20595360
#> 2 Brazil       172006362  174504898
#> 3 China       1272915272 1280428583

将一个数据集变整洁有以下3点:

  1. 每个变量必须有自己的一列
  2. 每个观测值必须有自己的一行
  3. 每个值必须有自己的一个单元格

这样看得话,上述数据框中只有table1是整洁的

三、Pivoting

分析数据经常遇到这两种情况:

  1. 一个变量可能分布在多个列中。如table4a 和 table4b
  2. 一项观测值可能分散在多行中。 如table2

3.1 pivot_longer

table4a
#> # A tibble: 3 x 3
#>   country     `1999` `2000`
#> * <chr>        <int>  <int>
#> 1 Afghanistan    745   2666
#> 2 Brazil       37737  80488
#> 3 China       212258 213766

因为后两列都是显示的case,所以把这后面两栏合并为一栏

table4a %>%
    pivot_longer(cols = c("1999","2000"), names_to = "year", values_to = "cases")
#> # A tibble: 6 x 3
#>   country     year   cases
#>   <chr>       <chr>  <int>
#> 1 Afghanistan 1999     745
#> 2 Afghanistan 2000    2666
#> 3 Brazil      1999   37737
#> 4 Brazil      2000   80488
#> 5 China       1999  212258
#> 6 China       2000  213766

现在我们可以把table4b也转换下,并且将转换后的table4a和table4b合并

tidy4a = table4a %>%
    pivot_longer(cols = c("1999","2000"), names_to = "year", values_to = "cases")
tidy4b = table4b %>%
    pivot_longer(cols = c("1999","2000"), names_to = "year", values_to = "population")
left_join(tidy4a, tidy4b, by = c("country", "year"))
#> # A tibble: 6 x 4
#>   country     year   cases population
#>   <chr>       <chr>  <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

3.2 pivot_wider

table2中的观测是每年每个国家,可见有重复。

table2
#> # A tibble: 12 x 4
#>   country      year type           count
#>   <chr>       <int> <chr>          <int>
#> 1 Afghanistan  1999 cases            745
#> 2 Afghanistan  1999 population  19987071
#> 3 Afghanistan  2000 cases           2666
#> 4 Afghanistan  2000 population  20595360
#> 5 Brazil       1999 cases          37737
#> 6 Brazil       1999 population 172006362
#> # … with 6 more rows

转换后

table2 %>%
    pivot_wider(names_from = "type", values_from = "count")
#> # 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

注意:pivot_longerpivot_wider并非完全对称,举例如下:

stocks <- tibble(
  year   = c(2015, 2015, 2016, 2016),
  half  = c(   1,    2,     1,    2),
  return = c(1.88, 0.59, 0.92, 0.17)
)
stocks
## A tibble: 4 x 3
#   year  half return
#  <dbl> <dbl>  <dbl>
#1  2015     1   1.88
#2  2015     2   0.59
#3  2016     1   0.92
#4  2016     2   0.17

stocks %>% 
  pivot_wider(names_from = year, values_from = return) %>% 
  pivot_longer(`2015`:`2016`, names_to = "year", values_to = "return")
## A tibble: 4 x 3
#   half year  return
#  <dbl> <chr>  <dbl>
#1     1 2015    1.88
#2     1 2016    0.92
#3     2 2015    0.59
#4     2 2016    0.17

观察上面的数据可以看到原始的stocks中year的类型为数值型变量,但是转换完之后变成了字符型

找原因:

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

stocks %>% 
  pivot_wider(names_from = year, values_from = return) %>% 
  pivot_longer(`2015`:`2016`, names_to = "year", values_to = "return")
## A tibble: 4 x 3
#   half year  return
#  <dbl> <chr>  <dbl>
#1     1 2015    1.88
#2     1 2016    0.92
#3     2 2015    0.59
#4     2 2016    0.17

pivot_wider将原始数据转换后不会保存原始数据的类型,所以pivot_longer再次转化数据时使用的默认的格式即character

如果想保留原始类型,pivot_longer后加参数names_transform = list(year = as.numeric)

stocks %>% 
    pivot_wider(names_from = year, values_from = return) %>% 
    pivot_longer(`2015`:`2016`, names_to = "year", values_to = "return", names_transform = list(year = as.numeric))
## A tibble: 4 x 3
#   half  year return
#  <dbl> <dbl>  <dbl>
#1     1  2015   1.88
#2     1  2016   0.92
#3     2  2015   0.59
#4     2  2016   0.17

四、Separating and uniting

3.1 Separate

通过在出现分隔符的任何地方进行拆分,separate() 将一列拆分为多列。举例如:table3

table3 %>%
    separate(rate, into = c("cases", "population"), sep = "/")
#> # 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

注意:

1、此处的sep在形式上是一个正则表达式,后续会更新正则表达式的内容。

2、拆分后,cases和population的类型变成了character,加参数convert = TRUE可以纠正。

table3 %>%
    separate(rate, into = c("cases", "population"), sep = "/", 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也可以是一个整数或整数向量。separator() 会将该整数或整数向量理解为要拆分的位置。举例:

table5 = 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

3.2 Unite

unite()与separate()相反:unite()将多列合并为一列。

以上面保存的table5举例:

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

unite()默认分隔为“_”,我们可以修改

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

五、Missing values

缺失值在数据中的表现形式有两种:

  • 显性:例如,以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)
)
stocks
## A tibble: 7 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     2   0.92
#6  2016     3   0.17
#7  2016     4   2.66

上述数据有一个显性缺失值:2015年第4季度的return值为NA;一个阴性缺失值:2016年第1季度的return没有出现在表中。

1、我们可以让阴性缺失值出现,如:

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

2、利用参数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,
        names_transform = list(year = as.numeric)
    )
#> # A tibble: 6 x 3
#>     qtr year  return
#>   <dbl> <dbl>  <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

3、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。

4、fill()填充某一列的缺失值

treatment <- tribble(
    ~ person,           ~ treatment, ~response,
    "Derrick Whitmore", 1,           7,
    NA,                 2,           10,
    NA,                 3,           9,
    "Katherine Burke",  1,           4
)
treatment
## A tibble: 4 x 3
#  person           treatment response
#  <chr>                <dbl>    <dbl>
#1 Derrick Whitmore         1        7
#2 NA                       2       10
#3 NA                       3        9
#4 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

fill()的第3个参数为.direction =默认为down,即向下填充,所以NA被其上方的名字向下填充为Derrick Whitmore。

treatment %>%
    fill(person, .direction = "up")
#> # A tibble: 4 x 3
#>   person           treatment response
#>   <chr>                <dbl>    <dbl>
#> 1 Derrick Whitmore         1        7
#> 2 Katherine Burke          2       10
#> 3 Katherine Burke          3        9
#> 4 Katherine Burke          1        4

修改为up后即为由NA下方的的Katherine Burke向上填充。

最后

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
#>   <chr>   <chr> <chr> <int>       <int>        <int>        <int>        <int>
#> 1 Afghan… AF    AFG    1980          NA           NA           NA           NA
#> 2 Afghan… AF    AFG    1981          NA           NA           NA           NA
#> 3 Afghan… AF    AFG    1982          NA           NA           NA           NA
#> 4 Afghan… AF    AFG    1983          NA           NA           NA           NA
#> 5 Afghan… AF    AFG    1984          NA           NA           NA           NA
#> 6 Afghan… AF    AFG    1985          NA           NA           NA           NA
#> # … with 7,234 more rows, and 52 more variables: new_sp_m4554 <int>,
#> #   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>

对该数据进行清洗的代码为

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)

如果大家对上面代码有不理解的地方可以阅读下r4ds的英文版:https://r4ds.had.co.nz/tidy-data.html#case-study

如果还有不理解的地方可以私信,小编看到后会回复。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容