在数据框中通过查找双重矩阵来填充一个新的列。

9

I have a dataframe df:

colour  shape
'red'   circle
'blue'  square
'blue'  circle
'green' sphere

一个带有命名行/列的双重矩阵m

      circle square sphere  
red   1      4      7
blue  2      5      8
green 3      6      9

我想在DF中添加一个新列,使得我可以得到:

id  colour  shape
1   'red'   circle
5   'blue'  square
2   'blue'  circle
9   'green' sphere

我尝试使用以下代码来实现,但似乎并没有起作用:
df$id <- m[df$colour,df$shape]

我也尝试了apply()等方法,但没有成功。有人能告诉我在不使用循环的情况下正确的做法吗?


感谢大家的帮助。@Tommy和DWin在下面的回答都非常好用。考虑到我的实际数据中有字符向量,我选择了DWin的方案。 - Ina
6个回答

8

如果这些是字符向量而不是因子(除非你特别避免),我认为我可能会赢得最短答案比赛。它只需要添加 cbind 将两个 df "character" 向量转换为 [.matrix 函数所期望的两列矩阵,你在使用该函数时已经非常接近成功了。(而且它似乎也相当表达力。)

# Data construct
d <- data.frame(color=c('red','blue','blue','green'), 
shape=c('circle','square','circle','sphere'), stringsAsFactors=FALSE)
 m <- matrix(1:9, 3,3, dimnames=list(c('red','blue','green'), c('circle','square','sphere')))
# Code:

 d$id <- with( d, m [ cbind(color, shape) ] )
 d
  color  shape id
1   red circle  1
2  blue square  5
3  blue circle  2
4 green sphere  9

请注意,这仅适用于d中的级别与m中的rownames/colnames具有相同顺序的情况。我在我的答案中试图解释这一点。使用m<-m[3:1,]再次尝试并查看结果失败... - Tommy
哦,抱歉,我没有仔细阅读:鉴于d包含字符向量而不是因子,它实际上可以工作... 不过我的解决方案在任何情况下都有效;-) - Tommy
3
可以使用m[ cbind(as.character(d$color), as.character(d$shape))],我认为这样更加通用和清晰。 - IRTFM
这正是我在之前的回答中建议的;-) - 我只是为了清晰起见将其分成了两行。然而,这个解决方案比我使用match的解决方案慢一些... - Tommy
@Tommy:我确实看到你的答案有相似之处,但我认为(现在检查是错误的)使用第二个cbind会将其所有列静默地强制转换为公共类。我现在看到cbind.data.frame并不这样做。帮助页面在两个不同的地方说cbind.data.frame将会将字符列静默转换为因子。在这种情况下,这不是问题,但我过去曾被cbind.data.frame的行为所困扰,所以我避免使用它。 - IRTFM

7
一种相对简单(且快速!)的替代方法是使用矩阵来索引您的矩阵:
# Your data
d <- data.frame(color=c('red','blue','blue','green'), shape=c('circle','square','circle','sphere'))
m <- matrix(1:9, 3,3, dimnames=list(c('red','blue','green'), c('circle','square','sphere')))

# Create index matrix - each row is a row/col index
i <- cbind(match(d$color, rownames(m)), match(d$shape, colnames(m)))

# Now use it and add as the id column...
d2 <- cbind(id=m[i], d)

d2
#  id color  shape
#1  1   red circle
#2  5  blue square
#3  2  blue circle
#4  9 green sphere
match 函数用于查找特定字符串的相应数字索引。
请注意,在新版本的 R(2.13 及更高版本,我想)中,您可以在索引矩阵中使用字符字符串。不幸的是,颜色和形状列通常是 factors,而 cbind 对此不太友好(它使用整数代码),因此您需要使用 as.character 进行强制转换:
i <- cbind(as.character(d$color), as.character(d$shape))

我猜使用match更有效率,虽然我进行了测量,但似乎使用match要快大约20%。
# Make 1 million rows
d <- d[sample.int(nrow(d), 1e6, TRUE), ]

system.time({
  i <- cbind(match(d$color, rownames(m)), match(d$shape, colnames(m)))
  d2 <- cbind(id=m[i], d)
}) # 0.46 secs


