Chapter 13 Tidy data
有这么一句俗话说到:
幸福的家庭都是相似的,不幸的家庭各有各的不幸。(列夫·托尔斯泰)
放到我们的数据里也是这样的:
整洁的数据集都是相似的,但每个凌乱的数据集都以自己的方式凌乱。(哈德利·威克姆)
一个好的数据集应该:变量在列中,观察结果在行中,值储存在单元格中;不整洁的数据,我们可以用 tidyr 包中的 pivot_longer() 和 pivot_wider() 辅助修正
library(tidyverse)
13.1 对 table1 数据分析
table1 数据集中 cases 为增长人数(单位:万)。这份数据展现得很不错,我们可以轻松绘制想要的图像。
table1#> # A tibble: 6 × 4
#> country year cases population
#> <chr> <dbl> <dbl> <dbl>
#> 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
ggplot(table1, aes(year, cases)) +
geom_line(aes(group = country, colour = country)) +
geom_point(aes(colour = country))
13.2 对 table2 数据整理
行中不应该出现将 cases 和 population 放在一起的现象。它们应该作为筛选变量,方便后续对 “新增” “目前总人数” 这个两个变量进行分析,即 type 拆分为列,count 跟随到新列:
table2#> # A tibble: 12 × 4
#> country year type count
#> <chr> <dbl> <chr> <dbl>
#> 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
#> 7 Brazil 2000 cases 80488
#> 8 Brazil 2000 population 174504898
#> 9 China 1999 cases 212258
#> 10 China 1999 population 1272915272
#> 11 China 2000 cases 213766
#> 12 China 2000 population 1280428583
%>%
table2 pivot_wider(names_from = type, values_from = count)
#> # A tibble: 6 × 4
#> country year cases population
#> <chr> <dbl> <dbl> <dbl>
#> 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
13.3 对 table3 数据整理
行中不应该出现将 cases 和 population 放在一起的现象,需要手动拆分:
table3#> # A tibble: 6 × 3
#> country year rate
#> <chr> <dbl> <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
# 默认情况下,将在看到非字母数字字符(即不是数字或字母的字符)的位置拆分值
%>%
table3 separate(rate, into = c("cases", "population"))
#> # A tibble: 6 × 4
#> country year cases population
#> <chr> <dbl> <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
# convert 可以自动将数据的格式进行转化。如这里的 cases 和 population 都应该是 int 类型数据
%>%
table3 separate(rate, into = c("cases", "population"), convert = TRUE)
#> # A tibble: 6 × 4
#> country year cases population
#> <chr> <dbl> <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
# 分隔符也可以手动设置
%>%
table3 separate(rate, into = c("cases", "population"), sep = "/")
#> # A tibble: 6 × 4
#> country year cases population
#> <chr> <dbl> <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
# 设置为数字表示分割位置,如这里分割为世纪 + 两位数年
%>%
table3 separate(year, into = c("century", "year"), sep = 2)
#> # A tibble: 6 × 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
13.4 对 table4a & table4b 数据整理
列中不应该出现将 1999 和 2000 分开的现象。它们应该作为筛选变量,方便后续对 “年份” 这个总变量进行分析,即旧变量合并为 year,值合并为 cases:
<- table4a %>%
table4a_new pivot_longer(
c(`1999`, `2000`), # 指定修改列。注意这里变量是以数字开头,所以有必要用 `` 引用(当然数字开头是不规范的)
names_to = "year", # 上述变量名汇总到 year 上
values_to = "cases" # 上述对应数值汇总到 cases 上
)<- table4b %>%
table4b_new pivot_longer(
c(`1999`, `2000`),
names_to = "year",
values_to = "population"
)# 最后合并两个表的数据内容
#* dplyr::left_join()
left_join(table4a_new, table4b_new)
#> Joining with `by = join_by(country, year)`
#> # A tibble: 6 × 4
#> country year cases population
#> <chr> <chr> <dbl> <dbl>
#> 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
13.5 对 table5 数据整理
我们需要手动拆分 cases 和 population 同时要将年份数据进行合并:
%>%
table5 # 注意如果不声明 sep,默认加间隔符号 “_”!
unite(year_4cs, century, year, sep = "", na.rm = TRUE) %>%
separate(rate, into = c("cases", "population"), sep = "/")
#> # A tibble: 6 × 4
#> country year_4cs cases population
#> <chr> <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
13.6 对 stocks 和 treatment 的缺失数据处理
<- tibble(
stocks 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 %>%
new_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 # 这会对含 NA 的数据行剔除隐藏
%>%
) print()
#> # A tibble: 6 × 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
%>%
new_stocks complete(year, qtr) # 这会把所有隐藏的 NA 数据重新找回
#> # A tibble: 8 × 3
#> year qtr return
#> <chr> <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
<- tribble(
treatment ~person, ~treatment, ~response,
"Derrick Whitmore", 1, 7,
NA, 2, 10,
NA, 3, 9,
"Katherine Burke", 1, 4
)%>%
treatment fill(person) # 对 treatment 的 person 列进行补全处理,碰到 NA 时会将 NA 改为上一个不是 NA 的数据
#> # A tibble: 4 × 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
13.7 对 who 数据整理
who 是一个流行病统计数据集。
who#> # A tibble: 7,240 × 60
#> country iso2 iso3 year new_s…¹ new_s…² new_s…³ new_s…⁴ new_s…⁵ new_s…⁶
#> <chr> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 Afghanistan AF AFG 1980 NA NA NA NA NA NA
#> 2 Afghanistan AF AFG 1981 NA NA NA NA NA NA
#> 3 Afghanistan AF AFG 1982 NA NA NA NA NA NA
#> 4 Afghanistan AF AFG 1983 NA NA NA NA NA NA
#> 5 Afghanistan AF AFG 1984 NA NA NA NA NA NA
#> 6 Afghanistan AF AFG 1985 NA NA NA NA NA NA
#> 7 Afghanistan AF AFG 1986 NA NA NA NA NA NA
#> 8 Afghanistan AF AFG 1987 NA NA NA NA NA NA
#> 9 Afghanistan AF AFG 1988 NA NA NA NA NA NA
#> 10 Afghanistan AF AFG 1989 NA NA NA NA NA NA
#> # … with 7,230 more rows, 50 more variables: new_sp_m65 <dbl>,
#> # new_sp_f014 <dbl>, new_sp_f1524 <dbl>, new_sp_f2534 <dbl>,
#> # new_sp_f3544 <dbl>, new_sp_f4554 <dbl>, new_sp_f5564 <dbl>,
#> # new_sp_f65 <dbl>, new_sn_m014 <dbl>, new_sn_m1524 <dbl>,
#> # new_sn_m2534 <dbl>, new_sn_m3544 <dbl>, new_sn_m4554 <dbl>,
#> # new_sn_m5564 <dbl>, new_sn_m65 <dbl>, new_sn_f014 <dbl>,
#> # new_sn_f1524 <dbl>, new_sn_f2534 <dbl>, new_sn_f3544 <dbl>, …
<- who %>%
who1 pivot_longer(
cols = new_sp_m014:newrel_f65, # 将病症的种类转换成变量(key)
names_to = "key",
values_to = "cases", # 将数据转换成 cases 列
values_drop_na = TRUE # 删除 NA 数据
)
who1#> # A tibble: 76,046 × 6
#> country iso2 iso3 year key cases
#> <chr> <chr> <chr> <dbl> <chr> <dbl>
#> 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
count(who1, key, sort = TRUE) # 对不同病症人数统计
#> # A tibble: 56 × 2
#> key n
#> <chr> <int>
#> 1 new_sp_m4554 3223
#> 2 new_sp_m3544 3219
#> 3 new_sp_m5564 3218
#> 4 new_sp_m1524 3209
#> 5 new_sp_m65 3209
#> 6 new_sp_m2534 3206
#> 7 new_sp_f4554 3204
#> 8 new_sp_f2534 3200
#> 9 new_sp_f3544 3199
#> 10 new_sp_f65 3197
#> # … with 46 more rows
对病症名称进行分析得知:
前三个字母:是否包含新的或旧的结核病例
中间两个字母:结核病的类型
- rel:复发病例
- ep:肺外结核病例
- sn:无法通过肺涂片诊断的肺结核病例(涂片阴性)
- sp:可通过肺涂片诊断的肺结核病例(涂片阳性)
第六个字母:结核病患者的性别。其中 m 为男性,f 为女性
最后的数字:年龄组
- 014:0 ~ 14 岁
- 1524:15 ~ 24 岁
- 2534:25 ~ 34 岁
- 3544:35 ~ 44 岁
- 4554:45 ~ 54 岁
- 5564:55 ~ 64岁
- 65:65 岁或以上
<- who1 %>%
who2 # 使用 stringr 的 str_replace 可以进行简单的替换(当然切割时使用位置切割也可以)
mutate(key = stringr::str_replace(key, "newrel", "new_rel")) %>%
# 通过字符串 “_” 分割为是否包含新病例、结核病类型和性别年龄
separate(key, c("new", "type", "sexage"), sep = "_") %>%
# 通过位置继续分割性别和年龄
separate(sexage, c("sex", "age"), sep = 1)
who2#> # A tibble: 76,046 × 9
#> country iso2 iso3 year new type sex age cases
#> <chr> <chr> <chr> <dbl> <chr> <chr> <chr> <chr> <dbl>
#> 1 Afghanistan AF AFG 1997 new sp m 014 0
#> 2 Afghanistan AF AFG 1997 new sp m 1524 10
#> 3 Afghanistan AF AFG 1997 new sp m 2534 6
#> 4 Afghanistan AF AFG 1997 new sp m 3544 3
#> 5 Afghanistan AF AFG 1997 new sp m 4554 5
#> 6 Afghanistan AF AFG 1997 new sp m 5564 2
#> 7 Afghanistan AF AFG 1997 new sp m 65 0
#> 8 Afghanistan AF AFG 1997 new sp f 014 5
#> 9 Afghanistan AF AFG 1997 new sp f 1524 38
#> 10 Afghanistan AF AFG 1997 new sp f 2534 36
#> # … with 76,036 more rows
深度观察,我们会发现数据还有进一步的优化空间:
count(who2, new) # 可以发现这个数据集的 “是否包含新病例” 值其实全部都是 “new”,所以是不必要的数据
#> # A tibble: 1 × 2
#> new n
#> <chr> <int>
#> 1 new 76046
# 同时 “iso2” 和 “iso3” 是国家缩写,也是不需要的数据
<- who2 %>%
who3 select(-new, -iso2, -iso3)
who3#> # A tibble: 76,046 × 6
#> country year type sex age cases
#> <chr> <dbl> <chr> <chr> <chr> <dbl>
#> 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