Chia cột chuỗi khung dữ liệu thành nhiều cột


245

Tôi muốn lấy dữ liệu của mẫu

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

và sử dụng split()trên cột " type" từ trên để có được một cái gì đó như thế này:

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

Tôi đã nghĩ ra một thứ phức tạp không thể tin được liên quan đến một số hình thức applyhoạt động, nhưng từ đó tôi đã đặt nhầm chỗ. Nó dường như quá phức tạp để trở thành cách tốt nhất. Tôi có thể sử dụng strsplitnhư dưới đây, nhưng sau đó không rõ làm thế nào để lấy lại thành 2 cột trong khung dữ liệu.

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

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

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

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

Cảm ơn cho bất kỳ con trỏ. Tôi chưa hoàn toàn mò mẫm danh sách R.

Câu trả lời:


279

Sử dụng stringr::str_split_fixed

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

2
điều này cũng hoạt động khá tốt đối với vấn đề của tôi ngày hôm nay .. nhưng nó đã thêm một 'c' vào đầu mỗi hàng. Bất cứ ý tưởng tại sao đó là ??? left_right <- str_split_fixed(as.character(split_df),'\">',2)
Tìm hiểu

Tôi muốn phân chia với một mẫu có "...", khi tôi áp dụng chức năng đó, nó không trả về gì cả. Điều gì có thể là vấn đề. loại của tôi là một cái gì đó như "kiểm tra ... điểm số"
user3841581

2
@ user3841581 - Tôi biết truy vấn cũ của bạn, nhưng điều này được đề cập trong tài liệu - str_split_fixed("aaa...bbb", fixed("..."), 2)hoạt động tốt với fixed()"Ghép chuỗi cố định" trong pattern=đối số. .có nghĩa là 'bất kỳ nhân vật' trong regex.
thelHRail

Cảm ơn hadley, phương pháp rất thuận tiện, nhưng có một điều có thể được cải thiện, nếu có NA trong cột ban đầu, sau khi tách nó sẽ trở thành chuỗi rỗng thứ bảy trong các cột kết quả, điều không mong muốn, tôi muốn giữ NA vẫn NA sau tách
đám mây

Hoạt động tốt tức là nếu thiếu dấu phân cách! tức là nếu tôi có một vectơ 'a <-c ("1N", "2N")' mà tôi muốn tách trong các cột '1,1, "N", "N"' Tôi chạy 'str_split_fixed (s, " ", 2) '. Tôi chỉ không chắc chắn làm thế nào để đặt tên cho các cột mới của mình theo cách tiếp cận này, 'col1 <-c (1,1)' và 'col2 <-c ("N", "N")'
maycca

173

Một lựa chọn khác là sử dụng gói tidyr mới.

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ó cách nào để hạn chế số lần chia tách riêng biệt không? Giả sử tôi muốn tách trên '_' chỉ một lần (hoặc thực hiện với str_split_fixedvà thêm các cột vào khung dữ liệu hiện có)?
JelenaČuklina

66

5 năm sau thêm data.tablegiải pháp bắt buộc

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

Cả hai chúng ta cũng có thể đảm bảo rằng các cột kết quả sẽ có các loại chính xác cải thiện hiệu suất bằng cách thêm type.convertfixedđối số (vì "_and_"thực sự không phải là biểu thức chính quy)

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

nếu số lượng '_and_'mẫu của bạn thay đổi, bạn có thể tìm ra số lượng kết hợp tối đa (tức là các cột trong tương lai) vớimax(lengths(strsplit(before$type, '_and_')))
andschar

Đây là câu trả lời yêu thích của tôi, hoạt động rất tốt! Bạn có thể vui lòng giải thích làm thế nào nó hoạt động. Tại sao chuyển vị (strsplit (Bắt)) và không dán0 để nối chuỗi - không tách chúng ...
Tắc kè

1
@Gecko Tôi không chắc câu hỏi là gì. Nếu bạn chỉ sử dụng strsplitnó sẽ tạo ra một vectơ duy nhất có 2 giá trị trong mỗi vị trí, do đó, tstrsplitchuyển nó thành 2 vectơ với một giá trị trong mỗi vectơ. paste0chỉ được sử dụng để tạo tên cột, nó không được sử dụng trên các giá trị. Trên LHS của phương trình là các tên cột, trên RHS là phép toán chia + hoán vị trên cột. :=là viết tắt của " gán tại chỗ ", do đó bạn không thấy <-toán tử gán ở đó.
David Arenburg

57

Một cách tiếp cận khác: sử dụng rbindtrên 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"

Và để kết hợp:

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

4
Một lựa chọn khác trên các phiên bản R mới hơn làstrcapture("(.*)_and_(.*)", as.character(before$type), data.frame(type_1 = "", type_2 = ""))
alexis_laz

