Teilen Sie die Datenrahmen-Zeichenfolgenspalte in mehrere Spalten auf

245

Ich möchte Daten des Formulars übernehmen

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

und benutze split()auf der Spalte " type" von oben, um so etwas zu bekommen:

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

Ich habe mir etwas unglaublich Komplexes ausgedacht, das eine Form davon beinhaltet apply, aber seitdem habe ich das verlegt. Es schien viel zu kompliziert, um der beste Weg zu sein. Ich kann strsplitwie folgt verwenden, aber dann unklar, wie man das wieder in 2 Spalten im Datenrahmen bekommt.

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

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

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

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

Vielen Dank für Hinweise. Ich habe R-Listen noch nicht ganz durchgearbeitet.

jkebinger
quelle

Antworten:

279

Verwenden stringr::str_split_fixed

library(stringr)
str_split_fixed(before$type, "_and_", 2)
Hadley
quelle
2
Dies funktionierte auch heute ziemlich gut für mein Problem. Aber am Anfang jeder Zeile wurde ein 'c' hinzugefügt. Irgendeine Idee warum ist das ??? left_right <- str_split_fixed(as.character(split_df),'\">',2)
LearneR
Ich möchte mit einem Muster teilen, das "..." hat. Wenn ich diese Funktion anwende, wird nichts zurückgegeben. Was könnte das Problem sein. Mein Typ ist so etwas wie "test ... score"
user3841581
2
@ user3841581 - alte Abfrage von Ihnen, die ich kenne, aber dies wird in der Dokumentation behandelt - str_split_fixed("aaa...bbb", fixed("..."), 2)funktioniert gut mit fixed()"Match a fixed string" im pattern=Argument. .bedeutet "beliebiges Zeichen" in Regex.
E-Mail
Vielen Dank, Hadley, sehr praktische Methode, aber es gibt eine Sache, die verbessert werden kann, wenn es NA in der ursprünglichen Spalte gibt, nach der Trennung wird es mehrere leere Zeichenfolgen in den Ergebnisspalten, was unerwünscht ist, ich möchte die NA danach immer noch NA halten Trennung
Cloudscomputes
Funktioniert gut, dh wenn das Trennzeichen fehlt! dh wenn ich einen Vektor 'a <-c ("1N", "2N")' habe, den ich in den Spalten '1,1, "N", "N" trennen möchte, führe ich' str_split_fixed (s, "aus ", 2) '. Ich bin mir nur nicht sicher, wie ich meine neuen Spalten in diesem Ansatz benennen soll: 'col1 <-c (1,1)' und 'col2 <-c ("N", "N")'
Maycca
173

Eine weitere Option ist die Verwendung des neuen Tidyr-Pakets.

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
Hadley
quelle
Gibt es eine Möglichkeit, die Anzahl der Teilungen mit separaten zu begrenzen? Angenommen, ich möchte '_' nur einmal aufteilen (oder dies tun str_split_fixedund Spalten zu einem vorhandenen Datenrahmen hinzufügen).
JelenaČuklina
66

5 Jahre später Hinzufügen der obligatorischen data.tableLösung

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

Wir könnten auch beide sicherstellen, dass die resultierenden Spalten die richtigen Typen haben und die Leistung durch Hinzufügen type.convertund fixedArgumente verbessern (da dies "_and_"nicht wirklich ein regulärer Ausdruck ist).

setDT(before)[, paste0("type", 1:2) := tstrsplit(type, "_and_", type.convert = TRUE, fixed = TRUE)]
David Arenburg
quelle
Wenn die Anzahl Ihrer '_and_'Muster variiert, können Sie die maximale Anzahl von Übereinstimmungen (dh zukünftige Spalten) mitmax(lengths(strsplit(before$type, '_and_')))
andschar
Dies ist meine Lieblingsantwort, funktioniert sehr gut! Könnten Sie bitte erklären, wie es funktioniert. Warum transponieren (strsplit (…)) und nicht paste0 zum Verketten von Strings - nicht zum Teilen ...
Gecko
1
@Gecko Ich bin mir nicht sicher, was die Frage ist. Wenn Sie nur verwenden strsplit, wird ein einzelner Vektor mit 2 Werten in jedem Slot erstellt. tstrsplitTransponieren Sie ihn daher in 2 Vektoren mit jeweils einem einzelnen Wert. paste0wird nur verwendet, um die Spaltennamen zu erstellen, es wird nicht für die Werte verwendet. Auf der linken Seite der Gleichung befinden sich die Spaltennamen, auf der rechten Seite die Split + Transponierungsoperation für die Spalte. :=steht für " Zuweisen an Ort und Stelle ", daher wird der <-Zuweisungsoperator dort nicht angezeigt.
David Arenburg
57

Noch ein anderer Ansatz: Verwendung rbindauf 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"

Und zu kombinieren:

data.frame(before$attr, do.call(rbind, out))
Aniko
quelle
4
Eine andere Alternative auf neueren R-Versionen iststrcapture("(.*)_and_(.*)", as.character(before$type), data.frame(type_1 = "", type_2 = ""))
alexis_laz
36

