Dividi la colonna della stringa del frame di dati in più colonne


246

Vorrei prendere i dati del modulo

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

e usa split()la colonna " type" dall'alto per ottenere qualcosa del genere:

  attr type_1 type_2
1    1    foo    bar
2   30    foo  bar_2
3    4    foo    bar
4    6    foo  bar_2

Ho inventato qualcosa di incredibilmente complesso che coinvolge una qualche forma di applyciò che ha funzionato, ma da allora l'ho smarrito. Sembrava troppo complicato per essere il modo migliore. Posso usare strsplitcome di seguito, ma poi non è chiaro come riportarlo in 2 colonne nel frame di dati.

> strsplit(as.character(before$type),'_and_')
[[1]]
[1] "foo" "bar"

[[2]]
[1] "foo"   "bar_2"

[[3]]
[1] "foo" "bar"

[[4]]
[1] "foo"   "bar_2"

Grazie per eventuali suggerimenti. Non ho ancora sviluppato le liste R per ora.

Risposte:


280

Uso stringr::str_split_fixed

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

2
questo ha funzionato abbastanza bene anche per il mio problema oggi .. ma stava aggiungendo una 'c' all'inizio di ogni riga. Qualche idea sul perché ??? left_right <- str_split_fixed(as.character(split_df),'\">',2)
LearneR,

Vorrei dividere con un modello che ha "...", quando applico quella funzione, non restituisce nulla. Quale potrebbe essere il problema. il mio tipo è qualcosa come "test ... score"
user3841581

2
@ user3841581 - la tua vecchia query lo so, ma questo è coperto nella documentazione - str_split_fixed("aaa...bbb", fixed("..."), 2)funziona bene con fixed()"Abbina una stringa fissa" pattern=nell'argomento. .significa "qualsiasi personaggio" in regex.
thelatemail

Grazie hadley, metodo molto conveniente, ma c'è una cosa che può essere migliorata, se c'è NA nella colonna originale, dopo la separazione diventerà una stringa vuota settoriale nelle colonne dei risultati, che è indesiderata, voglio mantenere NA NA dopo separazione
nuvole computa il

Funziona bene, cioè se manca il separatore! cioè se ho un vettore 'a <-c ("1N", "2N")' che vorrei separare nelle colonne '1,1, "N", "N"' Corro 'str_split_fixed (s, " ", 2) '. Non sono sicuro di come nominare le mie nuove colonne in questo approccio, 'col1 <-c (1,1)' e 'col2 <-c ("N", "N")'
maycca,

175

Un'altra opzione è quella di utilizzare il nuovo pacchetto 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

C'è un modo per limitare il numero di divisioni con separato? Diciamo che voglio dividere su '_' una sola volta (o farlo con str_split_fixede aggiungere colonne al dataframe esistente)?
JelenaČuklina, l'

67

5 anni dopo aggiungendo la data.tablesoluzione obbligatoria

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

Potremmo anche assicurarci che le colonne risultanti abbiano tipi corretti e migliorino le prestazioni aggiungendo type.converte fixedargomenti (poiché "_and_"non è davvero una regex)

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

se il numero dei tuoi '_and_'pattern varia, puoi scoprire il numero massimo di corrispondenze (ovvero colonne future) conmax(lengths(strsplit(before$type, '_and_')))
andschar

Questa è la mia risposta preferita, funziona molto bene! Potresti spiegare come funziona. Perché trasporre (strsplit (...)) e non è paste0 per concatenare le stringhe - non dividerle ...
Gecko

1
@Geco Non sono sicuro di quale sia la domanda. Se lo usi strsplit, crea un singolo vettore con 2 valori in ogni slot, quindi lo tstrsplittraspone in 2 vettori con un singolo valore in ciascuno. paste0viene utilizzato solo per creare i nomi delle colonne, non viene utilizzato sui valori. Sull'LHS dell'equazione ci sono i nomi delle colonne, sull'RHS è l'operazione split + transpose sulla colonna. :=sta per " Assegna sul posto ", quindi non vedi l' <-operatore di assegnazione lì.
David Arenburg,

58

Ancora un altro approccio: usare rbindsu 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"

E combinare:

data.frame(before$attr, do.call(rbind, out))

4
Un'altra alternativa alle versioni R più recenti èstrcapture("(.*)_and_(.*)", as.character(before$type), data.frame(type_1 = "", type_2 = ""))
alexis_laz,

