Hallo zusammen,
ich bräuchte ein bisschen Hilfe. Ich habe Labordaten im ldt-Format bekommen und versuche, diese gerade so aufzulösen, dass ich pro Laborprobe eine Zeile habe in der die Proben-ID und danach die Testergebnisse mit den jeweiligen Einheiten, Grenzwerten etc. kommen. Ich habe dies auch schon geschafft, allerdings für 3 Laborwerte und ohne irgendeine Form von Makro. Jetzt muss ich das für 15 Laborwerte machen und es wird Zeit, dass ich mich an den Makros versuche. Ich bin auch schon ein Stück weit gekommen, stecke jetzt aber fest und brauche Hilfe.
Hier erstmal ein (gekürzter) Bespieldatensatz an dem Punkt an dem ich jetzt bin:
DATA labor;
input probe testident $ ergebnis einheit $ grenzw_ind $;
put _all_;
cards;
1 Ca/S 2.04 mmol/l -
1 Cl/S 116 mmol/l +
1 TGL 217 mg/dl +
2 Ca/S 2.36 mmol/l +
2 Cl/S 101 mmol/l -
2 UA/S 6.7 mg/dl +
3 Ca/S 1.86 mmol/l -
3 Cl/S 97 mmol/l -
3 TGL 35 mg/dl -
RUN;
„probe“ steht für die Proben-ID, d.h. alle Zeilen mit derselben probe gehören zu einer Serumprobe und diese sollen in eine Zeile nebeneinander geschrieben werden.
Und hier will ich hin, d.h. so soll der Datensatz am Ende aussehen:
Jetzt habe ich versucht, mich Schritt für Schritt dahin zu arbeiten, analog zu meinem alten Makro-freien Programm.
options symbolgen;
%macro anlegen(st_test);
format &st_test._value $10.
&st_test._unit $10.
&st_test._flag_patho $5.;
%mend anlegen;
%macro merken(st_test);
retain &st_test._value;
retain &st_test._unit;
retain &st_test._flag_patho;
%mend merken;
%macro leer(st_test);
&st_test._value = "";
&st_test._unit = "";
&st_test._flag_patho = "";
%mend leer;
%macro bedingung(lim_test, st_test);
%if &testid = &lim_test %then %do;
&st_test._value = ergebnis;
&st_test._unit = einheit;
&st_test._flag_patho = grenzw_ind;
%end;
%mend bedingung;
DATA labor2;
format h_probe 5.;
set work.labor;
%anlegen (ca)
%anlegen (cl)
%anlegen (trg)
%anlegen (ua);* Darf ich den Strichpunkt hier setzen?;
if probe=h_probe then do;
%merken (ca)
%merken (cl)
%merken (trg)
%merken (ua); * Darf ich den Strichpunkt hier setzen?;
end;
else do;
%leer (ca)
%leer (cl)
%leer (trg)
%leer (ua); * Darf ich den Strichpunkt hier setzen?;
end;
/* An dieser Stelle müsste der Aufruf des Makros „bedingung“ erfolgen, a la…*/
%bedingung (Ca/S, ca);
%bedingung (Cl/S, cl);
%bedingung (TRG, trg);
%bedingung (UA/S, ua);
/*… allerdings mit der Zuweisung einer laufende Variablen (testident) statt der gerade gezeigten Aufrufe. Dass es so nicht funktionieren kann, ist mir klar, weil die Makros vor dem DataStep ausgeführt werden. Aber wie kann’s funktionieren? Mit call symputx? Aber wie? */
call symputx ('testid', testident); /* call symputx: Ausführung zur Laufzeit des DataSteps*/
h_probe=probe;
retain h_probe;
RUN;
Kann mir jemand ein wenig auf die Sprünge helfen?
Vielen Dank und viele Grüße
Barbara
Hallo Barbara,
ich denke an eine Lösung mittels "Transponieren". So ungefaehr
proc transpose data=labor out=labor_transposed;
by probe;
VAR ergebnis;
ID ident;
run;
Gruß
Wolfgang
Hallo Wolfgang,
danke für Deine Antwort. Ich habe die ldt-Datei schon ziemlich weit mit Transpose bearbeitet, aber leider sind die Daten nicht immer so schön, dass ich sie einfach in eine Transpose stopfen kann und es funktioniert...
Viele Grüße
Barbara
DATA labor;
input probe testident $ ergebnis einheit $ grenzw_ind $;
put _all_;
cards;
1 Ca/S 2.04 mmol/l -
1 Cl/S 116 mmol/l +
1 TGL 217 mg/dl +
2 Ca/S 2.36 mmol/l +
2 Cl/S 101 mmol/l -
2 UA/S 6.7 mg/dl +
3 Ca/S 1.86 mmol/l -
3 Cl/S 97 mmol/l -
3 TGL 35 mg/dl -
RUN;
options symbolgen;
%macro anlegen(st_test);
format &st_test._value $10.
&st_test._unit $10.
&st_test._flag_patho $5.;
%mend anlegen;
%macro merken(st_test);
retain &st_test._value;
retain &st_test._unit;
retain &st_test._flag_patho;
%mend merken;
%macro leer(st_test);
&st_test._value = "";
&st_test._unit = "";
&st_test._flag_patho = "";
%mend leer;
%macro bedingung(lim_test, st_test);
/* Wenn hier Datastep-Code anstelle von Makrocode generiert wird, läuft das Programm */
if &testid = &lim_test then do;
&st_test._value = ergebnis;
&st_test._unit = einheit;
&st_test._flag_patho = grenzw_ind;
end;
%mend bedingung;
DATA labor2;
format h_probe 5.;
set work.labor;
%anlegen (ca)
%anlegen (cl)
%anlegen (trg)
%anlegen (ua) /* Darf ich den Strichpunkt hier setzen? Ja, sinnlos ist er trotzdem :-) */
if probe=h_probe then do;
%merken (ca)
%merken (cl)
%merken (trg)
%merken (ua) /* Darf ich den Strichpunkt hier setzen? Ja, sinnlos ist er trotzdem :-) */
end;
else do;
%leer (ca)
%leer (cl)
%leer (trg)
%leer (ua) /* Darf ich den Strichpunkt hier setzen? Ja, sinnlos ist er trotzdem :-) */
end;
/* An dieser Stelle müsste der Aufruf des Makros „bedingung“ erfolgen, a la…*/
%bedingung (Ca/S, ca);
%bedingung (Cl/S, cl);
%bedingung (TRG, trg);
%bedingung (UA/S, ua);
/*… allerdings mit der Zuweisung einer laufende Variablen (testident) statt der gerade gezeigten Aufrufe. Dass es so nicht funktionieren kann, ist mir klar, weil die Makros vor dem DataStep ausgeführt werden. Aber wie kann’s funktionieren? Mit call symputx? Aber wie? */
call symputx ('testid', testident); /* call symputx: Ausführung zur Laufzeit des DataSteps*/
h_probe=probe;
retain h_probe;
RUN;
Wenn das Makro %Bedingung Datastep code generiert funktioniert es auch 🙂
Hallo Barbara,
ich würde eher zu einem Data Step ohne weitere Makros greifen. Das mag persönliche Präferenz sein, ich finde den Data Step übersichtlicher.
Die Werte mit Slash habe ich zum späteren Abgleich (select when) vorab definiert.
%let ca = %str(Ca/S);
%let cl = %str(Cl/S);
%let ua = %str(UA/S);
data want (drop= testident ergebnis einheit grenzw_ind);
set labor;
by probe;
retain ca_value ca_unit ca_flag_patho
cl_value cl_unit cl_flag_patho
tgl_value tgl_unit tgl_flag_patho
ua_value ua_unit ua_flag_patho;
select (testident);
when ("&ca.") do; ca_value = ergebnis; ca_unit = einheit; ca_flag_patho = grenzw_ind; end;
when ("&cl.") do; cl_value = ergebnis; cl_unit = einheit; cl_flag_patho = grenzw_ind; end;
when ("TGL") do; tgl_value = ergebnis; tgl_unit = einheit; tgl_flag_patho = grenzw_ind; end;
when ("&ua.") do; ua_value = ergebnis; ua_unit = einheit; ua_flag_patho = grenzw_ind; end;
otherwise put "error 123";
end;
if last.probe then do; /* nur ein Satz pro Labor ausgeben */
output;
/* alle Zählvariablen auf missing setzen */
call missing(ca_value, ca_unit, ca_flag_patho,
cl_value, cl_unit, cl_flag_patho,
tgl_value, tgl_unit, tgl_flag_patho,
ua_value, ua_unit, ua_flag_patho);
end;
run;
Viele Grüße
Michael
edit: hatte wohl Probe mit Labor verwechselt
Hallo Michael,
Deine Antwort ist vielleicht für mich die spannenste, wenn auch nicht die zielführendste. Ich versuche gerade, für Makros ein Gefühl zu bekommen und habe deshalb mit Makros "experimentiert". Deine Lösung werde ich aber bestimmt auch nochmal nachvollziehen.
Vielen Dank.
Viele Grüße
Barbara
Hallo Andreas,
danke für Deine schnelle Antwort, zusammen mit Hans seiner Antwort habt Ihr mich auf den richtigen Pfad gebracht.
Viele Grüße
Barbara
Hallo Barbra,
die Frage 'Aber wie kann’s funktionieren?' ist in Worten schwer zu beantworten, deshalb die Antwort als Code.
Von Andreas kam in der Zwischenzeit fast die gleiche Lösung wie mein Vorschlag, aber bei ihm fehlt meiner Meinung nach das Füllen der Macro-Variable testid bzw. das Vermeiden dieser Macro-Variable, siehe mein Code.
options symbolgen;
%macro anlegen(st_test);
format &st_test._value $10.
&st_test._unit $10.
&st_test._flag_patho $5.;
%mend anlegen;
%macro merken(st_test);
retain &st_test._value;
retain &st_test._unit;
retain &st_test._flag_patho;
%mend merken;
%macro leer(st_test);
&st_test._value = "";
&st_test._unit = "";
&st_test._flag_patho = "";
%mend leer;
%macro bedingung(testid, lim_test, st_test);
if &testid. = "&lim_test" then do;
&st_test._value = ergebnis;
&st_test._unit = einheit;
&st_test._flag_patho = grenzw_ind;
end;
%mend bedingung;
DATA labor2;
format h_probe 5.;
set work.labor;
%anlegen (ca)
%anlegen (cl)
%anlegen (trg)
%anlegen (ua);* Darf ich den Strichpunkt hier setzen? -->> JA ! ;
if probe=h_probe then do;
%merken (ca)
%merken (cl)
%merken (trg)
%merken (ua); * Darf ich den Strichpunkt hier setzen? -->> JA ! ;
end;
else do;
%leer (ca)
%leer (cl)
%leer (trg)
%leer (ua); * Darf ich den Strichpunkt hier setzen? -->> JA ! ;
end;
/* An dieser Stelle müsste der Aufruf des Makros „bedingung“ erfolgen, a la…*/
%bedingung (testident, Ca/S, ca);
%bedingung (testident, Cl/S, cl);
%bedingung (testident, TRG, trg);
%bedingung (testident, UA/S, ua);
/*… allerdings mit der Zuweisung einer laufende Variablen (testident) statt der gerade gezeigten Aufrufe. Dass es so nicht funktionieren kann, ist mir klar, weil die Makros vor dem DataStep ausgeführt werden. Aber wie kann’s funktionieren? Mit call symputx? Aber wie? */
*call symputx ('testid', testident); /* call symputx: Ausführung zur Laufzeit des DataSteps -->> unnötig !!! kann nicht klappen! */
h_probe=probe;
retain h_probe;
RUN;
Gruß Hans
Hallo Hans,
vielen Dank für Deine Antwort. Jetzt funktioniert's und ich kann weiter an meinem Code basteln. Danke.
Ich habe noch 2 Verständnisfragen dazu:
1. Wann würde ich in einem Makro %if verwenden und wann if (den reinen DataStep-Code)?
2. Die Frage nach meinem Strichpunkt hast Du sehr deutlich mit "JA !" beantwortet. Ich folgere darauf, dass ich ihn nicht nur setzen darf, sondern auch setzen muss. Warum? (Ich hab's daraus gefolgert, weil dann das "end" der if-Abfrage die richtige Farbe im Editor hat.)
Viele Grüße
Barbara
Hallo Barbara,
... so war es mit meinem 'JA!' nicht gemeint.
Also: Das Semikolon ist dort nicht verboten, aber auf keinem Fall zwingend nötig. Wie Andreas weiter oben so schön schrieb 'Ja, sinnlos ist er trotzdem'.
Das Semikolon ist einfach ein 'zu vielenes' Semikolon und wird wie ein korrektes, aber leeres Statement behandelt.
Zur Frage 'Wann würde ich in einem Makro %if verwenden und wann if' :
Der Unterschied zwischen 'if' und '%if' ist eigentlich ganz einfach:
Das DataStep-if wird zur Laufzeit bearbeitet, wenn der DataStep seine Daten verarbeitet. Das DataStep-if 'kennt' also z.B. den Inhalt der diversen Felder eines Datensatzes.
Das Macro-%if wird vor der Laufzeit des DataSteps bearbeitet, es kennt die Daten des DataStep nicht, das Macro-%if 'kennt' also den Inhalt der diversen Felder eines Datensatzes nicht.
Eine einfache Verdeutlichung ist: Macro-Sprache/Macro-Programmierung ist wie Code im Editor hintippen. Alles was ich/man zum Tipp-Zeitpunkt weiß kann ich/man in meinem Macro-Code einbauen. Was mein Programm erst weiß, wenn die Daten eingelesen und verabeitet werden, dass kann ich/man im Macro-Code nicht verwenden.
Was mir immer sehr geholfen hat, war das sehr gründliche studieren vom LOG. Nützlicher als die von Dir verwendete Option 'options symbolgen;' finde ich die Option 'options mprint;', speziell wenn man/frau versucht den LOG zu verstehen.
(Ich habe es gerade mal ohne options mprint; ausprobiert. Man hat meiner Meinung nach keine Chance zu verstehen, was warum passiert ... ! )
Wikipedia schreibt (hier😞 'Programme, die Zeichenfolgen durch andere Zeichenfolgen ersetzen, werden als Makroprozessoren bezeichnet'.
Was meint das?
Macro-Code besteht aus Zeichenfolgen (klar) und wird ersetzt durch eine andere Zeichenfolge.
Grob vereinfacht kann man sagen: Macros bestehen aus Text und erzeugen Text.
Im Gegensatz dazu erzeugt ein DataStep keinen Text, sondern ... (hoffentlich/wahrscheinlich klar, was er macht)
Papiere zur Macro-Programmierung: SAS-Makro-Programmierung, Makro-Sprache und Datenschritt,
Dein Macro 'leer' hat noch eine kleine Unsauberkeit: Die erste Zuweisung darin müsste '&st_test._value = 0;' lauten, dann kommt auch kein 'NOTE: Numeric values have been converted to character values at the places given by: (Line):(Column)', was hier in unserer Umgebung als Fehler behandelt wird.
Gruß Hans
Hallo Hans,
danke für die ausführliche Antwort, sie bringt mich wieder ein Schrittchen weiter auf dem Weg ins Makro-Universum.
Mein Makro 'leer' erzeugt keine Note, was daran liegt, dass in meinen Echtdaten ergebnis historisch bedingt character ist (das hatte ich aber in meinen Testdaten nicht berücksichtigt). Aber danke für den Hinweis, normalerweise versuche ich, solche Fehler auch zu entfernen....
Viele Grüße
Barbara
Are you ready for the spotlight? We're accepting content ideas for SAS Innovate 2025 to be held May 6-9 in Orlando, FL. The call is open until September 16. Read more here about why you should contribute and what is in it for you!