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.

Antworten:


279

Verwenden stringr::str_split_fixed

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

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

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

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

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

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

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

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

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

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.


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

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

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

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

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)))
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.