Dividir columna de cadena de dataframe en múltiples columnas

Me gustaría tomar datos del formulario

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 use split() en la columna ” type ” de 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 implicaba alguna forma de apply que funcionó, pero desde entonces he extraviado eso. Parecía demasiado complicado para ser la mejor manera. Puedo usar strsplit como a continuación, pero luego no veo cómo volver a ponerlo en 2 columnas en el dataframe.

 > 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 puntero. Todavía no he comido completamente las listas de R.

Utilice stringr::str_split_fixed

 library(stringr) str_split_fixed(before$type, "_and_", 2) 

Otra opción es usar 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 más tarde agregando la solución obligatoria data.table

 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 asegurarnos de que las columnas resultantes tengan los tipos correctos y mejoren el rendimiento agregando type.convert y argumentos fixed (ya que "_and_" no es realmente una expresión regular)

 setDT(before)[, paste0("type", 1:2) := tstrsplit(type, "_and_", type.convert = TRUE, fixed = TRUE)] 

Otro enfoque más: use rbind on 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)) 

Tenga en cuenta que sapply con “[” se puede usar para extraer el primer elemento o el segundo elemento en esas listas de modo que:

 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í hay un método gsub:

 before$type_1 < - gsub("_and_.+$", "", before$type) before$type_2 <- gsub("^.+_and_", "", before$type) before$type <- NULL 

aquí hay un trazador de líneas en la misma línea que la solución de aniko, pero utilizando el paquete stringr de hadley:

 do.call(rbind, str_split(before$type, '_and_')) 

Para agregar a las opciones, también podría usar mi función splitstackshape::cSplit como esta:

 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 manera fácil es usar 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 

sapply() es una matriz y necesita transposición y conversión a un dataframe. Es entonces algunas manipulaciones simples que producen el resultado que deseaba:

 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ías

 > 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í hay un revestimiento base R one que se superpone a varias 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 dividir la variable y data.frame con do.call / rbind para volver a poner los datos en un data.frame. La mejora incremental adicional es el uso de setNames para agregar nombres de variables al data.frame.

Otro enfoque si desea seguir con strsplit() es usar el unlist() . Aquí hay 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") 

Desde la versión R 3.4.0, puede usar strcapture() desde el paquete utils (incluido con las instalaciones base R), vinculando el resultado a la (s) otra (s) 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 

El tema está casi agotado, pero me gustaría ofrecer una solución a una versión un poco más general en la que no se conoce el número de columnas de salida, a priori. Entonces, por ejemplo, tienes

 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 podemos usar dplyr separate() porque no conocemos el número de columnas de resultados antes de la división, entonces he creado una función que usa stringr para dividir una columna, dado el patrón y un prefijo de nombre para el generado columnas Espero que los patrones de encoding utilizados sean 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) } 

Entonces podemos usar 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  2 30 foo bar_2  3 4 foo bar_2 bar_3 4 6 foo bar  

Y luego podemos usar 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 

Esta pregunta es bastante antigua, pero añadiré que la solución que encontré es 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 

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 
 tp < - c("ac","def","ghi","mn") temp = strsplit(as.character(tp),'-') x=c(); y=c(); z=c(); #tab=data.frame() #tab= cbind(tab,c(x,y,z)) for(i in 1:length(temp) ) { l = length(temp[[i]]); if(l==2) { x=c(x,temp[[i]][1]); y=c(y,"NA") z=c(z,temp[[i]][2]); df= as.data.frame(cbind(x,y,z)) }else { x=c(x,temp[[i]][1]); y=c(y,temp[[i]][2]); z=c(z,temp[[i]][3]); df= as.data.frame(cbind(x,y,z)) } }