Aplicando uma function para cada linha de uma tabela usando dplyr?

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 

Atualização 2017-08-03

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.

Postagem original

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.

Exemplos

 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)