PROC FCMP – Eigene Funktionen im Datenschritt erstellen
Von Carina Ortseifen: Carina Ortseifen, Grischa Pfister, Heribert Ramroth, Marianne Weires: Tipps und Tricks für den leichteren Umgang mit der SAS Software, KSFE 2009
Die SAS Software stellt dem Anwender sehr viele vorbereitete Funktionen zur Verfügung, die im Datenschritt und bei manchen Prozeduren, SQL, NLIN, MODEL etc. eingesetzt werden können. Um eigene Funktionen verwenden zu können, mussten im Datenschritt Makros zum Einsatz kommen oder mittels Link- und Return-Anweisung verbundene Datenschritt-Abschnitte. Mit SAS Version 9.1.3 steht die Prozedur FCMP zur Verfügung und erlaubt eigene Funktionen zu definieren, die mit den oben erwähnten Prozeduren verwendet werden können. Mit SAS 9.2 ist nun die Lücke zum Datenschritt geschlossen und die mittels der Prozedur FCMP definierten Funktionen können auch im Datenschritt eingesetzt werden.
Mit den folgenden Beispielen möchten wir Ihr Interesse wecken, sich selbst mit diesen neuen Möglichkeiten zu beschäftigen.
Inhaltsverzeichnis |
Ein erstes Beispiel: Berechnung des Alters
Zunächst wird die neue Funktion Alter mit der Prozedur FCMP definiert:
PROC FCMP OUTLIB=sasuser.funktionen.paket;
FUNCTION alter (geburtsdatum, stichtag);
RETURN (floor((intck('month',geburtsdatum,stichtag)
- (day(stichtag) < day(geburtsdatum))) / 12));
ENDSUB;
RUN;
Die Funktion alter wird in der Bibliothek sasuser innerhalb der SAS-Tabelle funktionen im Paket paket abgelegt (Option OUTLIB=). PROC FCMP OUTLIB=bib.tab.paket;
Die Funktion alter wird mit zwei Argumenten definiert, geburtsdatum und stichtag. FUNCTION(argument-1, argument-2);
Der Rechenausdruck für das Alter wurde der SAS-Web-Seite „Sample 24808: Accurately Calculating Age with Only One Line of Code“ 13.03.2009. Die RETURN-Anweisung liefert das Ergebnis, den Rückgabewert der Funktion. (Es gibt auch Funktionen ohne Rückgabewert. Diese werden mit der Anweisung SUBROUTINE definiert.) RETURN (ergebnis);
Die Anweisung ENDSUB beendet die Definiton und RUN den Prozedurschritt.
Im zweiten Schritt benötigen Sie nun den Suchpfad, der SAS anzeigt, wo nach Benutzer-eigenen Funktionen gesucht wird (Option CMPLIB=).
OPTIONS CMPLIB=sasuser.funktionen;
Und schließlich folgt der Datenschritt, in dem die neue Funktion eingesetzt wird.
DATA daten; INPUT geburt ddmmyy10.; heute="05MAR2009"d; altr=alter(geburtsdatum,stichtag); FORMAT geburt heute ddmmyy10.; DATALINES; 05.03.2009 05.03.2008 06.03.2008 01.01.2008 01.01.2000 01.01.1960 ; TITLE "Alter in Jahren am 05.03.2009"; PROC PRINT DATA=daten; VAR geburt altr; RUN;
Was (unter Windows XP und SAS 9.2) zu folgendem Ergebnis führt:
Alter in Jahren am 05.03.2009
| Obs | geburt | altr |
| 1 | 05/03/2009 | 0 |
| 2 | 05/03/2008 | 1 |
| 3 | 06/03/2008 | 0 |
| 4 | 01/01/2008 | 1 |
| 5 | 01/01/2000 | 9 |
| 6 | 01/01/1960 | 49 |
Anmerkung: Glücklicherweise können Sie keine SAS-eigenen Funktionen überschreiben, wie das folgende Beispiel zeigt:
PROC FCMP OUTLIB=sasuser.funktionen.paket;
FUNCTION normal(zahl);
RETURN (zahl*2);
ENDSUB;
RUN;
OPTIONS CMPLIB=sasuser.funktionen;
data zufall;
do i=1 to 10;
x=normal(i);
output;
end;
run;
proc print data=zufall;
run;
Die Funktion NORMAL() erzeugt standardnormalverteilte Pseudozufallszahlen. Obiger Prozedur-FCMP-Schritt definiert eine Funktion Normal, die das Argument verdoppeln soll. Im Ergebnis und der Tabelle Zufall sehen wir aber, dass SAS Zufallszahlen erzeugt und nicht verdoppelt. Was uns als Anwender davor bewahrt, SAS-eigene Funktionen absichtlich oder unabsichtlich zu überschreiben.
Leider kommt es dabei zu keinerlei Warnung oder Hinweis, dass die eigene Funktion nicht zum Einsatz kommt.
Eine, wenn auch unvollständige Liste der Funktionen in SAS 9.1.3 findet man unter http://support.sas.com/techsup/technote/ts486.pdf
Subroutinen – Funktionen ohne Rückgabewert
Analog zu den Funktionen können Sie sich mit Hilfe der Prozedur FCMP auch eigene Unterroutinen schreiben, die ähnlich wie die Call Routinen keine Rückgabewerte haben.
PROC FCMP OUTLIB=sasuser.funktionen.paket;
SUBROUTINE inverse (in, inv);
OUTARGS inv;
IF in=0 THEN inv=.;
ELSE inv=1/in;
ENDSUB;
RUN;
Anstelle der Anweisung FUNCTION steht hier SUBROUTINE. Die Anweisung RETURN ist optional, d.h. sie kann wie in obigem Beispiel auch weggelassen werden.
Die Beispielroutine inverse soll das Inverse einer Zahl berechnen und – falls 0 eingegeben wird, einen fehlenden Wert. zurückgeben. Das Ergebnis dieser Routine wird nicht mit Return ausgegeben, sondern in eine Variable gesteckt, die als Argument mit übergeben wurde, inv. Damit diese Variable Veränderungen speichert, muss die Anweisung OUTARGS gesetzt werden.
DATA _NULL_; INPUT a @@; b=.; CALL inverse(a, b); PUT a b; DATALINES; 1 . 2 0 ;
Im Log-Fenster erhalten wir:
1 1 . . 2 0.5 0 .
Geltungsbereiche der Variablen
Im Gegensatz zu Makrovariablen sind die innerhalb der Funktionen und Subroutinen definierten Variablen stets lokale Variablen, d.h. die Geltungsbereiche sind absolut gegeneinander abgegrenzt und Überschreibungen sind dadurch ausgeschlossen, wie folgendes einfache Beispiel zeigen soll:
PROC FCMP OUTLIB=sasuser.funktionen.varscope;
SUBROUTINE subB();
x="subB";
PUT "In subB:" x=;
ENDSUB;
SUBROUTINE subA();
x=10;
call subB();
put 'In subA:' x=;
ENDSUB;
RUN;
OPTIONS CMPLIB=sasuser.funktionen;
DATA _NULL_;
x=99;
CALL subA();
PUT "Im Datenschritt:" x=;
RUN;
Innerhalb des aufrufenden Datenschritts wird die Variable x definiert und auf 99 gesetzt. Die beiden Subroutinen subA und subB verändern diese Variable im Wert und auch im Variablentyp, ohne dass dabei Probleme auftreten:
In subB: x=subB In subA: x=10 Im Datenschritt:x=99
Was noch geht
Die weiteren Möglichkeiten der Funktionen sollen hier nur noch aufgezählt werden. Für Details verweisen wir auf die Beispielprogramme auf der Begleit-CD und die Literatur.
- Rekursion
Sie können Funktionen rekursiv aufrufen, um z.B. alle k aus n Permutationen zu erzeugen (Beispielprogramm coFCMP4.sas).
- Dynamische Arrays
Wenn man nicht genau vorhersagen kann, wie viele Arrayfelder notwendig sein werden, können dynamische Arrays eingesetzt werden. Z.B. um Verzeichnisinhalte auszulesen (Beispielprogramm coFCMP4.sas).
- Weitere Einsatzbereiche der eigenen Funktionen
Mit FCMP definierte Funktionen können auch in Where-Anweisungen in Prozedurschritten und mit der Prozedur SQL eingesetzt werden (Beispielprogramm coFCMP6.sas).
- FCMP-Routinen sind auch in Proc Report-Compute-Blöcken einsetzbar.
- Die Inlib-Option erlaubt Zugriff auf vorhandene Pakete und auch C-Programme (Proc Proto).
- Mit der Funktion SOLVE können Routinen implizit innerhalb des gleichen FCMP-Schritts aufgelöst werden.
Was anders geht
Einige Anweisungen funktionieren beim Definieren von eigenen Funktionen mit der FCMP-Prozedur anders als im normalen Datenschritt. Dazu gehören die
- PUT-Anweisung: Sie kann nicht zum Formatieren eingesetzt werden, nur zum Debuggen, dabei sind aber Funktionen erlaubt, wie z.B. PUT (x/100) (sqrt(y)/2);
- IF-Anweisung: x = IF y < 100 THEN 1 ELSE 0;
- Arrays können mit eckigen und auch geschweiften Klammern definiert warden.
Was nicht oder noch nicht geht
Folgende Anweisungen können nicht für die Definition von eigenen Funktionen verwendet werden: Data, Set, Merge, Update, Modify, Input, Output, Infile, Do-Anweisung mit Textwerten, Data Step Debugger.
Und noch nicht nutzbar sind die eigenen Funktionen in Kombination mit %sysfunc:
data _null_;
put "Das Alter beträgt
%sysfunc(alter('01JAN1960'd,'30JUN2008'd)) Jahre.";
run;
Eine direkte Berechnung der Funktion alter (Definition siehe oben) bei der Ausgabe mit PUT geht noch nicht, sondern nur in zwei Schritten zerlegt:
data _null_;
alter=alter("01JAn1960"d,"30JUN2008"d);
put "Das Alter beträgt " alter "Jahre.";
run;
Für spätere Versionen ist dieses Feature jedoch angekündigt.
Literatur
- SAS Institute Inc.: Base SAS 9.2 Procedures Guide. SAS Institute Inc., Cary, NC 2009, ISBN 978-1-59994-714-3 (PDF, 9,4 MB).
- Jason Secosky: User Written DATA Step Functions. SAS Institute Inc., Cary, NC 2007 (PDF, 157 kB; PDF, 52 kB als Paper 008-2007 beim SAS Global Forum 2007).