R - %in% 中使用的样本修改正在子集化的数据帧

R - sample used in %in% modify dataframe which is being subsetted

不确定我的问题标题是否正确,因为我不完全理解以下行为的原因:

dfSet <- data.frame(ID = sample(1:15, size = 15, replace = FALSE), va1 = NA, va3 = 0, stringsAsFactors = FALSE)

dfSet[1:10, ]$va1 <- 'o1'
dfSet[11:15, ]$va1 <- 'o2'

dfSet[dfSet$ID %in% sample(dfSet[dfSet$va1 == 'o1', ]$ID, 7, replace = FALSE), ]$va3 <- 1

print(length(unique(dfSet$ID)))

我希望最终打印显示 15,但事实并非如此。取而代之的是 13 或 14 出现并且 dfSet 被修改为至少有两行具有相同的 ID。好像是这部分代码:

dfSet[dfSet$ID %in% sample(dfSet[dfSet$va1 == 'o1', ]$ID, 7, replace = FALSE), ]$va3 <- 1

修改 $ID 列 - 我不知道为什么?

解决方法:

temp <- sample(dfSet[dfSet$va1 == 'o1', ]$ID, 7, replace = FALSE)
dfSet[dfSet$ID %in% temp, ]$va3 <- 1

在这种情况下,一切都按预期进行 - 有 15 行具有唯一 ID。

问题是为什么在 %in% 中直接使用样本会修改数据框?

虽然我不是 100% 确定,但我怀疑 R 是 运行 sample 的两倍。当您在 R 中进行子集化和赋值时,例如:

x[i:j,]$v1 <- 1

它被评估为 "take out rows i to j from x as a temporary data frame, assign 1 to the v1 column of that data frame, then copy the temporary data frame back into rows i to j in x"。

所以也许索引表达式 (i:j) 被执行了两次(一次提取,一次放回),如果它是一个随机变量,它将把结果放回到与之前不同的行中最初选择。

考虑这个更简单的例子:

x <- data.frame(a=1:10, b=10:1)
x$b <- 5

第二行实际做的是

x <- `$<-`(x, 'b', 5)

你可以看到$<-只是一个接受三个参数的函数,一个 对象、名称和值。 (请注意,如果您想直接使用 $<-,反引号是必需的。)

我认为问题是在你的例子中 x 是一个表达式 由于调用了 sample,所以你应该避免这种情况。

另一种方法是使用 [<-,显然没有这个问题:

dfSet[dfSet$ID %in% sample(dfSet[dfSet$va1 == 'o1', ]$ID, 7, replace = FALSE), 'va3'] <- 1

问题似乎在于,当您为函数 return 值赋值时,R 做了一些棘手的事情。例如,

a <- c(1,3)
names(a) <- c("one", "three")

在大多数语言中看起来很奇怪。如何为函数的 return 值赋值?真正发生的是定义了一个名为 names<- 的函数。基本上这是 returning 原始对象的转换版本,然后可用于替换传递给该函数的值。所以它真的看起来像这样

.temp. <- `names<-`(a, c("one","three"))
a <- .temp.

变量 a 总是被完全替换,而不仅仅是它的名字。

当你做类似

的事情时
dfSet$a<-1

真正再次发生的是

.temp. <- "$<-"(dfSet, a, 1)
dfSet <- .temp.

现在,当您尝试同时进行 []$ 子集设置时,事情会变得有点棘手。看看这个样本

#for subsetting
f <- function(x,v) {print("testing"); x==v}
x <- rep(0:1, length.out=nrow(dfSet))
dfSet$a <- 0

dfSet[f(x,1),]$a<-1

注意 "testing" 是如何打印两次的。怎么回事真的更像

.temp1. <- "$<-"(dfSet[f(x,1),], a, 1)
.temp2. <- "[<-"(dfSet, f(x,1), , .temp1.)
dfSet <- .temp2.

所以 f(x,1) 被评估了两次。这意味着 sample 也会被计算两次。

这个错误比较明显是你尝试替换一个还不存在的变量

dfSet[f(x,1),]$b<-1
# Warning message:
# In `[<-.data.frame`(`*tmp*`, f(x, 1), , value = list(ID = c(6L,  :
#  provided 4 variables to replace 3 variables

这里你会收到警告,因为 .temp1. 变量已添加到列中,现在有 4 列,但是当你尝试对 .temp2. 进行赋值时,你现在遇到了一个问题,即您尝试替换的数据框大小不同。

ID 被替换,因为 $<- 运算符不只是 return 一个新列,它 return 是一个新的 data.frame,该列已更新为任何值你分配的。这意味着更新的行与分配发生时的 ID 一起 returned。这保存在 .temp1. 变量中。然后,当您执行 [<- 分配时,您正在选择一组新的行来换出。这些行的所有列的值都替换为 .temp1. 中的值。这意味着您将覆盖替换行的 ID,并且它们可能不同,因此您可能会得到给定 ID 的两个或更多副本。