Ao trabalhar com plyr
, muitas vezes achei útil usar adply
para funções escalares que eu tenho que aplicar a cada linha.
por exemplo
data(iris) library(plyr) head( adply(iris, 1, transform , Max.Len= max(Sepal.Length,Petal.Length)) ) Sepal.Length Sepal.Width Petal.Length Petal.Width Species Max.Len 1 5.1 3.5 1.4 0.2 setosa 5.1 2 4.9 3.0 1.4 0.2 setosa 4.9 3 4.7 3.2 1.3 0.2 setosa 4.7 4 4.6 3.1 1.5 0.2 setosa 4.6 5 5.0 3.6 1.4 0.2 setosa 5.0 6 5.4 3.9 1.7 0.4 setosa 5.4
Agora eu estou usando dplyr
mais, eu estou querendo saber se existe uma maneira arrumada / natural para fazer isso? Como isso não é o que eu quero:
library(dplyr) head( mutate(iris, Max.Len= max(Sepal.Length,Petal.Length)) ) Sepal.Length Sepal.Width Petal.Length Petal.Width Species Max.Len 1 5.1 3.5 1.4 0.2 setosa 7.9 2 4.9 3.0 1.4 0.2 setosa 7.9 3 4.7 3.2 1.3 0.2 setosa 7.9 4 4.6 3.1 1.5 0.2 setosa 7.9 5 5.0 3.6 1.4 0.2 setosa 7.9 6 5.4 3.9 1.7 0.4 setosa 7.9
Como dplyr 0.2 (eu acho) rowwise()
é implementado, então a resposta para este problema torna-se:
iris %>% rowwise() %>% mutate(Max.Len= max(Sepal.Length,Petal.Length))
Você precisa agrupar por linha:
iris %>% group_by(1:n()) %>% mutate(Max.Len= max(Sepal.Length,Petal.Length))
Isto é o que o 1
fez em adply
.
A abordagem idiomática será criar uma function adequadamente vetorizada.
R
fornecer pmax
que é adequado aqui, no entanto, também fornece Vectorize
como um wrapper para mapply
para permitir que você crie uma versão arbitrária mapply
de uma function arbitrária.
library(dplyr) # use base R pmax (vectorized in C) iris %>% mutate(max.len = pmax(Sepal.Length, Petal.Length)) # use vectorize to create your own function # for example, a horribly inefficient get first non-Na value function # a version that is not vectorized coalesce <- function(a,b) {r <- c(a[1],b[1]); r[!is.na(r)][1]} # a vectorized version Coalesce <- Vectorize(coalesce, vectorize.args = c('a','b')) # some example data df <- data.frame(a = c(1:5,NA,7:10), b = c(1:3,NA,NA,6,NA,10:8)) df %>% mutate(ab =Coalesce(a,b))
Observe que a implementação da vetorização em C / C ++ será mais rápida, mas não há um pacote magicPony
que escreva a function para você.
Estendendo a resposta do BrodieG,
Se a function retorna mais de uma linha, então ao invés de mutate()
, do()
deve ser usado. Então, para combiná-lo novamente, use rbind_all()
no pacote dplyr
.
Na versão dplyr_0.1.2
, usar 1:n()
na cláusula group_by()
não funciona para mim. Espero que Hadley implementará rowwise()
breve.
iris %>% group_by(1:nrow(iris)) %>% do(do_fn) %>% rbind_all()
Testando o desempenho,
library(dplyr) library(plyr) library(microbenchmark) d1_count <- 1000 d2_count <- 10 d1 <- data.frame(a=runif(d1_count)) do_fn <- function(row){ data.frame( a=row$a, b=runif(d2_count))} op <- microbenchmark( dplyr_version = d1 %>% group_by(1:nrow(d1)) %>% do(do_fn) %>% rbind_all(), plyrs_version = adply(d1, 1, do_fn), times=10)
tem os seguintes resultados:
Unit: milliseconds expr min lq median uq max neval dplyr_version 474.8283 509.5577 517.4723 549.9897 703.3613 10 plyrs_version 830.1255 831.0652 862.5729 903.2783 1039.8510 10
Depois de escrever isso, Hadley mudou algumas coisas novamente. As funções que costumavam estar em purrr estão agora em um novo pacote misto chamado purrrlyr , descrito como:
Purrrlyr contém algumas funções que se encontram na intersecção de purrr e dplyr. Eles foram removidos da purrr para tornar a embalagem mais leve e porque foram substituídos por outras soluções no tidyverse.
Então, você precisará instalar o pacote + load para fazer o código abaixo funcionar.
Hadley frequentemente muda de idéia sobre o que devemos usar, mas acho que devemos mudar para as funções em purrr para obter a funcionalidade por linha. Pelo menos, eles oferecem a mesma funcionalidade e têm quase a mesma interface que a do plyr .
Existem duas funções relacionadas, by_row
e invoke_rows
. Meu entendimento é que você usa o by_row
quando deseja fazer um loop por linhas e adicionar os resultados ao data.frame. invoke_rows
é usado quando você faz um loop por linhas de um data.frame e passa cada col como um argumento para uma function. Nós só vamos usar o primeiro.
library(tidyverse) iris %>% by_row(..f = function(this_row) { browser() })
Isso nos permite ver os internos (para que possamos ver o que estamos fazendo), o que é o mesmo que fazê-lo com adply
.
Called from: ..f(.d[[i]], ...) Browse[1]> this_row # A tibble: 1 × 5 Sepal.Length Sepal.Width Petal.Length Petal.Width Species 1 5.1 3.5 1.4 0.2 setosa Browse[1]> Q
Por padrão, o by_row
adiciona uma coluna de lista com base na saída:
iris %>% by_row(..f = function(this_row) { this_row[1:4] %>% unlist %>% mean })
dá:
# A tibble: 150 × 6 Sepal.Length Sepal.Width Petal.Length Petal.Width Species .out 1 5.1 3.5 1.4 0.2 setosa 2 4.9 3.0 1.4 0.2 setosa 3 4.7 3.2 1.3 0.2 setosa 4 4.6 3.1 1.5 0.2 setosa 5 5.0 3.6 1.4 0.2 setosa 6 5.4 3.9 1.7 0.4 setosa 7 4.6 3.4 1.4 0.3 setosa 8 5.0 3.4 1.5 0.2 setosa 9 4.4 2.9 1.4 0.2 setosa 10 4.9 3.1 1.5 0.1 setosa # ... with 140 more rows
se, em vez disso, retornarmos um data.frame
, obteremos uma lista com data.frame
s:
iris %>% by_row( ..f = function(this_row) { data.frame( new_col_mean = this_row[1:4] %>% unlist %>% mean, new_col_median = this_row[1:4] %>% unlist %>% median ) })
dá:
# A tibble: 150 × 6 Sepal.Length Sepal.Width Petal.Length Petal.Width Species .out 1 5.1 3.5 1.4 0.2 setosa 2 4.9 3.0 1.4 0.2 setosa 3 4.7 3.2 1.3 0.2 setosa 4 4.6 3.1 1.5 0.2 setosa 5 5.0 3.6 1.4 0.2 setosa 6 5.4 3.9 1.7 0.4 setosa 7 4.6 3.4 1.4 0.3 setosa 8 5.0 3.4 1.5 0.2 setosa 9 4.4 2.9 1.4 0.2 setosa 10 4.9 3.1 1.5 0.1 setosa # ... with 140 more rows
A forma como adicionamos a saída da function é controlada pelo parâmetro .collate
. Existem três opções: list, rows, cols. Quando nossa saída tem comprimento 1, não importa se usamos linhas ou cols.
iris %>% by_row(.collate = "cols", ..f = function(this_row) { this_row[1:4] %>% unlist %>% mean }) iris %>% by_row(.collate = "rows", ..f = function(this_row) { this_row[1:4] %>% unlist %>% mean })
ambos produzem:
# A tibble: 150 × 6 Sepal.Length Sepal.Width Petal.Length Petal.Width Species .out 1 5.1 3.5 1.4 0.2 setosa 2.550 2 4.9 3.0 1.4 0.2 setosa 2.375 3 4.7 3.2 1.3 0.2 setosa 2.350 4 4.6 3.1 1.5 0.2 setosa 2.350 5 5.0 3.6 1.4 0.2 setosa 2.550 6 5.4 3.9 1.7 0.4 setosa 2.850 7 4.6 3.4 1.4 0.3 setosa 2.425 8 5.0 3.4 1.5 0.2 setosa 2.525 9 4.4 2.9 1.4 0.2 setosa 2.225 10 4.9 3.1 1.5 0.1 setosa 2.400 # ... with 140 more rows
Se produzirmos um data.frame com 1 linha, importa apenas ligeiramente o que usamos:
iris %>% by_row(.collate = "cols", ..f = function(this_row) { data.frame( new_col_mean = this_row[1:4] %>% unlist %>% mean, new_col_median = this_row[1:4] %>% unlist %>% median ) }) iris %>% by_row(.collate = "rows", ..f = function(this_row) { data.frame( new_col_mean = this_row[1:4] %>% unlist %>% mean, new_col_median = this_row[1:4] %>% unlist %>% median ) })
ambos dão:
# A tibble: 150 × 8 Sepal.Length Sepal.Width Petal.Length Petal.Width Species .row new_col_mean new_col_median 1 5.1 3.5 1.4 0.2 setosa 1 2.550 2.45 2 4.9 3.0 1.4 0.2 setosa 2 2.375 2.20 3 4.7 3.2 1.3 0.2 setosa 3 2.350 2.25 4 4.6 3.1 1.5 0.2 setosa 4 2.350 2.30 5 5.0 3.6 1.4 0.2 setosa 5 2.550 2.50 6 5.4 3.9 1.7 0.4 setosa 6 2.850 2.80 7 4.6 3.4 1.4 0.3 setosa 7 2.425 2.40 8 5.0 3.4 1.5 0.2 setosa 8 2.525 2.45 9 4.4 2.9 1.4 0.2 setosa 9 2.225 2.15 10 4.9 3.1 1.5 0.1 setosa 10 2.400 2.30 # ... with 140 more rows
exceto que o segundo tem a coluna chamada .row
e o primeiro não.
Finalmente, se a nossa saída for maior que o comprimento 1, seja como um vector
seja como um data.frame
com linhas, então importa se usamos linhas ou cols para .collate
:
mtcars[1:2] %>% by_row(function(x) 1:5) mtcars[1:2] %>% by_row(function(x) 1:5, .collate = "rows") mtcars[1:2] %>% by_row(function(x) 1:5, .collate = "cols")
produz, respectivamente:
# A tibble: 32 × 3 mpg cyl .out 1 21.0 6 2 21.0 6 3 22.8 4 4 21.4 6 5 18.7 8 6 18.1 6 7 14.3 8 8 24.4 4 9 22.8 4 10 19.2 6 # ... with 22 more rows # A tibble: 160 × 4 mpg cyl .row .out 1 21 6 1 1 2 21 6 1 2 3 21 6 1 3 4 21 6 1 4 5 21 6 1 5 6 21 6 2 1 7 21 6 2 2 8 21 6 2 3 9 21 6 2 4 10 21 6 2 5 # ... with 150 more rows # A tibble: 32 × 7 mpg cyl .out1 .out2 .out3 .out4 .out5 1 21.0 6 1 2 3 4 5 2 21.0 6 1 2 3 4 5 3 22.8 4 1 2 3 4 5 4 21.4 6 1 2 3 4 5 5 18.7 8 1 2 3 4 5 6 18.1 6 1 2 3 4 5 7 14.3 8 1 2 3 4 5 8 24.4 4 1 2 3 4 5 9 22.8 4 1 2 3 4 5 10 19.2 6 1 2 3 4 5 # ... with 22 more rows
Então, linha de fundo. Se você quiser a funcionalidade adply(.margins = 1, ...)
, você pode usar o by_row
.
Algo assim?
iris$Max.Len <- pmax(iris$Sepal.Length, iris$Petal.Length)