columna de cadena de tramas de datos dividida en varias columnas
Pregunta
Me gustaría tomar los datos de la forma
before = data.frame(attr = c(1,30,4,6), type=c('foo_and_bar','foo_and_bar_2'))
attr type
1 1 foo_and_bar
2 30 foo_and_bar_2
3 4 foo_and_bar
4 6 foo_and_bar_2
y el uso split()
en el "type
" columna desde arriba para obtener algo como esto:
attr type_1 type_2
1 1 foo bar
2 30 foo bar_2
3 4 foo bar
4 6 foo bar_2
Se me ocurrió algo increíblemente complejo que implica alguna forma de apply
que funcionaba, pero desde que he extraviado eso. Se parecía demasiado complicado para ser la mejor manera. Puedo usar strsplit
como abajo, pero no está claro cómo conseguir que de nuevo en 2 columnas en la trama de datos.
> strsplit(as.character(before$type),'_and_')
[[1]]
[1] "foo" "bar"
[[2]]
[1] "foo" "bar_2"
[[3]]
[1] "foo" "bar"
[[4]]
[1] "foo" "bar_2"
Gracias por cualquier punteros. No he bastante groked listas de R por el momento.
Solución
Uso stringr::str_split_fixed
library(stringr)
str_split_fixed(before$type, "_and_", 2)
Otros consejos
Otra opción es utilizar el nuevo paquete tidyr.
library(dplyr)
library(tidyr)
before <- data.frame(
attr = c(1, 30 ,4 ,6 ),
type = c('foo_and_bar', 'foo_and_bar_2')
)
before %>%
separate(type, c("foo", "bar"), "_and_")
## attr foo bar
## 1 1 foo bar
## 2 30 foo bar_2
## 3 4 foo bar
## 4 6 foo bar_2
5 años después de añadir la solución data.table
obligatoria
library(data.table) ## v 1.9.6+
setDT(before)[, paste0("type", 1:2) := tstrsplit(type, "_and_")]
before
# attr type type1 type2
# 1: 1 foo_and_bar foo bar
# 2: 30 foo_and_bar_2 foo bar_2
# 3: 4 foo_and_bar foo bar
# 4: 6 foo_and_bar_2 foo bar_2
También podríamos tanto asegurarse de que las columnas resultantes tendrán tipos correctos y mejorar el rendimiento mediante la adición de type.convert
y fixed
argumentos (ya "_and_"
no es realmente una expresión regular)
setDT(before)[, paste0("type", 1:2) := tstrsplit(type, "_and_", type.convert = TRUE, fixed = TRUE)]
Sin embargo, otro enfoque: el uso rbind
en out
:
before <- data.frame(attr = c(1,30,4,6), type=c('foo_and_bar','foo_and_bar_2'))
out <- strsplit(as.character(before$type),'_and_')
do.call(rbind, out)
[,1] [,2]
[1,] "foo" "bar"
[2,] "foo" "bar_2"
[3,] "foo" "bar"
[4,] "foo" "bar_2"
Y para combinar:
data.frame(before$attr, do.call(rbind, out))
Observe que sapply con "[" puede ser utilizado para extraer cualquiera de los primeros o segundos elementos en esas listas así:
before$type_1 <- sapply(strsplit(as.character(before$type),'_and_'), "[", 1)
before$type_2 <- sapply(strsplit(as.character(before$type),'_and_'), "[", 2)
before$type <- NULL
Y aquí está un método gsub:
before$type_1 <- gsub("_and_.+$", "", before$type)
before$type_2 <- gsub("^.+_and_", "", before$type)
before$type <- NULL
aquí es un un trazador de líneas a lo largo de las mismas líneas que la solución de aniko, pero utilizando el paquete stringr de Hadley:
do.call(rbind, str_split(before$type, '_and_'))
Para añadir a las opciones, también se podría usar mi función splitstackshape::cSplit
como esto:
library(splitstackshape)
cSplit(before, "type", "_and_")
# attr type_1 type_2
# 1: 1 foo bar
# 2: 30 foo bar_2
# 3: 4 foo bar
# 4: 6 foo bar_2
Una forma sencilla es utilizar sapply()
y la función [
:
before <- data.frame(attr = c(1,30,4,6), type=c('foo_and_bar','foo_and_bar_2'))
out <- strsplit(as.character(before$type),'_and_')
Por ejemplo:
> data.frame(t(sapply(out, `[`)))
X1 X2
1 foo bar
2 foo bar_2
3 foo bar
4 foo bar_2
resultado de sapply()
es una matriz y necesita la transposición y la fundición de nuevo a una trama de datos. Es entonces algunas manipulaciones simples que producen el resultado que quería:
after <- with(before, data.frame(attr = attr))
after <- cbind(after, data.frame(t(sapply(out, `[`))))
names(after)[2:3] <- paste("type", 1:2, sep = "_")
En este punto, after
es lo que quería
> after
attr type_1 type_2
1 1 foo bar
2 30 foo bar_2
3 4 foo bar
4 6 foo bar_2
Aquí es un forro de una base de R que se superpone una serie de soluciones anteriores, pero devuelve un data.frame con los nombres propios.
out <- setNames(data.frame(before$attr,
do.call(rbind, strsplit(as.character(before$type),
split="_and_"))),
c("attr", paste0("type_", 1:2)))
out
attr type_1 type_2
1 1 foo bar
2 30 foo bar_2
3 4 foo bar
4 6 foo bar_2
Utiliza strsplit
para romper la variable, y data.frame
con do.call
/ rbind
para poner la parte posterior de datos en un hoja.de.datos. La mejora incremental adicional es el uso de setNames
añadir los nombres de variables a la hoja.de.datos.
El tema es casi agotado, me gustaría pesar de ofrecer una solución a una versión ligeramente más general en el que no se conoce el número de columnas de salida, a priori. Así, por ejemplo, tiene
before = data.frame(attr = c(1,30,4,6), type=c('foo_and_bar','foo_and_bar_2', 'foo_and_bar_2_and_bar_3', 'foo_and_bar'))
attr type
1 1 foo_and_bar
2 30 foo_and_bar_2
3 4 foo_and_bar_2_and_bar_3
4 6 foo_and_bar
No se puede utilizar dplyr separate()
porque no sabemos el número de las columnas de resultados antes de la división, por lo que a continuación, he creado una función que utiliza stringr
para dividir una columna, dado el patrón y un prefijo para el nombre columnas generadas. Espero que los patrones de codificación utilizado, son correctos.
split_into_multiple <- function(column, pattern = ", ", into_prefix){
cols <- str_split_fixed(column, pattern, n = Inf)
# Sub out the ""'s returned by filling the matrix to the right, with NAs which are useful
cols[which(cols == "")] <- NA
cols <- as.tibble(cols)
# name the 'cols' tibble as 'into_prefix_1', 'into_prefix_2', ..., 'into_prefix_m'
# where m = # columns of 'cols'
m <- dim(cols)[2]
names(cols) <- paste(into_prefix, 1:m, sep = "_")
return(cols)
}
A continuación, puede utilizar split_into_multiple
en una tubería dplyr de la siguiente manera:
after <- before %>%
bind_cols(split_into_multiple(.$type, "_and_", "type")) %>%
# selecting those that start with 'type_' will remove the original 'type' column
select(attr, starts_with("type_"))
>after
attr type_1 type_2 type_3
1 1 foo bar <NA>
2 30 foo bar_2 <NA>
3 4 foo bar_2 bar_3
4 6 foo bar <NA>
Y entonces podemos utilizar gather
para poner en orden ...
after %>%
gather(key, val, -attr, na.rm = T)
attr key val
1 1 type_1 foo
2 30 type_1 foo
3 4 type_1 foo
4 6 type_1 foo
5 1 type_2 bar
6 30 type_2 bar_2
7 4 type_2 bar_2
8 6 type_2 bar
11 4 type_3 bar_3
Desde R versión 3.4.0 se puede utilizar strcapture()
de la paquete utils (incluido con instalaciones de base R), de unión a la salida en la otra columna (s).
out <- strcapture(
"(.*)_and_(.*)",
as.character(before$type),
data.frame(type_1 = character(), type_2 = character())
)
cbind(before["attr"], out)
# attr type_1 type_2
# 1 1 foo bar
# 2 30 foo bar_2
# 3 4 foo bar
# 4 6 foo bar_2
Esta pregunta es bastante viejo, pero voy a añadir la solución que encontré al ser la más simple en la actualidad.
library(reshape2)
before = data.frame(attr = c(1,30,4,6), type=c('foo_and_bar','foo_and_bar_2'))
newColNames <- c("type1", "type2")
newCols <- colsplit(before$type, "_and_", newColNames)
after <- cbind(before, newCols)
after$type <- NULL
after
Otro enfoque si desea seguir con strsplit()
es utilizar el comando unlist()
. He aquí una solución en ese sentido.
tmp <- matrix(unlist(strsplit(as.character(before$type), '_and_')), ncol=2,
byrow=TRUE)
after <- cbind(before$attr, as.data.frame(tmp))
names(after) <- c("attr", "type_1", "type_2")
base, pero probablemente lenta:
n <- 1
for(i in strsplit(as.character(before$type),'_and_')){
before[n, 'type_1'] <- i[[1]]
before[n, 'type_2'] <- i[[2]]
n <- n + 1
}
## attr type type_1 type_2
## 1 1 foo_and_bar foo bar
## 2 30 foo_and_bar_2 foo bar_2
## 3 4 foo_and_bar foo bar
## 4 6 foo_and_bar_2 foo bar_2