36

Lưu ý rằng sapply với "[" có thể được sử dụng để trích xuất các mục đầu tiên hoặc thứ hai trong các danh sách đó:

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

Và đây là một phương pháp gsub:

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

31

đây là một lớp lót dọc theo cùng dòng với giải pháp của aniko, nhưng sử dụng gói Stringr của hadley:

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

1
Bắt tốt, giải pháp tốt nhất cho tôi. Mặc dù chậm hơn một chút so với stringrgói.
Melka

20

Để thêm vào các tùy chọn, bạn cũng có thể sử dụng splitstackshape::cSplitchức năng của tôi như thế này:

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 năm sau - tùy chọn này hoạt động tốt nhất cho một vấn đề tương tự tôi gặp phải - tuy nhiên khung dữ liệu tôi đang làm việc có 54 cột và tôi cần chia tất cả chúng thành hai. Có cách nào để thực hiện việc này bằng phương pháp này - không cần gõ lệnh trên 54 lần? Rất cám ơn, Nicki.
Nicki

@Nicki, Bạn đã thử cung cấp một vectơ tên cột hoặc vị trí cột chưa? Điều đó sẽ làm điều đó ....
A5C1D2H2I1M1N2O1R2T1

Nó không chỉ đổi tên các cột - tôi cần phải phân chia các cột theo nghĩa đen như trên nhân đôi hiệu quả số lượng cột trong df của tôi. Dưới đây là những gì tôi đã sử dụng cuối cùng: df2 <- cSplit (df1, splitCols = 1:54, "/")
Nicki

14

Một cách dễ dàng là sử dụng sapply()[chức năng:

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

Ví dụ:

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

sapply()Kết quả là một ma trận và cần chuyển đổi và chuyển trở lại khung dữ liệu. Sau đó, một số thao tác đơn giản mang lại kết quả bạn muốn:

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

Tại thời điểm này, afterlà những gì bạn muốn

> 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

Chủ đề gần như đã cạn kiệt, tôi muốn đưa ra một giải pháp cho một phiên bản tổng quát hơn một chút, nơi bạn không biết số lượng cột đầu ra, một tiên nghiệm. Vì vậy, ví dụ bạn có

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

Chúng tôi không thể sử dụng dplyr separate()vì chúng tôi không biết số lượng cột kết quả trước khi phân tách, do đó tôi đã tạo một hàm sử dụng stringrđể phân tách một cột, đưa ra mẫu và tiền tố tên cho các cột được tạo. Tôi hy vọng các mẫu mã được sử dụng, là chính xác.

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

Sau đó chúng ta có thể sử dụng split_into_multipletrong một ống dplyr như sau:

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>

Và sau đó chúng ta có thể sử dụng gatherđể dọn dẹp ...

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

Chúc mừng, tôi nghĩ rằng điều này là vô cùng hữu ích.
Tjebo

8

Dưới đây là một lớp cơ sở R một lớp phủ chồng lên một số giải pháp trước đó, nhưng trả về một data.frame với tên thích hợp.

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

Nó sử dụng strsplitđể phá vỡ biến và data.framevới do.call/ rbindđể đưa dữ liệu trở lại vào data.frame. Cải tiến gia tăng bổ sung là việc sử dụng setNamesđể thêm tên biến vào data.frame.


6

Câu hỏi này khá cũ nhưng tôi sẽ thêm giải pháp tôi thấy đơn giản nhất hiện nay.

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

Đây là cách dễ nhất khi quản lý các vectơ df
Apricot

5

Vì phiên bản R 3.4.0, bạn có thể sử dụng strcapture()từ gói utils (kèm theo cài đặt R cơ sở), ràng buộc đầu ra vào (các) cột khác.

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

Một cách tiếp cận khác nếu bạn muốn gắn bó strsplit()là sử dụng unlist()lệnh. Đây là một giải pháp dọc theo những dòng đó.

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

cơ sở nhưng có lẽ chậm:

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

Đây là một giải pháp cơ sở R. Chúng ta có thể sử dụng read.tablenhưng vì nó chỉ chấp nhận sepđối số một byte và ở đây chúng ta có trình phân tách nhiều byte, chúng ta có thể sử dụng gsubđể thay thế dấu tách đa phân tử thành bất kỳ phân tách một byte nào và sử dụng làm sepđối số trongread.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

Trong trường hợp này, chúng tôi cũng có thể làm cho nó ngắn hơn bằng cách thay thế nó bằng sepđối số mặc định để chúng tôi không phải đề cập rõ ràng

cbind(before[1], read.table(text = gsub('_and_', ' ', before$type), 
                 col.names = paste0("type_", 1:2)))
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.