anscombe 数据的重塑不那么笨重
less clunky reshaping of anscombe data
我试图使用 ggplot2
绘制 R 中内置的 anscombe
数据集(其中包含四个不同的小数据集,它们具有相同的相关性但 X 和 Y 之间的关系完全不同) .我试图正确重塑数据的尝试都非常丑陋。我使用了 reshape2
和 base R 的组合; Hadleyverse 2 (tidyr
/dplyr
) 或 data.table
解决方案对我来说没问题,但理想的解决方案是
- short/no重复代码
- 易于理解(与标准 #1 有点冲突)
- 尽可能少地对列号等进行硬编码
原格式:
anscombe
## x1 x2 x3 x4 y1 y2 y3 y4
## 1 10 10 10 8 8.04 9.14 7.46 6.58
## 2 8 8 8 8 6.95 8.14 6.77 5.76
## 3 13 13 13 8 7.58 8.74 12.74 7.71
## ...
## 11 5 5 5 8 5.68 4.74 5.73 6.89
所需格式:
## s x y
## 1 1 10 8.04
## 2 1 8 6.95
## ...
## 44 4 8 6.89
这是我的尝试:
library("reshape2")
ff <- function(x,v)
setNames(transform(
melt(as.matrix(x)),
v1=substr(Var2,1,1),
v2=substr(Var2,2,2))[,c(3,5)],
c(v,"s"))
f1 <- ff(anscombe[,1:4],"x")
f2 <- ff(anscombe[,5:8],"y")
f12 <- cbind(f1,f2)[,c("s","x","y")]
现在剧情:
library("ggplot2"); theme_set(theme_classic())
th_clean <-
theme(panel.margin=grid::unit(0,"lines"),
axis.ticks.x=element_blank(),
axis.text.x=element_blank(),
axis.ticks.y=element_blank(),
axis.text.y=element_blank()
)
ggplot(f12,aes(x,y))+geom_point()+
facet_wrap(~s)+labs(x="",y="")+
th_clean
我不知道非重塑解决方案是否可以接受,但你可以:
library(data.table)
#create the pattern that will have the Xs
#this will make it easy to create the Ys
pattern <- 1:4
#use Map to create a list of data.frames with the needed columns
#and also use rbindlist to rbind the list produced by Map
lists <- rbindlist(Map(data.frame,
pattern,
anscombe[pattern],
anscombe[pattern+length(pattern)]
)
)
#set the correct names
setnames(lists, names(lists), c('s','x','y'))
输出:
> lists
s x y
1: 1 10 8.04
2: 1 8 6.95
3: 1 13 7.58
4: 1 9 8.81
5: 1 11 8.33
6: 1 14 9.96
7: 1 6 7.24
8: 1 4 4.26
9: 1 12 10.84
10: 1 7 4.82
....
我认为这符合 1) 简短 2) 易于理解和 3) 没有硬编码列号的标准。而且它不需要任何其他包。
reshape(anscombe, varying=TRUE, sep="", direction="long", timevar="s")
# s x y id
#1.1 1 10 8.04 1
#...
#11.1 1 5 5.68 11
#1.2 2 10 9.14 1
#...
#11.2 2 5 4.74 11
#1.3 3 10 7.46 1
#...
#11.3 3 5 5.73 11
#1.4 4 8 6.58 1
#...
#11.4 4 8 6.89 11
如果你真的在处理 "anscombe" 数据集,那么我会说@Thela 的 reshape
解决方案非常直接。
但是,这里有一些其他选项需要考虑:
选项 1:基础 R
您可以编写自己的 "reshape" 函数,也许是这样的:
myReshape <- function(indf = anscombe, stubs = c("x", "y")) {
temp <- sapply(stubs, function(x) {
unlist(indf[grep(x, names(indf))], use.names = FALSE)
})
s <- rep(seq_along(grep(stubs[1], names(indf))), each = nrow(indf))
data.frame(s, temp)
}
备注:
- 我不确定这是否一定没有您已经在做的那么笨拙
- 如果数据为 "unbalanced"(例如,"x" 列多于 "y" 列),此方法将不起作用。
选项 2:"dplyr" + "tidyr"
现在管道很流行,你也可以试试:
library(dplyr)
library(tidyr)
anscombe %>%
gather(var, val, everything()) %>%
extract(var, into = c("variable", "s"), "(.)(.)") %>%
group_by(variable, s) %>%
mutate(ind = sequence(n())) %>%
spread(variable, val)
备注:
- 我不确定这是否一定比您已经在做的更简单,但有些人喜欢管道方法。
- 这种方法应该能够处理不平衡的数据。
选项 3:"splitstackshape"
在@Arun 去完成 melt.data.table
的所有出色工作之前,我已经在我的 "splitstackshape" 包中编写了 merged.stack
。这样,方法将是:
library(splitstackshape)
setnames(
merged.stack(
data.table(anscombe, keep.rownames = TRUE),
var.stubs = c("x", "y"), sep = "var.stubs"),
".time_1", "s")[]
一些注意事项:
merged.stack
需要将某些东西视为 "id",因此需要 data.table(anscombe, keep.rownames = TRUE)
,它添加了一个名为 "rn" 的列,行号为
-
sep = "var.stubs"
基本上意味着我们没有真正的分隔符变量,所以我们将去掉存根并使用 "time" 变量 的任何剩余部分
如果数据不平衡,merged.stack
将起作用。例如,尝试将它与 anscombe2 <- anscombe[1:7]
一起用作数据集而不是 "anscombe".
- 同一个包还有一个名为
Reshape
的函数,该函数建立在 reshape
的基础上,可以重塑不平衡的数据。但它比 merged.stack
更慢且更不灵活。基本方法是 Reshape(data.table(anscombe, keep.rownames = TRUE), var.stubs = c("x", "y"), sep = "")
然后使用 setnames
. 重命名 "time" 变量
选项 4:melt.data.table
上面的评论中提到了这一点,但尚未作为答案分享。在基数 R 之外 reshape
,这是一种非常直接的方法,可以从函数本身内部处理列重命名:
library(data.table)
melt(as.data.table(anscombe),
measure.vars = patterns(c("x", "y")),
value.name=c('x', 'y'),
variable.name = "s")
备注:
- 会快得离谱。
- 比 "splitstackshape" 或
reshape
得到更好的支持 ;-)
- 可以很好地处理不平衡数据。
中建议使用更新的 tidyverse 选项
anscombe %>%
pivot_longer(everything(),
names_to = c(".value", "set"),
names_pattern = "(.)(.)"
) %>%
arrange(set)
#> # A tibble: 44 x 3
#> set x y
#> <chr> <dbl> <dbl>
#> 1 1 10 8.04
#> 2 1 8 6.95
#> 3 1 13 7.58
#> 4 1 9 8.81
#> 5 1 11 8.33
#> 6 1 14 9.96
#> 7 1 6 7.24
#> 8 1 4 4.26
#> 9 1 12 10.8
#> 10 1 7 4.82
#> # … with 34 more rows
我试图使用 ggplot2
绘制 R 中内置的 anscombe
数据集(其中包含四个不同的小数据集,它们具有相同的相关性但 X 和 Y 之间的关系完全不同) .我试图正确重塑数据的尝试都非常丑陋。我使用了 reshape2
和 base R 的组合; Hadleyverse 2 (tidyr
/dplyr
) 或 data.table
解决方案对我来说没问题,但理想的解决方案是
- short/no重复代码
- 易于理解(与标准 #1 有点冲突)
- 尽可能少地对列号等进行硬编码
原格式:
anscombe
## x1 x2 x3 x4 y1 y2 y3 y4
## 1 10 10 10 8 8.04 9.14 7.46 6.58
## 2 8 8 8 8 6.95 8.14 6.77 5.76
## 3 13 13 13 8 7.58 8.74 12.74 7.71
## ...
## 11 5 5 5 8 5.68 4.74 5.73 6.89
所需格式:
## s x y
## 1 1 10 8.04
## 2 1 8 6.95
## ...
## 44 4 8 6.89
这是我的尝试:
library("reshape2")
ff <- function(x,v)
setNames(transform(
melt(as.matrix(x)),
v1=substr(Var2,1,1),
v2=substr(Var2,2,2))[,c(3,5)],
c(v,"s"))
f1 <- ff(anscombe[,1:4],"x")
f2 <- ff(anscombe[,5:8],"y")
f12 <- cbind(f1,f2)[,c("s","x","y")]
现在剧情:
library("ggplot2"); theme_set(theme_classic())
th_clean <-
theme(panel.margin=grid::unit(0,"lines"),
axis.ticks.x=element_blank(),
axis.text.x=element_blank(),
axis.ticks.y=element_blank(),
axis.text.y=element_blank()
)
ggplot(f12,aes(x,y))+geom_point()+
facet_wrap(~s)+labs(x="",y="")+
th_clean
我不知道非重塑解决方案是否可以接受,但你可以:
library(data.table)
#create the pattern that will have the Xs
#this will make it easy to create the Ys
pattern <- 1:4
#use Map to create a list of data.frames with the needed columns
#and also use rbindlist to rbind the list produced by Map
lists <- rbindlist(Map(data.frame,
pattern,
anscombe[pattern],
anscombe[pattern+length(pattern)]
)
)
#set the correct names
setnames(lists, names(lists), c('s','x','y'))
输出:
> lists
s x y
1: 1 10 8.04
2: 1 8 6.95
3: 1 13 7.58
4: 1 9 8.81
5: 1 11 8.33
6: 1 14 9.96
7: 1 6 7.24
8: 1 4 4.26
9: 1 12 10.84
10: 1 7 4.82
....
我认为这符合 1) 简短 2) 易于理解和 3) 没有硬编码列号的标准。而且它不需要任何其他包。
reshape(anscombe, varying=TRUE, sep="", direction="long", timevar="s")
# s x y id
#1.1 1 10 8.04 1
#...
#11.1 1 5 5.68 11
#1.2 2 10 9.14 1
#...
#11.2 2 5 4.74 11
#1.3 3 10 7.46 1
#...
#11.3 3 5 5.73 11
#1.4 4 8 6.58 1
#...
#11.4 4 8 6.89 11
如果你真的在处理 "anscombe" 数据集,那么我会说@Thela 的 reshape
解决方案非常直接。
但是,这里有一些其他选项需要考虑:
选项 1:基础 R
您可以编写自己的 "reshape" 函数,也许是这样的:
myReshape <- function(indf = anscombe, stubs = c("x", "y")) {
temp <- sapply(stubs, function(x) {
unlist(indf[grep(x, names(indf))], use.names = FALSE)
})
s <- rep(seq_along(grep(stubs[1], names(indf))), each = nrow(indf))
data.frame(s, temp)
}
备注:
- 我不确定这是否一定没有您已经在做的那么笨拙
- 如果数据为 "unbalanced"(例如,"x" 列多于 "y" 列),此方法将不起作用。
选项 2:"dplyr" + "tidyr"
现在管道很流行,你也可以试试:
library(dplyr)
library(tidyr)
anscombe %>%
gather(var, val, everything()) %>%
extract(var, into = c("variable", "s"), "(.)(.)") %>%
group_by(variable, s) %>%
mutate(ind = sequence(n())) %>%
spread(variable, val)
备注:
- 我不确定这是否一定比您已经在做的更简单,但有些人喜欢管道方法。
- 这种方法应该能够处理不平衡的数据。
选项 3:"splitstackshape"
在@Arun 去完成 melt.data.table
的所有出色工作之前,我已经在我的 "splitstackshape" 包中编写了 merged.stack
。这样,方法将是:
library(splitstackshape)
setnames(
merged.stack(
data.table(anscombe, keep.rownames = TRUE),
var.stubs = c("x", "y"), sep = "var.stubs"),
".time_1", "s")[]
一些注意事项:
merged.stack
需要将某些东西视为 "id",因此需要data.table(anscombe, keep.rownames = TRUE)
,它添加了一个名为 "rn" 的列,行号为-
sep = "var.stubs"
基本上意味着我们没有真正的分隔符变量,所以我们将去掉存根并使用 "time" 变量 的任何剩余部分
如果数据不平衡, merged.stack
将起作用。例如,尝试将它与anscombe2 <- anscombe[1:7]
一起用作数据集而不是 "anscombe".- 同一个包还有一个名为
Reshape
的函数,该函数建立在reshape
的基础上,可以重塑不平衡的数据。但它比merged.stack
更慢且更不灵活。基本方法是Reshape(data.table(anscombe, keep.rownames = TRUE), var.stubs = c("x", "y"), sep = "")
然后使用setnames
. 重命名 "time" 变量
选项 4:melt.data.table
上面的评论中提到了这一点,但尚未作为答案分享。在基数 R 之外 reshape
,这是一种非常直接的方法,可以从函数本身内部处理列重命名:
library(data.table)
melt(as.data.table(anscombe),
measure.vars = patterns(c("x", "y")),
value.name=c('x', 'y'),
variable.name = "s")
备注:
- 会快得离谱。
- 比 "splitstackshape" 或
reshape
得到更好的支持 ;-) - 可以很好地处理不平衡数据。
anscombe %>%
pivot_longer(everything(),
names_to = c(".value", "set"),
names_pattern = "(.)(.)"
) %>%
arrange(set)
#> # A tibble: 44 x 3
#> set x y
#> <chr> <dbl> <dbl>
#> 1 1 10 8.04
#> 2 1 8 6.95
#> 3 1 13 7.58
#> 4 1 9 8.81
#> 5 1 11 8.33
#> 6 1 14 9.96
#> 7 1 6 7.24
#> 8 1 4 4.26
#> 9 1 12 10.8
#> 10 1 7 4.82
#> # … with 34 more rows