以下图所示的成绩表为例,这种每个样本的信息占据一行的记录方式就是宽数据,它看着非常直观,也是录入数据常使用的形式。
宽数据示意
长数据则是另外一种记录方式,如下图所示,姓名+班级构成了样本标识(表中有重名,仅使用姓名无法唯一确定样本),而所有科目以及总分的成绩则被折叠放置在同一栏,并使用新变量“科目”作为变量标识。这种记录方式虽然看起来不太直观,但有时在进行可视化、数学建模等处理时会很方便。
长数据示意
从外部导入宽数据:
library(readxl)
chji <- read_xlsx("45-1.xlsx", sheet = 1)
chji
## # A tibble: 4 x 6
## 姓名 班级 语文 数学 英语 总分
## <chr> <chr> <dbl> <dbl> <dbl> <dbl>
## 1 张三 1班 90 98 72 260
## 2 李四 1班 81 70 90 241
## 3 王五 2班 78 88 82 248
## 4 李四 2班 92 86 NA 178
在R中有一些函数可以实现两种数据形式的相互转换。
gather
/spread
这两个函数来自tidyr
工具包。gather
函数将宽数据转换为长数据,spread
函数将长数据转换为宽数据。
gather()
函数的语法结构如下:
# wider to longer
gather(data, key = "key",
value = "value", ...,
na.rm = FALSE, convert = FALSE, factor_key = FALSE)
- data:待转换的宽数据;
- key:新标识变量的名称,相当于上文的“科目”;
- value:放置数据的变量名称,相当于上文的“成绩”;
- ...:需要折叠的变量。可以使用符号
-
可以反向选择变量。
library(tidyr)
chji_long <- gather(chji, key = "科目", value = "成绩",
-`姓名`, -`班级`)
chji_long
## # A tibble: 16 x 4
## 姓名 班级 科目 成绩
## <chr> <chr> <chr> <dbl>
## 1 张三 1班 语文 90
## 2 李四 1班 语文 81
## 3 王五 2班 语文 78
## 4 李四 2班 语文 92
## 5 张三 1班 数学 98
## 6 李四 1班 数学 70
## 7 王五 2班 数学 88
## 8 李四 2班 数学 86
## 9 张三 1班 英语 72
## 10 李四 1班 英语 90
## 11 王五 2班 英语 82
## 12 李四 2班 英语 NA
## 13 张三 1班 总分 260
## 14 李四 1班 总分 241
## 15 王五 2班 总分 248
## 16 李四 2班 总分 178
spread()
spread
函数是 gather
函数的逆向操作:
# longer to wider
spread(data, key, value,
fill = NA, convert = FALSE,
drop = TRUE, sep = NULL)
chji_width <- spread(chji_long, key = "科目",
value = "成绩")
chji_width
## # A tibble: 4 x 6
## 姓名 班级 数学 英语 语文 总分
## <chr> <chr> <dbl> <dbl> <dbl> <dbl>
## 1 李四 1班 70 90 81 241
## 2 李四 2班 86 NA 92 178
## 3 王五 2班 88 82 78 248
## 4 张三 1班 98 72 90 260
表中2班的李四的英语成绩为缺失值,使用fill
参数可以设置缺失值的代替值,这里可以认为其缺考,将该成绩记为0,即fill = 0
:
chji_width2 <- spread(chji_long, key = "科目",
value = "成绩", fill = 0)
chji_width2
## # A tibble: 4 x 6
## 姓名 班级 数学 英语 语文 总分
## <chr> <chr> <dbl> <dbl> <dbl> <dbl>
## 1 李四 1班 70 90 81 241
## 2 李四 2班 86 0 92 178
## 3 王五 2班 88 82 78 248
## 4 张三 1班 98 72 90 260
pivot_longer
/pivot_wider
这两个函数也来自tidyr
包,工具包的作者可能希望采用更规整的函数命名方式来代替gather
和spread
。但个人认为它们的参数不如后者直观:
# wider to longer
pivot_longer(data, cols,
names_to = "name", names_prefix = NULL,
names_sep = NULL, names_pattern = NULL,
names_ptypes = list(), names_transform = list(),
names_repair = "check_unique",
values_to = "value", values_drop_na = FALSE,
values_ptypes = list(),
values_transform = list(), ...)
# longer to wider
pivot_wider(data, id_cols = NULL,
names_from = name, names_prefix = "",
names_sep = "_", names_glue = NULL,
names_sort = FALSE,
names_repair = "check_unique",
values_from = value, values_fill = NULL,
values_fn = NULL, ...)
以下代码分别与gather
和spread
函数的实现方式等效:
chji_long <- pivot_longer(chji, cols = -c(`姓名`, `班级`),
names_to = "科目", values_to = "成绩")
chji_width <- pivot_wider(chji_long, names_from = "科目",
values_from = "成绩")
melt
/cast
melt
和cast
函数来自reshape
工具包(或它的升级版reshape2
),该包和tidyr
工具包出自同一作者,这两个函数也早于spread
和 gather
而出现。
spread
和 gather
函数作为后来者,本身就是melt
和cast
函数的重塑,使用起来更方便,但也省略了部分功能。
melt()
# wider to longer
## data.frame
melt(data, id.vars, measure.vars,
variable_name = "variable", na.rm = !preserve.na,
preserve.na = TRUE, ...)
## array
melt(data, varnames = names(dimnames(data)), ...)
## list
melt(data, ..., level = 1)
melt
函数的功能与gather
函数一样,但在用法上有所区别:
第一,melt
函数是通过id.vars
和measure.vars
分别指定标识变量和折叠变量名,而gather
函数通过...
指定折叠变量,但也可以通过在变量前加符号-
来指定标识变量;
第二,melt
函数指定变量需要加双引号""
,而gather
函数无需如此;
第三,在reshape
工具包中,melt
函数只能处理data.frame数据结构,而不能处理tibble数据结构。不过reshape2
工具包中的melt
函数也能处理tibble数据结构。
通过readxl
工具包导入的数据结构默认为tibble,需要进行格式转换:
library(reshape)
chji2 <- as.data.frame(chji)
以下两种书写方式等效:
chji_long <- melt(chji2, id.vars = c("姓名", "班级"))
chji_long <- melt(chji2, measure.vars = c("语文", "数学",
"英语", "总分"),
variable_name = "科目")
cast()
cast
函数与spread
函数类似,但功能更多些:
# longer to wider
cast(data, formula = ... ~ variable,
fun.aggregate = NULL, ...,
margins = FALSE, subset = TRUE,
df=FALSE, fill=NULL, add.missing=FALSE,
value = guess_value(data))
cast
函数的formula
参数是通过表达式的形式y1 + y2 + ... ~ x1 + x2 + ...
进行数据转换的:
~
左边的变量是标识变量;
~
右边的变量是存放折叠变量的标识;
不在表达式中的变量是汇总变量,fun.aggregate
参数指定汇总函数。
当标识变量能够唯一指定样本时,cast
函数实现长数据向宽数据转换:
cast(chji_long, `姓名` + `班级` ~ `科目`)
## 姓名 班级 语文 数学 英语 总分
## 1 李四 1班 81 70 90 241
## 2 李四 2班 92 86 NA 178
## 3 王五 2班 78 88 82 248
## 4 张三 1班 90 98 72 260
当标识变量不能唯一指定样本时,在形式转换的同时还可以实现分类汇总功能。如计算各班的各科和总分的平均分:
cast(chji_long, `班级` ~ `科目`, mean,
na.rm = T)
## 班级 语文 数学 英语 总分
## 1 1班 85.5 84 81 250.5
## 2 2班 85.0 87 82 213.0
使用reshape2
工具包不需要对原始数据进行格式转换:
library(reshape2)
melt(chji, id.vars = c("姓名", "班级"))
总结
在这三对函数中,gather
和spread
函数目前还是首选,但未来可能会被pivot_longer
和pivot_wider
函数取代;在涉及分类汇总时,可以考虑使用cast
函数。