LesezeichenAbonnierenRSS-Feed abonnieren
mfab
Quartz | Level 8

Hallo zusammen,

 

mit SAS unter Linux (falls das hier von Bedeutung sein sollte) möchte ich mehrere Dateien einlesen, die jeweils einige Kopfzeilen haben, die mich nicht interessieren.

Dazu kann man im INFILE-Statement eine Wildcard verwenden.

Das klappt auch super, allerdings scheint SAS die FIRSTOBS Option nur bei der ersten Datei zu berücksichtigen und nicht bei Folgedateien.

 

Hier ein kleines Beispiel:

data a1;
var1 = 26.3;
var2 = "abc";
run;

data a2;
diese_zeile = 578.3;
macht_probleme = "def";
run;

proc export data=a1 dbms=csv outfile="/home/user/a1.csv" replace;
delimiter=';';
run;

proc export data=a2 dbms=csv outfile="/home/user/a2.csv" replace;
delimiter=';';
run;

data c;
  length readfile datei $150.;

  infile "/home/user/a*.csv" 
    firstobs = 2
    filename = readfile
    dlm=';'
  ;

  input
    a : best.
    b : $char3.
  ;

  datei = readfile;
run;

Zunächst werden zwei Dateien (als Beispiel) erzeugt, die ich anschließend wieder einlesen möchte.

Hierbei sieht man, dass es zu einem Fehler kommt, sobald SAS an die zweite Datei gerät. Hier werden dann unerwünschterweise die Überschriften in der Datei eingelesen, was zu einer NOTE "Invalid data for a [...]" führt und darüberhinaus zu nicht erwünschten Daten im Ergebnis.

 

Vielleicht kennt jemand eine pfiffige Lösung für diese Problemstellung.

Die Optionen jeweils nur eine Datei in einem Durchlauf einzulesen (ggf. mit Macro oder Schleife innerhalb eines Data-Step) sind mir bekannt, allerdings finde ich es schade, dass SAS hier die FIRSTOBS-Option nur einmalig anwendet. Auch ein vorheriges Bearbeiten der Dateien auf Linux-Ebene wäre möglich, ist aber nicht das, was ich mir wünsche.

Ich würde gerne die praktische und übersichtliche Lösung mit einer Wildcard im INFILE-Statement einsetzen. Nur brauche ich dann die Option mit dem FIRSTOBS für jede Datei. (in der Praxis sogar mit Firstobs = 4 ...)

 

Herzliche Grüße

Michael

6 ANTWORTEN 6
Kurt_Bremser
Super User

Statt firstobs zu verwenden, lese ich bei sowas immer alle Zeilen ein. Allerdings lese ich gerade so viel, um bestimmen zu können, ob es sich um Überschrift oder Daten handelt (mit @ die Zeile halten). Mit subsetting if breche ich dann die Iteration ab, oder mache mit @1 wieder weiter.

mfab
Quartz | Level 8

Das ist schonmal ein guter Hinweis! 🙂

Ich vermute die Verarbeitung ist wie hier gemeint: support.sas.com - Holding a Record in the Input Buffer

 

Nun stellt sich mir die Frage, wie ich die Daten erkennen kann.

Folgendes Beispiel:

1: Hier steht etwas
2: 
3: 
4: nach zwei leeren Zeilen steht wieder etwas.
5: Hier kommt noch ein weiterer String; sogar mit ; getrennt;
6: Artikel1;2038;VariableXY
7: Artikel2;2453;VariableXY
8: Artikel1;2342;VariableXY

In der Datei stehen verschiede Kopfzeilen, die ggf. leer sind oder auch mal Sonderzeichen und Semicolon enthalten können.

Ich weiß, dass ich immer ab einer bestimmten Zeilennummer lesen muss, daher war mein Ansatz "Firstobs".

 

Wenn ich nun alles einlese, wie kann ich denn dann unterscheiden, ob die Daten korrekt sind oder nicht? In Zeile 5 könnte ja der erste String bis zum Semicolon auch ein String sein, den ich in Zeile 6 als "Artikel" erwarte. Hier würde ja erst nach dem ersten Semicolon auffallen, dass ein String geliefert wird, obwohl ich dort einen numerischen Wert erwarte.

Somit müsste ich ja jeweils alle Variablen prüfen oder prüfst Du auf _error_ ?

jh_ti_bw
Obsidian | Level 7

Hallo mfab,

 

bei Textdateien gibt es leider keinen allgemeinen Weg, weil alles in eine Textdatei schreibbar ist. Man sollte also prüfen, ob alle einzulesenden Dateien ab einer bestimmten Zeile anfangen.

Ist dies nicht der Fall, müssen die Daten auf bestimmte Muster geprüft werden. Ich lese mir dafür meist die komplette Zeile zum Prüfen ein, da ich dabei noch keine Fehler bekomme. Der Eingabe hält dabei die Zeile.

