Essas soluções (1) mantêm o pipeline, (2) não substituem a entrada e (3) exigem apenas que a condição seja especificada uma vez:
1a) mutate_cond Crie uma função simples para frames de dados ou tabelas de dados que podem ser incorporados em pipelines. Esta função é semelhante, mutate
mas age apenas nas linhas que satisfazem a condição:
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 Esta é uma função alternativa para quadros de dados ou tabelas de dados que são semelhantes, mutate
mas são usados apenas dentro group_by
(como no exemplo abaixo) e só opera no último grupo ao invés de todos os grupos. Observe que TRUE> FALSE, portanto, se group_by
especifica uma condição, mutate_last
só operará em linhas que satisfaçam essa condição.
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) fatorar a condição Fatorar a condição tornando-a uma coluna extra que é posteriormente removida. Em seguida ifelse
, use replace
ou aritmética com lógica, conforme ilustrado. Isso também funciona para tabelas de dados.
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 Poderíamos usar SQL update
por meio do pacote sqldf no pipeline para quadros de dados (mas não tabelas de dados, a menos que os convertamos - isso pode representar um bug no dplyr. Veja o problema 1579 do dplyr ). Pode parecer que estamos modificando indesejavelmente a entrada neste código devido à existência do, update
mas na verdade o update
está agindo em uma cópia da entrada no banco de dados gerado temporariamente e não na entrada real.
library(sqldf)
DF %>%
do(sqldf(c("update '.'
set 'qty.exit' = qty, cf = 0, 'delta.watts' = 13
where measure = 'exit'",
"select * from '.'")))
4) row_case_when Verifique também row_case_when
definido em
Retornando uma tabela: como vetorizar com case_when? . Ele usa uma sintaxe semelhante a, case_when
mas se aplica a linhas.
library(dplyr)
DF %>%
row_case_when(
measure == "exit" ~ data.frame(qty.exit = qty, cf = 0, delta.watts = 13),
TRUE ~ data.frame(qty.exit, cf, delta.watts)
)
Nota 1: Usamos isso comoDF
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))
Nota 2: O problema de como especificar facilmente a atualização de um subconjunto de linhas também é discutido nas questões de dplyr 134 , 631 , 1518 e 1573, com 631 sendo o thread principal e 1573 sendo uma revisão das respostas aqui.
Você pode fazer isso com
magrittr
o tubo bidirecional de%<>%
:library(dplyr) library(magrittr) dt[dt$measure=="exit",] %<>% mutate(qty.exit = qty, cf = 0, delta.watts = 13)
Isso reduz a quantidade de digitação, mas ainda é muito mais lento do que
data.table
.fonte
data.frame
/tibble
já contiver a coluna definida pormutate
. Não funcionará se você estiver tentando adicionar uma nova coluna, por exemplo, pela primeira vez executando um loop e modificando adata.frame
.data.frame
. FWIW, acabei de voltar a usar emdata.table
vez dedplyr
porque suai
expressão lida com isso facilmente - além disso, o loop geral é executado muito mais rápido.Aqui está uma solução de que gosto:
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 }
Ele permite que você escreva coisas como, por exemplo
mtcars %>% mutate_when( mpg > 22, list(cyl = 100), disp == 160, list(cyl = 200) )
que é bastante legível - embora possa não ter o desempenho que poderia ser.
fonte
Como mostra o eipi10 acima, não há uma maneira simples de fazer uma substituição de subconjunto em dplyr porque o DT usa semântica de passagem por referência vs dplyr usando passagem por valor. dplyr requer o uso de
ifelse()
em todo o vetor, enquanto o DT fará o subconjunto e atualizará por referência (retornando o DT inteiro). Portanto, para este exercício, o DT será substancialmente mais rápido.Você pode alternativamente primeiro subconjunto, a seguir atualizar e finalmente recombinar:
dt.sub <- dt[dt$measure == "exit",] %>% mutate(qty.exit= qty, cf= 0, delta.watts= 13) dt.new <- rbind(dt.sub, dt[dt$measure != "exit",])
Mas o DT será substancialmente mais rápido: (editado para usar a nova resposta do 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
fonte
Acabei
mutate_cond()
de descobrir isso e realmente gostei do @G. Grothendieck, mas achou que poderia ser útil também para lidar com novas variáveis. Portanto, abaixo tem duas adições:Não relacionado: a penúltima linha foi um pouco mais
dplyr
complicada usandofilter()
Três novas linhas no início obtêm nomes de variáveis para uso em
mutate()
e inicializam quaisquer novas variáveis no quadro de dados antes demutate()
ocorrer. Novas variáveis são inicializadas para o restante dodata.frame
usonew_init
, que é definido como missing (NA
) como padrão.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 }
Aqui estão alguns exemplos usando os dados da íris:
Mude
Petal.Length
para 88 ondeSpecies == "setosa"
. Isso funcionará na função original, bem como nesta nova versão.iris %>% mutate_cond(Species == "setosa", Petal.Length = 88)
O mesmo que acima, mas também cria uma nova variável
x
(NA
em linhas não incluídas na condição). Não era possível antes.iris %>% mutate_cond(Species == "setosa", Petal.Length = 88, x = TRUE)
O mesmo que acima, mas as linhas não incluídas na condição para
x
são definidas como FALSE.iris %>% mutate_cond(Species == "setosa", Petal.Length = 88, x = TRUE, new_init = FALSE)
Este exemplo mostra como
new_init
pode ser definido como alist
para inicializar várias novas variáveis com valores diferentes. Aqui, duas novas variáveis são criadas com linhas excluídas sendo inicializadas usando valores diferentes (x
inicializado comoFALSE
,y
asNA
)iris %>% mutate_cond(Species == "setosa" & Sepal.Length < 5, x = TRUE, y = Sepal.Length ^ 2, new_init = list(FALSE, NA))
fonte
mutate_cond
função mostra um erro em meu conjunto de dados, e a função de Grothendiecks não.Error: incorrect length (4700), expecting: 168
Parece estar relacionado à função de filtro.if_else
oucase_when
.mutate_cond é uma ótima função, mas dá um erro se houver um NA na (s) coluna (s) usada (s) para criar a condição. Acho que uma mutação condicional deve simplesmente deixar essas linhas de lado. Isso corresponde ao comportamento de filter (), que retorna linhas quando a condição é TRUE, mas omite ambas as linhas com FALSE e NA.
Com esta pequena mudança, a função funciona perfeitamente:
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 }
fonte
Na verdade, não vejo nenhuma mudança
dplyr
que torne isso muito mais fácil.case_when
é ótimo para quando há várias condições e resultados diferentes para uma coluna, mas não ajuda neste caso em que você deseja alterar várias colunas com base em uma condição. Da mesma forma,recode
economiza digitação se você estiver substituindo vários valores diferentes em uma coluna, mas não ajuda a fazer isso em várias colunas de uma vez. Finalmente,mutate_at
etc. aplicam apenas condições aos nomes das colunas, não às linhas do dataframe. Você poderia potencialmente escrever uma função para mutate_at que faria isso, mas não consigo descobrir como você faria com que ela se comportasse de maneira diferente para colunas diferentes.Dito isso, é como eu abordaria isso usando o
nest
formuláriotidyr
e amap
partir depurrr
.library(data.table) library(dplyr) library(tidyr) library(purrr) # 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)) dt2 <- dt %>% nest(-measure) %>% mutate(data = if_else( measure == "exit", map(data, function(x) mutate(x, qty.exit = qty, cf = 0, delta.watts = 13)), data )) %>% unnest()
fonte
nest(-measure)
para evitar ogroup_by
Uma solução concisa seria fazer a mutação no subconjunto filtrado e, em seguida, adicionar de volta as linhas de não saída da tabela:
library(dplyr) dt %>% filter(measure == 'exit') %>% mutate(qty.exit = qty, cf = 0, delta.watts = 13) %>% rbind(dt %>% filter(measure != 'exit'))
fonte
Com a criação de
rlang
, uma versão ligeiramente modificada do exemplo 1a de Grothendieck é possível, eliminando a necessidade doenvir
argumento, poisenquo()
captura o ambiente que.p
é criado automaticamente.mutate_rows <- function(.data, .p, ...) { .p <- rlang::enquo(.p) .p_lgl <- rlang::eval_tidy(.p, .data) .data[.p_lgl, ] <- .data[.p_lgl, ] %>% mutate(...) .data } dt %>% mutate_rows(measure == "exit", qty.exit = qty, cf = 0, delta.watts = 13)
fonte
Você pode dividir o conjunto de dados e fazer uma chamada mutate regular na
TRUE
parte.O dplyr 0.8 apresenta a função
group_split
que divide por grupos (e os grupos podem ser definidos diretamente na chamada), então vamos usá-la aqui, mas tambémbase::split
funciona.library(tidyverse) df1 %>% group_split(measure == "exit", keep=FALSE) %>% # or `split(.$measure == "exit")` modify_at(2,~mutate(.,qty.exit = qty, cf = 0, delta.watts = 13)) %>% bind_rows() # site space measure qty qty.exit delta.watts cf # 1 1 4 led 1 0 73.5 0.246240409 # 2 2 3 cfl 25 0 56.5 0.360315879 # 3 5 4 cfl 3 0 38.5 0.279966850 # 4 5 3 linear 19 0 40.5 0.281439486 # 5 2 3 linear 18 0 82.5 0.007898384 # 6 5 1 linear 29 0 33.5 0.392412729 # 7 5 3 linear 6 0 46.5 0.970848817 # 8 4 1 led 10 0 89.5 0.404447182 # 9 4 1 led 18 0 96.5 0.115594622 # 10 6 3 linear 18 0 15.5 0.017919745 # 11 4 3 led 22 0 54.5 0.901829577 # 12 3 3 led 17 0 79.5 0.063949974 # 13 1 3 led 16 0 86.5 0.551321441 # 14 6 4 cfl 5 0 65.5 0.256845013 # 15 4 2 led 12 0 29.5 0.340603733 # 16 5 3 linear 27 0 63.5 0.895166931 # 17 1 4 led 0 0 47.5 0.173088800 # 18 5 3 linear 20 0 89.5 0.438504370 # 19 2 4 cfl 18 0 45.5 0.031725246 # 20 2 3 led 24 0 94.5 0.456653397 # 21 3 3 cfl 24 0 73.5 0.161274319 # 22 5 3 led 9 0 62.5 0.252212124 # 23 5 1 led 15 0 40.5 0.115608182 # 24 3 3 cfl 3 0 89.5 0.066147321 # 25 6 4 cfl 2 0 35.5 0.007888337 # 26 5 1 linear 7 0 51.5 0.835458916 # 27 2 3 linear 28 0 36.5 0.691483644 # 28 5 4 led 6 0 43.5 0.604847889 # 29 6 1 linear 12 0 59.5 0.918838163 # 30 3 3 linear 7 0 73.5 0.471644760 # 31 4 2 led 5 0 34.5 0.972078100 # 32 1 3 cfl 17 0 80.5 0.457241602 # 33 5 4 linear 3 0 16.5 0.492500255 # 34 3 2 cfl 12 0 44.5 0.804236607 # 35 2 2 cfl 21 0 50.5 0.845094268 # 36 3 2 linear 10 0 23.5 0.637194873 # 37 4 3 led 6 0 69.5 0.161431896 # 38 3 2 exit 19 19 13.0 0.000000000 # 39 6 3 exit 7 7 13.0 0.000000000 # 40 6 2 exit 20 20 13.0 0.000000000 # 41 3 2 exit 1 1 13.0 0.000000000 # 42 2 4 exit 19 19 13.0 0.000000000 # 43 3 1 exit 24 24 13.0 0.000000000 # 44 3 3 exit 16 16 13.0 0.000000000 # 45 5 3 exit 9 9 13.0 0.000000000 # 46 2 3 exit 6 6 13.0 0.000000000 # 47 4 1 exit 1 1 13.0 0.000000000 # 48 1 1 exit 14 14 13.0 0.000000000 # 49 6 3 exit 7 7 13.0 0.000000000 # 50 2 4 exit 3 3 13.0 0.000000000
Se a ordem das linhas for importante, use
tibble::rowid_to_column
primeiro, depoisdplyr::arrange
onrowid
e selecione-o no final.dados
df1 <- 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), stringsAsFactors = F)
fonte
Acho que essa resposta não foi mencionada antes. Funciona quase tão rápido quanto o 'padrão'
data.table
solução .Usar
base::replace()
df %>% mutate( qty.exit = replace( qty.exit, measure == 'exit', qty[ measure == 'exit'] ), cf = replace( cf, measure == 'exit', 0 ), delta.watts = replace( delta.watts, measure == 'exit', 13 ) )
substituir recicla o valor de substituição, então quando você quiser que os valores das colunas sejam
qty
inseridos nas colunasqty.exit
, você deve subconjuntoqty
... portanto,qty[ measure == 'exit']
na primeira substituição ..agora, você provavelmente não desejará redigitar o
measure == 'exit'
o tempo todo ... portanto, você pode criar um vetor de índice contendo essa seleção e usá-lo nas funções acima.#build an index-vector matching the condition index.v <- which( df$measure == 'exit' ) df %>% mutate( qty.exit = replace( qty.exit, index.v, qty[ index.v] ), cf = replace( cf, index.v, 0 ), delta.watts = replace( delta.watts, index.v, 13 ) )
benchmarks
# Unit: milliseconds # expr min lq mean median uq max neval # data.table 1.005018 1.053370 1.137456 1.112871 1.186228 1.690996 100 # wimpel 1.061052 1.079128 1.218183 1.105037 1.137272 7.390613 100 # wimpel.index 1.043881 1.064818 1.131675 1.085304 1.108502 4.192995 100
fonte
À custa de quebrar a sintaxe dplyr usual, você pode usar
within
do básico:dt %>% within(qty.exit[measure == 'exit'] <- qty[measure == 'exit'], delta.watts[measure == 'exit'] <- 13)
Parece se integrar bem com o tubo e você pode fazer praticamente tudo o que quiser dentro dele.
fonte
dt %>% within({ delta.watts[measure == 'exit'] <- 13 ; qty.exit[measure == 'exit'] <- qty[measure == 'exit'] ; cf[measure == 'exit'] <- 0 })
isso, ele funcionará