Von zu Zeit zu Zeit werde ich nach Möglichkeiten gefragt eine größere Zahl von PDF-Reports im Batch zu erzeugen, die danach per E-Mail verteilt werden sollen. Einige Male habe ich bereits eine solche Anforderung mittels SAS ODS PDF umgesetzt. Sucht man allerdings im Internet nach Lösungen im Bereich SAS ODS (PDF) werden kaum aktuelle Informationen gefunden. Der große Teil der Informationen stammt aus der Zeit um 2010 herum. Ich werde im Folgenden zeigen, dass ODS heute immer noch eine sinnvolle Option zur Massenerstellung von Reports ist.
Hierfür habe ich ein Beispiel erstellt, dass aus frei verfügbaren Einkommensdaten für Deutschland Reports für alle Bundesländer erstellt, die zwei Tabellen und eine Karte enthalten.
An allen Stellen im Code, an denen "YOURPATH\" eingesetzt ist, müssen die Pfade auf die Verzeichnisse des Nutzers angepasst werden.
Datenaufbereitung
Zunächst wird der Tabellenbereich $A7:AJ450 des Blatts „2.4“ der Excel-Tabelle vgrdl_r2b3_bs2023.xlsx per Excel-Libname-Engine in die SAS-Tabelle WORK.EK geladen.
Die Struktur des Excel-Blatts erfordert eine Umbenennung der Spaltennamen von den SAS-Standardnamen zurück zu den Namen des Excel-Blatts.
/* Import Verfügbares Einkommen der privaten Haushalte einschl. der privaten Organisationen ohne Erwerbszweck
Excel-Blatt 2.4 */
libname xl Excel "YOURPATH\vgrdl_r2b3_bs2023.xlsx" access=readonly header=no;
DATA EK;
SET xl.'2.4$A7:AJ450'n;
RENAME
F1=LNR
F2=EU_Code
F3=REG_KEY /* District in MAPSSAS.GERMANY */
F4=LAND
F5=NUTS_1
F6=NUTS_2
F7=NUTS_3
F8=GEBIETSEINHEIT
F9=JAHR_1995
F10=JAHR_1996
F11=JAHR_1997
F12=JAHR_1998
F13=JAHR_1999
F14=JAHR_2000
F15=JAHR_2001
F16=JAHR_2002
F17=JAHR_2003
F18=JAHR_2004
F19=JAHR_2005
F20=JAHR_2006
F21=JAHR_2007
F22=JAHR_2008
F23=JAHR_2009
F24=JAHR_2010
F25=JAHR_2011
F26=JAHR_2012
F27=JAHR_2013
F28=JAHR_2014
F29=JAHR_2015
F30=JAHR_2016
F31=JAHR_2017
F32=JAHR_2018
F33=JAHR_2019
F34=JAHR_2020
F35=JAHR_2021
F36=JAHR_2022;
RUN;
Anhand der Nuts-Klassifikation werden die Datensätze auf die Datasets für Kreise, Regierungsbezirke, Bundesländer und die Bundesebene aufgeteilt. Zusätzlich wird die neue Spalte ID in der gleichen Struktur der Tabelle MAPSGFK.GERMANY erstellt. Dies wird für die Darstellung der Kreisdaten in Karten über PROC GMAP gebraucht.
/* Datei in Kreise, Regierungsbezirke, Bundesländer und BRD aufspalten */
DATA EK_KREISE EK_REGBEZIRK EK_BLAND EK_BRD;
SET EK;
LENGTH ID $15;
ID=CATS("DE-",REG_KEY);
FORMAT JAHR: COMMAX20.0;
IF NUTS_1 NE "" AND NUTS_1 NE "0" THEN
OUTPUT EK_BLAND;
IF NUTS_1 NE "" AND NUTS_1 = "0" THEN
OUTPUT EK_BRD;
IF NUTS_2 NE "" THEN
OUTPUT EK_REGBEZIRK;
IF NUTS_3 NE "" THEN
OUTPUT EK_KREISE;
RUN;
Zuletzt müssen die IDs für Berlin (REG_KEY = „11“) und Hamburg (REG_KEY = „02“) angepasst werden, damit sie der Struktur der IDs in Tabelle MAPSGFK.GERMANY entsprechen.
/* Bei Stadtstaaten muss die ID um 000 ergänzt werden, da die Werte sonst nicht zu MAPSGFK.GERMANY passen */
DATA EK_KREISE;
SET EK_KREISE;
/*Korrektur der Kreisschlüssel für Berlin und Hamburg*/
IF REG_KEY IN ("11" "02") THEN
ID=CATS("DE-",REG_KEY,"000");
RUN;
Formate für bedingte Formatierung und Kartenlegende erstellen
Zunächst werden die folgenden Gruppen für die Karten-Legende erstellt:
Proc Format;
Value EK_SKALA
low -< 20000 = "Unter 20"
20000 -< 25000 = "20 - U25"
25000 -< 30000 = "25 - U30"
30000 - high = "Über 30"
;
RUN;
Zusätzlich wird ein Format in der gleichen Skalierung zur bedingten Formatierung der Kreis-Tabellen generiert.
Proc Format;
Value EK_SKALA_COLOR
low -< 20000 = "RED"
20000 -< 25000 = "YELLOW"
25000 -< 30000 = "LIGHT GREEN"
30000 - high = "GREEN"
;
RUN;
SAS-Makro zur Reporterstellung
Das Makro enthält diverse Schleifen zur Erstellung der Reports für alle 16 Bundesländer. Zusätzlich teilt es die Kreis-Tabelle in mehrere Tabellen auf, falls diese, aufgrund der Anzahl der Datensätze, nicht auf einer PDF-Seite dargestellt werden kann.
Die einzelnen Elemente des Makros werden im Nachfolgenden genauererläutert.
Befüllung von Makrovariablen
Im ersten Teil des Makros werden Länderkürzel, die Namen der Bundesländer, deren ID und deren Anzahl in Makrovariablen geschrieben.
Diese Informationen werden gebraucht, um in einer Schleife für alle 16 Bundesländer dynamisch Reports zu generieren.
%MACRO REP_BLAENDER;
/********************************/
/* Makrovariablen definieren */
/********************************/
DATA _NULL_;
SET EK_BLAND END=EOF;
CALL SYMPUTX("BLAND_CODE"||LEFT(_N_),LAND); /* 2-stellige Kürzel für die Bundesländer in Makrovariablen schreiben */
CALL SYMPUTX("BLAND_NAME"||LEFT(_N_),GEBIETSEINHEIT); /* Namen der Bundesländer in Makrovariablen schreiben */
CALL SYMPUTX("BLAND_ID"||LEFT(_N_),ID); /* ID der Bundesländer für PROC GMAP in Makrovariablen schreiben */
IF EOF THEN CALL SYMPUTX("ANZ_BLAENDER",_N_); /* Anzahl der Bundesländer ermitteln*/
RUN;
%PUT NOTE: &=ANZ_BLAENDER;
Erstellung der Daten für die Berichtselemente je Bundesland
Für jedes Bundesland wird eine Kreis-Tabelle (Beispielname TAB_KR_BW_2022 für Baden-Württemberg) erstellt. Zusätzlich wird geprüft, ob die Tabelle mehr als 62 Sätze enthält. Dies würde erfordern, dass die Tabellen aufgesplittet und auf verschiedene PDF-Seiten verteilt werden.
%DO i=1 %TO &ANZ_BLAENDER; /* Schleife über alle Bundesländer */
/* Tabelle 1 Durchschnittseinkommen in den Kreisen des Bundeslands*/
DATA TAB_KR_&&BLAND_CODE&i.._2022; /* Erstellung Kreis-Tabelle für das gerade verarbeitete Bundesland*/
SET EK_KREISE (KEEP=ID LAND GEBIETSEINHEIT JAHR_2022) END=EOF;
LENGTH JAHR_2022_KAT $8;
JAHR_2022_KAT = PUT (JAHR_2022,EK_SKALA.);
IF EOF THEN
DO;
CALL SYMPUTX("ANZ_KR",_N_); /* Anzahl Landkreise Ermitteln für Tabellen Split*/
ANZ_TEILTABELLEN = CEIL(_N_ / 62); /* Anzahl benötigte Teiltabellen ermitteln */
CALL SYMPUTX("ANZ_TEILTABELLEN",ANZ_TEILTABELLEN);
END;
WHERE LAND="&&BLAND_CODE&i.";
RUN;
Gegebenenfalls Tabellensplit
Bayern hat als einziges Bundesland mehr als 62 Kreise, daher muss die Tabelle auf mehrere Seiten verteilt werden.
Hierzu werden zunächst in einem Data Step die Grenzen für die FIRSTOBS= und OBS= Optionen bei der Tabellendarstellung durch Proc Print generiert.
Der Code ist so gehalten, dass eine Tabelle mit beliebig vielen Sätzen in 62 Sätze zählende Datasets aufgespalten werden kann.
/* Tabellen splitten falls notwenig */
%IF &ANZ_TEILTABELLEN > 1 %THEN
%DO;
/* Hilfstabelle Grenzen Bestimmen */
DATA GRENZEN;
DO i = 1 TO &ANZ_TEILTABELLEN;
IF i = 1 THEN
DO;
FIRSTOBS=1;
OBS = 62;
PUTLOG i= FIRSTOBS= OBS=;
END;
IF i > 1 AND i < &ANZ_TEILTABELLEN THEN
DO;
FIRSTOBS = ((i-1) * 62) +1;
OBS = FIRSTOBS + 61;
PUTLOG i= FIRSTOBS= OBS=;
END;
IF i = &ANZ_TEILTABELLEN THEN
DO;
FIRSTOBS = ((i-1) * 62) +1;
OBS = FIRSTOBS + (&ANZ_KR - ((i-1)*62));
PUTLOG i= FIRSTOBS= OBS=;
END;
OUTPUT;
END;
RUN;
Die Grenzen werden danach in Makrovariablen geschrieben, die zur Erstellung der aufgeteilten Tabellen in einer Schleife genutzt werden.
/* Grenzen in Makrovariablen laden */
DATA _NULL_;
SET GRENZEN;
CALL SYMPUTX ("FIRSTOBS_"||LEFT(_N_),FIRSTOBS);
CALL SYMPUTX ("OBS_"||LEFT(_N_),OBS);
RUN;
%DO k=1 %TO &ANZ_TEILTABELLEN; /*Schleife zur Erzeugung der Teiltabellen */
DATA TAB_KR_&&BLAND_CODE&i.._2022_&k;
SET TAB_KR_&&BLAND_CODE&i.._2022 (FIRSTOBS = &&FIRSTOBS_&k OBS=&&OBS_&k);
;
RUN;
%END;
%END;
%PUT NOTE: &=ANZ_KR;
%PUT NOTE: &=ANZ_TEILTABELLEN;
Zusätzlich zur Kreistabelle wird eine Tabelle für das Bundesland erstellt, in dem das Durchschnittseinkommen des Bundeslands und der BRD enthalten ist.
/* Tabelle 2 Durchschnittseinkommen im Bundesland und Durchschnittseinkommen BRD*/
DATA TAB_&&BLAND_CODE&i.._2022;
SET EK_BLAND (KEEP=ID LAND GEBIETSEINHEIT JAHR_2022);
WHERE LAND="&&BLAND_CODE&i.";
SET EK_BRD (KEEP=JAHR_2022 RENAME=(JAHR_2022=BRD_JAHR_2022));
RUN;
PDF-Erstellung
Im Folgenden werden aus den aufbereiteten Tabellen die Berichtselemente für den PDF-Bericht generiert. Hierzu werden zunächst einige Optionen/ Parameter definiert.
Optionen/ Parameter
/********************************/
/* PDF Erstellung */
/********************************/
ODS ESCAPECHAR='^'; /* Nötig für ODS-Inline-Formatting */
OPTIONS NODATE NONUMBER; /* Auf den PDF-Seiten soll kein Datum und keine Seitennummer angezeigt werden. */
ods graphics / noborder; /* Um die erstellten Karten soll kein Rand gezeichnet werden */
ODS PDF FILE="YOURPATH\&&BLAND_CODE&i.._EK.pdf" /* Pfad und Name für Reports */
Style=htmlblue /* Standardstyle der für alle Berichtselemente genutzt werden soll */
DPI=600 /* Erhöhte DPI-Zahl für bessere Kartendarstellung */
STARTPAGE=NO /* Option um ggf. das Erstellen einer neuen Seite möglich zu machen */
;
/* Bild oben Links in den Report einfügen */
title j=l '^S={preimage=" YOURPATH\SAS_Logo_small.png"}';
/* Gesamtberichtsüberschrift in der Farbe der Überschriften des Styles htmlblue einfügen */
TITLE2 COLOR=cx112277 "Einkommen in &&BLAND_NAME&i";
ODS LAYOUT ABSOLUTE; /* Layout Typ für PDF-Erstellung festlegen*/
ODS REGION
HEIGHT=100% WIDTH=35% /* Erstellt eine Berichtsfläche für die Kreistabelle mit maximaler Höhe und 35% Breite */
x=0px y=0%; /*Die Fläche beginnt oben links unterhalb der Gesamtberichtsüberschrift */
Kreistabelle
Jetzt folgt die Erstellung des ersten Berichtselements, der Kreistabelle. Diese wird, falls sie auf eine Seite passt, direkt erstellt. Falls nicht, wird nur der erste Tail der Tabelle hier eingefügt.
Es sind einige Style Anpassungen gesetzt, z.B. werden keine Zellenrahmen in den Tabellen generiert (RULES=NONE) und eine Umrandung um die Tabelle erstellt (FRAME=BOX).
Zusätzlich wird eine bedingte Formatierung für die Variable JAHR_2022 über das oben erstellte Format EK_SKALA_COLOR definiert.
/* Pfad nur eine Tabelle benötigt */
%IF &ANZ_TEILTABELLEN = 1 %THEN
%DO;
proc print data=TAB_KR_&&BLAND_CODE&i.._2022 (OBS=62) noobs label
style(table)=[font_size=6pt RULES=NONE FRAME=BOX]
style(header)=[font_size=6pt]
;
VAR GEBIETSEINHEIT /style(data)=[font_size=6pt background=white.];
VAR JAHR_2022 /style(data)=[font_size=6pt background=EK_SKALA_COLOR.];
Label gebietseinheit = "Kreis/ Stadt";
Label JAHR_2022 = "Durschnittseikommen 2022";
run;
%END;
/* Pfad Tabellensplit nötig da zu viele Einträge für eine Seite vorhanden */
%ELSE
%DO;
/* Druck ersten Teil der gesplitteten Tabelle für Kreise */
proc print data=TAB_KR_&&BLAND_CODE&i.._2022_1 (OBS=62) noobs label
style(table)=[font_size=6pt RULES=NONE FRAME=BOX]
style(header)=[font_size=6pt]
;
VAR GEBIETSEINHEIT /style(data)=[font_size=6pt background=white.];
VAR JAHR_2022 /style(data)=[font_size=6pt background=EK_SKALA_COLOR.];
Label gebietseinheit = "Kreis/ Stadt";
Label JAHR_2022 = "Durschnittseikommen 2022";
run;
%END;
Karte Durchschnittseinkommen
Jetzt erfolgt das Erstellen einer Karte für das jeweilige Bundesland zur Darstellung des Durchschnittseinkommens auf Kreisebene.
/*Erstelle Rahmen um die Karte auf der Rechten Seite in grau passend zu html blue*/
ODS REGION
HEIGHT=75% x=35% y=0%
style={borderwidth=1pt bordercolor=cxB0B7BB}
;
goptions reset=all noborder device=png;
/* Karte 1 Kreise */
/* Erstelle Überschrift für Karte*/
ODS PDF TEXT= " ";
ODS PDF TEXT="^S={just=c foreground=cx112277 font_weight=Bold } Durchschnittseinkommen in &&BLAND_NAME&i. in Tausend Euro";
/* Legende definieren */
LEGEND1 LABEL=NONE;
/*Definition Position und Groesse der Karte*/
ODS REGION
WIDTH=55% x=40% y=4%;
PROC GMAP MAP=MAPSGFK.GERMANY (where=(id1 =("&&BLAND_ID&i")))
DATA=TAB_KR_&&BLAND_CODE&i.._2022;
ID ID;
CHORO JAHR_2022_KAT / MIDPOINTS=
"Unter 20"
"20 - U25"
"25 - U30"
"Über 30"
LEGEND=LEGEND1
;
PATTERN1 COLOR=RED;
PATTERN2 COLOR=YELLOW;
PATTERN3 COLOR=LIGHTGREEN;
PATTERN4 COLOR=GREEN;
RUN;
TITLE1;
Über die MIDPOINTS= Option werden die 4 Kategorien definiert, die immer in jedem Report vorhanden sein müssen. Die PATTERN1-PATTERN4 legen die die zu nutzenden Farben fest. Diese sind identisch mit den Farben der bedingten Formatierung und der Kreistabelle.
Vergleichstabelle Bundesland mit BRD
Zum Schluss wird auf der rechten Seite unterhalb der Karten noch die Tabelle mit dem Durchschnittseinkommen des Bundeslands und der BRD positioniert.
/* Einfügen Vergleichstabelle mit BRD */
/*Bestimmung der Position auf der Seite*/
ODS REGION
x=35% y=67%;
proc print data=TAB_&&BLAND_CODE&i.._2022 noobs label width=full
style(table)=[font_size=6pt RULES=NONE FRAME=BOX]
style(header)=[font_size=6pt]
style(data)=[font_size=6pt]
;
VAR GEBIETSEINHEIT /style(data)=[font_size=6pt background=white.];
VAR JAHR_2022 /style(data)=[font_size=6pt background=white.];
VAR BRD_JAHR_2022 /style(data)=[font_size=6pt background=white.];
Label gebietseinheit = "Bundesland";
Label JAHR_2022 = "Durschnittseikommen &&BLAND_CODE&i. 2022";
Label BRD_JAHR_2022 = "Durschnittseikommen BRD 2022";
FORMAT _NUMERIC_ COMMAX32.0;
run;
ODS LAYOUT END; ODS LAYOUT END; /* Ende des Seitenlayouts, PDF-Seite 1 */
Schleife aufgesplittete Tabellen
Sofern wie im Bundesland Bayern notwendig die Kreistabelle über mehrere Seiten geht, wird An dieser Stelle jeweils eine Seite für jeden Tabellenteil, der notwendig ist, angelegt und entsprechend befüllt.
/* Drucken der restlichen Teile der aufgesplitteten Kreis-Tabelle */
%IF &ANZ_TEILTABELLEN > 1 %THEN
%DO;
%DO z = 2 %TO &ANZ_TEILTABELLEN;
ODS PDF STARTPAGE=NOW; /* Jeweils Starten einer neuen Seite im PDF für jeden Teil der
Kreistabelle */
/* Bestimmung der Position der Tabellenteile*/
ODS LAYOUT ABSOLUTE;
ODS REGION
WIDTH=35%
x=0px y=0%;
proc print data=TAB_KR_&&BLAND_CODE&i.._2022_&z noobs label
style(table)=[font_size=6pt RULES=NONE FRAME=BOX]
style(header)=[font_size=6pt]
;
VAR GEBIETSEINHEIT /style(data)=[font_size=6pt background=white.];
VAR JAHR_2022 /style(data)=[font_size=6pt background=EK_SKALA_COLOR.];
Label gebietseinheit = "Kreis/ Stadt";
Label JAHR_2022 = "Durschnittseikommen 2022";
run;
ODS LAYOUT END;
%END; /* Schleifenende Anzahl Teiltabellen drucken*/
%END; /* Ende des Druckens der aufgesplitteten Kreis-Tabellen */
ODS PDF CLOSE;
%END; /* Ende der Schleife über alle Bundesländer */
goptions reset=all;
%MEND REP_BLAENDER;
%REP_BLAENDER;
Kompletter Code
/* Einkommensdaten unter https://www.statistikportal.de/sites/default/files/2024-11/vgrdl_r2b3_bs2023.xlsx*/
/* Import Verfügbares Einkommen der privaten Haushalte einschl. der privaten Organisationen ohne Erwerbszweck
Excel-Blatt 2.4 */
libname xl Excel YOURPATH\vgrdl_r2b3_bs2023.xlsx" access=readonly header=no;
DATA EK;
SET xl.'2.4$A7:AJ450'n;
RENAME
F1=LNR
F2=EU_Code
F3=REG_KEY /* District in MAPSSAS.GERMANY */
F4=LAND
F5=NUTS_1
F6=NUTS_2
F7=NUTS_3
F8=GEBIETSEINHEIT
F9=JAHR_1995
F10=JAHR_1996
F11=JAHR_1997
F12=JAHR_1998
F13=JAHR_1999
F14=JAHR_2000
F15=JAHR_2001
F16=JAHR_2002
F17=JAHR_2003
F18=JAHR_2004
F19=JAHR_2005
F20=JAHR_2006
F21=JAHR_2007
F22=JAHR_2008
F23=JAHR_2009
F24=JAHR_2010
F25=JAHR_2011
F26=JAHR_2012
F27=JAHR_2013
F28=JAHR_2014
F29=JAHR_2015
F30=JAHR_2016
F31=JAHR_2017
F32=JAHR_2018
F33=JAHR_2019
F34=JAHR_2020
F35=JAHR_2021
F36=JAHR_2022;
RUN;
/* Datei in Kreise, Regierungsbezirke, Bundesländer und BRD aufspalten */
DATA EK_KREISE EK_REGBEZIRK EK_BLAND EK_BRD;
SET EK;
LENGTH ID $15;
ID=CATS("DE-",REG_KEY);
FORMAT JAHR: COMMAX20.0;
IF NUTS_1 NE "" AND NUTS_1 NE "0" THEN
OUTPUT EK_BLAND;
IF NUTS_1 NE "" AND NUTS_1 = "0" THEN
OUTPUT EK_BRD;
IF NUTS_2 NE "" THEN
OUTPUT EK_REGBEZIRK;
IF NUTS_3 NE "" THEN
OUTPUT EK_KREISE;
RUN;
/* Bei Stadtstaaten muss die ID um 000 ergänzt werden, da die Werte sonst nicht zu MAPSGFK.GERMANY passen */
DATA EK_KREISE;
SET EK_KREISE;
/*Korrektur der Kreisschlüssel für Berlin und Hamburg*/
IF REG_KEY IN ("11" "02") THEN
ID=CATS("DE-",REG_KEY,"000");
RUN;
/* Format für spätere Karten-Legende */
Proc Format;
Value EK_SKALA
low -< 20000 = "Unter 20"
20000 -< 25000 = "20 - U25"
25000 -< 30000 = "25 - U30"
30000 - high = "Über 30"
;
RUN;
/* Format für bedingte Formatierung der Kreis-Tabellen-Spalte */
Proc Format;
Value EK_SKALA_COLOR
low -< 20000 = "RED"
20000 -< 25000 = "YELLOW"
25000 -< 30000 = "LIGHT GREEN"
30000 - high = "GREEN"
;
RUN;
%MACRO REP_BLAENDER;
/********************************/
/* Makrovariablen definieren */
/********************************/
DATA _NULL_;
SET EK_BLAND END=EOF;
CALL SYMPUTX("BLAND_CODE"||LEFT(_N_),LAND); /* 2-stellige Kürzel für die Bundesländer in Makroveriablen schreiben */
CALL SYMPUTX("BLAND_NAME"||LEFT(_N_),GEBIETSEINHEIT); /* Namen der Bundesländer in Makroveriablen schreiben */
CALL SYMPUTX("BLAND_ID"||LEFT(_N_),ID); /* ID der Bundesländer für PROC GMAP in Makroveriablen schreiben */
IF EOF THEN CALL SYMPUTX("ANZ_BLAENDER",_N_); /* Anzahl der Bundesländer ermitteln*/
RUN;
%PUT NOTE: &=ANZ_BLAENDER;
%DO i=1 %TO &ANZ_BLAENDER; /* Schleife über alle Bundesländer */
/* Tabelle 1 Durchschnittseinkommen in den Kreisen des Bundesland*/
DATA TAB_KR_&&BLAND_CODE&i.._2022; /* Erstellung Kreis-Tabelle für das gerade verarbeitete Bundesland*/
SET EK_KREISE (KEEP=ID LAND GEBIETSEINHEIT JAHR_2022) END=EOF;
LENGTH JAHR_2022_KAT $8;
JAHR_2022_KAT = PUT (JAHR_2022,EK_SKALA.);
IF EOF THEN
DO;
CALL SYMPUTX("ANZ_KR",_N_); /* Anzahl Landkreise Ermitteln für Tabellen Split*/
ANZ_TEILTABELLEN = CEIL(_N_ / 62); /* Anzahl benötigte Teiltabellen ermitteln */
CALL SYMPUTX("ANZ_TEILTABELLEN",ANZ_TEILTABELLEN);
END;
WHERE LAND="&&BLAND_CODE&i.";
RUN;
/* Tabellen splitten falls notwenig */
%IF &ANZ_TEILTABELLEN > 1 %THEN
%DO;
/* Hilfstabelle Grenzen Bestimmen */
DATA GRENZEN;
DO i = 1 TO &ANZ_TEILTABELLEN;
IF i = 1 THEN
DO;
FIRSTOBS=1;
OBS = 62;
PUTLOG i= FIRSTOBS= OBS=;
END;
IF i > 1 AND i < &ANZ_TEILTABELLEN THEN
DO;
FIRSTOBS = ((i-1) * 62) +1;
OBS = FIRSTOBS + 61;
PUTLOG i= FIRSTOBS= OBS=;
END;
IF i = &ANZ_TEILTABELLEN THEN
DO;
FIRSTOBS = ((i-1) * 62) +1;
OBS = FIRSTOBS + (&ANZ_KR - ((i-1)*62));
PUTLOG i= FIRSTOBS= OBS=;
END;
OUTPUT;
END;
RUN;
/* Grenzen in Makrovariablen laden */
DATA _NULL_;
SET GRENZEN;
CALL SYMPUTX ("FIRSTOBS_"||LEFT(_N_),FIRSTOBS);
CALL SYMPUTX ("OBS_"||LEFT(_N_),OBS);
RUN;
%DO k=1 %TO &ANZ_TEILTABELLEN; /*Schleife zur Erzeugung der Teiltabellen */
DATA TAB_KR_&&BLAND_CODE&i.._2022_&k;
SET TAB_KR_&&BLAND_CODE&i.._2022 (FIRSTOBS = &&FIRSTOBS_&k OBS=&&OBS_&k);
;
RUN;
%END;
%END;
%PUT NOTE: &=ANZ_KR;
%PUT NOTE: &=ANZ_TEILTABELLEN;
/* Tabelle 2 Durchschnittseinkommen im Bundesland und Durchschnittseinkommen BRD*/
DATA TAB_&&BLAND_CODE&i.._2022;
SET EK_BLAND (KEEP=ID LAND GEBIETSEINHEIT JAHR_2022);
WHERE LAND="&&BLAND_CODE&i.";
SET EK_BRD (KEEP=JAHR_2022 RENAME=(JAHR_2022=BRD_JAHR_2022));
RUN;
/********************************/
/* PDF Erstellung */
/********************************/
ODS ESCAPECHAR='^'; /* Nötig für ODS-Inline-Formatting */
OPTIONS NODATE NONUMBER; /* Auf den PDF-Seiten soll kein Datum und keine Seitennummer angezeigt werden. */
ods graphics / noborder; /* Um die erstellten Karten soll kein Rand gezeichnet werden */
ODS PDF FILE=" YOURPATH\&&BLAND_CODE&i.._EK.pdf" /* Pfad und Name für Reports */
Style=htmlblue /* Standardstyle der für alle Berichtselemente genutzt werden soll */
DPI=600 /* Erhöhte DPI-Zahl für bessere Kartendarstellung */
STARTPAGE=NO /* Option um ggf. das Erstellen einer neuen Seite möglich zu machen */
;
/* Bild oben Links in den Report einfügen */
title j=l '^S={preimage="YOURPATH\SAS_Logo_small.png"}';
/* Gesamtberichtsüberschrift in der Farbe der Überschriften des Styles htmlblue einfügen */
TITLE2 COLOR=cx112277 "Einkommen in &&BLAND_NAME&i";
ODS LAYOUT ABSOLUTE; /* Layout Typ für PDF-Erstellung festlegen*/
ODS REGION
HEIGHT=100% WIDTH=35% /* Erstellt eine Berichtsfläche für die Kreistabelle mit maximaler Höhe und 35% Breite */
x=0px y=0%; /*Die Fläche beginnt oben links unterhalb der Gesamtberichtsüberschrift */
/* Pfad nur eine Tabelle benötigt */
%IF &ANZ_TEILTABELLEN = 1 %THEN
%DO;
proc print data=TAB_KR_&&BLAND_CODE&i.._2022 (OBS=62) noobs label
style(table)=[font_size=6pt RULES=NONE FRAME=BOX]
style(header)=[font_size=6pt]
;
VAR GEBIETSEINHEIT /style(data)=[font_size=6pt background=white.];
VAR JAHR_2022 /style(data)=[font_size=6pt background=EK_SKALA_COLOR.];
Label gebietseinheit = "Kreis/ Stadt";
Label JAHR_2022 = "Durschnittseikommen 2022";
run;
%END;
/* Pfad Tabellensplit nötig da zu viele Einträge für eine Seite vorhanden */
%ELSE
%DO;
/* Druck ersten Teil der gesplitteten Tabelle für Kreise */
proc print data=TAB_KR_&&BLAND_CODE&i.._2022_1 (OBS=62) noobs label
style(table)=[font_size=6pt RULES=NONE FRAME=BOX]
style(header)=[font_size=6pt]
;
VAR GEBIETSEINHEIT /style(data)=[font_size=6pt background=white.];
VAR JAHR_2022 /style(data)=[font_size=6pt background=EK_SKALA_COLOR.];
Label gebietseinheit = "Kreis/ Stadt";
Label JAHR_2022 = "Durschnittseikommen 2022";
run;
%END;
/*Erstelle Rahmen um die Karte auf der Rechten Seite in grau passend zu html blue*/
ODS REGION
HEIGHT=75% x=35% y=0%
style={borderwidth=1pt bordercolor=cxB0B7BB}
;
goptions reset=all noborder device=png;
/* Karte 1 Kreise */
/* Erstelle Überschrift für Karte*/
ODS PDF TEXT= " ";
ODS PDF TEXT="^S={just=c foreground=cx112277 font_weight=Bold } Durchschnittseinkommen in &&BLAND_NAME&i. in Tausend Euro";
/* Legende definieren */
LEGEND1 LABEL=NONE;
/*Definition Position und Groesse der Karte*/
ODS REGION
WIDTH=55% x=40% y=4%;
PROC GMAP MAP=MAPSGFK.GERMANY (where=(id1 =("&&BLAND_ID&i")))
DATA=TAB_KR_&&BLAND_CODE&i.._2022;
ID ID;
CHORO JAHR_2022_KAT / MIDPOINTS=
"Unter 20"
"20 - U25"
"25 - U30"
"Über 30"
LEGEND=LEGEND1
;
PATTERN1 COLOR=RED;
PATTERN2 COLOR=YELLOW;
PATTERN3 COLOR=LIGHTGREEN;
PATTERN4 COLOR=GREEN;
RUN;
TITLE1;
/* Einfügen Vergleichstabelle mit BRD */
/*Bestimmung der Position auf der Seite*/
ODS REGION
x=35% y=67%;
proc print data=TAB_&&BLAND_CODE&i.._2022 noobs label width=full
style(table)=[font_size=6pt RULES=NONE FRAME=BOX]
style(header)=[font_size=6pt]
style(data)=[font_size=6pt]
;
VAR GEBIETSEINHEIT /style(data)=[font_size=6pt background=white.];
VAR JAHR_2022 /style(data)=[font_size=6pt background=white.];
VAR BRD_JAHR_2022 /style(data)=[font_size=6pt background=white.];
Label gebietseinheit = "Bundesland";
Label JAHR_2022 = "Durschnittseikommen &&BLAND_CODE&i. 2022";
Label BRD_JAHR_2022 = "Durschnittseikommen BRD 2022";
FORMAT _NUMERIC_ COMMAX32.0;
run;
ODS LAYOUT END; /* Ende des Seitenlayouts, PDF-Seite 1 */
/* Drucken der restlichen Teile der aufgesplitteten Kreis-Tabelle */
%IF &ANZ_TEILTABELLEN > 1 %THEN
%DO;
%DO z = 2 %TO &ANZ_TEILTABELLEN;
ODS PDF STARTPAGE=NOW;
/* Jeweils Starten einer neuen Seite im PDF für jeden Teil der
Kreistabelle */
/* Bestimmung der Position der Tabellenteile*/
ODS LAYOUT ABSOLUTE;
ODS REGION
WIDTH=35%
x=0px y=0%;
proc print data=TAB_KR_&&BLAND_CODE&i.._2022_&z noobs label
style(table)=[font_size=6pt RULES=NONE FRAME=BOX]
style(header)=[font_size=6pt]
;
VAR GEBIETSEINHEIT /style(data)=[font_size=6pt background=white.];
VAR JAHR_2022 /style(data)=[font_size=6pt background=EK_SKALA_COLOR.];
Label gebietseinheit = "Kreis/ Stadt";
Label JAHR_2022 = "Durschnittseikommen 2022";
run;
ODS LAYOUT END;
%END; /* Schleifenende Anzahl Teiltabellen drucken*/
%END; /* Ende des Druckens der aufgesplitteten Kreis-Tabellen */
ODS PDF CLOSE;
%END; /* Ende der Schleife über alle Bundesländer */
goptions reset=all;
%MEND REP_BLAENDER;
%REP_BLAENDER;
Schlussbemerkung
Dieses kleine Beispiel sollte zeigen, dass ODS immer noch eine gute Möglichkeit darstellt, um schnell Hunderte oder Tausende passende Reports (insbesondere auch in nächtlichen Batch-Läufen) zu erstellen.
Diese können dann ebenso leicht per Mail direkt von SAS aus verteilt werden.
Im Bezug auf das Reportdesign ist noch wesentlich mehr möglich. Verwiesen sei hier auf das Erstellen eigener Styles und erweiterte Möglichkeiten, die sich über das SAS Report Writing Interface realisieren lassen.
Ich habe versucht das Beispiel so zu gestalten, dass es sich auf viele Anwendungsfälle adaptieren lässt.
Links
Verwendete Einkommensdaten erhältlich unter:
https://www.statistikportal.de/sites/default/files/2024-11/vgrdl_r2b3_bs2023.xlsx
Hilfreiche Informationen zu SAS ODS (PDF):
https://www.wku.edu/instres/documents/huff_ods_report_writer_2014.pdf
https://www.lexjansen.com/pnwsug/2005/Pete%20Lund%20-%20PDF%20Can%20be%20Pretty%20Darn%20Fancy.pdf
https://support.sas.com/resources/papers/proceedings09/222-2009.pdf