当前位置:  开发笔记 > 数据库 > 正文

dplyr对行的子集进行mutate/replace

如何解决《dplyr对行的子集进行mutate/replace》经验,为你挑选了6个好方法。

我正在尝试基于dplyr的工作流程(而不是主要使用我习惯的data.table),而且我遇到了一个问题,我无法找到一个等效的dplyr解决方案.我经常遇到需要根据单个条件有条件地更新/替换多个列的场景.这是一些示例代码,我的data.table解决方案:

library(data.table)

# Create some sample data
set.seed(1)
dt <- data.table(site = sample(1:6, 50, replace=T),
                 space = sample(1:4, 50, replace=T),
                 measure = sample(c('cfl', 'led', 'linear', 'exit'), 50, 
                               replace=T),
                 qty = round(runif(50) * 30),
                 qty.exit = 0,
                 delta.watts = sample(10.5:100.5, 50, replace=T),
                 cf = runif(50))

# Replace the values of several columns for rows where measure is "exit"
dt <- dt[measure == 'exit', 
         `:=`(qty.exit = qty,
              cf = 0,
              delta.watts = 13)]

是否有一个简单的dplyr解决方案来解决同样的问题?我想避免使用ifelse,因为我不想多次输入条件 - 这是一个简化的例子,但有时很多基于单个条件的赋值.

在此先感谢您的帮助!



1> G. Grothendi..:

这些解决方案(1)维护管道,(2)覆盖输入,(3)只需要指定条件一次:

1a)mutate_cond为可以合并到管道中的数据帧或数据表创建一个简单的函数.此函数类似mutate但仅作用于满足条件的行:

mutate_cond <- function(.data, condition, ..., envir = parent.frame()) {
  condition <- eval(substitute(condition), .data, envir)
  .data[condition, ] <- .data[condition, ] %>% mutate(...)
  .data
}

DF %>% mutate_cond(measure == 'exit', qty.exit = qty, cf = 0, delta.watts = 13)

1b)mutate_last这是数据帧或数据表的替代函数,它又类似mutate但仅在其中使用group_by(如下例所示),并且仅对最后一个组而不是每个组进行操作.请注意,TRUE> FALSE,因此如果group_by指定条件,则mutate_last仅对满足该条件的行进行操作.

mutate_last <- function(.data, ...) {
  n <- n_groups(.data)
  indices <- attr(.data, "indices")[[n]] + 1
  .data[indices, ] <- .data[indices, ] %>% mutate(...)
  .data
}


DF %>% 
   group_by(is.exit = measure == 'exit') %>%
   mutate_last(qty.exit = qty, cf = 0, delta.watts = 13) %>%
   ungroup() %>%
   select(-is.exit)

2)因子分解条件通过使条件成为额外的列而将其删除后将其排除.然后使用ifelse,replace或如图所示使用逻辑算术.这也适用于数据表.

library(dplyr)

DF %>% mutate(is.exit = measure == 'exit',
              qty.exit = ifelse(is.exit, qty, qty.exit),
              cf = (!is.exit) * cf,
              delta.watts = replace(delta.watts, is.exit, 13)) %>%
       select(-is.exit)

3)sqldf我们可以update通过管道中的sqldf包使用SQL 来获取数据帧(但不是数据表,除非我们转换它们 - 这可能代表dplyr中的一个错误.请参阅dplyr issue 1579).可能看起来由于存在而不希望地修改该代码中的输入,update但实际上update是在临时生成的数据库中而不是在实际输入上作用于输入的副本.

library(sqldf)

DF %>% 
   do(sqldf(c("update '.' 
                 set 'qty.exit' = qty, cf = 0, 'delta.watts' = 13 
                 where measure = 'exit'", 
              "select * from '.'")))

注1:我们用它作为DF

set.seed(1)
DF <- data.frame(site = sample(1:6, 50, replace=T),
                 space = sample(1:4, 50, replace=T),
                 measure = sample(c('cfl', 'led', 'linear', 'exit'), 50, 
                               replace=T),
                 qty = round(runif(50) * 30),
                 qty.exit = 0,
                 delta.watts = sample(10.5:100.5, 50, replace=T),
                 cf = runif(50))

注2:如何轻松地更新指定行的子集的问题也讨论了dplyr问题134,631,1518和1573与631是主线程和1573是这里的答案进行了审查.



2> eipi10..:

你可以用magrittr双向管道做到这一点%<>%:

library(dplyr)
library(magrittr)

dt[dt$measure=="exit",] %<>% mutate(qty.exit = qty,
                                    cf = 0,  
                                    delta.watts = 13)

这减少了打字的数量,但仍然慢得多data.table.



3> Kevin Ushey..:

这是我喜欢的解决方案:

mutate_when <- function(data, ...) {
  dots <- eval(substitute(alist(...)))
  for (i in seq(1, length(dots), by = 2)) {
    condition <- eval(dots[[i]], envir = data)
    mutations <- eval(dots[[i + 1]], envir = data[condition, , drop = FALSE])
    data[condition, names(mutations)] <- mutations
  }
  data
}

它可以让你写出像

mtcars %>% mutate_when(
  mpg > 22,    list(cyl = 100),
  disp == 160, list(cyl = 200)
)

这是非常易读的 - 尽管它可能没有那么高效.



4> Alex W..:

正如上面的eipi10所示,没有一种简单的方法可以在dplyr中进行子集替换,因为DT使用pass-by-reference语法与使用pass-by-value的dplyr.dplyr需要在ifelse()整个向量上使用,而DT将执行子集并通过引用更新(返回整个DT).因此,对于本练习,DT将大大加快.

您可以选择先进行子集,然后进行更新,最后重新组合:

dt.sub <- dt[dt$measure == "exit",] %>%
  mutate(qty.exit= qty, cf= 0, delta.watts= 13)

dt.new <- rbind(dt.sub, dt[dt$measure != "exit",])

但DT会快得多:(编辑使用eipi10的新答案)

library(data.table)
library(dplyr)
library(microbenchmark)
microbenchmark(dt= {dt <- dt[measure == 'exit', 
                            `:=`(qty.exit = qty,
                                 cf = 0,
                                 delta.watts = 13)]},
               eipi10= {dt[dt$measure=="exit",] %<>% mutate(qty.exit = qty,
                                cf = 0,  
                                delta.watts = 13)},
               alex= {dt.sub <- dt[dt$measure == "exit",] %>%
                 mutate(qty.exit= qty, cf= 0, delta.watts= 13)

               dt.new <- rbind(dt.sub, dt[dt$measure != "exit",])})


Unit: microseconds
expr      min        lq      mean   median       uq      max neval cld
     dt  591.480  672.2565  747.0771  743.341  780.973 1837.539   100  a 
 eipi10 3481.212 3677.1685 4008.0314 3796.909 3936.796 6857.509   100   b
   alex 3412.029 3637.6350 3867.0649 3726.204 3936.985 5424.427   100   b



5> Simon Jackso..:

我偶然发现了这个,非常喜欢mutate_cond()@G.格洛腾迪克,但认为处理新变量可能会派上用场.所以,下面有两个补充:

无关:倒数第二行做多一点dplyr使用filter()

开头的三个新行获取用于的变量名称mutate(),并在mutate()发生之前初始化数据框中的任何新变量.新的变量初始化为的剩余data.frame使用new_init,它被设置为缺失(NA)作为默认值.

mutate_cond <- function(.data, condition, ..., new_init = NA, envir = parent.frame()) {
  # Initialize any new variables as new_init
  new_vars <- substitute(list(...))[-1]
  new_vars %<>% sapply(deparse) %>% names %>% setdiff(names(.data))
  .data[, new_vars] <- new_init

  condition <- eval(substitute(condition), .data, envir)
  .data[condition, ] <- .data %>% filter(condition) %>% mutate(...)
  .data
}

以下是使用虹膜数据的一些示例:

更改Petal.Length到88何处Species == "setosa".这将适用于原始功能以及此新版本.

iris %>% mutate_cond(Species == "setosa", Petal.Length = 88)

与上面相同,但也创建一个新变量x(NA不包括在条件中的行).以前不可能.

iris %>% mutate_cond(Species == "setosa", Petal.Length = 88, x = TRUE)

与上面相同,但条件中未包含的行x设置为FALSE.

iris %>% mutate_cond(Species == "setosa", Petal.Length = 88, x = TRUE, new_init = FALSE)

此示例显示如何new_init将a设置list为初始化具有不同值的多个新变量.在这里,两个新的变量与排除行创建使用不同的值被初始化(x初始化为FALSE,yNA)

iris %>% mutate_cond(Species == "setosa" & Sepal.Length < 5,
                  x = TRUE, y = Sepal.Length ^ 2,
                  new_init = list(FALSE, NA))



6> Magnus..:

mutate_cond是一个很棒的函数,但是如果用于创建条件的列中不存在NA,则会产生错误。我觉得有条件的mutation应该只留下这样的行。这与filter()的行为相匹配,当条件为TRUE时,filter()返回行,但是省略了两行都为FALSE和NA。

有了这个小的改动,该功能就可以发挥出魅力:

mutate_cond <- function(.data, condition, ..., envir = parent.frame()) {
    condition <- eval(substitute(condition), .data, envir)
    condition[is.na(condition)] = FALSE
    .data[condition, ] <- .data[condition, ] %>% mutate(...)
    .data
}

推荐阅读
喜生-Da
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有