Textdatei mit fester Breite lesen

89

Ich versuche, diesen hässlich formatierten Datensatz in meine R-Sitzung zu laden: 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

Bisher kann ich die Zeilen mit lesen

  x = readLines(path)

Aber die Datei mischt 'Leerraum' mit '-' als Trennzeichen, und ich bin kein Regex-Experte. Ich schätze jede Hilfe, die sich daraus ergibt, dass daraus ein schöner und sauberer R-Datenrahmen wird. Vielen Dank!

Fernando
quelle
5
Schauen Sie read.fwfsich die gelesenen formatierten Daten mit fester Breite an.
Paul Hiemstra
1
Ich denke, es ist eine bessere Idee, jede Zeile zu verarbeiten. Es mischt '-' mit '' Zeichen.
Fernando
Alternativ können Sie sagen, Leerzeichen oder - ist nur ein Zeichen. Ersetzen Sie also zuerst alle mehrfachen Vorkommen eines Leerzeichens durch ein Tabulatorzeichen und teilen Sie dann alle durch Tabulatoren getrennten Einträge auf - oder Leerzeichen auf.
GitaarLAB
Feste Breite = keine Trennzeichen. Das heißt, das "-" ist ein Minuszeichen und die Leerzeichen sind auch keine Trennzeichen. Sie treten nur auf, wenn die Zahl nicht die gesamte verfügbare Breite
ausfüllt

Antworten:

179

Dies ist eine Datei mit fester Breite. Verwenden Sie read.fwf(), um es zu lesen:

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

Aktualisieren

Das Paket readr(veröffentlicht im April 2015) bietet eine einfache und schnelle Alternative.

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

Geschwindigkeitsvergleich: readr::read_fwf()war ~ 2x schneller als utils::read.fwf ().

Andrie
quelle
8
@Andrie woher wusstest du was die Breiten und Sprünge waren?
Koba
12
@Koba: Ich habe eine der Zeilen in einen Texteditor mit einer Spaltenanzahl kopiert und eingefügt und die Breiten für jede Spalte manuell gezählt (einschließlich Leerzeichen, falls erforderlich). Sie können auch feststellen, dass Sie 4 ganze Zeilen überspringen müssen, bevor Sie zu den Rohdaten gelangen.
Rayryeng
5
Die Antwort von @ Pavithra unten mit negativen Spaltenbreiten zum Überspringen unerwünschter Leerzeichen ist möglicherweise besser für die akzeptierte Antwort geeignet.
Marius Butuc
1
@Andrie Wie haben Sie die fwf_widths-Werte erhalten?
BICube
3
@Ala Ich glaube, ich readr::fwf_emptywerde versuchen, die Breiten für Sie zu erraten. Die Beispiele für readr::read_fwfzeigen die Verwendung für readr::fwf_empty.
Jake Fisher
55

Eine andere Möglichkeit, Breiten zu bestimmen ...

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
)

Das Argument -1 im Argument widths gibt an, dass eine Spalte mit einem Zeichen ignoriert werden sollte. Das Argument -5 im Argument widths gibt an, dass eine Spalte mit fünf Zeichen ebenfalls ignoriert werden sollte.

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

Pavithra Gunasekara
quelle
20

Zunächst einmal stammt diese Frage direkt aus dem Coursera-Kurs "Get Data and Clean It" von Leeks. Während es einen anderen Teil der Frage gibt, ist der schwierige Teil das Lesen der Datei.

Der Kurs ist jedoch hauptsächlich zum Lernen gedacht.

Ich hasse Rs Verfahren mit fester Breite. Es ist langsam und für eine große Anzahl von Variablen wird es sehr schnell zum Problem, bestimmte Spalten usw. zu negieren.

Ich denke, es ist einfacher zu verwenden readLines()und dann daraus substr()Ihre Variablen zu erstellen

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
quelle
2
Dieser Ansatz hat bei mir funktioniert. Zwei zusätzliche Tipps: 1) Sie können mydata so definieren, dass sie nur die Daten sind, die Sie benötigen. Es könnte also so einfach sein, als mydata <- data.frame(var4 = substr(x,29,32))ob Sie nur die vierte Datenspalte benötigen. Für Windows-Benutzer bietet Notepad ++ mit dem TextFX-Plugin ein einfaches Lineal mit gezählten Zeichen, mit dem Sie herausfinden können, in was die Start- und Stoppwerte eingefügt werden sollen substr. Beachten Sie jedoch, dass der Stoppwert um eins höher ist als die Position des letzten Zeichens, das Sie beibehalten möchten.
GlobalSchmidt
5

Ich dokumentiere hier die Liste der Alternativen zum Lesen von Dateien mit fester Breite in R sowie einige Benchmarks, für die die schnellsten sind.

Mein bevorzugter Ansatz ist es, freadmit zu kombinieren stringi; Es ist als schnellster Ansatz wettbewerbsfähig und hat den zusätzlichen Vorteil (IMO), dass Ihre Daten wie folgt gespeichert werden 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

Beachten Sie, dass freadführende und nachfolgende Leerzeichen automatisch entfernt werden - manchmal ist dies unerwünscht. In diesem Fall wird dies festgelegt strip.white = FALSE.


Wir hätten auch mit einem Vektor der Spaltenbreiten beginnen können, wwindem wir Folgendes getan hätten:

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)

Und wir hätten auswählen können, welche Spalten robuster ausgeschlossen werden sollen, indem wir negative Indizes wie:

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

Dann ersetzen Sie col_ends$beg[ii]mit abs(col_ends$beg[ii])und in der nächsten Zeile:

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

Wenn Sie möchten, dass die Spaltennamen auch programmgesteuert gelesen werden, können Sie Folgendes bereinigen 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 != ""]

(Beachten Sie, dass zum Kombinieren dieses Schritts mit freaddas Erstellen einer Kopie der Tabelle erforderlich ist, um die Kopfzeile zu entfernen, und daher für große Datenmengen ineffizient wäre.)

MichaelChirico
quelle
4

Ich weiß nichts über R, aber ich kann Ihnen einen regulären Ausdruck geben, der zu solchen Zeilen passt:

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