Leer archivo de texto de ancho fijo

90

Estoy intentando cargar este conjunto de datos con formato feo en mi sesión de 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

Hasta ahora, puedo leer las líneas con

  x = readLines(path)

Pero el archivo mezcla 'espacio en blanco' con '-' como separadores, y no soy un experto en expresiones regulares. Agradezco cualquier ayuda para convertir esto en un marco de datos R agradable y limpio. ¡Gracias!

Fernando
fuente
5
Y eche un vistazo read.fwfa leer datos con formato de ancho fijo.
Paul Hiemstra
1
Creo que es una mejor idea procesar cada fila. Mezcla caracteres '-' con ''.
Fernando
Alternativamente, podría decir espacio en blanco o - es solo un carácter, así que primero reemplace todas las ocurrencias múltiples de un espacio con un carácter de tabulación, luego divida todas las entradas separadas por tabulaciones en - o espacios en blanco.
GitaarLAB
Ancho fijo = sin separadores. Eso significa que el "-" es un signo menos y los espacios tampoco son separadores, solo ocurren cuando el número no llena todo el ancho disponible
Eusebio Rufian-Zilbermann

Respuestas:

183

Este es un archivo de ancho fijo. Use read.fwf()para leerlo:

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

Actualizar

El paquete readr(lanzado en abril de 2015) ofrece una alternativa sencilla y 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)))

Comparación de velocidad: readr::read_fwf()fue ~ 2 veces más rápido que utils::read.fwf ().

Andrie
fuente
8
@Andrie, ¿cómo supiste cuáles eran los anchos y los saltos?
Koba
12
@Koba: Copié y pegué una de las líneas en un editor de texto que tenía un recuento de columnas y conté manualmente los anchos de cada columna (incluidos los espacios en blanco cuando sea necesario). También puede decir que debe omitir 4 líneas completas antes de acceder a los datos sin procesar.
rayryeng
5
La respuesta de @ Pavithra a continuación con anchos de columna negativos para omitir espacios en blanco no deseados podría ser más adecuada para la respuesta aceptada.
Marius Butuc
1
@Andrie ¿Cómo obtuviste los valores de fwf_widths?
BICube
3
@Ala, creo readr::fwf_emptyque intentará adivinar los anchos por ti. Los ejemplos de readr::read_fwfmuestran el uso de readr::fwf_empty.
Jake Fisher
55

Otra forma de determinar anchos ...

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
)

El -1 en el argumento de anchos dice que hay una columna de un carácter que debe ignorarse, el -5 en el argumento de anchos dice que hay una columna de cinco caracteres que debería ignorarse, de la misma manera ...

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

Pavithra Gunasekara
fuente
20

En primer lugar, esa pregunta proviene directamente de un curso de Coursera "Obtener datos y limpiarlos" de Leeks. Si bien hay otra parte de la pregunta, la parte difícil es leer el archivo.

Dicho esto, el curso está destinado principalmente al aprendizaje.

Odio el procedimiento de ancho fijo de R. Es lento y para una gran cantidad de variables, rápidamente se convierte en una molestia negar ciertas columnas, etc.

Creo que es más fácil de usar readLines()y luego de ese uso substr()para hacer sus variables

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
fuente
2
Este enfoque funcionó para mí. Dos consejos adicionales: 1) puede definir mydata como solo los datos que necesita. Entonces podría ser tan simple como mydata <- data.frame(var4 = substr(x,29,32))si solo necesitara la cuarta columna de datos. Además, para los usuarios de Windows, Notepad ++ con el complemento TextFX le proporcionará una regla de caracteres contados simple y simple para que pueda averiguar qué poner en los valores de inicio y finalización substr. Sin embargo, tenga en cuenta que el valor de parada es uno más que la posición del último carácter que desea conservar.
globalSchmidt
5

Documento aquí la lista de alternativas para leer archivos de ancho fijo en R, además de proporcionar algunos puntos de referencia para los que es más rápido.

Mi enfoque preferido es combinar freadcon stringi; es competitivo como el enfoque más rápido y tiene el beneficio adicional (IMO) de almacenar sus datos 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

Tenga en cuenta que freadelimina automáticamente los espacios en blanco iniciales y finales; a veces, esto no es deseable, en cuyo caso se establece strip.white = FALSE.


También podríamos haber comenzado con un vector de anchos de columna wwhaciendo:

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)

Y podríamos haber elegido qué columnas excluir de manera más sólida mediante el uso de í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))

Luego reemplace col_ends$beg[ii]con abs(col_ends$beg[ii])y en la siguiente línea:

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

Por último, si desea que los nombres de las columnas también se lean mediante programación, puede limpiar con 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 != ""]

(tenga en cuenta que combinar este paso con freadrequeriría crear una copia de la tabla para eliminar la fila del encabezado y, por lo tanto, sería ineficiente para conjuntos de datos grandes)

MichaelChirico
fuente
4

No sé nada sobre R, pero puedo proporcionarle una expresión regular que coincidirá con esas líneas:

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