读取固定宽度文本文件

96

我正在尝试将这个格式不整齐的数据集加载到我的R会话中:http://www.cpc.ncep.noaa.gov/data/indices/wksst8110.for

Weekly SST data starts week centered on 3Jan1990

Nino1+2      Nino3        Nino34        Nino4
Week          SST SSTA     SST SSTA     SST SSTA     SST SSTA 
03JAN1990     23.4-0.4     25.1-0.3     26.6 0.0     28.6 0.3 
10JAN1990     23.4-0.8     25.2-0.3     26.6 0.1     28.6 0.3 
17JAN1990     24.2-0.3     25.3-0.3     26.5-0.1     28.6 0.3

到目前为止,我可以读出这些行

  x = readLines(path)

但该文件在分隔符上混合了“空白”和“-”,而我不是正则表达式专家。 感谢任何帮助使其变成漂亮干净的R数据框架。 谢谢!


5
请查看 read.fwf 以读取固定宽度格式的数据。 - Paul Hiemstra
1
我认为逐行处理是更好的想法。它混合了“-”和“ ”字符。 - Fernando
或者,你可以说空格或 - 只是一个字符,所以首先用制表符替换所有多个空格的出现,然后在 - 或空格上拆分所有以制表符分隔的条目。 - GitaarLAB
固定宽度 = 没有分隔符。这意味着"-"是减号,空格也不是分隔符,它们只在数字没有填满整个可用宽度时出现。 - Eusebio Rufian-Zilbermann
6个回答

191

这是一个固定宽度的文件。使用read.fwf()函数来读取:

x <- read.fwf(
  file=url("http://www.cpc.ncep.noaa.gov/data/indices/wksst8110.for"),
  skip=4,
  widths=c(12, 7, 4, 9, 4, 9, 4, 9, 4))

head(x)

            V1   V2   V3   V4   V5   V6   V7   V8  V9
1  03JAN1990   23.4 -0.4 25.1 -0.3 26.6  0.0 28.6 0.3
2  10JAN1990   23.4 -0.8 25.2 -0.3 26.6  0.1 28.6 0.3
3  17JAN1990   24.2 -0.3 25.3 -0.3 26.5 -0.1 28.6 0.3
4  24JAN1990   24.4 -0.5 25.5 -0.4 26.5 -0.1 28.4 0.2
5  31JAN1990   25.1 -0.2 25.8 -0.2 26.7  0.1 28.4 0.2
6  07FEB1990   25.8  0.2 26.1 -0.1 26.8  0.1 28.4 0.3

更新

软件包readr于2015年4月发布,提供了一种简单而快速的替代方案。

library(readr)

x <- read_fwf(
  file="http://www.cpc.ncep.noaa.gov/data/indices/wksst8110.for",   
  skip=4,
  fwf_widths(c(12, 7, 4, 9, 4, 9, 4, 9, 4)))

速度比较:使用readr::read_fwf()的速度是utils::read.fwf()的约两倍。


11
@Andrie,你是怎么知道宽度和跳过的? - Koba
12
@Koba说:“我将其中一行复制并粘贴到一个带有列计数的文本编辑器中,并手动计算了每列的宽度(包括需要使用空格的地方)。此外,你可以发现在获取原始数据之前需要跳过4行。” - rayryeng
5
@Pavithra 的回答中使用负列宽来跳过不想要的空白可能更适合被接受为答案。 - Marius Butuc
1
@Andrie,你是怎么得到 fwf_widths 值的? - BICube
3
@Ala 我相信 readr::fwf_empty 会尝试为您猜测宽度。readr::read_fwf 的示例展示了如何使用 readr::fwf_empty - Jake Fisher

56

确定宽度的另一种方法是...

df <- read.fwf(
  file=url("http://www.cpc.ncep.noaa.gov/data/indices/wksst8110.for"),
  widths=c(-1, 9, -5, 4, 4, -5, 4, 4, -5, 4, 4, -5, 4, 4),
  skip=4
)

widths参数中的-1表示应该忽略一个字符宽度的列,widths参数中的-5表示应该忽略五个字符宽度的列,以此类推...

参考文献:https://www.inkling.com/read/r-cookbook-paul-teetor-1st/chapter-4/recipe-4-6


20

首先,这个问题直接来自于Leeks的Coursera“获取数据和清理数据”课程。虽然问题还有另一个部分,但难点在于读取文件。

话虽如此,该课程主要是为了学习而设的。

我讨厌R的固定宽度过程。它很慢,并且对于大量变量,否定某些列等操作会非常麻烦。

我认为更容易使用readLines(),然后从中使用substr()生成变量。

x <- readLines(con=url("http://www.cpc.ncep.noaa.gov/data/indices/wksst8110.for"))

