Veri çerçevesi dizesi sütununu birden çok sütuna böl


246

Formun verilerini almak istiyorum

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

ve split()"type " yukarıdaki gibi bir şey elde etmek için kullanın:

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

İşe yarayanın bazı biçimlerini içeren inanılmaz derecede karmaşık bir şey applybuldum, ama o zamandan beri yanlış yerleştirdim. En iyi yol olamayacak kadar karmaşık görünüyordu. kullanabilirimstrsplitAşağıdaki gibi , ancak daha sonra veri çerçevesindeki 2 sütuna nasıl geri alabileceğinizi net değil.

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

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

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

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

İşaretçiler için teşekkürler. Henüz R listelerini tam olarak değerlendiremedim.

Yanıtlar:


280

kullanım stringr::str_split_fixed

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

2
Bu da bugünkü sorunum için gayet iyi çalıştı .. ama her satırın başına bir 'c' ekliyordu. Herhangi bir fikir neden bu ??? left_right <- str_split_fixed(as.character(split_df),'\">',2)
LearneR

Ben "..." olan bir desen ile bölmek istiyorum, bu işlevi uyguladığınızda, hiçbir şey döndürmez. Sorun ne olabilir. benim tür "test ... puan" gibi bir şey
user3841581

2
@ user3841581 - senin eski sorgu biliyorum, ama bu belgelerinde kapsanan - argüman "Sabit bir dize Maç" str_split_fixed("aaa...bbb", fixed("..."), 2)ile iyi çalışıyor . normal ifade içinde 'herhangi bir karakter' anlamına gelir. fixed()pattern=.
thelatemail

Teşekkürler hadley, çok rahat bir yöntem, ancak bir şey geliştirilebilir, orijinal sütunda NA varsa, ayrıldıktan sonra sonuç sütunlarında sevaral boş dize olacak, istenmeyen, sonra NA hala NA tutmak istiyorum ayırma
bulutlar, 15:17

Ayırıcı eksikse iyi çalışır! yani '1,1, "N", "N" sütunlarında ayırmak istediğim bir' a <-c ("1N", "2N") 'vektörüm varsa' str_split_fixed (s, " ", 2) '. Bu sütundaki yeni sütunları nasıl adlandıracağımdan emin değilim, 'col1 <-c (1,1)' ve 'col2 <-c ("N", "N")'
maycca

175

Başka bir seçenek de yeni tidyr paketini kullanmaktır.

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

Bölme sayısını ayrı olarak sınırlamanın bir yolu var mı? Diyelim ki '_' str_split_fixedişaretini yalnızca bir kez bölmek istiyorum (ya da var olan veri çerçevesine sütun eklemek ve mevcut veri çerçevesine sütun eklemek)?
JelenaČuklina

67

5 yıl sonra zorunlu data.tableçözüm eklendi

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

Biz de hem çıkan kolonlar doğru türlerine sahip olacak emin olabilir ve ekleyerek performansı artırmak type.convertve fixed(çünkü argümanlar "_and_"gerçekten regex değildir)

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

senin sayısı ise '_and_'desenleri değişir, maksimum ile eşleşme sayısını (yani gelecek sütun) bulabilirsinizmax(lengths(strsplit(before$type, '_and_')))
andschar

Bu benim en sevdiğim cevap, çok iyi çalışıyor! Nasıl çalıştığını açıklar mısınız? Neden devrik (strsplit (…)) ve dizeleri birleştirmek için yapıştırılmıyor - onları bölmüyor ...
Gecko

1
@Gecko Sorunun ne olduğundan emin değilim. Sadece kullanırsanız strsplit, her yuvada 2 değer içeren tek bir vektör oluşturur, bu yüzden tstrsplither birinde tek bir değer içeren 2 vektöre dönüştürür. paste0yalnızca sütun adlarını oluşturmak için kullanılır, değerlerde kullanılmaz. Denklemin LHS'sinde sütun adları, RHS'de sütun üzerinde split + transpoze işlemi bulunur. :=" yerinde ata " anlamına gelir , dolayısıyla <-orada atama işlecini görmezsiniz .
David Arenburg

58

Henüz başka yaklaşım: kullanım rbindüzerine 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"

Ve birleştirmek için:

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

4
Daha yeni R versiyonlarında başka bir alternatifstrcapture("(.*)_and_(.*)", as.character(before$type), data.frame(type_1 = "", type_2 = ""))
alexis_laz

