Expertentipp:
 

Wussten Sie schon, wie Sie im DATA STEP auf Werte in der vorigen Zeile oder der nachfolgenden Zeile zugreifen können?

Und so geht’s:

Der DATA STEP arbeitet zeilenweise, d.h. es wird immer genau eine Zeile eingelesen und verarbeitet. Manchmal muss man aber die eingelesenen Werte mit den vorigen oder nachfolgenden Werten vergleichen oder mit ihnen rechnen. Hier erfahren Sie, wie Ihnen das gelingt:

Im DATA STEP ist Zurückschauen einfacher, als Vorausschauen. Im SQL ist beides gleich aufwendig. Zuerst erstellen wir uns eine Testdatei:


data have;
	input ID $ Index Measure;
	cards;
A 1 11
A 2 12
A 3 13
B 1 21
B 2 22
;
run;

Erstellte Testdatei:

Base SAS Software - DATA STEP Example

Jetzt wollen wir jede Zeile um den Wert von MEASURE aus der vorigen Zeile der jeweiligen ID und aus der nachfolgenden Zeile der jeweiligen ID erweitern. Falls es keine vorige Zeile bzw. nachfolgende Zeile zu der ID gibt, soll jeweils der Wert auf fehlend gesetzt werden:

Folgendes soll dabei herauskommen:

Base SAS Software - DATA STEP Example

Und hier ist die Lösung im DATA STEP:


/* Lösung im Data Step */
data look_both_ways;
	/* 1. SET: Satzweises Lesen der Datei HAVE */
	set have; 
	by ID;
	/* 2. SET: Gleichzeitiges Lesen der nächsten Zeile */
	set have ( firstobs = 2 keep = Measure 
			     rename = (Measure = Next_Measure) )
		have ( obs = 1 drop = _all_); 
	Prev_Measure = ifn( first.ID, (.), lag(Measure) );
	Next_Measure = ifn(  last.ID, (.), Next_Measure );
run;

Die vorherigen Werte werden mit der LAG-Funktion ermittelt und die nachfolgenden Werte erfordern eine zusätzliche SET-Anweisung, die gut mit der ersten SET-Anweisung abgestimmt werden muss, um gleichzeitig noch die Variable MEASURE aus der nächsten Zeile einzulesen und dabei in Next_Measure umzubenennen.

Damit auch die allerletzte Zeile aus der Datei HAVE im 1.SET noch gelesen wird, wenn die Datei im 2. Set ja schon zu Ende ist, wird noch ein Dummy-HAVE im zweiten SET eingefügt. Sonst würde der Lesevorgang beendet werden, wenn die letzte Zeile der Datei HAVE im zweiten Set gelesen worden ist.

Dieser Lösungsansatz ist schnell und übersichtlich, wenn man den Trick mit der zweiten SET-Anweisung verstanden hat.

Im Folgenden finden Sie noch einen Lösungsansatz auf Basis von PROC SQL mit mehreren LEFT-Joins der Datei mit sich selbst:


proc sql;
	create table look_both_ways as
		select have.*
			, next.Measure as Next_Measure
			, prev.Measure as Prev_Measure           
		from have
			left join
				have as prev
				on have.ID    = prev.ID
				and 
				have.Index = prev.Index + 1
			left join
				have as next
				on have.ID    = next.ID
				and
				have.Index = next.Index - 1
	;
quit;

Diese Lösung benötigt die INDEX-Werte. Sie basiert nicht auf der Reihenfolge der Zeilen in der Datei.

Hier noch eine weitere Lösung mit PROC SQL ohne Verwendung der INDEX-Spalte, sondern durch Nutzen der Reihenfolge der Zeilen unter Verwendung von verschachtelten Queries:


proc sql;
	create table look_both_ways as
		select lookback.*, next.Measure as Next_Measure         
			from  ( select have.*, prev.Measure as 
							   Prev_Measure         
			from have left join have as prev
				on have.ID = prev.ID and 
               	   prev.Measure < have.Measure
			group by have.ID, have.Measure
				having prev.Measure = max(prev.Measure)
					) as lookback 
				left join have as next
					on lookback.ID = next.ID and 
				        next.Measure > lookback.Measure
				group by lookback.ID, lookback.Measure
					having next.Measure = 
						  min(next.Measure)
	;
quit;

Das ist ganz schön schwere Kost und schon sehr fortgeschrittenes SQL. Vielleicht ist es einfacher zu verstehen, wenn man es in zwei Schritte aufteilt und im ersten Schritt einen View erstellt, auf den man im zweiten Schritt zurückgreift.


/* Aufgesplittet in 2 Schritte:*/
proc sql;
	create view lookback as
		select have.*, prev.Measure as Prev_Measure         
			from have left join have as prev
			on have.ID = prev.ID and 
        prev.Measure < have.Measure
			group by have.ID, have.Measure
				having prev.Measure = max(prev.Measure)
	;
	create table look_both_ways as
		select lookback.*, next.Measure as Next_Measure         
			from lookback left join have as next
				on lookback.ID = next.ID and 
             next.Measure > lookback.Measure
			group by lookback.ID, lookback.Measure
				having next.Measure = min(next.Measure)
	;
quit;

In diesem Fall erzeugen alle vier Lösungen dieselben Ergebnisse. Allerdings ist die Verarbeitungsweise sehr unterschiedlich, so dass man diese vier Lösungsansätze nicht grundsätzlich gleichstellen kann.

Empfehlenswert ist der Ansatz mit dem DATA STEP, da mehrere Joins mit sich selbst bzw. verschachtelte Joins die Sache nicht wirklich schneller machen.

Diese und weitere Tipps erhalten Sie in den Kursen SAS® Programmierung 2: Datenmanagement im Data Step und SAS® SQL 1: Grundlagen.