system.time({
  i <- cbind(as.character(d$color), as.character(d$shape))
  d2 <- cbind(id=m[i], d)
}) # 0.55 secs

只要@Tommy提出来,将m转换为向量的解决方案在我的机器上只需要0.14秒,而上面第一个示例需要0.50秒 ;) - BenBarnes
我已经标记了@DWin的答案为正确答案,因为那是我使用的答案(我更喜欢简单性并且没有时间限制),但这个答案也非常好,我真的很感激付出的努力。谢谢! - Ina

2

另一种方法是使用reshape2plyr(仅用于连接)软件包。

require(plyr)
require(reshape2)

Df <- data.frame(colour = c("red", "blue", "blue", "green"), 
                  shape = c("circle", "square", "circle", "sphere"))

Mat <- matrix(1:9, dimnames = list(c("red", "blue", "green"),
                                   c("circle", "square", "sphere")), 
                    nrow = 3)

Df2 <- melt.array(Mat, varnames = c("colour", "shape"))

join(Df, Df2)
result <- join(Df, Df2)

join(Df, Df2)
Joining by: colour, shape
  colour  shape value
1    red circle     1
2   blue square     5
3   blue circle     2
4  green sphere     9

希望这可以帮到您。

1

merge() 在这里是你的好朋友。要使用它,我们需要一个适当的数据框与包含 ID 矩阵堆叠版本的数据框进行合并。我用下面的代码创建了 newdf

df <- data.frame(matrix(1:9, ncol = 3))
colnames(df) <- c("circle","square","sphere")
rownames(df) <- c("red","blue","green")

newdf <- cbind.data.frame(ID = unlist(df), 
                          expand.grid(colour = rownames(df), 
                                      shape = colnames(df)))

这将导致:

> newdf
        ID colour  shape
circle1  1    red circle
circle2  2   blue circle
circle3  3  green circle
square1  4    red square
square2  5   blue square
square3  6  green square
sphere1  7    red sphere
sphere2  8   blue sphere
sphere3  9  green sphere

然后,使用您的原始数据对象df2进行定义,如下所示:

df2 <- data.frame(colour = c("red","blue","blue","green"),
                  shape = c("circle","square","circle","sphere"))

使用 merge()
> merge(newdf, df2, sort = FALSE)
  colour  shape ID
1    red circle  1
2   blue circle  2
3   blue square  5
4  green sphere  9

如果需要,您可以存储并重新排列这些列:

> res <- merge(newdf, df2, sort = FALSE)
> res <- res[,c(3,1,2)]
> res
  ID colour  shape
1  1    red circle
2  2   blue circle
3  5   blue square
4  9  green sphere

1
你也可以将矩阵 m 转换为向量,然后将 ID 与颜色和形状值匹配:
df<-data.frame(colour=c("red","blue","blue","green"),
  shape=c("circle","square","circle","sphere"))


m<-matrix(1:9,nrow=3,dimnames=list(c("red","blue","green"),
  c("circle","square","sphere")))


mVec<-as.vector(m)

下一步是将 df 中的颜色与 m 矩阵中相应的 dimname 匹配,然后添加一个对应形状的整数。结果是在具有相应 ID 的 m 向量索引中。
df$ID<-mVec[match(df$colour, dimnames(m)[[1]]) + (dim(m)[1]*
  (match(df$shape, dimnames(m)[[2]]) - 1))]

0
#recreating your data
dat <- read.table(text="colour  shape
'red'   circle
'blue'  square
'blue'  circle
'green' sphere", header=TRUE)

d2 <- matrix(c(1:9), ncol=3, nrow=3, byrow=TRUE)
dimnames(d2) <-list(c('circle', 'square', 'sphere'),
c("red", "blue", "green"))
d2<-as.table(d2)

#make a list of matching to the row and column names of the look up matrix
LIST <- list(match(dat[, 2], rownames(d2)), match(dat[, 1], colnames(d2)))
#use sapply to index the lookup matrix using the row and col values from LIST 
id <- sapply(seq_along(LIST[[1]]), function(i) d2[LIST[[1]][i], LIST[[2]][i]])
#put it all back together
data.frame(id=id, dat)

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