将数据框中的字符串列拆分为多个不同的列

36

我想要实现的是将一列拆分成多列。我希望第一列包含“F”,第二列为“US”,第三列为“CA6”或“DL”,第四列为“Z13”或“U13”等等。我的整个数据框都遵循X.XX.XXXX.XXX或X.XX.XXX.XXX或X.XX.XX.XXX的相同模式,我知道问题在于第三列由于长度不同而有所不同。过去我只使用了substr函数,在这里我可以使用一些if语句来解决,但我想学习如何使用stringr包和POSIX来完成这个任务(除非有更好的选择)。谢谢。

这是我的df:

c("F.US.CLE.V13", "F.US.CA6.U13", "F.US.CA6.U13", "F.US.CA6.U13", 
"F.US.CA6.U13", "F.US.CA6.U13", "F.US.CA6.U13", "F.US.CA6.U13", 
"F.US.DL.U13", "F.US.DL.U13", "F.US.DL.U13", "F.US.DL.Z13", "F.US.DL.Z13"
)
4个回答

57

一种非常直接的方法就是在你的字符向量上使用read.table

> read.table(text = text, sep = ".", colClasses = "character")
   V1 V2  V3  V4
1   F US CLE V13
2   F US CA6 U13
3   F US CA6 U13
4   F US CA6 U13
5   F US CA6 U13
6   F US CA6 U13
7   F US CA6 U13
8   F US CA6 U13
9   F US  DL U13
10  F US  DL U13
11  F US  DL U13
12  F US  DL Z13
13  F US  DL Z13

colClasses需要被指定,否则F会被转换成FALSE(这是我在“splitstackshape”中需要修复的问题,否则我会推荐这个 :))


更新(>一年后)...

或者,您可以使用我的cSplit函数,像这样:

cSplit(as.data.table(text), "text", ".")
#     text_1 text_2 text_3 text_4
#  1:      F     US    CLE    V13
#  2:      F     US    CA6    U13
#  3:      F     US    CA6    U13
#  4:      F     US    CA6    U13
#  5:      F     US    CA6    U13
#  6:      F     US    CA6    U13
#  7:      F     US    CA6    U13
#  8:      F     US    CA6    U13
#  9:      F     US     DL    U13
# 10:      F     US     DL    U13
# 11:      F     US     DL    U13
# 12:      F     US     DL    Z13
# 13:      F     US     DL    Z13

或者像这样从 "tidyr" 中 分离

library(dplyr)
library(tidyr)

as.data.frame(text) %>% separate(text, into = paste("V", 1:4, sep = "_"))
#    V_1 V_2 V_3 V_4
# 1    F  US CLE V13
# 2    F  US CA6 U13
# 3    F  US CA6 U13
# 4    F  US CA6 U13
# 5    F  US CA6 U13
# 6    F  US CA6 U13
# 7    F  US CA6 U13
# 8    F  US CA6 U13
# 9    F  US  DL U13
# 10   F  US  DL U13
# 11   F  US  DL U13
# 12   F  US  DL Z13
# 13   F  US  DL Z13

1
+1 <facepalm> 该死的安南达,你让我感到很蠢。 :-) - Simon O'Hanlon
你是不是指的是 shapeshifter 而不是 splitstackshape - Simon O'Hanlon
哇。这太简单了。 - Tim
shapeshifter或者shapeshiftR更酷。 - Tyler Rinker
现在,勾选标记是你的了! - Simon O'Hanlon
显示剩余3条评论

18

这是你想要做的吗?

# Our data
text <- c("F.US.CLE.V13", "F.US.CA6.U13", "F.US.CA6.U13", "F.US.CA6.U13", 
"F.US.CA6.U13", "F.US.CA6.U13", "F.US.CA6.U13", "F.US.CA6.U13", 
"F.US.DL.U13", "F.US.DL.U13", "F.US.DL.U13", "F.US.DL.Z13", "F.US.DL.Z13"
)

#  Split into individual elements by the '.' character
#  Remember to escape it, because '.' by itself matches any single character
elems <- unlist( strsplit( text , "\\." ) )

#  We know the dataframe should have 4 columns, so make a matrix
m <- matrix( elems , ncol = 4 , byrow = TRUE )

#  Coerce to data.frame - head() is just to illustrate the top portion
head( as.data.frame( m ) )
#  V1 V2  V3  V4
#1  F US CLE V13
#2  F US CA6 U13
#3  F US CA6 U13
#4  F US CA6 U13
#5  F US CA6 U13
#6  F US CA6 U13

