150 votes

Divise les chaînes de caractères séparées par des virgules dans une colonne en lignes séparées.

J'ai un cadre de données, comme ça :

data.frame(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'))

Comme vous pouvez le constater, certaines entrées dans le director sont des noms multiples séparés par des virgules. Je voudrais diviser ces entrées en lignes distinctes tout en conservant les valeurs de l'autre colonne. Par exemple, la première ligne du cadre de données ci-dessus doit être divisée en deux lignes, chacune contenant un seul nom dans la colonne director et "A" dans la colonne AB colonne.

140voto

Jaap Points 3814

Plusieurs possibilités :

1) deux voies avec table.de.données :

library(data.table)
# method 1 (preferred)
setDT(v)[, lapply(.SD, function(x) unlist(tstrsplit(x, ",", fixed=TRUE))), by = AB
         ][!is.na(director)]
# method 2
setDT(v)[, strsplit(as.character(director), ",", fixed=TRUE), by = .(AB, director)
         ][,.(director = V1, AB)]

2) a dplyr / tidyr combinaison :

library(dplyr)
library(tidyr)
v %>% 
  mutate(director = strsplit(as.character(director), ",")) %>%
  unnest(director)

3) avec tidyr seulement : Avec tidyr 0.5.0 (et plus), vous pouvez aussi simplement utiliser separate_rows :

separate_rows(v, director, sep = ",")

Vous pouvez utiliser le convert = TRUE pour convertir automatiquement les nombres en colonnes numériques.

4) avec la base R :

# if 'director' is a character-column:
stack(setNames(strsplit(df$director,','), df$AB))

# if 'director' is a factor-column:
stack(setNames(strsplit(as.character(df$director),','), df$AB))

98voto

Uwe Points 21553

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

enter image description here

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.

52voto

Matthew Lundberg Points 21747

Nommer votre data.frame original v nous avons ceci :

> s <- strsplit(as.character(v$director), ',')
> data.frame(director=unlist(s), AB=rep(v$AB, sapply(s, FUN=length)))
                      director AB
1                 Aaron Blaise  A
2                   Bob Walker  A
3               Akira Kurosawa  B
4               Alan J. Pakula  A
5                  Alan Parker  A
6           Alejandro Amenabar  B
7  Alejandro Gonzalez Inarritu  B
8  Alejandro Gonzalez Inarritu  B
9             Benicio Del Toro  B
10 Alejandro González Iñárritu  A
11                 Alex Proyas  B
12              Alexander Hall  A
13              Alfonso Cuaron  B
14            Alfred Hitchcock  A
15              Anatole Litvak  A
16              Andrew Adamson  B
17                 Marilyn Fox  B
18              Andrew Dominik  B
19              Andrew Stanton  B
20              Andrew Stanton  B
21                 Lee Unkrich  B
22              Angelina Jolie  B
23              John Stevenson  B
24               Anne Fontaine  B
25              Anthony Harvey  A

Notez l'utilisation de rep pour construire la nouvelle colonne AB. Ici, sapply renvoie le nombre de noms dans chacune des lignes d'origine.

31voto

Ananda Mahto Points 67213

J'arrive tard dans la soirée, mais une autre alternative généralisée consiste à utiliser cSplit à partir de mon paquet "splitstackshape" qui a un direction argument. Donnez-lui la valeur "long" pour obtenir le résultat que vous spécifiez :

library(splitstackshape)
head(cSplit(mydf, "director", ",", direction = "long"))
#              director AB
# 1:       Aaron Blaise  A
# 2:         Bob Walker  A
# 3:     Akira Kurosawa  B
# 4:     Alan J. Pakula  A
# 5:        Alan Parker  A
# 6: Alejandro Amenabar  B

2voto

zhang jing Points 61
devtools::install_github("yikeshu0611/onetree")

library(onetree)

dd=spread_byonecolumn(data=mydata,bycolumn="director",joint=",")

head(dd)
            director AB
1       Aaron Blaise  A
2         Bob Walker  A
3     Akira Kurosawa  B
4     Alan J. Pakula  A
5        Alan Parker  A
6 Alejandro Amenabar  B

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X