Ler arquivo de texto de largura fixa

89

Estou tentando carregar este conjunto de dados formatado feio em minha sessão 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

Até agora, posso ler as linhas com

  x = readLines(path)

Mas o arquivo mistura 'espaço em branco' com '-' como separadores, e eu não sou um especialista em regex. Agradeço qualquer ajuda em transformar isso em um quadro de dados R bonito e limpo. obrigado!

Fernando
fonte
5
E dê uma olhada em read.fwfpara ler dados formatados de largura fixa.
Paul Hiemstra
1
Acho que é uma ideia melhor processar cada linha. Ele mistura caracteres '-' com ''.
Fernando
Como alternativa, você pode dizer espaço em branco ou - é apenas um caractere, portanto, primeiro substitua todas as ocorrências múltiplas de um espaço por um caractere de tabulação e, em seguida, divida todas as entradas separadas por tabulação em - ou espaço em branco.
GitaarLAB de
Largura fixa = sem separadores. Isso significa que o "-" é um sinal de menos e os espaços também não são separadores, eles apenas ocorrem quando o número não preenche toda a largura disponível
Eusebio Rufian-Zilbermann

Respostas:

181

Este é um arquivo de largura fixa. Use read.fwf()para ler:

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

Atualizar

O pacote readr(lançado em abril de 2015) oferece uma alternativa simples e rápida.

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)))

Comparação de velocidade: readr::read_fwf()foi ~ 2x mais rápido que utils::read.fwf ().

Andrie
fonte
8
@Andrie, como você sabia quais eram as larguras e saltos?
Koba de
12
@Koba: Copiei e colei uma das linhas em um editor de texto que tinha uma contagem de colunas e contei manualmente as larguras de cada coluna (incluindo espaços em branco quando necessário). Além disso, você pode dizer que precisa pular 4 linhas inteiras antes de chegar aos dados brutos.
rayryeng de
5
A resposta de @ Pavithra abaixo com larguras de coluna negativas para pular espaços em branco indesejados pode ser mais adequada para a resposta aceita.
Marius Butuc
1
@Andrie Como você conseguiu os valores de fwf_widths?
BICube
3
@Ala, acredito readr::fwf_emptyque tentará adivinhar as larguras para você. Os exemplos de readr::read_fwfmostra o uso de readr::fwf_empty.
Jake Fisher
55

Outra forma de determinar larguras ...

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
)

O -1 no argumento de larguras diz que há uma coluna de um caractere que deve ser ignorada, o -5 no argumento de larguras diz que há uma coluna de cinco caracteres que deve ser ignorada, da mesma forma ...

ref: https://www.inkling.com/read/r-cookbook-paul-teetor-1st/chapter-4/recipe-4-6

Pavithra Gunasekara
fonte
20

Em primeiro lugar, essa pergunta vem diretamente de um curso "Obter dados e limpar" do Coursera da Leeks. Embora haja outra parte da questão, a parte difícil é ler o arquivo.

Dito isso, o curso é voltado principalmente para o aprendizado.

Eu odeio o procedimento de largura fixa de R. É lento e para um grande número de variáveis, muito rapidamente se torna uma dor negar certas colunas, etc.

Eu acho que é mais fácil de usar readLines()e, a partir daí, substr()fazer suas variáveis

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
                     )
James Holland
fonte
2
Essa abordagem funcionou para mim. Duas dicas adicionais: 1) você pode definir mydata para ser apenas os dados de que você precisa. Portanto, pode ser tão simples como mydata <- data.frame(var4 = substr(x,29,32))se você só precisasse da quarta coluna de dados. Além disso, para usuários do Windows, o Notepad ++ com o plug-in TextFX fornecerá uma régua de caracteres simples e simples para que você possa descobrir o que inserir nos valores de início e parada substr. Observe, entretanto, que o valor de parada é mais um do que a posição do último caractere que você deseja preservar.
globalSchmidt
5

Documento aqui a lista de alternativas para leitura de arquivos de largura fixa em R, além de fornecer alguns benchmarks para qual é o mais rápido.

Minha abordagem preferida é combinar freadcom stringi; é competitivo por ser a abordagem mais rápida e tem o benefício adicional (IMO) de armazenar seus dados como 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

Observe que freadremove automaticamente os espaços em branco à esquerda e à direita - às vezes, isso é indesejável e, nesse caso, defina strip.white = FALSE.


Também poderíamos ter começado com um vetor de larguras de coluna wwfazendo:

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)

E poderíamos ter escolhido quais colunas excluir de forma mais robusta, usando índices negativos como:

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))

Em seguida, substitua col_ends$beg[ii]por abs(col_ends$beg[ii])e na próxima linha:

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

Por último, se quiser que os nomes das colunas também sejam lidos de maneira programática, você pode limpar com 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 != ""]

(observe que combinar esta etapa com freadexigiria a criação de uma cópia da tabela a fim de remover a linha do cabeçalho e, portanto, seria ineficiente para grandes conjuntos de dados)

MichaelChirico
fonte
4

Não sei nada sobre R, mas posso fornecer uma regex que corresponderá a essas linhas:

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