Saltar al contenido

Dividir cadenas separadas por comas en una columna en filas separadas

Solución:

Varias alternativas:

1) dos formas con data.table:

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) una combinación dplyr / tidyr:

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

3) solo con tidyr: Con tidyr 0.5.0 (y posterior), también puede usar separate_rows:

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

Puedes usar el convert = TRUE parámetro para convertir automáticamente números en columnas numéricas.

4) con 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))

Esta vieja pregunta se utiliza con frecuencia como objetivo engañoso (etiquetada con r-faq). Hasta el día de hoy, se ha respondido tres veces ofreciendo 6 enfoques diferentes, pero carece de un punto de referencia como guía, cuál de los enfoques es el más rápido1.

Las soluciones comparadas incluyen

  • El enfoque de base R de Matthew Lundberg pero modificado de acuerdo con el comentario de Rich Scriven,
  • Los dos de Jaap data.table métodos y dos dplyr / tidyr enfoques,
  • De Ananda splitstackshapesolución,
  • y dos variantes adicionales de Jaap data.table métodos.

En general, se compararon 8 métodos diferentes en 6 tamaños diferentes de marcos de datos utilizando el microbenchmark paquete (ver código a continuación).

Los datos de muestra proporcionados por el OP constan solo de 20 filas. Para crear marcos de datos más grandes, estas 20 filas simplemente se repiten 1, 10, 100, 1000, 10000 y 100000 veces, lo que genera problemas de tamaño de hasta 2 millones de filas.

Resultados comparativos

ingrese la descripción de la imagen aquí

Los resultados de la evaluación comparativa muestran que para marcos de datos suficientemente grandes todos data.table Los métodos son más rápidos que cualquier otro método. Para marcos de datos con más de 5000 filas, Jaap’s data.table método 2 y la variante DT3 son las magnitudes más rápidas y más rápidas que los métodos más lentos.

Sorprendentemente, los tiempos de los dos tidyverse métodos y el splistackshape Las soluciones son tan similares que es difícil distinguir las curvas en el gráfico. Son los métodos comparativos más lentos de todos los tamaños de marcos de datos.

Para marcos de datos más pequeños, la solución R base de Matt y data.table el método 4 parece tener menos gastos generales que los otros métodos.

Código

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)

Definir función para ejecuciones de referencia de tamaño de problema 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
  )

Ejecute un banco de pruebas para diferentes tamaños de problemas

# define vector of problem sizes
n_rep <- 10L^(0:5)
# run benchmark for different problem sizes
mb <- lapply(n_rep, run_mb)

Preparar datos para graficar

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)]

Crear gráfico

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()

Información de la sesión y versiones del paquete (extracto)

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)

1Mi curiosidad fue picada por este exuberante comentario. ¡Brillante! ¡Órdenes de magnitud más rápido! a un tidyverse respuesta de una pregunta que se cerró como un duplicado de esta pregunta.

Nombrar su data.frame original v, tenemos esto:

> 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

Tenga en cuenta el uso de rep para construir la nueva columna AB. Aquí, sapply devuelve el número de nombres en cada una de las filas originales.

Sección de Reseñas y Valoraciones

Recuerda que te permitimos decir si te fue de ayuda.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *