本次整理内容参考 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点:
- 每个变量必须有自己的一列
- 每个观测值必须有自己的一行
- 每个值必须有自己的一个单元格
这样看得话,上述数据框中只有table1是整洁的
三、Pivoting
分析数据经常遇到这两种情况:
- 一个变量可能分布在多个列中。如table4a 和 table4b
- 一项观测值可能分散在多行中。 如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_longer和pivot_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
如果还有不理解的地方可以私信,小编看到后会回复。