PROC FCMP – Eigene Funktionen im Datenschritt erstellen

Aus SAS-Wiki
Wechseln zu: Navigation, Suche

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.

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

Alphanumerische Funktionswerte und Argumente

Das oben gegebene Beispiel verarbeitet numerische Argumente und liefert einen numerischen Funktionswert. Soll eine Funktion einen character-Wert zurückliefern, so muss dies in der Deklaration durch ein nachgestelltes $ gekennzeichnet werden. Der Kopf der Funktions-Deklaration sähe dann beispielsweise folgendermaßen aus: FUNCTION zahl_als_wort(zahl) $; Entsprechend werden character-Argumente mit einem nachgestellten $ gekennzeichnet. Beispiel: FUNCTION wort_als_zahl(wort $); Das Leerzeichen vor dem $-Zeichen kann in beiden Fällen entfallen.

Anmerkung zum Überschreiben von Funktionsdefinitionen und SAS-eigenen Funktionen

Definiert man eine benutzerdefinierte Funktion in einem bestimmten Paket neu, so wird die bestehende Funktion dort überschrieben und man erhält eine Warnung im Log. SAS-eigene Funktionen können nicht überschrieben werden, und wenn es eine SAS-eigene Funktion mit einem bestimmten Namen gibt, so wird diese auch dann verwendet, wenn man eine namensgleiche Funktion in einem eigenen Paket definiert hat. Dieses Verhalten wird im folgenden Beispiel demonstriert:

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