2.3 数据连接
数据分析经常会涉及相互关联的多个数据表,称为关系数据库。关系数据库通用语言是SQL(结构化查询语言),dplyr 包提供了一系列类似SQL 语法的函数,可以很方便地操作关系数据库。
一个项目的数据,通常都是用若干数据表分别存放,它们之间通过‘‘键” 连接在一起,根据数据分析的需要,通过键匹配进行数据连接。
例如,纽约机场航班数据的关系结构:
比如,想要考察天气状况对航班的影响,就需要先将数据表flights 和wheather 根据其键值匹配连接为一个新数据表。
键列(可以不止1 列),能够唯一识别自己或别人数据表的每一个观测。要判断某(些)列是否是键列,可以先用count() 计数,再看是否没有n > 1 出现:
load("datas/planes.rda")
planes %>%
count(tailnum) %>%
filter(n > 1)
## # A tibble: 0 x 2
## # ... with 2 variables: tailnum <chr>, n <int>
load("datas/weather.rda")
weather %>%
count(year, month, day, hour, origin) %>%
filter(n > 1)
## # A tibble: 0 x 6
## # ... with 6 variables: year <int>, month <int>, day <int>,
## # hour <int>, origin <chr>, n <int>
注:不唯一匹配的列,也可以作为键列进行数据连接,只是当有‘‘一对多’’ 关系时,会按‘‘多’’ 重复生成观测,有时候这恰好是需要的。
2.3.1 合并行与合并列
合并数据框最基本的方法是:
- 合并行:下方堆叠新行,根据列名匹配列,注意列名相同,否则作为新列(NA 填充);
- 合并列:右侧拼接新列,根据位置匹配行,行数必须相同。
分别用dplyr 包中的bind_rows() 和bind_cols() 实现。
bind_rows(
sample_n(iris, 2),
sample_n(iris, 2),
sample_n(iris, 2)
)
## Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1 4.4 3.0 1.3 0.2 setosa
## 2 6.5 3.0 5.8 2.2 virginica
## 3 6.4 2.7 5.3 1.9 virginica
## 4 5.1 3.8 1.6 0.2 setosa
## 5 5.5 2.6 4.4 1.2 versicolor
## 6 6.1 2.9 4.7 1.4 versicolor
one = mtcars[1:4, 1:3]
two = mtcars[1:4, 4:5]
bind_cols(one, two)
## mpg cyl disp hp drat
## Mazda RX4 21.0 6 160 110 3.90
## Mazda RX4 Wag 21.0 6 160 110 3.90
## Datsun 710 22.8 4 108 93 3.85
## Hornet 4 Drive 21.4 6 258 110 3.08
利用purrr 包中map_dfr() 和map_dfc() 函数可以在批量读入数据的同时做合并行/合并列。
2.3.2 根据值匹配合并数据框
只介绍最常用的六种合并:左连接、右连接、全连接、内连接、半连接、反连接,前四种连接又称为修改连接,后两种连接又称为过滤连接。
这六种连接对应的六个接口一致的函数,其基本格式为:
left_join(x, y, by)
right_join(x, y, by)
full_join(x, y, by)
inner_join(x, y, by)
semi_join(x, y, by)
anti_join(x, y, by)
下面以dplyr 包自带的两个小数据集进行演示:
band = band_members
band
## # A tibble: 3 x 2
## name band
## <chr> <chr>
## 1 Mick Stones
## 2 John Beatles
## 3 Paul Beatles
instrument = band_instruments
instrument
## # A tibble: 3 x 2
## name plays
## <chr> <chr>
## 1 John guitar
## 2 Paul bass
## 3 Keith guitar
- 左连接:left_join()
外连接至少保留一个数据表中的所有观测,分为左连接、右连接、全连接,其中最常用的是左连
接:保留x 所有行,合并匹配的y 中的列。
band %>%
left_join(instrument, by = "name")
## # A tibble: 3 x 3
## name band plays
## <chr> <chr> <chr>
## 1 Mick Stones <NA>
## 2 John Beatles guitar
## 3 Paul Beatles bass
若两个表中的键列列名不同,用by = c("name1" = "name2");若根据多个键列匹配,用by =c("name1", "name2").
- 右连接:right_join()
保留y 所有行,合并匹配的x 中的列。
band %>%
right_join(instrument, by = "name")
## # A tibble: 3 x 3
## name band plays
## <chr> <chr> <chr>
## 1 John Beatles guitar
## 2 Paul Beatles bass
## 3 Keith <NA> guitar
- 全连接:full_join()
保留x 和y 中的所有行,合并匹配的列。
band %>%
full_join(instrument, by = "name")
## # A tibble: 4 x 3
## name band plays
## <chr> <chr> <chr>
## 1 Mick Stones <NA>
## 2 John Beatles guitar
## 3 Paul Beatles bass
## 4 Keith <NA> guitar
- 内连接:inner_join()
内连接是保留两个数据表中所共有的观测:只保留x 中与y 匹配的行,合并匹配的y 中的列。
band %>%
inner_join(instrument, by = "name")
## # A tibble: 2 x 3
## name band plays
## <chr> <chr> <chr>
## 1 John Beatles guitar
## 2 Paul Beatles bass
- 半连接:semi_join()
根据在y 中,来筛选x 中的行。
band %>%
semi_join(instrument, by = "name")
## # A tibble: 2 x 2
## name band
## <chr> <chr>
## 1 John Beatles
## 2 Paul Beatles
- 反连接:anti_join()
根据不在y 中,来筛选x 中的行。
band %>%
anti_join(instrument, by = "name")
## # A tibble: 1 x 2
## name band
## <chr> <chr>
## 1 Mick Stones
前面讨论的都是连接两个数据表,若要连接多个数据表,将连接两个数据表的函数结合purrr 包中的reduce() 使用即可。
比如achieves 文件夹有3 个Excel 文件:
需要批量读取它们,再依次做全连接(做其他连接也是类似的)。reduce() 可以实现先将前两个表做全连接,再将结果表与第三个表做全连接(更多表就依次这样做下去):
files = list.files("datas/achieves/", pattern = "xlsx", full.names = TRUE)
map(files, read_xlsx) %>%
reduce(full_join, by = " 人名") # 读入并依次做全连接
## # A tibble: 7 x 4
## 人名`3月业绩` `4月业绩` `5月业绩`
## <chr> <dbl> <dbl> <dbl>
## 1 小明80 NA NA
## 2 小李85 NA 80
## 3 小张90 50 NA
## 4 小红NA 70 90
## 5 小白NA 60 NA
## 6 小王NA 40 NA
## # ... with 1 more row
若还是上述数据,但是在一个工作簿的多个工作表中,批量读取并依次做全连接:
path = "datas/3-5 月业绩.xlsx"
map(excel_sheets(path),
~ read_xlsx(path, sheet = .x)) %>%
reduce(full_join, by = " 人名") # 读入并依次做全连接
2.3.3 集合运算
集合运算有时候很有用,都是针对所有行,通过比较变量的值来实现。这就需要数据表x 和y 具有相同的变量,并将观测看成是集合中的元素:
intersect(x, y) # 返回x 和y 共同包含的观测;
union(x, y) # 返回x 和y 中所有的(唯一) 观测;
setdiff(x, y) # 返回在x 中但不在y 中的观测。
2.4 数据重塑
2.4.1 什么是整洁数据?
采用Hadley 的表述,脏的/不整洁的数据往往具有如下特点:
- 首行(列名) 是值,不是变量名
- 多个变量放在一列
- 变量既放在行也放在列
- 多种类型的观测单元在同一个单元格
- 一个观测单元放在多个表
而整洁数据具有如下特点: - 每个变量构成一列
- 每个观测构成一行
-
每个观测的每个变量值构成一个单元格
整洁数据的3 个特点
tidyverse 系列包中的函数操作的都是这种整洁数据框,而不整洁数据,首先需要变成整洁数据,这就是数据重塑。
数据重塑主要包括长宽表转化、拆分/合并列。长宽表转化最初是reshape2 包的melt() 和cast(); 后来又发展到早期tidyr 包的gather() 和spread(), 现在tidyr 1.0 之后提供了更加易用的pivot_longer() 和pivot_wider().
先看一个不整洁数据与整洁数据对比的例子:
dt = tribble(
~observation, ~A_count, ~B_count, ~A_dbh, ~B_dbh,
"Richmond(Sam)", 7, 2, 100, 110,
"Windsor(Ash)", 10, 5, 80, 87,
"Bilpin(Jules)", 5, 8, 95, 90)
knitr::kable(dt, align="c")
该数据框不整洁,表现在:
- observation 列有两个变量数据
- 列名中的A/B 应是分类变量species 的两个水平值
- 测量值列count 和dbh 应各占1 列,而不是2 列
下面借助tidyr 包中数据重塑函数,将其变成整洁数据,读者可以学完本节内容再回头看这段代码:
tidy_dt = dt %>%
pivot_longer(-observation,
names_to = c("speices", ".value"),
names_sep = "_") %>%
separate(observation, into = c("site", "surveyor"))
knitr::kable(tidy_dt, align = "c")
这里的关键是,要学会区分哪些是变量、观测、值。
2.4.2 宽表变长表
宽表的特点是:表比较宽,本来该是‘‘值” 的,却出现在‘‘变量(名)” 中。
这就需要给它变到‘‘值” 中,新起个列名存为一列,这就是所谓的宽表变长表。
用tidyr 包中的pivot_longer() 函数来实现宽表变长表,其基本格式为:
- pivot_longer(data, cols, names_to, values_to, values_drop_na, ...)
- data: 要重塑的数据框
- cols: 用选择列语法选择要变形的列
- names_to: 为存放变形列的列名中的’’ 值’’,指定新列名
- values_to: 为存放变形列中的’’ 值’’,指定新列名
- values_drop_na: 是否忽略变形列中的NA
若变形列的列名除了’’ 值’’ 外,还包含前缀、变量名+ 分隔符、正则表达式分组捕获模式,则可以借助参数names_prefix, names_sep, names_pattern 来提取出‘‘值’’。
- 每一行只有1 个观测的情形
也是最简单的情形,以分省年度GDP 数据为例,每一行只有一个观测,关于一个省份的信息。
df = read_csv("datas/分省年度GDP.csv")
df
## # A tibble: 4 x 4
## 地区 `2019年` `2018年` `2017年`
## <chr> <dbl> <dbl> <dbl>
## 1 北京市 35371. 33106. 28015.
## 2 天津市 14104. 13363. 18549.
## 3 河北省 35105. 32495. 34016.
## 4 黑龙江省 13613. 12846. 15903.
要变形的列是除了地区列之外的列
变量(名)中的2019 年、2018 年等是年份的值,需要作为1 列‘‘值” 来存放,新起一个列名年份
2019 年、2018 年等列中的值,属于同一个变量GDP,新起一个列名GDP 来存放:
df %>%
pivot_longer(-地区, names_to = " 年份", values_to = "GDP")
## # A tibble: 12 x 3
## 地区 年份 GDP
## <chr> <chr> <dbl>
## 1 北京市 2019年 35371.
## 2 北京市 2018年 33106.
## 3 北京市 2017年 28015.
## 4 天津市 2019年 14104.
## 5 天津市 2018年 13363.
## 6 天津市 2017年 18549.
## # ... with 6 more rows
- 每一行有多个观测的情形
以如下的family 数据集为例,每一行有两个观测,关于child1 和child2 的信息。
load("datas/family.rda")
knitr::kable(family, align = "c")
要变形的列是除了family 列之外的列;
变形列的列名以“_” 分割为两部分,用names_to 指定这两部分的用途:“.value” 指定第一部分不用管将继续留作列名,而第二部分,即包含“child1’’、“child2’’,作为新变量child 的‘‘值”
忽略变形列中的缺失值
family %>%
pivot_longer(-family,
names_to = c(".value", "child"),
names_sep = "_",
values_drop_na = TRUE)
## # A tibble: 9 x 4
## family child dob gender
## <int> <chr> <date> <int>
## 1 1 child1 1998-11-26 1
## 2 1 child2 2000-01-29 2
## 3 2 child1 1996-06-22 2
## 4 3 child1 2002-07-11 2
## 5 3 child2 2004-04-05 2
## 6 4 child1 2004-10-10 1
## # ... with 3 more rows
再来看一个数学建模报名信息整理的实例:每一行有3 个观测,关于3 名队员的信息,变成每一行只有1 名队员的信息。用到names_pattern 参数和正则表达式分组捕获。
df = read_csv("datas/参赛队信息.csv")
df
## # A tibble: 2 x 6
## 队员1姓名队员1专业队员2姓名队员2专业队员3姓名队员3专业
## <chr> <chr> <chr> <chr> <chr> <chr>
## 1 张三数学李四英语王五统计学
## 2 赵六经济学钱七数学孙八计算机
df %>%
pivot_longer(everything(),
names_to = c(" 队员", ".value"),
names_pattern = "(.*\\d)(.*)")
## # A tibble: 6 x 3
## 队员姓名专业
## <chr> <chr> <chr>
## 1 队员1 张三数学
## 2 队员2 李四英语
## 3 队员3 王五统计学
## 4 队员1 赵六经济学
## 5 队员2 钱七数学
## 6 队员3 孙八计算机
2.4.3 长表变宽表
长表的特点是:表比较长。
有时候需要将分类变量的若干水平值,变成变量(列名)。这就是长表变宽表,它与宽表变长表正好相反(二者互逆)。
用tidyr 包中的pivot_wider() 函数来实现长表变宽表,其基本格式为:
pivot_wider(data, id_cols, names_from, values_from, values_fill, ...)
- data: 要重塑的数据框
- id_cols: 唯一识别观测的列,默认是除了names_from 和values_from 指定列之外的列
- names_from: 指定列名来自哪个变量列
- values_from: 指定列’’ 值’’ 来自哪个变量列
- values_fill: 若变宽后单元格值缺失,设置用何值填充
另外还有若干帮助修复列名的参数:names_prefix, names_sep, names_glue.
最简单的情形是,只有一个列名列和一个值列,比如animals 数据集:
load("datas/animals.rda")
animals
## # A tibble: 228 x 3
## Type Year Heads
## <chr> <dbl> <dbl>
## 1 Sheep 2015 24943.
## 2 Cattle 1972 2189.
## 3 Camel 1985 559
## 4 Camel 1995 368.
## 5 Camel 1997 355.
## 6 Goat 1977 4411.
## # ... with 222 more rows
用names_from 指定列名来自哪个变量;values_from 指定‘‘值” 来自哪个变量:
animals %>%
pivot_wider(names_from = Type, values_from = Heads, values_fill = 0)
## # A tibble: 48 x 6
## Year Sheep Cattle Camel Goat Horse
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 2015 24943. 3780. 368. 23593. 3295.
## 2 1972 13716. 2189. 625. 4338. 2239.
## 3 1985 13249. 2408. 559 4299. 1971
## 4 1995 0 3317. 368. 8521. 2684.
## 5 1997 14166. 3613. 355. 10265. 2893.
## 6 1977 13430. 2388. 609 4411. 2104.
## # ... with 42 more rows
还可以有多个列名列或多个值列,比如us_rent_income 数据集有两个值列:
us_rent_income
## # A tibble: 104 x 5
## GEOID NAME variable estimate moe
## <chr> <chr> <chr> <dbl> <dbl>
## 1 01 Alabama income 24476 136
## 2 01 Alabama rent 747 3
## 3 02 Alaska income 32940 508
## 4 02 Alaska rent 1200 13
## 5 04 Arizona income 27517 148
## 6 04 Arizona rent 972 4
## # ... with 98 more rows
us_rent_income %>%
pivot_wider(names_from = variable, values_from = c(estimate, moe))
## # A tibble: 52 x 6
## GEOID NAME estimate_income estimate_rent moe_income moe_rent
## <chr> <chr> <dbl> <dbl> <dbl> <dbl>
## 1 01 Alabama 24476 747 136 3
## 2 02 Alaska 32940 1200 508 13
## 3 04 Arizona 27517 972 148 4
## 4 05 Arkansas 23789 709 165 5
## 5 06 California 29454 1358 109 3
## 6 08 Colorado 32401 1125 109 5
## # ... with 46 more rows
长变宽时,经常会遇到两个问题:
- 长变宽正常会压缩行,为什么行数没变呢?
- 值不能被唯一识别,输出将包含列表列
比如,现有这样的数据:
df = tibble(
x = 1:6,
y = c("A","A","B","B","C","C"),
z = c(2.13,3.65,1.88,2.30,6.55,4.21))
df
## # A tibble: 6 x 3
## x y z
## <int> <chr> <dbl>
## 1 1 A 2.13
## 2 2 A 3.65
## 3 3 B 1.88
## 4 4 B 2.3
## 5 5 C 6.55
## 6 6 C 4.21
想让y 列提供变量名,z 列提供值,做长变宽,但是
df %>%
pivot_wider(names_from = y, values_from = z)
## # A tibble: 6 x 4
## x A B C
## <int> <dbl> <dbl> <dbl>
## 1 1 2.13 NA NA
## 2 2 3.65 NA NA
## 3 3 NA 1.88 NA
## 4 4 NA 2.3 NA
## 5 5 NA NA 6.55
## 6 6 NA NA 4.21
这就是前面说到的第一个问题,本来该压缩成2 行,但是由于x 列的存在,无法压缩,只能填充NA,这不是想要的效果。所以,在长变宽时要注意,是不能带着类似x 列这种唯一识别各行的ID 列的。
那去掉x 列,重新做长变宽,但是又遇到了前面说的第二个问题:
df = df[-1]
df %>%
pivot_wider(names_from = y, values_from = z)
## # A tibble: 1 x 3
## A B C
## <list> <list> <list>
## 1 <dbl [2]> <dbl [2]> <dbl [2]>
值不能唯一识别,结果变成了列表列,同样不是想要的结果。
这里的值唯一识别,指的是各分组(A 组B 组C 组)组内元素必须要能唯一识别。咱们来增加一个各组的唯一识别列:
df = df %>%
group_by(y) %>%
mutate(n = row_number())
df
## # A tibble: 6 x 3
## # Groups: y [3]
## y z n
## <chr> <dbl> <int>
## 1 A 2.13 1
## 2 A 3.65 2
## 3 B 1.88 1
## 4 B 2.3 2
## 5 C 6.55 1
## 6 C 4.21 2
这才是能够长变宽的标准数据,再来做长变宽:
df %>%
pivot_wider(names_from = y, values_from = z)
## # A tibble: 2 x 4
## n A B C
## <int> <dbl> <dbl> <dbl>
## 1 1 2.13 1.88 6.55
## 2 2 3.65 2.3 4.21
这回是想要的结果,新增加的列n 若不想要,删除列即可。
回头再看一下,所谓的各组内值唯一识别,比如A 组有两个数2.13 和3.65, 给了它们唯一识别: n= 1 和n = 2, 当然1 和2 换成其他的两个不同值也是一样的,这样就知道谁作为第一个样本(行),谁作为第二个样本(行)。否则两个数无法区分,只能放到一个列表里了,就是前面的错误结果+ 警告。
最后再看一个特殊的实例:不规则通讯录整理。
contacts = tribble( ~field, ~value,
" 姓名", " 张三",
" 公司", " 百度",
" 姓名", " 李四",
" 公司", " 腾讯",
"Email", "Lisi@163.com",
" 姓名", " 王五")
contacts = contacts %>%
mutate(ID = cumsum(field == " 姓名"))
contacts
## # A tibble: 6 x 3
## field value ID
## <chr> <chr> <int>
## 1 姓名 张三 1
## 2 公司 百度 1
## 3 姓名 李四 2
## 4 公司 腾讯 2
## 5 Email Lisi@163.com 2
## 6 姓名 王五 3
contacts %>%
pivot_wider(names_from = field, values_from = value)
## # A tibble: 3 x 4
## ID 姓名公司Email
## <int> <chr> <chr> <chr>
## 1 1 张三 百度<NA>
## 2 2 李四 腾讯Lisi@163.com
## 3 3 王五 <NA> <NA>
2.4.4 拆分列与合并列
拆分列与合并列也是正好相反(二者互逆)。
用separate() 函数来拆分列,其基本语法为:
separate(data, col, into, sep, ...)
col: 要拆分的列
into: 拆开的新列,
sep: 指定根据什么分隔符拆分
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
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
还有separate_rows() 函数,可对不定长的列进行分列,并按行堆叠放置:
df = tibble(Class = c("1 班", "2 班"),
Name = c(" 张三,李四,王五", " 赵六,钱七"))
df
## # A tibble: 2 x 2
## Class Name
## <chr> <chr>
## 1 1班张三,李四,王五
## 2 2班赵六,钱七
df1 = df %>%
separate_rows(Name, sep = ",")
df1
## # A tibble: 5 x 2
## Class Name
## <chr> <chr>
## 1 1班张三
## 2 1班李四
## 3 1班王五
## 4 2班赵六
## 5 2班钱七
若要逆操作还原回去:
df1 %>%
group_by(Class) %>%
summarise(Name = str_c(Name, collapse = ","))
用unite() 函数来合并列,其基本语法为:
unite(data, col, sep, ...)
col: 要合并的列
sep: 指定合并各列添加的分隔符
table5
## # 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
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
最后看一个综合示例:重塑世界银行人口数据。
world_bank_pop
## # A tibble: 1,056 x 20
## country indicator `2000` `2001` `2002` `2003` `2004` `2005`
## <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 ABW SP.URB.TOTL 42444 4.30e4 4.37e4 4.42e4 4.47e+4 4.49e+4
## 2 ABW SP.URB.GROW 1.18 1.41e0 1.43e0 1.31e0 9.51e-1 4.91e-1
## 3 ABW SP.POP.TOTL 90853 9.29e4 9.50e4 9.70e4 9.87e+4 1.00e+5
## 4 ABW SP.POP.GROW 2.06 2.23e0 2.23e0 2.11e0 1.76e+0 1.30e+0
## 5 AFG SP.URB.TOTL 4436299 4.65e6 4.89e6 5.16e6 5.43e+6 5.69e+6
## 6 AFG SP.URB.GROW 3.91 4.66e0 5.13e0 5.23e0 5.12e+0 4.77e+0
## # ... with 1,050 more rows, and 12 more variables: 2006 <dbl>,
## # 2007 <dbl>, 2008 <dbl>, 2009 <dbl>, 2010 <dbl>, 2011 <dbl>,
## # 2012 <dbl>, 2013 <dbl>, 2014 <dbl>, 2015 <dbl>, 2016 <dbl>,
## # 2017 <dbl>
先从最显然的入手:年份跨过了多个列,应该宽表变长表:
pop2 = world_bank_pop %>%
pivot_longer(`2000`:`2017`, names_to = "year", values_to = "value")
pop2
## # A tibble: 19,008 x 4
## country indicator year value
## <chr> <chr> <chr> <dbl>
## 1 ABW SP.URB.TOTL 2000 42444
## 2 ABW SP.URB.TOTL 2001 43048
## 3 ABW SP.URB.TOTL 2002 43670
## 4 ABW SP.URB.TOTL 2003 44246
## 5 ABW SP.URB.TOTL 2004 44669
## 6 ABW SP.URB.TOTL 2005 44889
## # ... with 19,002 more rows
再来考虑indicator 变量:
pop2 %>%
count(indicator)
## # A tibble: 4 x 2
## indicator n
## <chr> <int>
## 1 SP.POP.GROW 4752
## 2 SP.POP.TOTL 4752
## 3 SP.URB.GROW 4752
## 4 SP.URB.TOTL 4752
这里,SP.POP.GROW 为人口增长率,SP.POP.TOTAL 为总人口,SP.URB.* 也类似,只是城市的。将该列值拆分为两个变量:area(URB, POP)和variable(GROW, TOTL):
pop3 = pop2 %>%
separate(indicator, c(NA, "area", "variable"))
pop3
## # A tibble: 19,008 x 5
## country area variable year value
## <chr> <chr> <chr> <chr> <dbl>
## 1 ABW URB TOTL 2000 42444
## 2 ABW URB TOTL 2001 43048
## 3 ABW URB TOTL 2002 43670
## 4 ABW URB TOTL 2003 44246
## 5 ABW URB TOTL 2004 44669
## 6 ABW URB TOTL 2005 44889
## # ... with 19,002 more rows
最后,再将分类变量variable 的水平值变为列名(长表变宽表),就完成重塑:
pop3 %>%
pivot_wider(names_from = variable, values_from = value)
## # A tibble: 9,504 x 5
## country area year TOTL GROW
## <chr> <chr> <chr> <dbl> <dbl>
## 1 ABW URB 2000 42444 1.18
## 2 ABW URB 2001 43048 1.41
## 3 ABW URB 2002 43670 1.43
## 4 ABW URB 2003 44246 1.31
## 5 ABW URB 2004 44669 0.951
## 6 ABW URB 2005 44889 0.491
## # ... with 9,498 more rows
2.4.5 方形化
方形化(Rectangling)是将一个深度嵌套的列表(通常来自JSON 或XML)驯服成一个整齐的行和列的数据集。主要通过组合使用以下函数实现:
- unnest_longer():提取列表列的每个元,再按行存放(横向展开)
- unnest_wider():提取列表列的每个元,再按列存放(纵向展开)
- unnest_auto():提取列表列的每个元,猜测按行或按列存放
- hoist():类似unnest_wider(),但只取出选择的组件,且可以深入多个层
以权力游戏角色数据集got_chars 为例,它是个长度为30 的列表,里面又嵌套很多列表。一种技巧是,先把它创建成tibble 方便后续操作:
library(repurrrsive) # 使用got_chars 数据集
chars = tibble(char = got_chars)
chars
## # A tibble: 30 x 1
## char
## <list>
## 1 <named list [18]>
## 2 <named list [18]>
## 3 <named list [18]>
## 4 <named list [18]>
## 5 <named list [18]>
## 6 <named list [18]>
## # ... with 24 more rows
char 是嵌套列表列,每个元素又是长度为18 的列表,先横向展开它们:
chars1 = chars %>%
unnest_wider(char)
chars1
## # A tibble: 30 x 18
## url id name gender culture born died alive titles aliases
## <chr> <int> <chr> <chr> <chr> <chr> <chr> <lgl> <list> <list>
## 1 https~ 1022 Theon~ Male "Ironb~ "In ~ "" TRUE <chr ~ <chr [~
## 2 https~ 1052 Tyrio~ Male "" "In ~ "" TRUE <chr ~ <chr [~
## 3 https~ 1074 Victa~ Male "Ironb~ "In ~ "" TRUE <chr ~ <chr [~
## 4 https~ 1109 Will Male "" "" "In ~ FALSE <chr ~ <chr [~
## 5 https~ 1166 Areo ~ Male "Norvo~ "In ~ "" TRUE <chr ~ <chr [~
## 6 https~ 1267 Chett Male "" "At ~ "In ~ FALSE <chr ~ <chr [~
## # ... with 24 more rows, and 8 more variables: father <chr>,
## # mother <chr>, spouse <chr>, allegiances <list>, books <list>,
## # povBooks <list>, tvSeries <list>, playedBy <list>
生成一个表,以匹配人物角色和他们的外号,name 直接选择列,外号来自列表列titles, 纵向展开它:
chars1 %>%
select(name, title = titles) %>%
unnest_longer(title)
## # A tibble: 60 x 2
## name title
## <chr> <chr>
## 1 Theon Greyjoy Prince of Winterfell
## 2 Theon Greyjoy Captain of Sea Bitch
## 3 Theon Greyjoy Lord of the Iron Islands (by law of the green la~
## 4 Tyrion Lannister Acting Hand of the King (former)
## 5 Tyrion Lannister Master of Coin (former)
## 6 Victarion Greyjoy Lord Captain of the Iron Fleet
## # ... with 54 more rows
或者改用hoist() 直接从内层提取想要的列,再对列表列title 做纵向展开:
chars %>%
hoist(char, name = "name", title = "titles") %>%
unnest_longer(title)
## # A tibble: 60 x 3
## name title char
## <chr> <chr> <list>
## 1 Theon Greyjoy Prince of Winterfell <named list~
## 2 Theon Greyjoy Captain of Sea Bitch <named list~
## 3 Theon Greyjoy Lord of the Iron Islands (by law of~ <named list~
## 4 Tyrion Lannister Acting Hand of the King (former) <named list~
## 5 Tyrion Lannister Master of Coin (former) <named list~
## 6 Victarion Greyjoy Lord Captain of the Iron Fleet <named list~
## # ... with 54 more rows
另外,还有tibblify 包专门做嵌套列表转化为tibble 数据框。