Membagi kolom string bingkai data menjadi beberapa kolom

245

Saya ingin mengambil data formulir

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

dan gunakan split()pada kolom " type" dari atas untuk mendapatkan sesuatu seperti ini:

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

Saya datang dengan sesuatu yang luar biasa rumit yang melibatkan beberapa bentuk applyyang berhasil, tetapi sejak itu saya salah menempatkannya. Tampaknya terlalu rumit untuk menjadi cara terbaik. Saya dapat menggunakan strsplitseperti di bawah ini, tetapi kemudian tidak jelas bagaimana mengembalikannya ke dalam 2 kolom dalam bingkai data.

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

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

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

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

Terima kasih atas petunjuknya. Saya belum cukup menyukai daftar R.

jkebinger
sumber

Jawaban:

279

Menggunakan stringr::str_split_fixed

library(stringr)
str_split_fixed(before$type, "_and_", 2)
Hadley
sumber
2
ini bekerja cukup baik untuk masalah saya hari ini juga .. tapi itu menambahkan 'c' di awal setiap baris. Tahu kenapa begitu ??? left_right <- str_split_fixed(as.character(split_df),'\">',2)
LearneR
Saya ingin membagi dengan pola yang memiliki "...", ketika saya menerapkan fungsi itu, ia tidak mengembalikan apa pun. Apa yang bisa menjadi masalah. tipe saya adalah sesuatu seperti "test ... score"
user3841581
2
@ user3841581 - permintaan lama Anda, saya tahu, tapi ini tercakup dalam dokumentasi - str_split_fixed("aaa...bbb", fixed("..."), 2)berfungsi dengan baik fixed()untuk "Cocokkan string yang tetap" dalam pattern=argumen. .berarti 'karakter apa saja' di regex.
thelatemail
Terima kasih hadley, metode yang sangat convinient, tetapi ada satu hal yang dapat ditingkatkan, jika ada NA di kolom asli, setelah pemisahan itu akan menjadi sevaral string kosong di kolom hasil, yang tidak diinginkan, saya ingin tetap NA tetap NA setelah pemisahan
cloudcomputes
Berfungsi dengan baik yaitu jika pemisah hilang! yaitu jika saya memiliki vektor 'a <-c ("1N", "2N")' yang ingin saya pisahkan dalam kolom '1,1, "N", "N"' Saya menjalankan 'str_split_fixed (s, " ", 2) '. Saya tidak yakin bagaimana memberi nama kolom baru saya dalam pendekatan ini, 'col1 <-c (1,1)' dan 'col2 <-c ("N", "N")'
maycca
173

Pilihan lain adalah dengan menggunakan paket rapi baru.

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
sumber
Apakah ada cara untuk membatasi jumlah pemisahan dengan terpisah? Katakanlah saya ingin membagi pada '_' hanya sekali (atau melakukannya dengan str_split_fixeddan menambahkan kolom ke kerangka data yang ada)?
JelenaČuklina
66

5 tahun kemudian menambahkan data.tablesolusi wajib

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

Kami juga dapat memastikan bahwa kolom yang dihasilkan akan memiliki tipe yang benar dan meningkatkan kinerja dengan menambahkan type.convertdan fixedargumen (karena "_and_"sebenarnya bukan regex)

setDT(before)[, paste0("type", 1:2) := tstrsplit(type, "_and_", type.convert = TRUE, fixed = TRUE)]
David Arenburg
sumber
jika jumlah '_and_'pola Anda bervariasi, Anda dapat mengetahui jumlah maksimum kecocokan (yaitu kolom masa depan) denganmax(lengths(strsplit(before$type, '_and_')))
andschar
Ini jawaban favorit saya, bekerja dengan sangat baik! Bisakah Anda jelaskan cara kerjanya? Mengapa transpose (strsplit (...)) dan bukan paste0 untuk string gabungan - tidak membelah mereka ...
Gecko
1
@ Tokek Saya tidak yakin apa pertanyaannya. Jika Anda hanya menggunakannya, strsplitia menciptakan vektor tunggal dengan 2 nilai di setiap slot, jadi tstrsplittranspos menjadi 2 vektor dengan nilai tunggal di masing- masing slot . paste0hanya digunakan untuk membuat nama kolom, itu tidak digunakan pada nilai. Pada LHS dari persamaan adalah nama kolom, pada RHS adalah operasi split + transpos pada kolom. :=singkatan dari " assign in place ", maka Anda tidak melihat <-operator penugasan di sana.
David Arenburg
57

Namun pendekatan lain: gunakan rbindpada 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"

Dan untuk menggabungkan:

