逐个元素地将两个数据框粘贴在一起

10

我需要逐个元素地粘贴两个数据帧的内容,以便输入到另一个程序中。我有一个均值数据帧和一个均值标准误差数据帧。

我尝试使用R中的paste()函数,但它似乎无法处理数据帧。当使用向量时,它似乎将第一个向量的所有元素连接成一个字符串,将第二个向量的所有元素连接成另一个字符串。相反,我需要将两个数据帧中的每个互补元素连接在一起。

有什么建议可以解决这个问题吗?我已经包含了虚拟输入数据(datMean和datSE)以及我想要的输出(datNew)。我的真实数据帧大约有10行150列。

# means and SEM
datMean <- data.frame(a=rnorm(10, 3), b=rnorm(10, 3), d=rnorm(10, 3))
datSE <- data.frame(a=rnorm(10, 3)/100, b=rnorm(10, 3)/100, d=rnorm(10, 3)/100)

# what the output should look like
# i've chosen some arbitrary values here, and show only the first row. 
datNew <- data.frame(a="2.889-2.926", b="1.342-1.389", d="2.569-2.576")

这个想法是让datNew中的每个元素都成为一个范围,该范围由'mean - se'和'mean + se'组成,用破折号'-'分隔。paste()函数可以对一个元素执行此操作,如何在整个数据框上执行此操作?

paste(datMean[1,1] - datSE[1,1], datMean[1,1] + datSE[1,1], sep="-")

编辑 1:看了一些答案后,我意识到在问题中遗漏了一个重要的信息。原始数据帧的每行都有名称,并且我需要使用这些名称重新构建最终的数据帧。例如:

rownames(datMean) <- LETTERS[1:10]
rownames(datSE) <- LETTERS[1:10]

我希望datNew最终能够再次拥有这10个行名称。 使用melt()的某些解决方案可能会带来问题。

5个回答

14

如果你先转换为矩阵,就可以完全不使用apply或循环来完成。

MdatMean <- as.matrix(datMean)
MdatSE <- as.matrix(datSE)
matrix( paste(MdatMean - MdatSE, MdatMean + MdatSE, sep="-"), 
        nrow=nrow(MdatMean), dimnames=dimnames(MdatMean) )

您还可以考虑使用formatC来进行更好的格式化。

lo <- formatC(MdatMean - MdatSE, format="f", digits=3)
hi <- formatC(MdatMean + MdatSE, format="f", digits=3)
matrix( paste(lo, hi, sep="-"), 
        nrow=nrow(MdatMean), dimnames=dimnames(MdatMean) )
如果你想要最终得到一个`data.frame`,只需将最后一行用`as.data.frame`包装即可。

非常感谢您的回答 - 我决定接受这个作为最佳答案,因为它不依赖于外部包,并且比其他方案更快,因为它不依赖于apply或循环。对于formatC(),我还没有见过,格式化效果更好,再次感谢。 - Steve

8

以下是一种无需手动指定每列的方法。首先,我们使用abind包制作数据并将它们放入数组中,舍入到3位,因为这样看起来更好:

datMean <- data.frame(a=rnorm(10, 3), b=rnorm(10, 3), d=rnorm(10, 3))
datSE <- data.frame(a=rnorm(10, 3)/100, b=rnorm(10, 3)/100, d=rnorm(10, 3)/100)

library(abind)

datArray <- round(abind(datMean,datSE,along=3),3)

然后我们可以将paste函数应用到该数组的每个元素和列:

apply(datArray,1:2,function(x)paste(x[1]-x[2],"-",x[1]+x[2]))

      a               b               d              
 [1,] "3.537 - 3.581" "3.358 - 3.436" "3.282 - 3.312"
 [2,] "2.452 - 2.516" "1.372 - 1.44"  "3.041 - 3.127"
 [3,] "3.017 - 3.101" "3.14 - 3.228"  "5.238 - 5.258"
 [4,] "3.397 - 3.451" "2.783 - 2.839" "3.381 - 3.405"
 [5,] "1.918 - 1.988" "2.978 - 3.02"  "3.44 - 3.504" 
 [6,] "4.01 - 4.078"  "3.014 - 3.068" "1.914 - 1.954"
 [7,] "3.475 - 3.517" "2.117 - 2.159" "1.871 - 1.929"
 [8,] "2.551 - 2.619" "3.907 - 3.975" "1.588 - 1.614"
 [9,] "1.707 - 1.765" "2.63 - 2.678"  "1.316 - 1.348"
[10,] "4.051 - 4.103" "3.532 - 3.628" "3.235 - 3.287"

非常感谢!这个方法非常有效,而且能够保留行名称(请参见我的编辑)。有一个问题:是否有办法在破折号前后省略空格? - Steve
绝妙的想法! - Gigi