这正是我一直在寻找的!而且像R这样的编程语言有很多方法可以实现。感谢您的帮助。只需使用合并命令将新的df合并回我的主要df即可? - Tim
@Tim,没有看到你的数据框架结构,有点难以讲述,但是你可以试一试看看是否有效?否则,你可以编辑你的问题,把dput(head(df))的输出粘贴进来,这样可靠性会更高! :-) - Simon O'Hanlon
我所需要的只是unlist()函数。谢谢 - Amit Kohli
1
感谢您对代码进行注释。注释“记得转义它,因为'.'本身匹配任何单个字符”帮助我解决了我的问题。非常感谢 :) - Sriram

9
通过`unlist`和`matrix`的方法似乎有些复杂,并且需要硬编码元素的数量(这实际上是一个非常不好的做法。当然,你可以绕开硬编码这个数字并在运行时确定它)。我会选择另一种方法,直接从`strsplit`返回的列表中构建数据框架。对我来说,这在概念上更简单。实际上有两种基本方法:
  1. as.data.frame – but since the list is exactly the wrong way round (we have a list of rows rather than a list of columns) we have to transpose the result. We also clear the rownames since they are ugly by default (but that’s strictly unnecessary!):

    `rownames<-`(t(as.data.frame(strsplit(text, '\\.'))), NULL)
    
  2. Alternatively, use rbind to construct a data frame from the list of rows. We use do.call to call rbind with all the rows as separate arguments:

    do.call(rbind, strsplit(text, '\\.'))
    

两种方法都可以得到相同的结果:

     [,1] [,2] [,3]  [,4]
[1,] "F"  "US" "CLE" "V13"
[2,] "F"  "US" "CA6" "U13"
[3,] "F"  "US" "CA6" "U13"
[4,] "F"  "US" "CA6" "U13"
[5,] "F"  "US" "CA6" "U13"
[6,] "F"  "US" "CA6" "U13"

显然,第二种方式比第一种更简单。

并且需要你硬编码元素的数量(这实际上是一个相当大的不可取之处),但列数已在 OP 中给出和指定,所以我对此没有什么问题。不过还有一些不错的替代方案。+1 - Simon O'Hanlon
1
事实上,我非常喜欢do.call(rbind, strsplit(text, '\\.')) - Simon O'Hanlon

1
我们可以使用 tidyr::extract()
x <- c("F.US.CLE.V13", "F.US.CA6.U13", "F.US.CA6.U13", "F.US.CA6.U13", 
  "F.US.CA6.U13", "F.US.CA6.U13", "F.US.CA6.U13", "F.US.CA6.U13", 
  "F.US.DL.U13", "F.US.DL.U13", "F.US.DL.U13", "F.US.DL.Z13", "F.US.DL.Z13"
)


library(tidyr)
extract(tibble(data=x),"data", regex = "^(.*?)\\.(.*?)\\.(.*?)\\.(.*?)$",into = LETTERS[1:4])
#> # A tibble: 13 x 4
#>    A     B     C     D    
#>    <chr> <chr> <chr> <chr>
#>  1 F     US    CLE   V13  
#>  2 F     US    CA6   U13  
#>  3 F     US    CA6   U13  
#>  4 F     US    CA6   U13  
#>  5 F     US    CA6   U13  
#>  6 F     US    CA6   U13  
#>  7 F     US    CA6   U13  
#>  8 F     US    CA6   U13  
#>  9 F     US    DL    U13  
#> 10 F     US    DL    U13  
#> 11 F     US    DL    U13  
#> 12 F     US    DL    Z13  
#> 13 F     US    DL    Z13

另一个选择是使用unglue::unglue_data()
# remotes::install_github("moodymudskipper/unglue")
library(unglue)
unglue_data(x,"{A}.{B}.{C}.{D}")
#>    A  B   C   D
#> 1  F US CLE V13
#> 2  F US CA6 U13
#> 3  F US CA6 U13
#> 4  F US CA6 U13
#> 5  F US CA6 U13
#> 6  F US CA6 U13
#> 7  F US CA6 U13
#> 8  F US CA6 U13
#> 9  F US  DL U13
#> 10 F US  DL U13
#> 11 F US  DL U13
#> 12 F US  DL Z13
#> 13 F US  DL Z13

这段内容是由reprex包(v0.3.0)于2019年9月14日创建的。


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