Cette ancienne question est fréquemment utilisée comme cible de doublon (tagged with r-faq
). À ce jour, il a été répondu trois fois en proposant 6 approches différentes mais manque de repère comme orientation laquelle des approches est la plus rapide 1 .
Les solutions évaluées comprennent
Dans l'ensemble, 8 méthodes différentes ont été évaluées sur 6 tailles différentes de trames de données en utilisant l'outil d'évaluation de la qualité des données. microbenchmark
(voir le code ci-dessous).
L'échantillon de données donné par le PO ne comporte que 20 lignes. Pour créer des bases de données plus grandes, ces 20 lignes sont simplement répétées 1, 10, 100, 1000, 10000 et 100000 fois, ce qui donne des tailles de problèmes allant jusqu'à 2 millions de lignes.
Résultats de l'évaluation comparative
Les résultats de l'évaluation comparative montrent que, pour des bases de données suffisamment grandes, tous les éléments de la base de données peuvent être utilisés. data.table
sont plus rapides que toutes les autres méthodes. Pour les cadres de données avec plus de 5000 lignes, la méthode de Jaap data.table
la méthode 2 et la variante DT3
sont les plus rapides, des magnitudes plus rapides que les méthodes les plus lentes.
Remarquablement, les timings des deux tidyverse
et les splistackshape
sont tellement similaires qu'il est difficile de distinguer les courbes dans le graphique. Elles sont les plus lentes parmi les méthodes évaluées, quelle que soit la taille de la trame de données.
Pour de plus petits cadres de données, la solution R de base de Matt et data.table
La méthode 4 semble avoir moins de frais généraux que les autres méthodes.
Code
director <-
c("Aaron Blaise,Bob Walker", "Akira Kurosawa", "Alan J. Pakula",
"Alan Parker", "Alejandro Amenabar", "Alejandro Gonzalez Inarritu",
"Alejandro Gonzalez Inarritu,Benicio Del Toro", "Alejandro González Iñárritu",
"Alex Proyas", "Alexander Hall", "Alfonso Cuaron", "Alfred Hitchcock",
"Anatole Litvak", "Andrew Adamson,Marilyn Fox", "Andrew Dominik",
"Andrew Stanton", "Andrew Stanton,Lee Unkrich", "Angelina Jolie,John Stevenson",
"Anne Fontaine", "Anthony Harvey")
AB <- c("A", "B", "A", "A", "B", "B", "B", "A", "B", "A", "B", "A",
"A", "B", "B", "B", "B", "B", "B", "A")
library(data.table)
library(magrittr)
Définir la fonction pour les exécutions de référence de la taille du problème n
run_mb <- function(n) {
# compute number of benchmark runs depending on problem size `n`
mb_times <- scales::squish(10000L / n , c(3L, 100L))
cat(n, " ", mb_times, "\n")
# create data
DF <- data.frame(director = rep(director, n), AB = rep(AB, n))
DT <- as.data.table(DF)
# start benchmarks
microbenchmark::microbenchmark(
matt_mod = {
s <- strsplit(as.character(DF$director), ',')
data.frame(director=unlist(s), AB=rep(DF$AB, lengths(s)))},
jaap_DT1 = {
DT[, lapply(.SD, function(x) unlist(tstrsplit(x, ",", fixed=TRUE))), by = AB
][!is.na(director)]},
jaap_DT2 = {
DT[, strsplit(as.character(director), ",", fixed=TRUE),
by = .(AB, director)][,.(director = V1, AB)]},
jaap_dplyr = {
DF %>%
dplyr::mutate(director = strsplit(as.character(director), ",")) %>%
tidyr::unnest(director)},
jaap_tidyr = {
tidyr::separate_rows(DF, director, sep = ",")},
cSplit = {
splitstackshape::cSplit(DF, "director", ",", direction = "long")},
DT3 = {
DT[, strsplit(as.character(director), ",", fixed=TRUE),
by = .(AB, director)][, director := NULL][
, setnames(.SD, "V1", "director")]},
DT4 = {
DT[, .(director = unlist(strsplit(as.character(director), ",", fixed = TRUE))),
by = .(AB)]},
times = mb_times
)
}
Exécuter le benchmark pour différentes tailles de problèmes
# define vector of problem sizes
n_rep <- 10L^(0:5)
# run benchmark for different problem sizes
mb <- lapply(n_rep, run_mb)
Préparer les données pour le traçage
mbl <- rbindlist(mb, idcol = "N")
mbl[, n_row := NROW(director) * n_rep[N]]
mba <- mbl[, .(median_time = median(time), N = .N), by = .(n_row, expr)]
mba[, expr := forcats::fct_reorder(expr, -median_time)]
Créer une carte
library(ggplot2)
ggplot(mba, aes(n_row, median_time*1e-6, group = expr, colour = expr)) +
geom_point() + geom_smooth(se = FALSE) +
scale_x_log10(breaks = NROW(director) * n_rep) + scale_y_log10() +
xlab("number of rows") + ylab("median of execution time [ms]") +
ggtitle("microbenchmark results") + theme_bw()
Informations sur la session et versions des paquets (extrait)
devtools::session_info()
#Session info
# version R version 3.3.2 (2016-10-31)
# system x86_64, mingw32
#Packages
# data.table * 1.10.4 2017-02-01 CRAN (R 3.3.2)
# dplyr 0.5.0 2016-06-24 CRAN (R 3.3.1)
# forcats 0.2.0 2017-01-23 CRAN (R 3.3.2)
# ggplot2 * 2.2.1 2016-12-30 CRAN (R 3.3.2)
# magrittr * 1.5 2014-11-22 CRAN (R 3.3.0)
# microbenchmark 1.4-2.1 2015-11-25 CRAN (R 3.3.3)
# scales 0.4.1 2016-11-09 CRAN (R 3.3.2)
# splitstackshape 1.4.2 2014-10-23 CRAN (R 3.3.3)
# tidyr 0.6.1 2017-01-10 CRAN (R 3.3.2)
1 Ma curiosité a été piquée par <a href="https://stackoverflow.com/questions/43409756/r-efficiently-separate-groups-in-data-frame#comment73879642_43409986">ce commentaire exubérant </a><em>Brillant ! Des ordres de grandeur plus rapides ! </em>à un <code>tidyverse</code> réponse de <a href="https://stackoverflow.com/q/43409756/3817004">une question </a>qui a été fermée en tant que duplicata de cette question.