【问题标题】:Import fixed width data file with no line separator导入不带行分隔符的固定宽度数据文件
【发布时间】:2016-05-15 07:29:28
【问题描述】:

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

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

一行的宽度是c(8,4,7,41),表示日期 (8)、一些时间度量 (4)、数据点 (7) 以及我可以在一个“休息”列 (41) 中总结的其他一些列。一行之后没有分隔符,下一行只是附加到第一行。所有时间步基本上都是连续写在一大行中。此文件中只有数字、字符和空格。

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

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

提前致谢。

【问题讨论】:

标签: r import dbf


【解决方案1】:

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

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) &lt;- 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

【讨论】:

  • 非常感谢您的建议,但是这个解决方案存在同样的问题,即 read.fwf() 在第一行之后停止。
  • @Ben 确实,我应该测试它。我会用适当的解决方案更新答案。
  • @Tensibai 没有花太多时间在上面,因为 Jaap 的回答效果很好。不过谢谢你的建议!
【解决方案2】:

readLinessubstrtrimwsseparate (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(.)))

【讨论】:

  • 干净完美的解决方案。谢谢!
  • @Ben Thanx :-) 现在还添加了关于正确获取列类的说明。
【解决方案3】:

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

除非这是一次性读取静态文件,否​​则最好先检查文件/字段结构,以防随着时间的推移发生变化。 dbf 文件的内部结构见here

但也许更重要:

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-07-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-27
    • 1970-01-01
    相关资源
    最近更新 更多