将按照起始和结束位置指定的列将行分隔为多列

3

我希望将由字符组成的数据集按照指定的起始和结束位置拆分成多列。

我的数据集大致如下所示:

>head(templines,3)
[1] "201801 1  78"
[2] "201801 2  67"
[3] "201801 1  13"

我希望通过使用数据字典来指定列拆分它:

>dictionary
col_name col_start col_end  
year      1         4  
week      5         6  
gender    8         8  
age       11        12  

因此,它变成:

year    week    gender    age
2018    01      1         78
2018    01      2         67
2018    01      1         13

实际上这些数据来自于长期运行的调查,某些列之间的空格代表不再收集的变量。由于它有很多变量,所以我需要一个可以扩展的解决方案。
tidyr::separate 中,似乎只能通过指定分割位置来进行分割,而不能使用起始和结束位置。是否有一种方法可以使用起始/结束位置?
我考虑使用read_fwf来实现此操作,但似乎无法对已加载的数据集使用它。我只能通过首先将其导出为txt文件,然后从该.txt文件中读取来使其工作:
write_lines(templines,"t1.txt")

read_fwf("t1.txt", 
     fwf_positions(start = dictionary$col_start,
                   end = dictionary$col_end,
                   col_names = dictionary$col_name)

在已加载的数据集上使用read_fwf是否可行?

6个回答

3
直接回答您的问题:是的,可以使用已加载的数据与read_fwf一起使用。文档中相关的部分是关于file参数的内容:
Either a path to a file, a connection, or literal data (either a single string or a raw vector).
...
Literal data is most useful for examples and tests. 
It must contain at least one new line to be recognised as data (instead of a path).

因此,您可以简单地折叠数据,然后使用 read_fwf:
templines %>% 
  paste(collapse = "\n") %>% 
  read_fwf(., fwf_positions(start = dictionary$col_start,
                            end = dictionary$col_end,
                            col_names = dictionary$col_name))

这应该可以适用于多列,并且对于许多行非常快(在我的机器上,对于100万行和四列大约半秒钟)。

有一些有关解析失败的警告,但它们源于您的字典。如果您将最后一行更改为age, 11, 12,则可以按预期工作。


2
一种使用substring的解决方案:
library(data.table)
x <- transpose(lapply(templines, substring, dictionary$col_start, dictionary$col_end))
setDT(x)
setnames(x, dictionary$col_name)
# > x
#    year week gender age
# 1: 2018   01      1  78
# 2: 2018   01      2  67
# 3: 2018   01      1  13

1
这个怎么样?
data.frame(year=substr(templines,1,4), 
           week=substr(templines,5,6), 
           gender=substr(templines,7,8), 
           age=substr(templines,11,13))

非常简单,不幸的是,抱歉我应该明确指出我需要一个能够扩展到数百个变量的解决方案... - chrisjacques

1
使用基础R:

m = list(`attr<-`(dat$col_start,"match.length",dat$col_end-dat$col_start+1))

d = do.call(rbind,regmatches(x,rep(m,length(x))))

setNames(data.frame(d),dat$col_name)

  year week gender age
1 2018   01      1  78
2 2018   01      2  67
3 2018   01      1  13

使用的数据:

x = c("201801 1  78", "201801 2  67", "201801 1  13")

dat=read.table(text="col_name col_start col_end  
           year      1         4  
           week      5         6  
           gender    8         8  
           age       11        13 ",h=T)

1
我们可以使用来自tidyverse的separate
library(tidyverse)
data.frame(Col = templines) %>% 
      separate(Col, into = dictionary$col_name, sep= head(dictionary$col_end, -1))
#  year week gender  age
#1 2018   01      1   78
#2 2018   01      2   67
#3 2018   01      1   13

convert = TRUE参数也可以与separate一起使用,使数值列作为输出。
tibble(Col = templines) %>% 
   separate(Col, into = dictionary$col_name, 
       sep= head(dictionary$col_end, -1), convert = TRUE)
# A tibble: 3 x 4
#   year  week gender   age
#  <int> <int>  <int> <int>
#1  2018     1      1    78
#2  2018     1      2    67
#3  2018     1      1    13

数据

dictionary <- structure(list(col_name = c("year", "week", "gender", "age"), 
col_start = c(1L, 5L, 8L, 11L), col_end = c(4L, 6L, 8L, 13L
)), .Names = c("col_name", "col_start", "col_end"),
 class = "data.frame", row.names = c(NA, -4L))

templines <- c("201801 1  78", "201801 2  67", "201801 1  13")

0

这是一个显式函数,看起来它正在按照你想要的方式工作。

split_func<-function(char,ref,name,start,end){
  res<-data.table("ID" = 1:length(char))
  for(i in 1:nrow(ref)){
    res[,ref[[name]][i] := substr(x = char,start = ref[[start]][i],stop = ref[[end]][i])]
  }
  return(res)
}

我已经创建了与你相同的输入文件:

templines<-c("201801 1  78","201801 2  67","201801 1  13")
dictionary<-data.table("col_name" = c("year","week","gender","age"),"col_start" = c(1,5,8,11),
                       "col_end" = c(4,6,8,13))
#   col_name col_start col_end
#1:     year         1       4
#2:     week         5       6
#3:   gender         8       8
#4:      age        11      13

关于参数,
char - 包含您想要拆分的值的字符向量
ref - 参考表或字典
name - 参考表中包含您想要的列名的列号
start - 参考表中包含起始点的列号
end - 参考表中包含停止点的列号

如果我使用这个函数和这些输入,我会得到以下结果:

out<-split_func(char = templines,ref = dictionary,name = 1,start = 2,end = 3)

#>out
#   ID year week gender age
#1:  1 2018   01      1  78
#2:  2 2018   01      2  67
#3:  3 2018   01      1  13

我必须包含一个“ID”列来初始化数据表并使其更容易。如果您以后想要删除它,只需使用:

out[,ID := NULL]

希望这个更接近你所寻找的解决方案。

谢谢,但是这种方法无法扩展到数百个变量...有没有一种方法可以提供起始和结束向量? - chrisjacques
你可以提供一个额外的数据集,就像你在这里展示的那样,它将用于获取起始和结束值。 - Rage
好的,让我看一下,然后我会相应地编辑我的答案。 - Rage
请检查更新后的解决方案是否更有效。这是一个有趣的练习 :) - Rage

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