在第一个空格处分割字符串

34

我想将一个字符向量(人名)分成两列(向量)。问题在于有些人的姓氏是“两个单词”。我希望将名字和姓氏分成两列。我可以使用下面的代码提取并获取名字,但姓氏一直逃之夭夭(请参考下面示例集中第29个观察值,其中Ford的“姓氏”为Pantera L必须保持在一起)。

到目前为止我尝试过的方法:

x<-rownames(mtcars)
unlist(strsplit(x, " .*"))

我希望它看起来像这样:

            MANUF       MAKE
27          Porsche     914-2
28          Lotus       Europa
29          Ford        Pantera L
30          Ferrari     Dino
31          Maserati    Bora
32          Volvo       142E
7个回答

31

正则表达式rexp匹配字符串开头的单词,一个可选的空格,然后是字符串的其余部分。括号是子表达式,通过反向引用\\1\\2来访问。

rexp <- "^(\\w+)\\s?(.*)$"
y <- data.frame(MANUF=sub(rexp,"\\1",x), MAKE=sub(rexp,"\\2",x))
tail(y)
#       MANUF      MAKE
# 27  Porsche     914-2
# 28    Lotus    Europa
# 29     Ford Pantera L
# 30  Ferrari      Dino
# 31 Maserati      Bora
# 32    Volvo      142E

21

对我来说,Hadley在reshape2包中的colsplit函数是实现此目的最直观的方法。Joshua的方法更加通用(即可以在任何可以使用正则表达式的地方使用),也更加灵活(如果您想要更改规范); 但是colsplit函数非常适合这个特定的设置:

library(reshape2)
y <- colsplit(x," ",c("MANUF","MAKE"))
tail(y)
#      MANUF      MAKE
#27  Porsche     914-2
#28    Lotus    Europa
#29     Ford Pantera L
#30  Ferrari      Dino
#31 Maserati      Bora
#32    Volvo      142E

1
非常有趣,因为我以为在这种情况下“colsplit”将返回超过三列。我错了。 - Andrie
我花了50分钟才找到这个极其简单的任务的解决方案。我真的很惊讶,如此简单的任务居然如此复杂。 - Seymour

13

以下是两种方法:

1) strsplit。这种方法只使用R语言核心函数,没有复杂的正则表达式。用sub将第一个空格替换为分号(不要用gsub),在分号处进行strsplit,然后rbind成一个2列矩阵:

mat <- do.call("rbind", strsplit(sub(" ", ";", x), ";"))
colnames(mat) <- c("MANUF", "MAKE")

2) gsubfn包中的strapply下面这行代码是使用 gsubfn 包中的 strapply 函数实现的。正则表达式中用括号括起来的两部分分别捕获所需的第一列和第二列,然后使用在公式表示法中指定的函数(与指定function(x, y) c(MANUF = x, MAKE = y)相同)将它们抓取并添加名称。 simplify=rbind参数用于将结果转换为矩阵,如前一个解决方案所示。

library(gsubfn)
mat <- strapply(x, "(\\S+)\\s+(.*)", ~ c(MANUF = x, MAKE = y), simplify = rbind)

注意:无论哪种情况下,都会返回一个"character"矩阵mat。 如果需要一个"character"列的数据框,则添加以下内容:

DF <- as.data.frame(mat, stringsAsFactors = FALSE)

如果需要"factor"列,则省略stringsAsFactors参数。


我刚刚回来看了一下这里。实际上,我最喜欢你提供的第一个解决方案。谢谢并为晚回复道歉。 - Tyler Rinker

9
另一种方法是:stringr中的str_split可以处理分割,但返回不同的形式(像strsplit一样是一个列表)。不过,将其转换为正确的格式很简单。
library(stringr)
split_x <- str_split(x, " ", 2)
(y <- data.frame(
  MANUF = sapply(split_x, head, n = 1),
  MAKE  = sapply(split_x, tail, n = 1)
))

或者,正如哈德利在评论中提到的那样,使用str_split_fixed
y <- as.data.frame(str_split_fixed(x, " ", 2))
colnames(y) <- c("MANUF", "MAKE")
y

1
有趣的是,这个答案和 Hadley 的评论与 colsplit 解决方案有关,因为 colsplit 使用了 str_split_fixed - Xu Wang

0

在将您的向量转换为数据框后,您还可以使用tidyr::extract - 我认为这也是比reshape2更现代的解决方案。

library(tidyr)

## first convert into a data frame
x <- data.frame(x = rownames(mtcars))

## use extract, and for example Joshua's regex
res <- extract(x, col = x, into = c("MANUF", "MAKE"), regex = "^(\\w+)\\s?(.*)$")

head(res)
#>     MANUF       MAKE
#> 1   Mazda        RX4
#> 2   Mazda    RX4 Wag
#> 3  Datsun        710
#> 4  Hornet    4 Drive
#> 5  Hornet Sportabout
#> 6 Valiant

0
如果您能够进行模式匹配和分组匹配,我建议尝试类似于以下代码(未经测试):
\s+(.*)\s+(.*)

6
请注意,R语言中的正则表达式工作方式略有不同。至少,您需要在每个s前面再添加一个\,以避免出现错误。 - joran

-2

我认为搜索[^\s]+应该可以。未经测试。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接