如何遍历向量并替换 R 中的值

How to iterate through vector and replace values in R

这是一项相当简单的任务,但我正在努力思考如何使用具有键和值的数据框来匹配值。我尝试过合并,但由于行数不同,我不确定是否合适。

是否有我可以编写的 for 循环,它将遍历输入数据框中的每个键并更改 Product 的值(如果它是查找中的值之一)table?

基本上,我的数据是这样的:

input_key <- c(9061,8680,1546,5376,9550,9909,3853,3732,9209) input_product <- c("Water", "Bread", NA, "Chips", "Chicken", NA, "Chocolate", "Donuts", "Juice") input <- as.data.frame(cbind(input_key, input_product))

我想用相应查找中的产品值替换 NA table:

lookup_key <- c(1245,1546, 7764, 9909)
lookup_product <- c("Ice Cream","Soda", "Bacon","Cheese")
lookup_data <- as.dataframe(cbind(lookup_key, lookup_product))

最后,我希望最终数据框看起来像这样:

output_key <- c(9061,8680,1546,5376,9550,9909,3853,3732,9209)
output_product <- c("Water", "Bread", "Soda", "Chips", "Chicken", "Cheese", Chocolate","Donuts", "Juice")
output_data <- as.data.frame(cbind(output_key, output_product))

很累,所以这很笨拙,但它应该适用于提供的数据(尽管你的输出样本可能是错误的):

require(dplyr)

rbind(input[!is.na(input$input_product),],
    inner_join(lookup_data,input,by=c("lookup_key"="input_key")) %>% 
    select(lookup_key,lookup_product) %>%
    rename(input_product = lookup_product, input_key = lookup_key))

这可以使用 data.table 包轻松完成,如下所示:

# load sample data
input_data <- structure(list(
    input_key = 
        structure(c(6L, 5L, 1L, 4L, 8L, 9L, 
                    3L, 2L, 7L), 
                  .Label = c("1546", "3732", "3853", "5376", "8680", 
                             "9061", "9209", "9550", "9909"), class = "factor"), 
    input_product = structure(c(7L, 1L, NA, 3L, 2L, NA, 4L, 5L, 6L), 
                              .Label = c("Bread", "Chicken", "Chips", "Chocolate", 
                                         "Donuts", "Juice", "Water"), class = "factor")), 
    .Names = c("input_key", 
               "input_product"), 
    row.names = c(NA, -9L), class = "data.frame")

lookup_data <- structure(list(
    lookup_key = structure(1:4, 
                           .Label = c("1245", "1546", "7764", "9909"), class = "factor"), 
    lookup_product = structure(c(3L, 
                                 4L, 1L, 2L), .Label = c("Bacon", "Cheese", "Ice Cream", "Soda"
                                 ), class = "factor")), .Names = c("lookup_key", "lookup_product"
                                 ), row.names = c(NA, -4L), class = "data.frame")

# convert to data.table and add keys for merging
library(data.table)
input <- data.table(input_data, key = 'input_key')
lookup <- data.table(lookup_data, key = 'lookup_key')

# merge the data (can use merge method as well)
DT <- lookup[input]

# where the input_product is NA, replace with lookup
DT[is.na(input_product), input_product := lookup_product]
print(DT)

# you can now get rid of lookup_product column, if you like
DT[, lookup_product:= NULL]
print(DT)

上面的最终输出是:

> print(DT)
   lookup_key input_product
1:       1546          Soda
2:       3732        Donuts
3:       3853     Chocolate
4:       5376         Chips
5:       8680         Bread
6:       9061         Water
7:       9209         Juice
8:       9550       Chicken
9:       9909        Cheese

选项 1:使用 R-base 函数:

矢量解:

input$input_product[input$input_key %in% lookup_data$lookup_key == TRUE] <- 
    lookup_product[lookup_data$lookup_key %in% input$input_key == TRUE]

注意:==TRUE是多余的,添加只是为了更好地理解。

使用lapply函数:

idx <- input$input_key %in% lookup_data$lookup_key
lapply((1:nrow(input)),
    function(i) {
        if (idx[i] == TRUE) {
            jdx <- lookup_data$lookup_key %in% input$input_key[i]
            input$input_product[i] <<- lookup_data$lookup_product[jdx == TRUE]
        }
    }
)

注意:注意全局赋值操作(<<)

使用for循环:

idx <- input$input_key %in% lookup_data$lookup_key
for (i in (1:nrow(input))) {
    if (idx[i] == TRUE) {
        jdx <- lookup_data$lookup_key %in% input$input_key[i]
        input$input_product[i] <- lookup_data$lookup_product[jdx == TRUE]
    }
}

注意:这里只需要简单赋值即可。

在上述情况下,您需要创建数据框并将输入参数设置为:stringsAsFactors as FALSE,例如:

input <- as.data.frame(cbind(input_key, input_product), stringsAsFactors = FALSE)
lookup_data <- as.data.frame(cbind(lookup_key, lookup_product), stringsAsFactors = FALSE)

然后你得到输出:

> input
  input_key input_product
1      9061         Water
2      8680         Bread
3      1546          Soda
4      5376         Chips
5      9550       Chicken
6      9909        Cheese
7      3853     Chocolate
8      3732        Donuts
9      9209         Juice
> 

选项 2:使用 data.table

我发现这个优雅解决方案使用内部连接:

require(data.table)
setkey(input,input_key)
setkey(lookup_data,lookup_key)
> setDT(input)[setDT(lookup_data), input_product := i.lookup_product, nomatch=0][]
 input_key input_product
1:      1546          Soda
2:      3732        Donuts
3:      3853     Chocolate
4:      5376         Chips
5:      8680         Bread
6:      9061         Water
7:      9209         Juice
8:      9550       Chicken
9:      9909        Cheese
> 

data.table对于数据集操作其实是非常强大的。让我们解释一下背后的语法:

  • setDT:将一个数据框通过引用(不发生复制)转换成data.table,因为原始数据集不是data.table类,就是通往 即时转换它们。请注意,现在没有必要使用属性 stringsAsFactors,因为对于 data.table,它的默认值是 FALSE.
  • input[lookup_data, nomatch=0]:就是这样,用data.table包来创建一个内部连接(见这个link)。意思是截取两个表。值为 0no match 选项意味着不会为 i 的那一行返回任何行(在我们的例子中:lookup_data)。

这将是输出:

> setDT(input)[setDT(lookup_data), nomatch=0][]
   input_key input_product lookup_product
   1:      1546            NA           Soda
   2:      9909            NA         Cheese
   > 
  • input_product := i.lookup_product:从外层赋值列 数据集,内层数据集的值。

  • []:打印结果(验证解法目的)

有关 data.table 的更多信息,我建议阅读包 documentation,其中包含许多示例。在 R 中使用以下命令 运行 也是一个好主意(在加载 data.table 程序包之后):

example(data.table)

它提供了 50 多个示例(与包文档中的示例相同)以及关于此包的不同用途的相应结果。

表现

让我们在性能方面比较所有可能的备选方案。那么我们需要修改 增加其大小的输入数据集:

rep.num <- 1000
input_key <- rep(c(9061,8680,1546,5376,9550,9909,3853,3732,9209),rep.num)
input_product <- rep(c("Water", "Bread", NA, "Chips", "Chicken", NA, "Chocolate", 
    "Donuts", "Juice"),rep.num)
input <- as.data.frame(cbind(input_key, input_product), stringsAsFactors=F)

将所有不同的备选方案包装到相应的给定函数中。我已经包括 @count

提出的 dplyr 解决方案
vectSol <- function(input, lookup_data) {
    input$input_product[input$input_key %in% lookup_data$lookup_key == TRUE] <- 
        lookup_product[lookup_data$lookup_key %in% input$input_key == TRUE]
    return(input)
}

lapplySol <- function(input, lookup_data) {
  idx <- input$input_key %in% lookup_data$lookup_key
    lapply((1:nrow(input)),
        function(i) {
            if (idx[i] == TRUE) {
                jdx <- lookup_data$lookup_key %in% input$input_key[i]
                input$input_product[i] <<- lookup_data$lookup_product[jdx == TRUE]
            }
        }
    )
    return(input)
}

forSol <- function(input, lookup_data) {
   idx <- input$input_key %in% lookup_data$lookup_key
    for (i in (1:nrow(input))) {
        if (idx[i] == TRUE) {
            jdx <- lookup_data$lookup_key %in% input$input_key[i]
            input$input_product[i] <- lookup_data$lookup_product[jdx == TRUE]
        }
    } 
   return(input)
}

dataTableSol <- function (input, lookup_data) {
    setkey(input,input_key)
    setkey(lookup_data,lookup_key)
    input[lookup_data, input_product := i.lookup_product, nomatch=0]
    return(input)
}

dplyrSol <- function(input, lookup_data) {
    rbind(input[!is.na(input$input_product),],
    inner_join(lookup_data,input,by=c("lookup_key"="input_key")) %>% 
    select(lookup_key,lookup_product) %>%
    rename(input_product = lookup_product, input_key = lookup_key))
    return(input)
}

现在测试每个解决方案(仔细检查)。

复制输入数据集,因为data.table按引用操作。我们需要从头开始创建一个副本。

input.copy <- setDT(as.data.frame(cbind(input_key, input_product), stringsAsFactors=F))
lookup_data.copy<- setDT(as.data.frame(cbind(lookup_key, lookup_product), 
    stringsAsFactors=F))

input1.out <- vectSol(input, lookup_data)
input2.out <- lapplySol(input, lookup_data)
input3.out <- forSol(input, lookup_data)
input4.out <- forSol(input, lookup_data)
input5.out <- dataTableSol(copy(input.copy), lookup_data.copy)

我们使用包 compare 因为 all.equal 无法比较数据帧 用一个 data.table 对象,因为属性值,所以我们需要一个 仅检查值的比较。

library(compare)
OK <- all(
all.equal(input1.out, input2.out) && all.equal(input1.out, input3.out)
&& all.equal(input1.out, input4.out)
&& compare(input1.out[order(input1.out$input_key),], 
    input5.out, ignoreAttrs=T)$result
)
try(if(!OK) stop("Result are not the same for all methods"))

现在让我们使用microbenchmark包来比较所有解决方案的时间性能

library(microbenchmark)
op <- microbenchmark(
    VECT = {vectSol(input, lookup_data)},
    FOR = {forSol(input, lookup_data)},
    LAPPLY = {lapplySol(input, lookup_data)},
    DPLYR = {dplyrSol(input, lookup_data)},
    DATATABLE = {dataTableSol(input.copy, lookup_data.copy)},
    times=100L)
print(op)

结果如下:

Unit: milliseconds
      expr        min         lq       mean     median         uq        max neval cld
      VECT   1.005890   1.078983   1.384964   1.108162   1.282269   6.562040   100  a 
       FOR 416.268583 438.545475 476.551526 449.679426 476.032938 740.027018   100   b
    LAPPLY 428.456092 454.664204 492.918478 464.204607 501.168572 751.786224   100   b
     DPLYR  13.371847  14.919726  16.482236  16.105815  17.086174  23.537866   100  a 
 DATATABLE   1.699995   2.059205   2.427629   2.279371   2.489406   8.542219   100  a 

此外,我们可以通过以下方式绘制解决方案:

library(ggplot2) #nice log plot of the output
qplot(y=time, data=op, colour=expr) + scale_y_log10()

此顺序的最佳性能是:Vectorial,data.table,dplyr,for-loop,lapply。