2010-12-03 83 views
136

我想采取的形式拆分数据帧的多个列的列

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 

的数据,并使用split()从上面列“type”得到这样的:

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

我想出了一些令人难以置信的复杂涉及某种形式的apply工作,但我从那里错位。要成为最好的方式似乎太复杂了。我可以使用下面的strsplit,但不清楚如何将它重新分成数据框中的2列。

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

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

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

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

感谢您的任何指示。我还没有完全掌握R列表。

回答

179

使用stringr::str_split_fixed

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

这对我今天的问题很好,但是它在每一行的开头添加了'c'。任何想法,为什么是? `left_right < - str_split_fixed(as.character(split_df),'\“>',2)` – LearneR 2015-07-28 06:53:12

+0

我想分割一个具有”...“的模式,当我应用该函数时,它什么都不返回。可能是问题,我的类型就像是“test ... score” – user3841581 2016-03-14 08:15:50

27

注意与sapply“[”可被用来提取无论是在这些列表,以便在第一或第二项:

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 

这里还有一个GSUB方法:

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

一个简单的方法是使用sapply()[功能:

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

˚F或例如:

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

sapply()的结果是一个矩阵,需要调换和铸造回的数据帧。它是那么一些简单的操作即得到你想要的结果:

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

在这一点上,after是你想要的东西

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

另一种方法,如果你想坚持strsplit()是使用unlist()命令。这里有一个解决方案。

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

另一种方法:在out使用rbind

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" 

以及组合:

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

another alternat在新的R版本上是`strcapture(“(。*)_和_(。*)”,as.character(在$ type之前),data.frame(type_1 =“”,type_2 =“”))` – 2016-11-10 18:23:33

24

这里是沿着相同的路线为兄子的解决方案一个衬,但使用哈德利的stringr package:

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

这也适用与基地包裹strsplit – schultem 2013-03-07 09:46:23

+1

好,赶上,对我来说最好的解决方案。虽然比`stringr`包慢一点。 – Melka 2016-03-30 11:34:08

94

另一种选择是使用新的tidyr包。

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 
15

为了增加选项,你也可以用我的splitstackshape::cSplit功能是这样的:

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 
35

5年以后加入强制性data.table解决方案

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 

我们也可以既能使确保生成的列将具有正确的类型通过添加可以提高性能0和fixed参数(因为"_and_"是不是一个真正的正则表达式)

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

这里是一个基础R一个衬垫重叠一些以前的解决方案,但将返回正确的名称的data.frame。

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 

它使用strsplit分手的变量,并用data.framedo.call/rbind把数据备份到一个data.frame。额外的增量改进是使用setNames将变量名称添加到data.frame。

-4
tp <- c("a-c","d-e-f","g-h-i","m-n") 

temp = strsplit(as.character(tp),'-') 

x=c(); 
y=c(); 
z=c(); 

#tab=data.frame() 
#tab= cbind(tab,c(x,y,z)) 

for(i in 1:length(temp)) 
{ 
    l = length(temp[[i]]); 

    if(l==2) 
    { 
    x=c(x,temp[[i]][1]); 
    y=c(y,"NA") 
    z=c(z,temp[[i]][2]); 

    df= as.data.frame(cbind(x,y,z)) 

    }else 
    { 
    x=c(x,temp[[i]][1]); 
    y=c(y,temp[[i]][2]); 
    z=c(z,temp[[i]][3]); 

    df= as.data.frame(cbind(x,y,z)) 
    } 
} 
2

由于R版本3.4.0可以使用strcapture()utils的包(包含在基础R安装),输出结合到另一列(多个)。

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 
1

这个问题很老,但我会添加我认为目前最简单的解决方案。

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 
1

主题是几乎疲惫,我倒是想,虽然提供了一个解决方案,一个稍微普通版,你不知道输出的列数,先验。因此,例如,你有

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 

我们不能用dplyr separate(),因为我们不知道在分裂之前的结果列的数目,所以我然后创建了一个使用stringr拆分列的函数,给定生成列的模式和名称前缀。我希望使用的编码模式是正确的。

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

然后我们可以使用split_into_multiple在dplyr管如下:

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> 

,然后我们可以使用gather整理...

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 
0

的基础,但可能慢:

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