separado - convertir una columna en una lista separada por comas
Separe las cadenas separadas por comas en una columna en filas separadas (4)
Esta vieja pregunta con frecuencia se está utilizando como blanco de engaño (etiquetado 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 orientación, que de los enfoques es el más rápido 1 .
Las soluciones de referencia incluyen
- Enfoque de base R de Matthew Lundberg, pero modificado de acuerdo con el comentario de Rich Scriven ,
- Jaap''s dos métodos de
data.table
Jaap''s y dosdplyr
/tidyr
, - La solución de
splitstackshape
de Ananda , - y dos variantes adicionales de los métodos
data.table
de Jaap.
En general, se compararon 8 métodos diferentes en 6 tamaños diferentes de marcos de datos utilizando el paquete microbenchmark
(ver el código a continuación).
Los datos de muestra proporcionados por el OP consisten solo en 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 da un tamaño de problema de hasta 2 millones de filas.
Resultados de referencia
Los resultados del índice de referencia muestran que, para marcos de datos suficientemente grandes, todos los métodos data.table
son más rápidos que cualquier otro método. Para marcos de datos con más de 5000 filas, el método data.table
de Jaap 2 y la variante DT3
son los más rápidos, las magnitudes más rápidas que los métodos más lentos.
Sorprendentemente, los tiempos de los dos métodos tidyverse
y la solución splistackshape
son tan similares que es difícil distinguir las curvas en el gráfico. Son los más lentos de los métodos de referencia en todos los tamaños de marcos de datos.
Para marcos de datos más pequeños, la base R de Matt y el método 4 de data.table
parecen tener menos sobrecarga 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 la función para las ejecuciones de referencia del tamaño del 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
)
}
Ejecutar benchmark para diferentes tamaños de problema
# define vector of problem sizes
n_rep <- 10L^(0:5)
# run benchmark for different problem sizes
mb <- lapply(n_rep, run_mb)
Prepare los datos para trazar
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)
1 ¡ Mi curiosidad se despertó con este comentario exuberante Brilliant! Órdenes de magnitud más rápido! a una respuesta tidyverse
de una pregunta que se cerró como un duplicado de esta pregunta.
Tengo un marco de datos, así:
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''))
Como puede ver, algunas entradas en la columna del director
son nombres múltiples separados por comas. Me gustaría dividir estas entradas en filas separadas mientras mantengo los valores de la otra columna. Como ejemplo, la primera fila en el marco de datos anterior debe dividirse en dos filas, con un solo nombre en la columna del director
y ''A'' en la columna AB
.
Nombrando su data.frame v
original, 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.
Tarde en la fiesta, pero otra alternativa generalizada es usar cSplit
de mi paquete "splitstackshape" que tiene un argumento de direction
. Establezca esto en "long"
para obtener el resultado que especifique:
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
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
: Alternativamente, también puede usar la 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 puedes usar separate_rows
:
separate_rows(v, director, sep = ",")
Puede usar el parámetro convert = TRUE
para convertir números automáticamente 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))