# Skip 4 lines
x <- x[-(1:4)]

mydata <- data.frame(var1 = substr(x, 1, 10),
                     var2 = substr(x, 16, 19),
                     var3 = substr(x, 20, 23),
                     var4 = substr(x, 29, 32)  # and so on and so on
                     )

2
这种方法对我很有效。另外两个提示:1)您可以定义mydata只包含您需要的数据。因此,如果您只需要数据的第四列,则可能只需mydata <- data.frame(var4 = substr(x,29,32))这么简单。 此外,对于Windows用户,使用带有TextFX插件的Notepad ++将获得一个简单明了、计数字符标尺,以便您可以确定在“substr”中放置起始和停止值。但请注意,停止值比您想要保留的最后一个字符的位置多一个。 - globalSchmidt

13

5

我在这里记录了读取定宽文件在R中的替代方法列表,并提供了一些速度最快的基准测试结果。

我的首选方法是将freadstringi结合使用;这是最快的方法之一,并具有作为data.table存储数据的附加优点(依我之见):

library(data.table)
library(stringi)

col_ends <- 
  list(beg = c(1, 10, 15, 19, 23, 28, 32, 36,
               41, 45, 49, 54, 58),
       end = c(9, 14, 18, 22, 27, 31, 35,
               40, 44, 48, 53, 57, 61))

data = fread(
  "http://www.cpc.ncep.noaa.gov/data/indices/wksst8110.for", 
  header = FALSE, skip = 4L, sep = NULL
  )[, lapply(1:(length(col_ends$beg)),
             function(ii) 
               stri_sub(V1, col_ends$beg[ii], col_ends$end[ii]))
    ][ , paste0("V", c(2, 5, 8, 11)) := NULL]
#              V1   V3   V4   V6   V7   V9  V10  V12  V13
#    1: 03JAN1990 23.4 -0.4 25.1 -0.3 26.6  0.0 28.6  0.3
#    2: 10JAN1990 23.4 -0.8 25.2 -0.3 26.6  0.1 28.6  0.3
#    3: 17JAN1990 24.2 -0.3 25.3 -0.3 26.5 -0.1 28.6  0.3
#    4: 24JAN1990 24.4 -0.5 25.5 -0.4 26.5 -0.1 28.4  0.2
#    5: 31JAN1990 25.1 -0.2 25.8 -0.2 26.7  0.1 28.4  0.2
#   ---                                                  
# 1365: 24FEB2016 27.1  0.9 28.4  1.8 29.0  2.1 29.5  1.4
# 1366: 02MAR2016 27.3  1.0 28.6  1.8 28.9  1.9 29.5  1.4
# 1367: 09MAR2016 27.7  1.2 28.6  1.6 28.9  1.8 29.6  1.5
# 1368: 16MAR2016 27.5  1.0 28.8  1.7 28.9  1.7 29.6  1.4
# 1369: 23MAR2016 27.2  0.9 28.6  1.4 28.8  1.5 29.5  1.2

请注意,fread会自动剥离前后的空格--有时这是不希望的,此时请设置strip.white = FALSE
我们也可以通过以下方式使用列宽向量ww来开始操作:
ww <- c(9, 5, 4, 4, 5, 4, 4, 5, 4, 4, 5, 4, 4)
nd <- cumsum(ww)

col_ends <-
  list(beg = c(1, nd[-length(nd)]+1L),
       end = nd)

我们可以使用负索引更加健壮地选择要排除的列,例如:

col_ends <- 
  list(beg = c(1, -10, 15, 19, -23, 28, 32, -36,
               41, 45, -49, 54, 58),
       end = c(9, 14, 18, 22, 27, 31, 35,
               40, 44, 48, 53, 57, 61))

然后将col_ends$beg[ii]替换为abs(col_ends$beg[ii]),在下一行中:

paste0("V", which(col_ends$beg < 0))

最后,如果您希望列名也能够被程序读取,您可以使用 readLines 进行清理:
cols <-
  gsub("\\s", "", 
       sapply(1:(length(col_ends$beg)),
              function(ii) 
                stri_sub(readLines(URL, n = 4L)[4L], 
                         col_ends$beg[ii]+1L,
                         col_ends$end[ii]+1L)))

cols <- cols[cols != ""]

(请注意,将此步骤与fread结合使用需要创建表的副本以删除标题行,因此对于大型数据集而言效率低下)

4

我对R一无所知,但我可以提供一个正则表达式来匹配这样的行:

\s[0-9]{2}[A-Z]{3}[0-9]{4}(\s{5}[0-9]+\.[0-9]+[ -][0-9]+\.[0-9]+){4}

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