3

我理解您的问题是这样的。我使用reshape2::melt将多列中的均值和SE的数据融合成一个列。

library(reshape2)
datMean <- melt(datMean)$value
datSE <- melt(datSE)$value
dat <- cbind(datMean, datSE)

apply(X = dat, MARGIN = 1, FUN = function(x) {
            paste(x[1] - x[2], x[1] + x[2], sep = " - ")
        })

结果如下

 [1] "3.03886802467251 - 3.08551547263516" 
 [2] "3.01803172559258 - 3.05247871975711" 
 [3] "3.4609230722069 - 3.56097173966387"  
 [4] "1.35368243309618 - 1.45548512578821" 
 [5] "2.39936853846605 - 2.47570756724791" 
 [6] "3.21849170272184 - 3.29653660329785" 

编辑

这个解决方案尊重您的原始数据维度。我的做法是创建一个三维数组,并逐个处理每个单元格,保持第三维度([x,y, 1:2])不变。

dat <- array(c(datMean, datSE), dim = c(10, 3, 2))

datNEW <- matrix(rep(NA, nrow(dat)*ncol(dat)), ncol = ncol(dat))

for (column in seq(ncol(dat))) {
    cls <- rep(NA, nrow(dat))
    for (rows in seq(nrow(dat))) {
        tmp <- dat[rows, column, 1:2]
        cls[rows] <- paste(tmp[1] - tmp[2], tmp[1] + tmp[2], sep = " - ")
    }
    datNEW[, column] <- cls
}

这个很好用,但我忘了提到我需要datNew与其他数据框具有相同的结构(相同的行名和列名-请参见我的EDIT1)。使用melt和cast似乎很难做到这一点。 - Steve
确实,@Steve。请稍等几秒钟,我将发布我的编辑。 - Roman Luštrik
此外,您可以将我的第一个解决方案拆分,以便它能够适应您的尺寸。 - Roman Luštrik
这似乎现在可以工作,但我接受Sacha的答案,因为它似乎更简单,需要更少的代码。不过还是谢谢你的帮助。 - Steve
我刚刚添加了另一种解决方案,以展示3D数组的优越性。我希望有人能向我展示如何通过保持一个维度不变来应用它,但你不能总是想要蛋糕并吃掉它。 :) - Roman Luštrik

2

您可以一次在每行上执行此操作,但是您正在应用于两个数据框之间的成对列。由于每次都有特定的粘贴工作要做,请定义函数:

pfun <- function(x, y) paste(x - y, x + y, sep = "-")

然后使用该函数构造新的data.frame:

 datNew <- data.frame(a = pfun(datMean$a, datSE$a), b = pfun(datMean$b, datSE$b), d = pfun(datMean$d, datSE$d))

有更简洁的方法来应用它,但也许这可以帮助您更好地理解。您可以将整个列传递给paste,但不能将整个数据框传递。

使用循环来匹配结果中的所有列而不需要逐个指定它们。

首先创建一个列表来存储所有列,我们将使用正确的列名称将其转换为数据框。

datNew <- vector("list", ncol(datMean))

命名假定两个输入数据框的列号、名称和顺序完全匹配。
names(datNew) <- names(datMean)

for (i in 1:ncol(datMean)) {
    datNew[[i]] <- pfun(datMean[[i]], datSE[[i]])
}

转换为 data.frame:

datNew <- as.data.frame(datNew)

这个真的很有效,谢谢。但对于一个有150多列的数据框来说,这将会是枯燥乏味的。我想知道是否有一种更进一步自动化的方法... - Steve

0

使用mapply函数进行paste操作,同时使用cbind函数来保留rownames

x <- cbind(
  datMean[, 0],
  mapply(paste, round(datMean - datSE, 3), round(datMean + datSE, 3), sep = " - "))

x
#               a             b             d
# A 3.268 - 3.321 5.226 - 5.308   2.3 - 2.358
# B 3.795 - 3.874 1.772 - 1.833 2.265 - 2.335
# C 1.305 - 1.346 1.238 - 1.291 2.812 - 2.874
# D 1.957 - 2.041 3.016 - 3.057 2.402 - 2.473
# E  4.73 - 4.786 2.909 - 2.963 2.245 - 2.297
# F 3.511 - 3.554 3.547 - 3.603 2.316 - 2.374
# G 3.601 - 3.689 3.073 - 3.144 3.145 - 3.215
# H 2.056 - 2.118  2.597 - 2.69  2.58 - 2.627
# I 1.802 - 1.835 2.794 - 2.895   2.452 - 2.5
# J 2.399 - 2.461 1.807 - 1.844 3.199 - 3.254

class(x)
# [1] "data.frame"
identical(rownames(datMean), rownames(x))
# [1] TRUE

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接