【问题标题】:Reading fixed-width data without linefeed无需换行读取固定宽度数据
【发布时间】:2012-03-16 14:23:57
【问题描述】:

我有一个固定宽度的平面文件,既没有换行符也没有换行符(从 AS400 转储)。

如何将此文件加载到 R data.frame 中?

我尝试了 textConnection 和 read.fwf 的不同组合,但无济于事。

下面的代码使 Rstudio 崩溃,所以我假设我正在超载系统。

len 下面是 24376400,就我通常使用 read.table 加载的文件而言,这是温和的。 记录长度为 400。

是否有任何我应该设置的 RECLEN 参数,类似于 SAS? 是否有设置 EOL = "\n" 或 "\r\n" 的选项?谢谢。

fname <- "AS400FILE.TXT"
len <- file.info(fname)$size
conn <- file(fname, 'r')
contents <- readChar(conn, len)
close(conn)

df <- read.fwf( textConnection(contents) , widths=layout$length , sep="")

> dput(layout)
structure(list(start = c(1L, 41L, 81L, 121L, 161L, 201L, 224L, 
226L, 231L, 235L, 237L, 238L, 240L, 280L, 290L, 300L, 305L, 308L, 
309L, 330L, 335L, 337L, 349L, 350L, 351L, 355L, 365L), end = c(40L, 
80L, 120L, 160L, 200L, 223L, 225L, 230L, 234L, 236L, 237L, 239L, 
279L, 289L, 299L, 304L, 307L, 308L, 329L, 334L, 336L, 348L, 349L, 
350L, 354L, 364L, 400L), length = c(40L, 40L, 40L, 40L, 40L, 
23L, 2L, 5L, 4L, 2L, 1L, 2L, 40L, 10L, 10L, 5L, 3L, 1L, 21L, 
5L, 2L, 12L, 1L, 1L, 4L, 10L, 36L), label = c("TITLE", "SUFFIX", 
"ADDRESS1", "ADDRESS2", "ADDRESS3", "CITY", "STATE", 
"ZIP", "ZIP+4", "DELIVERY", "CHECKD", "FILLER", "NAME", 
"SOURCECODE", "ID", "FILLER", "BATCH", "FILLER", "FILLER", 
"GRID", "LOT", "FILLER", "CONTROL", 
"ZIPIND", "TROUTE", "SOURCEA", "FILLER")), .Names = c("start", 
"end", "length", "label"), class = "data.frame", row.names = c(NA, 
-27L))
> dim(layout)
[1] 27  4
> 

【问题讨论】:

  • 你能给你的文本文件一个小的sn-p吗?我注意到你有sep="" - 所以它是(例如)原始数据08091011,宽度2 --> 08,09,10,11? layout$length 是什么?向量?整数?
  • layout 是一个数据框,有字段名和字段宽度:
  • 文本文件是带有路由信息的名称和地址数据:共 27 个字段。布局变量是一个包含 $length 字段的数据框。
  • 那么,为了重申我的问题,您能否提供一个小文本文件的 sn-p(用它更新您的问题)? (与这些字段的分隔符和格式相比,您拥有多少字段并不重要。)。另外,您说整个文件中没有换行符或换行符,然后要求将行尾设置为等于“\ n”的选项-就是这样(这就是为什么我要求sn-p文件)。 layout$length 是单个整数还是整数向量,都相同还是不同(它有什么值)?
  • 姓名和地址数据:我不能分享。相信我,它是纯 ASCII 并且没有任何类型的分隔符:我 sed'ed 和 awked 文件以在第 400 个位置之后插入一个 \n 以向前移动,但我确信 R 可以从内部处理这个问题。 RECLEN 和 EOL 问题是对 R 解决方案的建议,我仍在研究中。

标签: r fixed-width


【解决方案1】:

您可以为此使用readChar

首先制作一些示例数据(我认为格式与您所描述的一样,就我从问题中可以看出的那样?即每列具有指定宽度的文本墙,整个文件中没有新行):

lengths <- c(2,3,4,2,3,4)
nFields <- length(lengths)
nRows   <- 10              # let's make 10 rows.
contents <- paste(letters[sample.int(26,size=sum(lengths)*nRows,replace=TRUE)],
                  collapse="")
#> contents
#[1] "lepajmcgcqooekmedjprkmmicm.......
cat(contents,file='test.txt')

我可以想到 3 种方法,每种方法之间的不同之处:

如果您事先知道行数,您可以这样做:

# If you know #rows in advance..
conn <- file('test.txt','r')
data <- readChar( conn, rep(lengths,nRows) )
close(conn)
# reshape data to dataframe
df <- data.frame(matrix(data,ncol=nFields,byrow=T))

否则你可以使用循环(为什么读取文件一次以计算行数,然后再次解析?)

# Otherwise use a loop
conn <- file('test.txt','r')
df <- data.frame(matrix(nrow=0,ncol=6)) # initialise 0-row data frame
while ( length(data <- readChar(conn, lengths)) > 0 ) {
    df[nrow(df)+1,] <- data
}
close(conn)

或者,由于您已经将所有contents 包含在一个字符串中,您可以使用substring 拆分字符串:

# have already read in contents so can calculate nRows
nRows <- floor(nchar(contents)/sum(lengths)) # 10 for my example
starts <- c(0,cumsum(lengths[-nFields]))
df3 <- data.frame(t(
                    vapply( seq(1,nRows*sum(lengths),sum(lengths)),
                    function(r) 
                        substring(contents,starts+r,starts+r+lengths-1),
                    rep("",nFields) )))

如果你想尽可能少地读取文件,我建议使用第二种或第三种方法。

第三种方法对我来说“感觉”最优雅,但需要您一次阅读整个contents,这取决于文件大小,可能不可行。

如果是这种情况,我会选择第二个,它一次只能读取一组 nFields 字段。

我不推荐第一个,除非您提前知道行数 - 这只是我的第一次尝试。我不推荐它,因为您必须先读取文件以确定行数,然后将其关闭并再次读取。如果你想走那条路,那就用方法3吧!但是,如果您通过其他方式预先知道行数,则可以使用此方法。

【讨论】:

  • 指出 readChar 的正确用法(使用 rep(lengths,nRows) 而不是文件长度)确实很有帮助。我没有以正确的方式看待这个。感谢您的帮助。
  • 更新:上面建议的方法确实有效。然而,随着文件大小的增长,处理时间很快变得不切实际。我将使用 >fold -400 AS400DATA.TXT |等等...在 R 中加载之前。谢谢。
猜你喜欢
  • 2013-09-14
  • 1970-01-01
  • 1970-01-01
  • 2017-10-04
  • 1970-01-01
  • 1970-01-01
  • 2013-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多