Lorsque je travaillais avec plyr
, j’ai souvent trouvé utile d’utiliser adply
pour les fonctions scalaires que je dois appliquer à chaque rangée.
par exemple.
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
Maintenant, j'utilise plus dplyr
plus, je me demande s'il existe une façon naturelle de le faire? Comme c'est PAS ce que je veux:
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
A partir de dplyr 0.2 (je pense) rowwise()
est implémenté, la réponse à ce problème devient:
iris %>%
rowwise() %>%
mutate(Max.Len= max(Sepal.Length,Petal.Length))
rowwise
alternativeCinq ans plus tard (!), Cette réponse génère encore beaucoup de trafic. Depuis qu'il a été donné, rowwise
est de moins en moins recommandé, bien que beaucoup de gens semblent le trouver intuitif. Rendez-vous service et parcourez les informations de Jenny Bryan: flux de travaux orientés lignes en R avec le sens inverse pour bien maîtriser ce sujet.
Le moyen le plus simple que j'ai trouvé est basé sur l'un des exemples de Hadley utilisant pmap
:
iris %>%
mutate(Max.Len= purrr::pmap_dbl(list(Sepal.Length, Petal.Length), max))
En utilisant cette approche, vous pouvez donner un nombre arbitraire d'arguments à la fonction (.f
) dans pmap
.
pmap
est une bonne approche conceptuelle, car elle reflète le fait que lorsque vous effectuez des opérations par rangées, vous travaillez avec des n-uplets à partir d'une liste de vecteurs (les colonnes d'un cadre de données).
L’approche idiomatique consiste à créer une fonction vectorisée appropriée.
R
fournit pmax
ce qui convient ici, mais fournit également Vectorize
comme wrapper pour mapply
afin de vous permettre de créer une version arbitraire vectorisée d'une fonction arbitraire.
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))
Notez que l'implémentation de la vectorisation en C/C++ sera plus rapide, mais il n'y a pas de paquetage magicPony
pour écrire la fonction pour vous.
Vous devez grouper par rangée:
iris %>% group_by(1:n()) %>% mutate(Max.Len= max(Sepal.Length,Petal.Length))
C'est ce que le 1
a fait dans adply
.
Après avoir écrit cela, Hadley a encore changé certaines choses. Les fonctions qui étaient auparavant dans purrr sont maintenant dans n nouveau paquet mixte appelé purrrlyr , décrit comme suit:
purrrlyr contient des fonctions situées à l'intersection de purrr et de dplyr. Ils ont été supprimés de Purrr afin d'alléger le paquet et parce qu'ils ont été remplacés par d'autres solutions de Tidyverse.
Donc, vous devrez installer + charger ce paquet pour que le code ci-dessous fonctionne.
Hadley change souvent d’avis sur ce que nous devrions utiliser, mais je pense que nous sommes supposés passer aux fonctions de purrr pour obtenir la fonctionnalité par ligne. Au moins, ils offrent les mêmes fonctionnalités et ont presque la même interface que adply
de plyr .
Il existe deux fonctions associées, by_row
et invoke_rows
. Si je comprends bien, vous utilisez by_row
lorsque vous souhaitez effectuer une boucle sur des lignes et ajouter les résultats à data.frame. invoke_rows
est utilisé lorsque vous passez en boucle sur les lignes d'un data.frame et transmettez chaque colonne en tant qu'argument à une fonction. Nous n'utiliserons que le premier.
library(tidyverse)
iris %>%
by_row(..f = function(this_row) {
browser()
})
Cela nous permet de voir les éléments internes (pour que nous puissions voir ce que nous faisons), ce qui revient à le faire avec adply
.
Called from: ..f(.d[[i]], ...)
Browse[1]> this_row
# A tibble: 1 × 5
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
<dbl> <dbl> <dbl> <dbl> <fctr>
1 5.1 3.5 1.4 0.2 setosa
Browse[1]> Q
Par défaut, by_row
ajoute une colonne de liste basée sur la sortie:
iris %>%
by_row(..f = function(this_row) {
this_row[1:4] %>% unlist %>% mean
})
donne:
# A tibble: 150 × 6
Sepal.Length Sepal.Width Petal.Length Petal.Width Species .out
<dbl> <dbl> <dbl> <dbl> <fctr> <list>
1 5.1 3.5 1.4 0.2 setosa <dbl [1]>
2 4.9 3.0 1.4 0.2 setosa <dbl [1]>
3 4.7 3.2 1.3 0.2 setosa <dbl [1]>
4 4.6 3.1 1.5 0.2 setosa <dbl [1]>
5 5.0 3.6 1.4 0.2 setosa <dbl [1]>
6 5.4 3.9 1.7 0.4 setosa <dbl [1]>
7 4.6 3.4 1.4 0.3 setosa <dbl [1]>
8 5.0 3.4 1.5 0.2 setosa <dbl [1]>
9 4.4 2.9 1.4 0.2 setosa <dbl [1]>
10 4.9 3.1 1.5 0.1 setosa <dbl [1]>
# ... with 140 more rows
si au lieu de cela nous retournons un data.frame
, nous obtenons une liste avec 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
)
})
donne:
# A tibble: 150 × 6
Sepal.Length Sepal.Width Petal.Length Petal.Width Species .out
<dbl> <dbl> <dbl> <dbl> <fctr> <list>
1 5.1 3.5 1.4 0.2 setosa <data.frame [1 × 2]>
2 4.9 3.0 1.4 0.2 setosa <data.frame [1 × 2]>
3 4.7 3.2 1.3 0.2 setosa <data.frame [1 × 2]>
4 4.6 3.1 1.5 0.2 setosa <data.frame [1 × 2]>
5 5.0 3.6 1.4 0.2 setosa <data.frame [1 × 2]>
6 5.4 3.9 1.7 0.4 setosa <data.frame [1 × 2]>
7 4.6 3.4 1.4 0.3 setosa <data.frame [1 × 2]>
8 5.0 3.4 1.5 0.2 setosa <data.frame [1 × 2]>
9 4.4 2.9 1.4 0.2 setosa <data.frame [1 × 2]>
10 4.9 3.1 1.5 0.1 setosa <data.frame [1 × 2]>
# ... with 140 more rows
La manière dont nous ajoutons la sortie de la fonction est contrôlée par le paramètre .collate
. Il y a trois options: liste, lignes, colonnes. Lorsque notre sortie a une longueur de 1, peu importe que nous utilisions des lignes ou des colonnes.
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
})
les deux produisent:
# A tibble: 150 × 6
Sepal.Length Sepal.Width Petal.Length Petal.Width Species .out
<dbl> <dbl> <dbl> <dbl> <fctr> <dbl>
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
Si nous produisons un data.frame avec 1 ligne, peu importe ce que nous utilisons:
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
)
})
les deux donnent:
# A tibble: 150 × 8
Sepal.Length Sepal.Width Petal.Length Petal.Width Species .row new_col_mean new_col_median
<dbl> <dbl> <dbl> <dbl> <fctr> <int> <dbl> <dbl>
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
sauf que le second a la colonne appelée .row
et le premier pas.
Enfin, si notre sortie dépasse la longueur 1, soit en tant que vector
, soit en tant que data.frame
avec des lignes, il importe que nous utilisions des lignes ou des colonnes pour .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")
produit respectivement:
# A tibble: 32 × 3
mpg cyl .out
<dbl> <dbl> <list>
1 21.0 6 <int [5]>
2 21.0 6 <int [5]>
3 22.8 4 <int [5]>
4 21.4 6 <int [5]>
5 18.7 8 <int [5]>
6 18.1 6 <int [5]>
7 14.3 8 <int [5]>
8 24.4 4 <int [5]>
9 22.8 4 <int [5]>
10 19.2 6 <int [5]>
# ... with 22 more rows
# A tibble: 160 × 4
mpg cyl .row .out
<dbl> <dbl> <int> <int>
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
<dbl> <dbl> <int> <int> <int> <int> <int>
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
Donc, en bout de ligne. Si vous voulez la fonctionnalité adply(.margins = 1, ...)
, vous pouvez utiliser by_row
.
Prolonger la réponse de BrodieG,
Si la fonction renvoie plus d'une ligne, alors, au lieu de mutate()
, do()
doit être utilisé. Ensuite, pour le combiner, utilisez rbind_all()
dans le package dplyr
.
Dans dplyr
version dplyr_0.1.2
, utiliser 1:n()
dans la clause group_by()
ne fonctionne pas pour moi. Espérons que Hadley implémentera rowwise()
bientôt.
iris %>%
group_by(1:nrow(iris)) %>%
do(do_fn) %>%
rbind_all()
Tester la performance,
library(plyr) # plyr_1.8.4.9000
library(dplyr) # dplyr_0.8.0.9000
library(purrr) # purrr_0.2.99.9000
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))}
do_fn2 <- function(a){data.frame(a=a, b=runif(d2_count))}
op <- microbenchmark(
plyr_version = plyr::adply(d1, 1, do_fn),
dplyr_version = d1 %>%
dplyr::group_by(1:nrow(d1)) %>%
dplyr::do(do_fn(.)) %>%
dplyr::bind_rows(),
purrr_version = d1 %>% purrr::pmap_dfr(do_fn2),
times=50)
il a les résultats suivants:
Unit: milliseconds
expr min lq mean median uq max neval
plyr_version 1227.2589 1275.1363 1317.3431 1293.5759 1314.4266 1616.5449 50
dplyr_version 977.3025 1012.6340 1035.9436 1025.6267 1040.5882 1449.0978 50
purrr_version 609.5790 629.7565 643.8498 644.2505 656.1959 686.8128 50
Cela montre que la nouvelle version purrr
est la plus rapide
Quelque chose comme ça?
iris$Max.Len <- pmax(iris$Sepal.Length, iris$Petal.Length)