Nach der Prüfung auf ein bestimmtes Muster wird dann eingelesen oder nicht.

Dabei können auch wieder Fehler aufteten, die abgefangen werden müssen.

 

Für Dein Beispiel sähe das so aus:

Data Read_Test;
  format artikel $32. Zahl 8.0 Variable $32.;
  infile "C:\temp\test.txt" dlm = ";" dsd length = laenge missover truncover;
  input @;
/*  put _infile_;*/

  /* Erkennung ob gültiger Datensatz */
  if laenge = 0 then delete;
  if lowcase(substr(_infile_,1,7)) ne "artikel" then delete;

  /* Lesen der Daten */
  input artikel zahl variable;

  /* Eventuell weitere Überprüfungen */
run;

Ich hoffe, das Beispiel hilft Dir weiter.

 

Viele Grüße

Jan

 

FreelanceReinh
Jade | Level 19

Wenn die Anzahl der am Beginn jeder Datei zu überspringenden Sätze feststeht, kann man auch mit Hilfe der Option EOV= des INFILE-Statements den Wechsel von einer Datei zur nächsten erkennen und dann mit der entsprechenden Anzahl "leerer" INPUT-Statements die Sätze am Beginn "überspringen":

 

data c;
  length readfile datei $150.;

  infile "/home/user/a*.csv" 
    firstobs = 2
    filename = readfile
    eov=obs1 /* 0/1-Flag für Beginn einer neuen Input-Datei */
    dlm=';'
  ;

  input @; /* INPUT erforderlich, damit die EOV-Variable gesetzt wird */
  if obs1 then do;
    do _n_=1 to 1; /* in der "Praxis": to 3, also 3 Sätze nicht in die Variablen einlesen */
      input; 
    end;
    obs1=0; /* Rücksetzung der EOV-Variable */
  end;

  input
    a : best.
    b : $char3.
  ;

  datei = readfile;
run;

Die EOV-Variable wird für die erste Datei nicht auf 1 gesetzt. Für diese wird das Überspringen weiterhin durch FIRSTOBS= gewährleistet. Man könnte aber die IF-Bedingung zu if _n_=1 | obs1 erweitern und dann FIRSTOBS= weglassen.

 

Denkbar wäre auch, den Wechsel zu einer neuen Datei daran zu erkennen, dass READFILE nicht mehr mit DATEI (das müsste dann RETAINed werden) übereinstimmt. Dann bräuchte man EOV= nicht.

GrischaPfister
Fluorite | Level 6

Hallo MFAB,

 

es gibt die Möglichkeit, im Data Step auf den Wechsel der eingelesenen Datei zu reagieren, die automatische Variable EOV wird ab der zweiten gelesenen Datei bei der ersten gelesenen Zeile auf 1 gesetzt (muss aber von Hand wieder zurückgesetzt werden). Damit kannst Du kontrollieren, ob eine neue Datei gelesen wurde und dann einfach soviele Zeilen wie nötig "überlesen". Hier zunächst ein Beispiel mit Deinen Ausgangsdaten:

 

%Let root = f:/temp;
data a1;
var1 = 26.3;
var2 = "abc";
output;
var1 = 26.3;
var2 = "abc";
output;
run;

data a2;
diese_zeile = 578.3;
macht_probleme = "def";
output;
diese_zeile = 578.3;
macht_probleme = "def";
output;
run;

proc export data=a1 dbms=csv outfile="&root/a1.csv" replace;
delimiter=';';
run;

proc export data=a2 dbms=csv outfile="&root/a2.csv" replace;
delimiter=';';
run;

data c;
  length readfile datei $150.;
  infile "&root/a*.csv"
    firstobs = 2
    dlm=';'
    eov = newFile
    filename = readfile
  ;
  input @;
  datei = readfile;
  put newFile= datei=;
  If ( newFile ) Then Do;
    newFile=0 ;
  End;
  Else Do;
    input
    a : best.
    b : $char3.
    ;
    Output;
  End;
run;

Bei Einlesen der ersten Zeile der zweiten Datei wird NEWFILE auf 1 gesetzt, was dazu führt, dass kein INPUT ausgeführt wird, sondern lediglich der Wert von NEWFILE auf 0 zurückgesetzt wird. Beim Lesen der zweiten Zeile der zweiten Datei wird dann wieder INPUT ausgeführt.

Wenn die Anzahl der zu ignorierenden Zeilen immer gleich ist, kannst Du einfach entsprechend INPUT-Statements ergänzen, wie in diesem Beispiel:

%Let root = f:/temp;
data a1;
output;
output;
var1 = 26.3;
var2 = "abc";
output;
var1 = 26.3;
var2 = "abc";
output;
run;

