表达式评估,字符串中的对象......仍然不清楚

Expression evaluation, objects from string... still not clear

我阅读了很多关于如何使用 assign()get()as.name()eval(substitute()) 从字符串创建对象或计算表达式的主题,但有些东西我不是很清楚。

在我下面的示例中,我有一个输入数据集(长格式),我想要输出 3 个矩阵。

输入数据帧:

# Input dataframe
df <- data.frame(v1 = c(rep("A", 3), rep("B", 3)),
                 v2 = c(rep(letters[1:3], 2)),
                 dfA = sample(1:10, 6),
                 dfB = sample(1:10, 6),
                 dfC = sample(1:10, 6))

> df
  v1 v2 dfA dfB dfC
1  A  a   5  10   5
2  A  b   4   7   9
3  A  c  10   1   2
4  B  a   7   9   7
5  B  b   2   8   1
6  B  c   9   3   4

3个变量的前缀是数据集名称(然后是"A"、"B"、"C")。

当以 v2 作为键传播时,我想要 3 个分别具有 dfAdfBdfC 值的矩阵。 矩阵名称也将具有传播值的名称。 行名称将是 v1 值。

我想要的矩阵:

> dfA
  a b c
A 4 1 2
B 5 8 7

> dfB
   a b c
A  1 9 5
B 10 4 2

> dfC
  a b c
A 5 1 3
B 6 4 9

所以,手动,没问题(我只是在这里做第一个矩阵):

library(dplyr)
library(tidyr)

dfA            <- df %>% select(v1, v2, dfA) %>% spread(key = v2, value = dfA)
row.names(dfA) <- dfA[, 1]
dfA            <- dfA %>% select(-1) %>% as.matrix()


> dfA
  a b c
A 4 1 2
B 5 8 7

但是现在,我有很多输入数据框,所以我想自动完成。 我将做一个以 DF 名称作为参数的函数,但现在我想一步步向您展示没有函数。

dfName = "df"

# For : dfA <- df %>% select(v1, v2, dfA) :
assign(paste0(dfName, "A"), get(dfName) %>% select(v1, v2, get(paste0(dfName, "A"))))

> dfA
  v1 v2 dfA
1  A  a   5
2  A  b   4
3  A  c  10
4  B  a   7
5  B  b   2
6  B  c   9


# For : dfA <- dfA %>% spread(key = v2, value = dfA)
assign(paste0(dfName, "A"), get(paste0(dfName, "A")) %>%
 spread(key = v2, value = get(paste0(dfName, "A"))))

Error: Invalid column specification

那里,我的传播函数有错误。 它在上面的 select() 中起作用,但在 spread() 中不起作用...我应该怎么做?

好吧,我唯一能做到的方法是:

eval(substitute(var1 <- var1 %>% spread(key = v2, value = var1),
                list(var1 = as.name(paste0(dfName, "A")))))

> dfA
  v1 a b  c
1  A 5 4 10
2  B 7 2  9

然后,我想将 v1 值放入 row.names :

row.names(get(paste0(dfName, "A"))) <- get(paste0(dfName, "A"))[, 1]

...错误(我不明白):

Error in ... could not find function "get<-"

但我设法做到了:

eval(substitute(row.names(var1) <- var1[, 1],
                list(var1 = as.name(paste0(dfName, "A")))))

> dfA
  v1 a b  c
A  A 5 4 10
B  B 7 2  9

最后把它变成矩阵:

assign(paste0(dfName, "A"), get(paste0(dfName, "A")) %>% select(-1) %>% as.matrix())

> dfA
  a b  c
A 5 4 10
B 7 2  9

所以,我可以让它工作,但它对我来说不是 "pretty"...

assign() 太多还是 eval(substitute()) 太多? 这是做这件事的好方法吗?

我想我几乎把手指放在上面了,但我仍然不明白我可能有的错误。

我错过了什么吗?有更好的方法吗? (更好,我的意思是编码清晰,效率更高,遵守 R 编码规则...)

