当合并一个非空的data.frame和一个空的data.frame时,会出现奇怪的行为

izj3ouym  于 2023-05-11  发布在  其他
关注(0)|答案(3)|浏览(193)

我有一个非空 Dataframe df1

df1 <- read.table(header = TRUE, text = "
V1 V2
1  5
2  6
3  7
4  8
")

以及两个空 Dataframe df2.adf2.b,即,

df2.a <- data.frame(V1 = integer(0), V2 = integer(0), V3 = integer(0), V4 = integer(0))

df2.b <- structure(list(V1 = NULL, V2 = NULL, V3 = NULL, V4 = NULL), row.names = c(NA, 0L), class = "data.frame")

其中df2.adf2.b看起来几乎没有区别(唯一的区别是使用dput(df2.a)dput(df2.b)时显示的)

> df2.a
[1] V1 V2 V3 V4
<0 rows> (or 0-length row.names)
> df2.b
[1] V1 V2 V3 V4
<0 rows> (or 0-length row.names)

但是,当我尝试将df1df2.adf2.b合并时,出现了一些奇怪的情况

> merge(df1,df2.a,all = TRUE)
  V1 V2 V3 V4
1  1  5 NA NA
2  2  6 NA NA
3  3  7 NA NA
4  4  8 NA NA

> merge(df1,df2.b,all = TRUE)
  V1 V2 V4
1  1  5 NA
2  2  6 NA
3  3  7 NA
4  4  8 NA

正如您所看到的,当将df1df2.b合并时,V3被丢弃,而所需的输出应该类似于merge(df1,df2.a,all = TRUE)的输出。
有人能解释一下吗?如果有解决问题的变通方法,在使用merge超过df1df2.b时表示赞赏。

ds97pgxw

ds97pgxw1#

这是一个复杂的问题。错误步骤发生在base::merge的这一行:

y <- y[c(m$yi, if (all.x) rep.int(1L, nxx), if (all.y) m$y.alone), 
            -by.y, drop = FALSE]

当您将df2.b作为y参数传递给merge时,这一行实际上会产生一个无效的 Dataframe ,正如您在浏览器中看到的那样:

Browse[2]> y
#>        V4
#> NA   NULL
#> NA.1 <NA>
#> NA.2 <NA>
#> NA.3 <NA>
#> Warning message:
#> In format.data.frame(if (omit) x[seq_len(n0), , drop = FALSE] else x,  :
#>  corrupt data frame: columns will be truncated or padded with NAs

如果我们跟踪整个逻辑,我们可以看到我们可以通过调用在调试器外部重现错误:

df2.b[c(1, 1, 1, 1), -c(1:2), drop = FALSE]
#>        V4
#> NA   NULL
#> NA.1 <NA>
#> NA.2 <NA>
#> NA.3 <NA>
#> Warning message:
#> In format.data.frame(if (omit) x[seq_len(n0), , drop = FALSE] else x,  :
#>  corrupt data frame: columns will be truncated or padded with NAs

然而,对于db2.a,我们没有得到这个问题:

df2.a[c(1, 1, 1, 1), -c(1:2), drop = FALSE]
#>      V3 V4
#> NA   NA NA
#> NA.1 NA NA
#> NA.2 NA NA
#> NA.3 NA NA

这是为什么呢?即使df2.adf2.b在打印 Dataframe 时看起来相同,但它们并不相同。一个空的数字向量与NULL不太一样。主要的区别(也是导致这里问题的原因)是索引一个空的数字向量会给你一个非零长度的NA值,而NULL会给你一个NULL值。

df2.a$V1[1:4]
#> [1] NA NA NA NA

df2.b$V1[1:4]
#> NULL

我想这是预期的行为。问题是R完全允许NULL作为dataframe列。我很惊讶这种事不经常发生。

pu3pd22g

pu3pd22g2#

我跟踪了这个问题的原因,发现这个错误出现在merge.data.frame的以下部分:

y <- y[c(m$yi, if (all.x) rep.int(1L, nxx), if (all.y) m$y.alone), 
            -by.y, drop = FALSE]

要显示问题,请尝试以下代码:

df2.b[rep(1, 4), -(1:2), drop = FALSE]
#        V4
# NA   NULL
# NA.1 <NA>
# NA.2 <NA>
# NA.3 <NA>
# Warning message:
# In format.data.frame(if (omit) x[seq_len(n0), , drop = FALSE] else x,  :
#   corrupt data frame: columns will be truncated or padded with NAs

df2.a[rep(1, 4), -(1:2), drop = FALSE]
#    V3 V4
# 1: NA NA
# 2: NA NA
# 3: NA NA
# 4: NA NA

因此,此问题是由[.data.frame引起的。[.data.frame的一段源代码是:

for (j in seq_along(x)) {
        xj <- xx[[sxx[j]]]
        x[[j]] <- if (length(dim(xj)) != 2L){
            xj[i]
        }else{ xj[i, , drop = FALSE]}
    }

这里x是要返回的结果 Dataframe 。它现在只有V3和V4列。xx是输入data.frame的副本(在我们的例子中是df2.b)。这个for循环首先将NULL分配给x的第1列。因此,在该步骤中删除V3。接下来,for循环将NULL分配给x的第2列。然而,由于V3消失了,所以没有列2。因此,x不会受到影响。这就是为什么我们得到了意想不到的结果。
如果我们将df1df2.b设置为data.table,合并它们将抛出错误。似乎data.table::merge对这种情况的处理更为严格。错误消息将帮助我们避免获得意外结果。

wz3gfoph

wz3gfoph3#

我会尽我所能提供一个完整的答案...
(When我发布了答案,我注意到我加入派对太晚了:D我会留下答案,因为我希望它会提供另一个有趣的观点)

调试合并

让我们先看看merge函数。具体来说,这里调用的方法是merge.data.framebase包的导出函数)。
如果你调试merge.data.frame(df1,df2.b,all = TRUE),你会在第124行看到它被调用:

y <- y[c(m$yi, if (all.x) rep.int(1L, nxx), if (all.y) m$y.alone), 
   -by.y, drop = FALSE]

ydf2.b相同。
由于m$yi等于integer(0)all.x等于TRUEall.y等于FALSE,因此可以简化为:

y[rep.int(1L, nxx), -by.y, drop = FALSE]

它的输出是:

V2   V4
NA   NULL NULL
NA.1 <NA> <NA>
NA.2 <NA> <NA>
NA.3 <NA> <NA>
Warning message:
In format.data.frame(if (omit) x[seq_len(n0), , drop = FALSE] else x,  :
   corrupt data frame: columns will be truncated or padded with NAs

所以这就是merge没有告诉我们的幕后“问题”。
让我们深入调查一下。
首先,实际的输出不是这样的,这只是欺骗我们眼睛的默认print.data.frame方法。
的输出

unclass(y[rep.int(1L, nxx), -by.y, drop = FALSE])

$V4
NULL

attr(,"row.names")
[1] "NA"   "NA.1" "NA.2" "NA.3"

NULL不会被重复,这是有意义的,因为你不能用两个NULL来做一个向量

identical(c(NULL, NULL), NULL)
#> TRUE

正如警告所说,data.frame已损坏,打印可能有故障(确实如此!).
这是因为data.frame是以一种巧妙的方式创建的,使用的是structure()而不是data.frame()as.data.frame(),这不会导致您使用该结构。
这是一个关于你如何只看到一列的故事。
问题是为什么
因此,我们需要看看函数[.data.frame

DEBUG [.data.frame]

让我们先观察一些行为。

> df2.b[1,]
     V2   V4
NA NULL NULL
> df2.b[,1]
NULL
> df2.b[,1, drop = FALSE]
[1] V1
<0 rows> (or 0-length row.names)
> df2.b[1,1]
NULL
> df2.b[1,1, drop = FALSE]
data frame with 0 columns and 1 row
> df2.b[1,1:2]
     V2
NA NULL
> df2.b[c(1,1),1:2]
       V2
NA   NULL
NA.1 <NA>
Warning message:
In format.data.frame(if (omit) x[seq_len(n0), , drop = FALSE] else x,  :
   corrupt data frame: columns will be truncated or padded with NAs

最后三个看起来很出乎意料。最后一个是我们的案例。和我们之前看到的一样。
如果尝试调试:

debugonce(base:::[.data.frame)
df2.b[c(1,1),1:2]

你会在第109行找到这段代码:

for (j in seq_along(x)) {
  xj <- xx[[sxx[j]]]
  x[[j]] <- if (length(dim(xj)) != 2L) 
   xj[i]
  else xj[i, , drop = FALSE]
 }

可读性更强:

for (j in seq_along(x)) {
  xj <- xx[[sxx[j]]]
  x[[j]] <- if (length(dim(xj)) != 2L) xj[i] else xj[i, , drop = FALSE]
 }

此时,变量如下:

x = list(V1 = NULL, V2 = NULL)
xx = df2.b
sxx = 1:2
i = 1:2

如果你用这些变量运行for循环,你会得到x是:

> x
$V2
NULL

看来我们找到消失的柱子的来源了。
现在,问题究竟出在哪里?
j == 1x[[j]] <- ...等于x$V1 <- NULL时,在R中允许从列表中删除元素V1。因此x变成了一个只有一个元素的列表,this:

> x
$V2
NULL

j == 2x[[j]]不再存在,因为在第一次循环中,第一个项目被删除,现在只有一个可用。因此,R试图分配一个新的第二项,但由于您不能将NULL分配为项[如下所示:x[[2]] <- NULL],则x不会改变。
因此,您只有一列。

总结

merge有奇怪行为的原因是因为您以不正确的方式创建了 Dataframe 。
merge并没有告诉你 Dataframe 实际上已经损坏,它甚至在不应该的时候做了一些事情。
最终,[及其处理子集的方式定义了其中一列的最终丢失。

DPLYR

说实话,就用dplyr::full_join(df1, df2.b)。它没有给出任何理所当然的东西,实际上会导致您从一开始就预期的错误:

> dplyr::full_join(df1, df2.b)
Joining, by = c("V1", "V2")
Error: All columns in a tibble must be vectors.
x Column `V1` is NULL.
x Column `V2` is NULL.
x Column `V3` is NULL.
x Column `V4` is NULL.

相关问题