data.frame(before$attr, do.call(rbind, out))
Aniko
sumber
4
Alternatif lain pada versi R yang lebih baru adalahstrcapture("(.*)_and_(.*)", as.character(before$type), data.frame(type_1 = "", type_2 = ""))
alexis_laz
36

Perhatikan bahwa sapply dengan "[" dapat digunakan untuk mengekstrak item pertama atau kedua dalam daftar itu jadi:

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

Dan inilah metode gsub:

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

di sini adalah satu liner di sepanjang garis yang sama dengan solusi aniko, tetapi menggunakan paket stringr hadley:

do.call(rbind, str_split(before$type, '_and_'))
Ramnath
sumber
1
Tangkapan yang bagus, solusi terbaik untuk saya. Meskipun sedikit lebih lambat dibandingkan dengan stringrpaket.
Melka
20

Untuk menambah opsi, Anda juga dapat menggunakan splitstackshape::cSplitfungsi saya seperti ini:

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
sumber
3 tahun kemudian - opsi ini bekerja paling baik untuk masalah serupa yang saya miliki - namun kerangka data yang saya gunakan memiliki 54 kolom dan saya harus membagi semuanya menjadi dua. Apakah ada cara untuk melakukan ini menggunakan metode ini - singkat mengetik perintah di atas 54 kali? Terima kasih banyak, Nicki.
Nicki
@Nicki, Sudahkah Anda mencoba memberikan vektor nama kolom atau posisi kolom? Itu harus melakukannya ....
A5C1D2H2I1M1N2O1R2T1
Itu bukan hanya mengubah nama kolom - Saya harus benar-benar membagi kolom seperti di atas secara efektif menggandakan jumlah kolom di df saya. Di bawah ini adalah apa yang saya gunakan pada akhirnya: df2 <- cSplit (df1, splitCols = 1:54, "/")
Nicki
14

Cara mudah adalah menggunakan sapply()dan [fungsinya:

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

Sebagai contoh:

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

sapply()Hasilnya adalah sebuah matriks dan membutuhkan transposing dan casting kembali ke frame data. Maka beberapa manipulasi sederhana yang menghasilkan hasil yang Anda inginkan:

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

Pada titik ini, afteritulah yang Anda inginkan

> 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
sumber
12

Subjek hampir habis, saya ingin menawarkan solusi untuk versi yang sedikit lebih umum di mana Anda tidak tahu jumlah kolom output, apriori. Jadi misalnya kamu punya

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

Kami tidak dapat menggunakan dplyr separate()karena kami tidak tahu jumlah kolom hasil sebelum pemisahan, jadi saya kemudian membuat fungsi yang digunakan stringruntuk membagi kolom, mengingat pola dan awalan nama untuk kolom yang dihasilkan. Saya harap pola pengkodean yang digunakan, sudah benar.

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

Kita kemudian dapat menggunakan split_into_multiplepipa dplyr sebagai berikut:

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>

Dan kemudian kita bisa gunakan gatheruntuk merapikan ...

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.
sumber
Cheers, saya pikir ini sangat berguna.
Tjebo
8

Berikut ini adalah base R one liner yang tumpang tindih dengan sejumlah solusi sebelumnya, tetapi mengembalikan data.frame dengan nama yang tepat.

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

Ini digunakan strsplituntuk memecah variabel, dan data.framedengan do.call/ rbinduntuk mengembalikan data ke dalam data.frame. Peningkatan tambahan tambahan adalah penggunaan setNamesuntuk menambahkan nama variabel ke data.frame.

lmo
sumber
6

Pertanyaan ini cukup lama tetapi saya akan menambahkan solusi yang saya temukan menjadi yang paling sederhana saat ini.

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
Swifty McSwifterton
sumber
Sejauh ini ini adalah yang termudah dalam mengelola vektor df
Apricot
5

Karena R versi 3.4.0 dapat Anda gunakan strcapture()dari paket utils (disertakan dengan instalasi R dasar), mengikat output ke kolom lain.

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
Scriven Kaya
sumber
4

Pendekatan lain jika Anda ingin tetap dengan strsplit()adalah menggunakan unlist()perintah. Inilah solusi di sepanjang garis itu.

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
sumber
4

dasar tapi mungkin lambat:

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
sumber
1

Berikut ini adalah solusi base R lainnya. Kita dapat menggunakan read.tabletetapi karena hanya menerima separgumen satu byte dan di sini kita memiliki pemisah multi-byte yang dapat kita gunakan gsubuntuk mengganti pemisah multibyte ke pemisah satu byte dan menggunakannya sebagai separgumen dalamread.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

Dalam hal ini, kita juga dapat membuatnya lebih pendek dengan menggantinya dengan separgumen default sehingga kita tidak perlu menyebutkannya secara eksplisit

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