J'essaie d'utiliser l'instruction pipe mutate à l'aide d'une fonction personnalisée. J'ai regardé ce quelque peu similaire SO post mais en vain. Supposons que j'ai un bloc de données comme celui-ci (où blob
est une variable non liée à la tâche spécifique mais faisant partie de l'ensemble des données):
df <-
data.frame(exclude=c('B','B','D'),
B=c(1,0,0),
C=c(3,4,9),
D=c(1,1,0),
blob=c('fd', 'fs', 'sa'),
stringsAsFactors = F)
J'ai une fonction qui utilise les noms de variables, alors sélectionnez-en certaines en fonction de la valeur dans la colonne exclude
et par exemple calcule une somme sur les variables non spécifiées dans exclude
(qui est toujours un seul caractère).
FUN <- function(df){
sum(df[c('B', 'C', 'D')] [!names(df[c('B', 'C', 'D')]) %in% df['exclude']] )
}
Lorsque je donne une seule ligne (ligne 1) à FUN
j'obtiens la somme attendue de C
et D
(celles qui ne sont pas mentionnées par exclude
), à savoir 4:
FUN(df[1,])
Comment puis-je faire de même dans un tube avec mutate (en ajoutant le résultat à une variable s
). Ces deux essais ne fonctionnent pas:
df %>% mutate(s=FUN(.))
df %>% group_by(1:n()) %>% mutate(s=FUN(.))
[~ # ~] mise à jour [~ # ~] Cela ne fonctionne pas non plus comme prévu:
df %>% rowwise(.) %>% mutate(s=FUN(.))
Cela fonctionne de la cause mais n'est pas dans le mutate de dplyr (et les tuyaux):
df$s <- sapply(1:nrow(df), function(x) FUN(df[x,]))
Si vous souhaitez utiliser dplyr
vous pouvez le faire en utilisant rowwise
et votre fonction FUN
.
df %>%
rowwise %>%
do({
result = as_data_frame(.)
result$s = FUN(result)
result
})
La même chose peut être obtenue en utilisant group_by
Au lieu de rowwise
(comme vous l'avez déjà essayé) mais avec do
au lieu de mutate
df %>%
group_by(1:n()) %>%
do({
result = as_data_frame(.)
result$s = FUN(result)
result
})
La raison pour laquelle mutate
ne fonctionne pas dans ce cas, c'est que vous lui passez l'intégralité du tibble, c'est donc comme appeler FUN(df)
.
Une manière beaucoup plus efficace de faire la même chose est de créer une matrice de colonnes à inclure, puis d'utiliser rowSums
.
cols <- c('B', 'C', 'D')
include_mat <- outer(function(x, y) x != y, X = df$exclude, Y = cols)
# or outer(`!=`, X = df$exclude, Y = cols) if it's more readable to you
df$s <- rowSums(df[cols] * include_mat)
purrr
approcheNous pouvons utiliser une combinaison de nest
et map_dbl
Pour cela:
library(tidyverse)
df %>%
rowwise %>%
nest(-blob) %>%
mutate(s = map_dbl(data, FUN)) %>%
unnest
Décomposons cela un peu. Premièrement, rowwise
nous permet d'appliquer chaque fonction suivante pour prendre en charge des opérations complexes arbitraires qui doivent être appliquées à chaque ligne.
Ensuite, nest
créera une nouvelle colonne qui est une liste de nos données à alimenter dans FUN
(la beauté des tibbles vs data.frames!). Puisque nous appliquons ce rowwise
, chaque ligne contient un tibble d'une ligne de exclude:D
.
Enfin, nous utilisons map_dbl
Pour mapper notre FUN
à chacun de ces tibbles. map_dbl
Est utilisé par rapport à la famille d'autres fonctions map_*
Car notre sortie prévue est numérique (c'est-à-dire double).
unnest
renvoie notre tibble dans la structure plus standard.
purrrlyr
approcheAlors que purrrlyr
n'est peut-être pas aussi "populaire" que ses parents dplyr
et purrr
, sa fonction by_row
A ici une certaine utilité.
Dans votre exemple ci-dessus, nous utiliserions votre bloc de données df
et la fonction définie par l'utilisateur FUN
de la manière suivante:
df %>%
by_row(..f = FUN, .to = "s", .collate = "cols")
C'est tout! Te donne:
# tibble [3 x 6]
exclude B C D blob s
<chr> <dbl> <dbl> <dbl> <chr> <dbl>
1 B 1 3 1 fd 4
2 B 0 4 1 fs 5
3 D 0 9 0 sa 9
Certes, la syntaxe est un peu étrange, mais voici comment elle se décompose:
..f
= La fonction à appliquer à chaque ligne.to
= Le nom de la colonne de sortie, dans ce cas s
.collate
= La façon dont les résultats doivent être regroupés, par liste, ligne ou colonne. Étant donné que FUN
n'a qu'une seule sortie, nous serions d'accord pour utiliser "cols"
Ou "rows"
Voir ici pour plus d'informations sur l'utilisation de purrrlyr
...
Attention, même si j'aime la fonctionnalité de by_row
, Ce n'est pas toujours la meilleure approche pour la performance! purrr
est plus intuitif, mais aussi à une perte de vitesse assez importante. Voir le test microbenchmark
suivant:
library(microbenchmark)
mbm <- microbenchmark(
purrr.test = df %>% rowwise %>% nest(-blob) %>%
mutate(s = map_dbl(data, FUN)) %>% unnest,
purrrlyr.test = df %>% by_row(..f = FUN, .to = "s", .collate = "cols"),
rowwise.test = df %>%
rowwise %>%
do({
result = as_tibble(.)
result$s = FUN(result)
result
}),
group_by.test = df %>%
group_by(1:n()) %>%
do({
result = as_tibble(.)
result$s = FUN(result)
result
}),
sapply.test = {df$s <- sapply(1:nrow(df), function(x) FUN(df[x,]))},
times = 1000
)
autoplot(mbm)
Vous pouvez voir que l'approche purrrlyr
est plus rapide que l'approche consistant à utiliser une combinaison de do
avec rowwise
ou group_by(1:n())
(voir la réponse @konvas), et plutôt à égalité avec l'approche sapply
. Cependant, le package n'est certes pas le plus intuitif. L'approche standard purrr
semble être la plus lente, mais peut-être aussi plus facile à utiliser. Différentes fonctions définies par l'utilisateur peuvent modifier l'ordre de vitesse.