2010-12-03 22 views
136

me gustaría tomar los datos de la formadividir una columna de una trama de datos de varias columnas

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 utilizar 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 no estoy claro cómo volver a poner eso 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 punteros. Todavía no he comido completamente las listas de R.

Respuesta

179

Uso stringr::str_split_fixed

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

esto funcionó bastante bien para mi problema hoy también ... pero estaba agregando una 'c' al principio de cada fila. ¿Alguna idea de por qué es eso? 'left_right <- str_split_fixed (as.character (split_df), '\">', 2) ' – LearneR

+0

Me gustaría dividirme con un patrón que tiene" ... ", cuando aplico esa función, no devuelve nada. podría ser el problema. Mi tipo es algo así como "puntaje de prueba ..." – user3841581

+1

@ user3841581 - una vieja consulta tuya lo sé, pero esto está cubierto en la documentación - 'str_split_fixed (" aaa ... bbb ", fixed (" ... "), 2)' funciona bien con 'fixed()' para "Coincidir con una cadena fija" en el argumento 'pattern ='. '.' significa 'cualquier caracter' en la expresión regular. – thelatemail

27

en cuenta que sapply con "[" puede ser utilizado para extraer ya sea la primera o segunda puntos de dichas listas por lo 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í está un método gsub:

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

Una manera fácil es utilizar sapply() y la función de [:

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

F o ejemplo:

> data.frame(t(sapply(out, `[`))) 
    X1 X2 
1 foo bar 
2 foo bar_2 
3 foo bar 
4 foo bar_2 

sapply() 's resultado 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 
3

Otro enfoque si desea seguir con strsplit() es utilizar 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") 
36

Sin embargo, otro enfoque: utilizar 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)) 
+1

Otra alternativa ive en versiones R más nuevas es 'strcapture (" (. *) _ and _ (. *) ", as.character (before $ type), data.frame (type_1 =" ", type_2 =" "))' –

24

aquí es un revestimiento a lo largo de las mismas líneas como la solución de aniko, pero el uso de Hadley paquete stringr:

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

esto también funciona con strsplit del paquete base – schultem

+1

Buena captura, la mejor solución para mí.Aunque es un poco más lento que con el paquete 'stringr'. – Melka

94

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 
+0

¿Hay alguna manera? para limitar el número de divisiones con separado? Digamos que quiero dividir en '_' una sola vez (o hacerlo con 'str_split_fixed' y una dding columnas a dataframe existente)? –

+0

Sí. Consulte los documentos – hadley

+0

Además, 'tidyr :: separate' devuelve un marco de datos, y' str_split_fixed' devuelve una lista, haciendo que el primero funcione bien con pipes '%>%'. –

15

Para añadir a las opciones, también se podría usar mi función splitstackshape::cSplit 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 
+0

3 años después - esta opción funciona mejor para un problema similar que tengo - sin embargo, el marco de datos con el que estoy trabajando tiene 54 columnas y necesito dividirlas en dos. ¿Hay alguna manera de hacer esto con este método, sin escribir el comando anterior 54 veces? Muchas gracias, Nicki. – Nicki

+0

@Nicki, ¿Has intentado proporcionar un vector de los nombres de las columnas o las posiciones de las columnas? Eso debería hacerlo ... – A5C1D2H2I1M1N2O1R2T1

+0

No fue solo el cambio de nombre de las columnas: necesitaba literalmente dividir las columnas como arriba, doblando efectivamente el número de columnas en mi df. El siguiente fue lo que usé al final: df2 <- cSplit (df1, splitCols = 1:54, "/") – Nicki

35

5 años después de añadir 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 

Podríamos también tanto hacer Asegúrese de que las columnas resultantes tendrán los tipos correctos y mejorando el rendimiento agregando 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)] 
5

Aquí es un un trazador de líneas de base 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 con data.framedo.call/rbind para poner los datos de nuevo en un hoja.de.datos. La mejora incremental adicional es el uso de setNames para agregar nombres de variables al data.frame.

-4
tp <- c("a-c","d-e-f","g-h-i","m-n") 

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)) 
    } 
} 
2

Desde la versión R 3.4.0 puede utilizar strcapture() de la utils paquete (incluido con la base R instala), 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 
1

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

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, usted 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 podemos usar dplyr separate() porque no sabemos el número de las columnas de resultados antes de la división, por lo que entonces me ha creado una función que utiliza stringr para dividir una columna, dado el patrón y un prefijo de nombre para las columnas generadas. Espero que los patrones de codificación 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) 
} 

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

Y entonces podemos utilizar para poner en orden gather ...

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 
0

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 
Cuestiones relacionadas