根据其他列的条件,用第三列的值填充数据框中的一列

3

警告:对于我这个初学者来说,这个问题似乎非常容易,但在 SO 上更复杂的主题中可能没有找到正确的解决方案(请参见 这里这里这里 和其他地方)。

我想要根据另一列,并使用其他列作为输入,在我的数据框中填充一列。

以下是一个更清晰的示例:

  Version1 Version2 Version3 Version4 Presented_version Color
1     blue      red    green   yellow                 1    NA
2      red     blue   yellow    green                 4    NA
3   yellow    green      red     blue                 3    NA


我想用Version1/Version2/Version3/Version4中的一个值填充“Color”列。列Presented_version告诉我需要哪四个值之一。 例如,在第1行,Presented_version是1,所以所需的值在“Version1”(“blue”)中。第1行的颜色应该是蓝色。
有没有人能够向我展示一种不使用循环和大量“if”语句来完成此操作的方法?
structure(list(Version1 = structure(1:3, .Label = c("blue", "red", 
"yellow"), class = "factor"), Version2 = structure(c(3L, 1L, 
2L), .Label = c("blue", "green", "red"), class = "factor"), Version3 = structure(c(1L, 
3L, 2L), .Label = c("green", "red", "yellow"), class = "factor"), 
    Version4 = structure(3:1, .Label = c("blue", "green", "yellow"
    ), class = "factor"), Presented_version = c(1L, 4L, 3L), 
    Color = c(NA, NA, NA)), class = "data.frame", row.names = c(NA, 
-3L))

======================= 编辑完成!

我简化了例子以解释我的问题,但上面的例子与我的实际数据集在几个方面存在差异,因此解决方案做出了假设,而我的数据实际上并不符合这些假设。 以下是数据框的更准确表示。特别是,Presented_version与Version1...Version4列的内容之间没有固定的匹配(这取决于一个额外的列,我现在称之为Painter),而Version1到Version4不一定在我的数据集的第1到第4列中。

  FillerColumn Painter Version1 Version2 Version3 Version4 Version_presented Color FillerColumn.1
1           77       A     blue      red    green   yellow                 1    NA             77
2           77       B      red     blue   yellow    green                 4    NA             77
3           77       C   yellow    green      red     blue                 3    NA             77
4           77       D      red     blue   yellow    green                 1    NA             77

structure(list(FillerColumn = c(77L, 77L, 77L, 77L), Painter = structure(1:4, .Label = c("A", 
"B", "C", "D"), class = "factor"), Version1 = structure(c(1L, 
2L, 3L, 2L), .Label = c("blue", "red", "yellow"), class = "factor"), 
    Version2 = structure(c(3L, 1L, 2L, 1L), .Label = c("blue", 
    "green", "red"), class = "factor"), Version3 = structure(c(1L, 
    3L, 2L, 3L), .Label = c("green", "red", "yellow"), class = "factor"), 
    Version4 = structure(c(3L, 2L, 1L, 2L), .Label = c("blue", 
    "green", "yellow"), class = "factor"), Version_presented = c(1L, 
    4L, 3L, 1L), Color = c(NA, NA, NA, NA), FillerColumn.1 = c(77L, 
    77L, 77L, 77L)), class = "data.frame", row.names = c(NA, 
-4L))
3个回答

4
我们可以使用向量化选项与 `行/列` 索引来提取数值,而不是任何循环。
df1$color <- df1[1:4][cbind(1:nrow(df1), df1$Presented_version)]
df1$color
#[1] "blue"  "green" "red"  

基准测试

dfN <- df1[rep(seq_len(nrow(df1)), 1e6),]


system.time({
   dfN[1:4][cbind(1:nrow(dfN), dfN$Presented_version)]

 })
# user  system elapsed 
#   1.216   0.110   1.321


system.time({
 cols <- grep("^Version", names(dfN))
 unlist(mapply(function(x, y) dfN[x, cols][y], 
                    1:nrow(dfN),dfN$Presented_version))

 })
#  user  system elapsed 
# 319.907   1.644 322.418 

现在,让我们来看看使用apply的另一个选项