37

Nota che sapply con "[" può essere usato per estrarre il primo o il secondo elemento in quegli elenchi così:

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

Ed ecco un metodo gsub:

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

32

ecco una fodera sulla stessa linea della soluzione di aniko, ma usando il pacchetto stringr di hadley:

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

1
Buona cattura, la migliore soluzione per me. Anche se un po 'più lento rispetto al stringrpacchetto.
Melka,

20

Per aggiungere alle opzioni, puoi anche usare la mia splitstackshape::cSplitfunzione in questo modo:

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

3 anni dopo - questa opzione funziona meglio per un problema simile che ho - tuttavia il frame di dati con cui sto lavorando ha 54 colonne e devo dividerle tutte in due. C'è un modo per farlo usando questo metodo - a corto di digitare 54 volte il comando sopra? Mille grazie, Nicki.
Nicki,

@Nicki, hai provato a fornire un vettore dei nomi delle colonne o delle posizioni delle colonne? Dovrebbe farlo ...
A5C1D2H2I1M1N2O1R2T1

Non era solo rinominare le colonne - avevo bisogno di dividere letteralmente le colonne come sopra raddoppiando effettivamente il numero di colonne nel mio df. Quello che segue è quello che ho usato alla fine: df2 <- cSplit (df1, splitCols = 1:54, "/")
Nicki

14

Un modo semplice è usare sapply()e la [funzione:

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

Per esempio:

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

sapply()Il risultato è una matrice e deve essere trasposto e convertito in un frame di dati. Sono quindi alcune semplici manipolazioni che producono il risultato desiderato:

after <- with(before, data.frame(attr = attr))
after <- cbind(after, data.frame(t(sapply(out, `[`))))
names(after)[2:3] <- paste("type", 1:2, sep = "_")

A questo punto, afterè quello che volevi

> 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

L'argomento è quasi esaurito, mi piacerebbe però offrire una soluzione a una versione leggermente più generale in cui non si conosce il numero di colonne di output, a priori. Quindi per esempio hai

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

Non possiamo usare dplyr separate()perché non conosciamo il numero delle colonne dei risultati prima della divisione, quindi ho quindi creato una funzione che utilizza stringrper dividere una colonna, dato lo schema e un prefisso del nome per le colonne generate. Spero che i modelli di codifica utilizzati siano corretti.

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

Possiamo quindi utilizzare split_into_multiplein una pipe dplyr come segue:

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>

E poi possiamo usare gatherper riordinare ...

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

Saluti, penso che questo sia estremamente utile.
Tjebo,

8

Ecco una linea di base R one che si sovrappone a una serie di soluzioni precedenti, ma restituisce un data.frame con i nomi propri.

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 strsplitper scomporre la variabile e data.framecon do.call/ rbindper reinserire i dati in un data.frame. Il miglioramento incrementale aggiuntivo è l'uso di setNamesper aggiungere nomi di variabili a data.frame.


6

Questa domanda è piuttosto vecchia ma aggiungerò la soluzione che ho trovato essere la più semplice al momento.

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

Questo è di gran lunga il più semplice quando si tratta di gestire i vettori di df
Apricot il

5

Dalla versione 3.4.0 di R è possibile utilizzare strcapture()dal pacchetto utils (incluso con le installazioni di base R), associando l'output alle altre colonne.

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

4

Un altro approccio se si desidera attenersi strsplit()è utilizzare il unlist()comando. Ecco una soluzione in tal senso.

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

base ma probabilmente lento:

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

1

Ecco un'altra soluzione R di base. Possiamo usare read.tablema poiché accetta solo un separgomento a un byte e qui abbiamo un separatore a più byte che possiamo usare gsubper sostituire il separatore a più byte con qualsiasi separatore a un byte e usarlo come separgomento inread.table

cbind(before[1], read.table(text = gsub('_and_', '\t', before$type), 
                 sep = "\t", col.names = paste0("type_", 1:2)))

#  attr type_1 type_2
#1    1    foo    bar
#2   30    foo  bar_2
#3    4    foo    bar
#4    6    foo  bar_2

In questo caso, possiamo anche accorciarlo sostituendolo con separgomento predefinito in modo da non doverlo menzionare esplicitamente

cbind(before[1], read.table(text = gsub('_and_', ' ', before$type), 
                 col.names = paste0("type_", 1:2)))
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.