Pregunta Dividir columna de cadena de marco de datos 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"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 involucraba alguna forma de apply eso funcionó, pero desde entonces he extraviado eso. Parecía demasiado complicado para ser la mejor manera. Puedo usar strsplit como se muestra a continuación, pero no está claro cómo volver a colocarlo en 2 columnas en el marco 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 puntero. Todavía no he comido completamente las listas de R.


159
2017-12-03 22:29


origen


Respuestas:


Utilizar stringr::str_split_fixed

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

196
2017-12-04 04:21



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

115
2018-06-11 16:50



5 años después agregando el obligatorio data.table solución

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 mejorar el rendimiento al agregar type.convert y fixed argumentos (desde "_and_" no es realmente una expresión regular)

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

44
2017-10-14 14:14



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

41
2017-12-04 00:51



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

30
2017-12-03 23:35



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_'))

27
2017-12-04 02:09



Para agregar a las opciones, también puedes usar mi splitstackshape::cSplit funciona así:

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

16
2017-09-27 15:46



Una manera fácil es usar sapply() y el [ 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()El resultado es una matriz y necesita transposición y conversión a un marco de datos. 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

12
2017-12-03 23:36



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

Usa strsplit para romper 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.


7
2017-07-22 20:34



Otro enfoque si quieres seguir con strsplit() es usar el unlist() mando. 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")

4
2017-12-03 23:52