system.time({
  apply(dfN, 1, function(x) x[cols][as.numeric(x["Presented_version"])])
 }) 
#  user  system elapsed 
# 14.240   0.365  14.550 

df1[1:4] 中的 [1:4] 是什么目的?没有它也可以运行。 - cropgen
可能是在每个循环中,调用了数据框并对行/列进行了子集化。 - akrun
谢谢你的帮助!它有效了!我不确定我是否理解了这段代码,所以为了保险起见:这个解决方案是否假定“版本”列按照从1到4的顺序进行排序并且完整?因此,如果我有的是Version3 Version1 Version5列,那么这个解决方案就不会起作用,对吗? - Kastany
2
@Kastany,这取决于“Presented_version”中的列索引,假设“Version”列的顺序是您想要的顺序。比如说,如果您将列排列为“Version1”、“Version2”、“Version3”、“Version4”,并且“Presented_version”中的第一行为3,则会获取“Version3”的值。如果列排列为“Version3”、“Version2”、“Version1”、“Version4”,则会获取“Version1”的值,否则必须更改列顺序。 - akrun
1
谢谢您的解释! - Kastany
显示剩余3条评论

2

我喜欢对数据集进行操作。尝试使用melt方法的data.table。

df <- setDT(df)

df1 <- melt.data.table(df,
                       id.vars = c('Presented_version'),
                       measure.vars = patterns('Version'),
                       value.name = 'Color',
                       variable.name = 'Version')[
  , version1 := str_extract(Version, '\\d+')][
    Presented_version == version1][
      version1 := NULL]

resulting in

   Presented_version  Version Color 
1:                 1 Version1  blue        
2:                 3 Version3   red        
3:                 4 Version4 green      

"最初的回答"可以翻译为"Original Answer"。如果你想保留相同的原始结构,请使用以下方法:
merge(df, 
      df1[, .(Presented_version, Color)],
      by = 'Presented_version')

   Presented_version Version1 Version2 Version3 Version4 Color
1:                 1     blue      red    green   yellow  blue
2:                 3   yellow    green      red     blue   red
3:                 4      red     blue   yellow    green green  

谢谢,这是我最终采用的解决方案!由于实际数据框架有些复杂,我不得不调整它以包括进一步的id.vars,但它确实满足了我的需要,而且我理解了这个解决方案 :) - Kastany

1
一种使用mapply的方法。
cols <- grep("^Version", names(df))
df$Color <- unlist(mapply(function(x, y) df[x, cols][y], 
                   1:nrow(df),df$Presented_version))

df
#  Version1 Version2 Version3 Version4 Presented_version Color
#1     blue      red    green   yellow                 1  blue
#2      red     blue   yellow    green                 4 green
#3   yellow    green      red     blue                 3   red

使用apply方法

apply(df, 1, function(x) x[cols][as.numeric(x["Presented_version"])])
#[1] "blue"  "green" "red" 

[.data.frame(df[x, cols], y)中出现错误:选择的列未定义。实际上,原始数据框中的列名是不同的,它们是:Story1、Story2、Story3、Story4和StoryPresented。(抱歉,我以为我错了,删除了评论,但如果我尝试在我的实际数据集上运行代码,确实会出现错误。) - Kastany
在这种情况下,您需要 cols <- grep("Story\\d+", names(df)),因为您不想考虑 StoryPresented 列。 - Ronak Shah
啊,非常抱歉,我觉得例子太简单了。我的数据框除了Story1/2/3/4之外还有其他列,现在我意识到grep是基于列索引的。所以可能它不起作用,因为我得到了
cols [1] 2 3 4 5 而这与StoryPresented不匹配。感谢您的帮助!
我不想占用您太多时间,我会尝试使用数据集的子集并进行合并来找到解决方法。
- Kastany
谢谢,我使用了以下更改的解决方案来处理数据中的na值并允许灵活的索引(我得到了一些帮助): cols <- mapply( function(x) match(x, names(df)), c("Story1", "Story2", "Story3", "Story4") )df$Color <- unlist( mapply( function(x, y) if(is.na(y)) return(NA) else return(df[x, cols][y]), 1:nrow(df), df$StoryVersion)) - Kastany

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