导入没有行分隔符的固定宽度数据文件

Import fixed width data file with no line separator

我有没有行分隔符的固定宽度数据文件 (.dbf)。这是该数据文件的两行内容:

20141101 77h  3.210                                  0    3 20141102 76h  3.090                                  0    3 

对于日期 (8)、一些时间度量 (4)、数据点 (7) 以及我可以总结在一个中的其他一些列,一行的宽度是 c(8,4,7,41) "rest" 栏目 (41)。在一行之后没有分隔符,下一行只是附加到第一行。所有时间步长基本上都连续地写在一行中。此文件中只有数字、字符和白色space。

With read.fwf('filepath', widths = c(8,4,7,41)) 由于缺少行分隔符,R 在第一行之后停止读取。

是否有参数告诉 read.fwf() 在没有行分隔符的情况下何时开始读取新行?或者我应该使用不同的读取命令?

提前致谢。

也许不是最好的主意,但这应该可行:

content <- scan('filepath','character',sep='~') # Warning choose a sep not appearing in datas to get the whole file.
# Split content in lines:
lines <- regmatches(content,gregexpr('.{60}',content))[[1]]
x <- tempfile()
write(lines,x)
data <- read.fwf(x, widths = c(8,4,7,41))
unlink(x)

想法是读取整个文件,将每次出现的 60 个字符放入一个条目中,将其写入临时文件,并在删除临时文件之前从该临时文件中读取数据。

另一种方法适用于正则表达式和包 stringr(仍然包含上述扫描产生的内容):

library(stringr)
d <- data.frame( str_match_all( content, "(.{8})(.{4})(.{7})(.{41})")[[1]][,2:5], stringsAsFactors=FALSE)

给出:

        V1   V2      V3                                        V4
1 20141101  77h   3.210                                   0    3 
2 20141102  76h   3.090                                   0    3 

str_match_all return 一个列表,这里有 1 个元素,因为只有一行作为输入,所以我们用 [[1]].

删除它

现在 return 是 5 列,第一列是完全匹配,其他是捕获组,所以我们对第 2 列到第 5 列的矩阵进行子集化,只得到我们需要的 4 列并包装它在 as.data.frame 中得到一个 data.frame 最后。

然后您可以使用 colnames(d) <- c('date','time','data_point','rest')

命名列

如果你想清理空白,你可以将 str_extract_all 结果包装在 trimws 中(感谢 @jaap 提醒这个功能),如下所示:

td <- data.frame( trimws( str_match_all( content, "(.{8})(.{4})(.{7})(.{41})")[[1]][,2:5] ), stringsAsFactors=FALSE)

输出:

        X1  X2    X3     X4
1 20141101 77h 3.210 0    3
2 20141102 76h 3.090 0    3

一个不同的,可能不太优雅的解决方案 readLines, substr, trimws, separate (tidyr ) 和 mutate_all (dplyr):

txt <- readLines('filepath')
dfx <- data.frame(V1 = sapply(seq(from=1, to=nchar(txt), by=60),
                              function(x) substr(txt, x, x+59)))

library(dplyr)
library(tidyr)
dfx %>% 
  separate(V1, c(paste0("V",LETTERS[1:5])), c(8,12,19,55)) %>% 
  mutate_all(trimws)

给出:

        VA  VB    VC VD VE
1 20141101 77h 3.210  0  3
2 20141102 76h 3.090  0  3

要获得不同的列名,只需将 c(paste0("V",LETTERS[1:5]) 替换为您想要的列名向量即可。

如果要将列转换为正确的 类 而不是 character,可以在 mutate_all.

中使用 funs(ul = type.convert(trimws(.)))

除了其他答案外,还有一些关于 dbf files 的一般信息:

除非这是静态文件的一次性读取,否则最好先检查 file/fields 结构,以防随时间变化。有关 dbf 文件的内部结构,请参阅 here

但也许更重要:

dbf 文件中的每条记录前面都有一个字节用于删除标志。如果这是 space,则记录不会被删除,如果它是星号 *,则记录被标记为删除(记录不会从 dbf 文件中删除,直到文件被 打包),您可能想跳过这些记录。例如,数据的第一部分也可以用“DELETED”覆盖。

因此,在您的记录 c(8,4,7,41) 中,rest 列的最后一个字节 (41) 实际上是其后记录的删除标志 - 以及文件中的最后一条记录对于该字段只有 40 个字节(但如果你幸运的话,该文件有一个 EOF 标记 (0x1a),所以你可能对那里的大小没有问题)。

因此,您的记录实际上应该是:c(1,8,4,7,40),其中 1 是删除标志,并且提前一个字节开始。