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

时间:2016-02-05 10:45:54

标签: r import dbf

我有固定宽度的数据文件(.dbf),它们没有行分隔符。以下是该数据文件的两行:

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

对于日期(8),一行的宽度是c(8,4,7,41),有些时间是度量(4),数据点(7),以及我可以在一个"休息&#中总结的一些其他列34;第(41)栏。在一行之后没有分隔符,下一行只是附加到第一行。所有时间步骤基本上都是连续写入一条大线。此文件中只包含数字,字符和空格。

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

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

提前致谢。

3 个答案:

答案 0 :(得分:4)

也许不是最好的主意,但这应该有效:

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返回一个列表,此处包含1个元素,因为只有一行作为输入,因此我们将其与[[1]]一起删除。

现在返回的是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

答案 1 :(得分:3)

readLinessubstrtrimwsseparate tidyr )和{{1>的另一种可能不那么优雅的解决方案( dplyr ):

mutate_all

给出:

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内使用funs(ul = type.convert(trimws(.)))

答案 2 :(得分:0)

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

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

但也许更重要的是:

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

所以,在你的记录c(8,4,7,41)中, rest列(41)的最后一个字节实际上是它后面的记录的删除标志 - 以及文件中的最后一个记录该字段只有40个字节(但如果你很幸运,该文件有一个EOF标记(0x1a),所以也许你的大小没有问题。)

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