非常感谢您的回答,很抱歉这么长post!

1) Map 通常最好创建一个对象列表,所以使用最后注释中的输入并利用 xtabs 试试这个。

没有使用包。

fun <- function(nm) xtabs(df[c(nm, head(names(df), 2))])
L <- Map(fun, tail(names(df), -2))
names(L) <- sub("df", "", names(L))  # optional - remove "df" from names

给予(输出后续):

> L
$A
   v2
v1  a b c
  A 3 8 4
  B 7 6 1

$B
   v2
v1   a  b  c
  A  6  9  5
  B  4 10  3

$C
   v2
v1   a  b  c
  A  7  6  1
  B 10  2  8

2) reshape 另一种可能性是通过将 df 重塑为长格式然后在其上使用 xtabs 来创建 3d table .

dfAdfBdfC 中的每个单元格在长表格中各占一行,还有两个新列:

  • 新的 "vnames" 列包含原始 "dfA""dfB""dfC" 列中的数值。 v.namesvarying 参数 reshape 定义了这一点。
  • 新的 "Group" 列标识 "vnames" 列中的每个值来自 df 的哪一列。 timevartimes 参数定义了这一点。

这种方法的优点是可以轻松查看所有切片。例如,尝试以下各项: tab["B", "b", "C"]tab[,"b", "C"]tab["B", "b", ]tab["B",,"C"]tab["B",,]tab[,"b",]tab[,,"C"]

没有使用包。

vnames <- tail(names(df), -2)  # c("dfA", "dfB", "dfC")
long <- reshape(df, dir = "long", 
  varying = vnames, v.names = "vnames",
  times = sub("df", "", vnames), timevar = "Group")
tab <- xtabs(vnames ~ v1 + v2 + Group, long)

给予:

> tab
, , Group = A

   v2
v1   a  b  c
  A  3  8  4
  B  7  6  1

, , Group = B

   v2
v1   a  b  c
  A  6  9  5
  B  4 10  3

, , Group = C

   v2
v1   a  b  c
  A  7  6  1
  B 10  2  8

2a) reshape2::melt 此变体使用 reshape2 包中的 melt 代替 R 基础中的 reshape 并且实质上是更紧凑:

library(reshape2)

xtabs(value ~ v1 + v2 + variable, data = melt(df))

注意: 为可重复性设置种子,我们将此输入用作 df:

set.seed(123)
df <- data.frame(v1 = c(rep("A", 3), rep("B", 3)),
                 v2 = c(rep(letters[1:3], 2)),
                 dfA = sample(1:10, 6),
                 dfB = sample(1:10, 6),
                 dfC = sample(1:10, 6))

您需要这些函数的 standard evaluation 版本:

assign(paste0(dfName, "A"), get(paste0(dfName, "A")) %>%
           spread_(key = 'v2', value = paste0(dfName, "A")))

注意动词末尾的 _,以及 "v2" 被引用的事实。

来自 dplyr 插图:

Standard evaluation basics

Every function in dplyr that uses NSE also has a version that uses SE. The name of the SE version is always the NSE name with an _ on the end. For example, the SE version of summarise() is summarise_(); the SE version of arrange() is arrange_(). These functions work very similarly to their NSE cousins, but their inputs must be “quoted”:

我们可以在不使用 dcastdata.table 转换为 list 的情况下执行此操作,它可以将多个列作为 value.var

library(data.table)
dcast(setDT(df), v1 ~v2, value.var = c("dfA", "dfB", "dfC"))
#   v1 dfA_a dfA_b dfA_c dfB_a dfB_b dfB_c dfC_a dfC_b dfC_c
#1:  A     3     8     4     6     9     5     7     6     1
#2:  B     7     6     1     4    10     3    10     2     8

数据

set.seed(123)
df <- data.frame(v1 = c(rep("A", 3), rep("B", 3)),
                  v2 = c(rep(letters[1:3], 2)),
                  dfA = sample(1:10, 6),
                  dfB = sample(1:10, 6),
                  dfC = sample(1:10, 6))