Beachten Sie, dass sapply with "[" verwendet werden kann, um entweder das erste oder das zweite Element in diesen Listen zu extrahieren.

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

Und hier ist eine gsub-Methode:

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

Hier ist ein Einzeiler nach dem Vorbild von Anikos Lösung, jedoch mit Hadleys Stringr-Paket:

do.call(rbind, str_split(before$type, '_and_'))
Ramnath
quelle
1
Guter Fang, beste Lösung für mich. Obwohl etwas langsamer als mit dem stringrPaket.
Melka
20

Um die Optionen zu erweitern, können Sie meine splitstackshape::cSplitFunktion auch folgendermaßen verwenden :

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
A5C1D2H2I1M1N2O1R2T1
quelle
3 Jahre später - diese Option funktioniert am besten für ein ähnliches Problem, das ich habe - hat der Datenrahmen, mit dem ich arbeite, jedoch 54 Spalten und ich muss alle in zwei Teile aufteilen. Gibt es eine Möglichkeit, dies mit dieser Methode zu tun - ohne den obigen Befehl 54 Mal einzugeben? Vielen Dank, Nicki.
Nicki
@ Nicki, haben Sie versucht, einen Vektor der Spaltennamen oder der Spaltenpositionen bereitzustellen? Das sollte es tun ...
A5C1D2H2I1M1N2O1R2T1
Es wurde nicht nur die Spalten umbenannt - ich musste die Spalten wie oben buchstäblich aufteilen, um die Anzahl der Spalten in meinem df effektiv zu verdoppeln. Das Folgende war das, was ich am Ende verwendet habe: df2 <- cSplit (df1, splitCols = 1:54, "/")
Nicki
14

Ein einfacher Weg ist die Verwendung sapply()und die [Funktion:

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

Beispielsweise:

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

sapply()Das Ergebnis ist eine Matrix und muss transponiert und in einen Datenrahmen zurückgesetzt werden. Es sind dann einige einfache Manipulationen, die das gewünschte Ergebnis liefern:

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

An diesem Punkt afterist, was Sie wollten

> after
  attr type_1 type_2
1    1    foo    bar
2   30    foo  bar_2
3    4    foo    bar
4    6    foo  bar_2
Gavin Simpson
quelle
12

Das Thema ist fast erschöpft, ich möchte jedoch eine Lösung für eine etwas allgemeinere Version anbieten, bei der Sie die Anzahl der Ausgabespalten a priori nicht kennen. So haben Sie zum Beispiel

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

Wir können dplyr nicht verwenden, separate()da wir die Anzahl der Ergebnisspalten vor dem Teilen nicht kennen. Daher habe ich eine Funktion erstellt, mit stringrder eine Spalte unter Berücksichtigung des Musters und eines Namenspräfix für die generierten Spalten geteilt wird. Ich hoffe die verwendeten Codierungsmuster sind korrekt.

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

Wir können dann split_into_multiplein einem dplyr-Rohr wie folgt verwenden:

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>

Und dann können wir gatheraufräumen ...

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
Yannis P.
quelle
Prost, ich denke das ist extrem nützlich.
Tjebo
8

Hier ist ein Basis-R-One-Liner, der eine Reihe vorheriger Lösungen überlappt, aber einen data.frame mit den richtigen Namen zurückgibt.

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

Es wird verwendet strsplit, um die Variable aufzubrechen und data.framemit do.call/ rbinddie Daten wieder in einen data.frame zu stellen. Die zusätzliche inkrementelle Verbesserung ist die Verwendung von setNames, um dem data.frame Variablennamen hinzuzufügen.

lmo
quelle
6

Diese Frage ist ziemlich alt, aber ich werde die Lösung hinzufügen, die ich derzeit als die einfachste empfand.

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
Schneller McSwifterton
quelle
Dies ist bei weitem die einfachste, wenn es um die Verwaltung von df-Vektoren geht
Apricot
5

Seit R Version 3.4.0 können Sie strcapture()aus dem Utils- Paket (das in Base R-Installationen enthalten ist) die Ausgabe an die anderen Spalten binden.

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
Rich Scriven
quelle
4

Ein anderer Ansatz, bei dem Sie bleiben möchten, strsplit()ist die Verwendung des unlist()Befehls. Hier ist eine Lösung in dieser Richtung.

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")
Ashaw
quelle
4

Basis aber wahrscheinlich langsam:

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
jpmorris
quelle
1

Hier ist eine andere Basis-R-Lösung. Wir können verwenden, read.tableaber da es nur ein Byte- sepArgument akzeptiert und wir hier ein Multi-Byte-Trennzeichen haben, können wir gsubdas Multibyte-Trennzeichen durch ein beliebiges Ein-Byte-Trennzeichen ersetzen und dieses als sepArgument in verwendenread.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 diesem Fall können wir es auch kürzer machen, indem sepwir es durch ein Standardargument ersetzen, sodass wir es nicht explizit erwähnen müssen

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