data a2;
output;
output;
diese_zeile = 578.3;
macht_probleme = "def";
output;
diese_zeile = 578.3;
macht_probleme = "def";
output;
run;

proc export data=a1 dbms=csv outfile="&root/a1.csv" replace;
delimiter=';';
run;

proc export data=a2 dbms=csv outfile="&root/a2.csv" replace;
delimiter=';';
run;

data c;
  length readfile datei $150.;
  infile "&root/a*.csv" 
    firstobs = 2
    dlm=';'
    eov = newFile
    filename = readfile
  ;
  input @;
  datei = readfile;
  put newFile= datei=;
  If ( newFile ) Then Do;
    newFile=0 ;
    Input;
    Input;
  End;
  Else Do;
    input
    a : best.
    b : $char3.
    ;
    Output;
  End;
run;

Ich habe einfach zwei Leerzeilen in den Text-Dateien ergänzt, entsprechend wird bei Wechsel der Einlesedatei die erste Zeile ignoriert sowie zwei Zeilen mit INPUT gelesen, ohne Variablen einzulesen.

 

(habe gerade gesehen das freelanceReinhard eine analoge Antwort schon geschrieben hat...)

 

Was das Einlesen und Erkennen von Sätzen angeht, verwende ich oft den Ansatz erst die gesammte Zeile zu lesen und dann zu interpretieren, was drinnen steht. Ein INPUT Statement ohne Bezeichner sorgt nämlich dafür, dass die gesamte Zeile in einer internen Variable _INFILE_ landet, und die kann dann untersucht werden. Beispiel:

Data Work.Test;
  Length var1 $16 var2 8 var3 $16;
  Input;
  If ( countw(_infile_,";") = 3 ) Then Do;
    var1 = scan(_infile_,1,";");
    var2 = input(scan(_infile_,2,";"),??best.);
    var3 = scan(_infile_,3,";");
    Output;
  End;
Cards4;
Hier steht etwas


nach zwei leeren Zeilen steht wieder etwas.
Hier kommt noch ein weiterer String; sogar mit ; getrennt;
Artikel1;2038;VariableXY
Artikel2;2453;VariableXY
Artikel1;2342;VariableXY
;;;;
Run;

Mit Input wird _INFILE_ erzeugt, wenn drei durch Semikolon getrennte Elemente vorhanden sind, wird der String genauer angeschaut und mit scan() werden die Elemente ausgelesen, für VAR2 wird dann noch die Konvertierung CHAR->NUM vorgenommen.

 

Hoffe, die Antwort hilft weiter,

 

Viele Grüße,

 

  Grischa

 

 

mfab
Quartz | Level 8

Hallo zusammen,

 

klasse, wenn so viele konstruktive Vorschläge kommen und schön zu sehen, wie unterschiedlich teilweise ein Thema gehandhabt wird.

Herzlichen Dank! Smiley (fröhlich)

 

Ich habe mich jetzt für eine Abwandlung der Lösung von Grischa und Reinhard entschieden. Die anderen Ansätze finde ich spannend, jedoch möchte ich mich nicht weiter mit der Prüfung des Zeileninhalts beschäftigen.

Daher:

data c;
  length readfile datei $150.;
  infile "&root./a*.csv" 
/*    firstobs = 2 -- nicht nötig, da unten bei _n_ = 1 newFile gesetzt wird */
    dlm=';'
    eov = newFile
    filename = readfile
  ;
  input @;
  if _n_ = 1 then newFile = 1;
  datei = readfile;
  put newFile= datei=;
  If ( newFile ) Then Do;
    newFile=0 ;
    Input;
    Input;
  End;

  input
    a : best.
    b : $char3.
    ; 
run;

Damit man nicht für das erste File und nachfolgende Files eine unterschiedliche Bearbeitung hat, habe ich firstobs nun ganz weggelassen und setze newFile einfach selbst auch für die erste Datei (die nach meinem Verständnis auch ein "newFile" ist).

 

Somit dürfte es auch einfach sein später Änderungen im Programm einzupflegen (falls die Logik mal nicht firstobs = n sondern firstobs = m sein soll, etc.).

 

Mir ist zwar noch nicht klar, weshalb SAS zwar erkennt, dass es sich um ein neues File handelt, aber dann nicht automatisch die Firstobs-Logik auf das neue File anwendet, aber das wird wohl auch niemand mehr ändern. Smiley (zwinkernd)

 

Besten Dank an Alle und herzliche Grüße

Michael

sas-innovate-2024.png

Join us for SAS Innovate April 16-19 at the Aria in Las Vegas. Bring the team and save big with our group pricing for a limited time only.

Pre-conference courses and tutorials are filling up fast and are always a sellout. Register today to reserve your seat.

 

Register now!

Diskussionsstatistiken
  • 6 Antworten
  • 3678 Aufrufe
  • 4 Kudos
  • 5 in Unterhaltung