web-dev-qa-db-fra.com

Lire un fichier texte de largeur fixe

J'essaie de charger cet ensemble de données au format moche dans ma session 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

Jusqu'à présent, je peux lire les lignes avec

  x = readLines(path)

Mais le fichier mélange "espace" avec "-" en tant que séparateurs, et je ne suis pas un expert en regex. J'apprécie toute aide pour transformer cela en un cadre de données R agréable et propre. Merci!

83
Fernando

C'est un fichier de largeur fixe. Utilisez read.fwf() pour le lire:

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

Mise à jour

Le paquetage readr (publié en avril 2015) offre une alternative simple et rapide.

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

Comparaison de la vitesse: readr::read_fwf() était environ 2x plus rapide que utils::read.fwf ().

172
Andrie

Une autre façon de déterminer les largeurs ...

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
)

-1 dans l'argument widths indique qu'il y a une colonne d'un caractère qui doit être ignorée, tandis que -5 dans l'argument widths indique qu'il y a une colonne de cinq caractères à ignorer, de même ...

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

53

Tout d'abord, cette question est directement issue du cours "Obtenez des données et nettoyez-le" de Coursera par Leeks. Bien qu'il y ait une autre partie de la question, la partie difficile est la lecture du fichier.

Cela dit, le cours est principalement destiné à l'apprentissage.

Je déteste la procédure de largeur fixe de R. C'est lent et pour un grand nombre de variables, il devient très vite pénible de nier certaines colonnes, etc.

Je pense qu'il est plus facile d'utiliser readLines(), puis d'utiliser substr() pour créer vos 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
                     )
17
James Holland

Vous pouvez maintenant utiliser la fonction read_fwf() dans le package readr de Hadley Wickham.

Une amélioration considérable des performances est à prévoir par rapport à la base read.fwf().

10
Lionel Henry

Je documente ici la liste des alternatives pour la lecture de fichiers à largeur fixe en R, ainsi que des repères pour ce qui est le plus rapide.

Mon approche préférée est de combiner fread avec stringi; il est compétitif en tant qu'approche la plus rapide et présente l'avantage supplémentaire de stocker vos données sous forme de 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

Notez que fread supprime automatiquement les espaces de début et de fin - parfois, cela n’est pas souhaitable, auquel cas définissez strip.white = FALSE.


Nous aurions aussi pu commencer avec un vecteur de largeur de colonne ww en faisant:

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)

Et nous aurions pu choisir les colonnes à exclure de manière plus robuste en utilisant des indices négatifs tels que:

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

Puis remplacez col_ends$beg[ii] Par abs(col_ends$beg[ii]) et à la ligne suivante:

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

Enfin, si vous souhaitez que les noms de colonne soient également lus par programmation, vous pouvez nettoyer avec 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 != ""]

(notez que la combinaison de cette étape avec fread nécessiterait la création d'une copie de la table afin de supprimer la ligne d'en-tête et serait donc inefficace pour les grands ensembles de données)

5
MichaelChirico

Je ne connais rien à R, mais je peux vous fournir un regex qui correspondra à de telles lignes:

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