37

"[" İle uygulanmış olan bu listelerdeki birinci veya ikinci öğeleri ayıklamak için kullanılabilir.

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

Ve işte bir gsub yöntemi:

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

32

İşte aniko'nun çözümü ile aynı satırlar boyunca, ancak hadley'in stringr paketini kullanan bir astar:

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

1
İyi yakaladın, benim için en iyi çözüm. stringrPaketten biraz daha yavaş olsa da .
Melka

20

Seçeneklere eklemek için splitstackshape::cSplitişlevimi şu şekilde de kullanabilirsiniz :

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 yıl sonra - bu seçenek benzer bir sorun için en iyi şekilde çalışıyor - ancak birlikte çalıştığım veri çerçevesinin 54 sütunu var ve hepsini ikiye bölmem gerekiyor. Bu komutu kullanarak bunu yapmanın bir yolu var mı - yukarıdaki komutu 54 kez yazmaktan yoksun? Çok teşekkürler, Nicki.
Nicki

@Nicki, Sütun adlarının veya sütun konumlarının bir vektörünü sağlamayı denediniz mi? Bunu yapmalı ....
A5C1D2H2I1M1N2O1R2T1

Sadece sütunları yeniden adlandırmak değildi - tam olarak df sütun sayısını etkili bir şekilde iki katına sütunları bölmek gerekiyordu. Aşağıda sonunda kullandığım şuydu: df2 <- cSplit (df1, splitCols = 1:54, "/")
Nicki

14

Kullanmanın kolay bir yolu sapply()ve [işlevi:

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

Örneğin:

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

sapply()Sonuç, bir matristir ve bir veri çerçevesine aktarılması ve geri çevrilmesi gerekir. O zaman istediğiniz sonucu veren bazı basit manipülasyonlar:

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

Bu noktada, afteristediğin buydu

> 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

Konu neredeyse tükenmiş, biraz daha genel bir versiyona bir çıkış teklif etmek istiyorum, ancak çıkış sütunlarının sayısını bilmiyorsunuz, a priori. Yani örneğin

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

Bölünmeden separate()önce sonuç sütunlarının sayısını bilmediğimiz için dplyr'i kullanamayız , bu nedenle stringroluşturulan sütunlar için desen ve ad öneki verildiğinde bir sütunu bölmek için kullanılan bir işlev oluşturdum . Umarım kullanılan kodlama kalıpları doğrudur.

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

Daha sonra split_into_multipleaşağıdaki gibi bir dplyr borusunda kullanabiliriz :

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>

Ve sonra gathertoparlamak için kullanabiliriz ...

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

Şerefe, bence bu son derece faydalı.
Tjebo

8

Burada, bir dizi önceki çözümle çakışan, ancak uygun adlara sahip bir data.frame döndüren bir temel R bir astar bulunmaktadır.

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

O kullandığı strsplitdeğişkeni kırmaya ve data.frameile do.call/ ' rbindbir data.frame içine veri geri koymak. Ek artan iyileştirme, setNamesdata.frame öğesine değişken adları eklemek için kullanılmasıdır .


6

Bu soru oldukça eski ama şu anda en basit bulduğum çözümü ekleyeceğim.

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

Df vektörlerini yönetmek söz konusu olduğunda bu çok kolay
Kayısı

5

R versiyonu 3.4.0 beri kullanabilir strcapture()gelen utils diğer kolon (ler) üzerine çıkış bağlama, (temel R yükler ile birlikte) bir paket.

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

Eğer bağlı kalmak istiyorsanız başka bir yaklaşım komutu strsplit()kullanmaktır unlist(). İşte bu çizgiler boyunca bir çözüm.

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

temel ama muhtemelen yavaş:

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

İşte başka bir baz R çözeltisi. Kullanabiliriz, read.tableancak yalnızca bir baytlık bir separgümanı kabul ettiğinden ve burada çok baytlı ayırıcıya sahip olduğumuz için, çok gsubbaytlı ayırıcıyı herhangi bir tek baytlık ayırıcıyla değiştirmek ve bunu separgüman olarak kullanmak için kullanabiliriz .read.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

Bu durumda, bunu varsayılan separgümanla değiştirerek kısaltabiliriz, böylece açıkça belirtmemiz gerekmez

cbind(before[1], read.table(text = gsub('_and_', ' ', before$type), 
                 col.names = paste0("type_", 1:2)))
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.