为什么 `stack` 不能处理 `tapply` 的结果?

why `stack` cannot work on the result of `tapply`?

假设我有一个数据框df

> dput(df)
structure(list(x = c("X", "X", "X", "Y", "Y", "Z", "Z", "Z"),
    y = c("A", "B", "C", "B", "C", "A", "C", "D")), class = "data.frame", row.names = c(NA,
-8L))

> df
  x y
1 X A
2 X B
3 X C
4 Y B
5 Y C
6 Z A
7 Z C
8 Z D

并生成如下列表u1

u1 <- with(
  df,
  tapply(y, x, combn, 2, toString)
)

哪里

> u1
$X
[1] "A, B" "A, C" "B, C"

$Y
[1] "B, C"

$Z
[1] "A, C" "A, D" "C, D"

> str(u1)
List of 3
 $ X: chr [1:3(1d)] "A, B" "A, C" "B, C"
 $ Y: chr [1(1d)] "B, C"
 $ Z: chr [1:3(1d)] "A, C" "A, D" "C, D"
 - attr(*, "dim")= int 3
 - attr(*, "dimnames")=List of 1
  ..$ : chr [1:3] "X" "Y" "Z"

当我运行stack(u1)时,会出现如下错误

> stack(u1)
Error in stack.default(u1) : at least one vector element is required

似乎我不能直接在 tapply 的输出上使用 stack,即使它是一个命名列表。

但是,当我使用 u2 <- Map(c,u1) 进行后处理时,一切又恢复正常了

> u2 <- Map(c, u1)

> u2
$X
[1] "A, B" "A, C" "B, C"

$Y
[1] "B, C"

$Z
[1] "A, C" "A, D" "C, D"


> str(u2)
List of 3
 $ X: chr [1:3] "A, B" "A, C" "B, C"
 $ Y: chr "B, C"
 $ Z: chr [1:3] "A, C" "A, D" "C, D"

> stack(u2)
  values ind
1   A, B   X
2   A, C   X
3   B, C   X
4   B, C   Y
5   A, C   Z
6   A, D   Z
7   C, D   Z

正如我们所见,在 str(u2) 中,属性被过滤掉了,这似乎解决了问题。


我的问题是:

为什么u1失败了,u2却成功了?有没有其他方法可以在 u1 上使用 tapply 而无需任何后处理(如 Map(c, u1))?

tapply returns 一个 array(或者 list 如果你设置 simplify = FALSE),stack 不喜欢一个数组输入。 tapply 文档听起来没有其他输出选项。来自 ?tapply(强调我的):

simplify:

logical; if FALSE, tapply always returns an array of mode "list"; in other words, a list with a dim attribute. If TRUE (the default), then if FUN always returns a scalar, tapply returns an array with the mode of the scalar.

所以我建议转换为角色:

stack(lapply(u1, as.character))
#   values ind
# 1   A, B   X
# 2   A, C   X
# 3   B, C   X
# 4   B, C   Y
# 5   A, C   Z
# 6   A, D   Z
# 7   C, D   Z

如果您关心速度,可以 运行 基准测试来查看,删除 dim 属性可能比 as.character()

更快
stack(lapply(u1, "dim<-", NULL))
# same result

或者也可以使用 as.vector/c 删除属性并将 1d 向量转换为没有模糊属性的向量

stack(lapply(u1, c))
  values ind
1   A, B   X
2   A, C   X
3   B, C   X
4   B, C   Y
5   A, C   Z
6   A, D   Z
7   C, D   Z

根据?stack

Note that stack applies to vectors (as determined by is.vector): non-vector columns (e.g., factors) will be ignored with a warning.

is.vectorreturnsFALSE为'u1'

的所有成员元素
> sapply(u1, is.vector)
    X     Y     Z 
FALSE FALSE FALSE 

正如@GregorThomas 提到的 tapply 中的 simplify 参数,combn 中还有一个 simplify 选项,默认情况下为 TRUE。如果我们指定 FALSE,它 returns 一个 list 并且应该工作

u1 <- with(
  df,
  tapply(y, x, FUN = function(u) combn(u, 2, FUN = toString, simplify = FALSE))
)
> stack(u1)
  values ind
1   A, B   X
2   A, C   X
3   B, C   X
4   B, C   Y
5   A, C   Z
6   A, D   Z
7   C, D   Z

但是,这也适用于 1d 向量上的 enframe

library(tibble)
library(tidyr)
enframe(u1) %>%
   unnest(value)
# A tibble: 7 × 2
  name  value
  <chr> <chr>
1 X     A, B 
2 X     A, C 
3 X     B, C 
4 Y     B, C 
5 Z     A, C 
6 Z     A, D 
7 Z     C, D