Hallo zusammen,
bei einer Problemstellung möchte ich ein Hash-Objekt für eine möglichst performante Lösung verwenden.
Ausgang ist folgende Tabelle:
data work.randomdata (drop=i j);
length key1 key2 $20. datafield $1.;
do i = 1 to 1000; /* hier absichtlich zunächst nur 1000 */
key1 = put(i, z20.);
key2 = put(floor(ranuni(0)*100000000), z20.);
if floor(2*ranuni(0)) then datafield = 'M'; else datafield = 'F';
output;
do j = 1 to 5;
if floor(2*ranuni(0)) then
do;
if floor(2*ranuni(0)) then
do;
key2 = put(floor(ranuni(0)*100000000), z20.);
if floor(2*ranuni(0)) then datafield = 'M'; else datafield = 'F';
end;
output;
end;
end;
end;
run;
Ich habe also ein Schlüsselfeld (key1). Hierfür existieren jeweils ein oder mehrere weitere Felder (key2).
Pro key2 sollte theoretisch nur ein Datenfeld (datafield) vorliegen, es kann jedoch auch vorkommen, dass hier unterschiedliche Werte pro key2 vorliegen.
Datafield ist immer entweder 'M' oder 'F'.
Der obige Code beschreibt meine Datenbasis wirklich erstaunlich gut. Aufgrund der zufälligen Werte könnte auch ein key2 mehrfach mit unterschiedlichem key1 vorkommen. Auch das habe ich in meinen Daten.
Nun möchte ich pro key1 die Anzahl der distinct key2 zählen und gleichzeitig auch noch zählen, wieviele Ausprägungen in datafield ich pro key1 habe.
Meiner Ansicht nach müsste sich ein Hash-Objekt hier bestens eignen.
Die angedachte Vorgehensweise ist für mich wie folgt:
- Sortieren der Daten nach key1
- Verarbeiten der Daten in einem Data-Step mit by-Statement (key1) und Ausgabe der Ergebnisse bei last.key1
- Nutzung eines Hash-Objektes, das pro key1 befüllt wird
-- Hash-Objekt und Zähler erzeugen
-- pro Zeile key2 als Schlüssel und datafield als Wert einfügen (wenn dieser Schlüssel schon vorliegt, wird der bestehende Satz ersetzt, somit ggf. das Datenfeld überschrieben - das ist in Ordnung für mich - pro key2 kann dann nur eine Ausprägung im Datenfeld vorliegen)
-- Ausgabe der Anzahl Sätze im Hash-Objekt mit obj.num_items
-- beim letzten Datensatz pro key1 (if last.key1) Iterierung über das Hash-Objekt und Zählung der Werte im Datenfeld (hier zähle ich zum Abgleich nochmal auch die Anzahl Datensätze im Hash komplett mit)
-- leeren des Hash für die nächste Zählung pro key1
- Da ich (per Definition) maximal 5 Datensätze pro key1 habe, sollte die Größe des Hash mit 2 hoch 10 (hashexp: 10) deutlich ausreichen.
Hier mein Code dazu:
proc sort data=work.randomdata;
by key1 key2;
run;
data work.test (keep=key1 count_m count_f count_key2 count_key2_2);
length key2 datafield $20.;
declare hash h_obj ;
h_obj = _new_ hash(hashexp: 10);
rc = h_obj.definekey("key2") ;
rc = h_obj.definedata("datafield") ;
declare hiter h_iter("h_obj") ;
rc = h_obj.definedone() ;
set work.randomdata;
by key1;
retain
count_m
count_f
count_key2
count_key2_2;
if first.key1 then
do;
count_m = 0;
count_f = 0;
count_key2 = 0;
count_key2_2 = 0;
end;
rc = h_obj.replace(key: key2, data: datafield);
if last.key1 then
do;
count_key2 = h_obj.num_items;
rc = h_iter.first();
do while (rc = 0);
if datafield = 'M' then count_m + 1;
else if datafield = 'F' then count_f + 1;
count_key2_2 + 1;
rc = h_iter.next();
end;
rc = h_obj.clear();
output;
end;
run;
Folgende Problematik habe ich noch nicht durchschaut:
- weshalb funktioniert die Zählung nicht richtig? Hier habe ich offensichtlich noch einen Fehler.
- wenn ich die Anzahl der Datensätze von 1000 unterschiedlichen key1 auf 100.000 erhöhe, erhalte ich den schönen Fehler, dass nicht ausreichend Memory zur Verfügung steht:
ERROR: Hash object added 0 items when memory failure occurred.
FATAL: Insufficient memory to execute DATA step program. Aborted during the EXECUTION phase.
ERROR: The SAS System stopped processing this step because of insufficient memory.
Das passiert bei 13592 eingelesenen Sätzen.
=> Offensichtlich funktioniert das leeren des Hash nicht korrekt.
Ich habe schon verschiedene Varianten durchgespielt, unter Anderem auch mit Löschen des Hash (h_obj.delete()) und einem vermeintlichen neuen Erzeugen des Hash pro neuem key1.
Auch mit neuem Aufruf des Konstruktors (h_obj = _new_ hash() ) komme ich leider nicht weiter.
Über sachdienliche Hinweise freue ich mich sehr
Vorerst werde ich das wohl klassisch mit einem Array lösen.
Beste Grüße
Michael
Hallo mfab,
wenn ich das richtig verstanden habe ist wahrscheinlich ein einfaches SQL-Statement völlig ausreichend:
Proc sql; create table randomdata_count as select * , count(*) as N_key2 , count(distinct key2) as N_key2_2 , count(case when datafield = 'F' then datafield else '' end) as N_F , count(case when datafield = 'M' then datafield else '' end) as N_M from randomdata group by key1 ; quit;
Viele Grüße
Jan
Hallo,
prinzipiell ist die von jh_ti_bw vorgeschlagene Lösung mit proc sql auf jeden Fall zu bevorzugen und performanter.
Das Problem bei deiner Verwendung des Hash Objects ist, dass du es bei jeder neuen Beobachtung aus dem Input-Dataset neu erstellst (und nicht nur einmalig bei der ersten Beobachtung). Im folgenden Code ist das korrigiert, jetzt sollte das Zählen funktionieren. Ich weiß nicht, ob das auch das Speicherproblem behebt, bei mir läuft es auch mit 100000 problemlos durch.
data work.test (keep=key1 count_m count_f count_key2 count_key2_2 );
set work.randomdata;
by key1;
if _N_ = 1 then
do;
declare hash h_obj;
h_obj = _new_ hash(hashexp: 10);
rc = h_obj.definekey("key2");
rc = h_obj.definedata("datafield");
rc = h_obj.definedone();
declare hiter h_iter("h_obj");
end;
retain
count_m
count_f
count_key2
count_key2_2;
rc = h_obj.replace();
if first.key1 then
do;
count_m = 0;
count_f = 0;
count_key2 = 0;
count_key2_2 = 0;
end;
if last.key1 then
do;
count_key2 = h_obj.num_items;
rc = h_iter.first();
do while (rc = 0);
if datafield = 'M' then
count_m + 1;
else if datafield = 'F' then
count_f + 1;
count_key2_2 + 1;
rc = h_iter.next();
end;
rc = h_obj.clear();
output;
end;
run;
Falls das Speicherproblem weiter besteht, kannst du mit
options fullstimer;
genauere Angaben über den Speicherverbrauch ausgeben um das Problem einzugrenzen.
Viele Grüße
Christoph
Herzlichen Dank für die Rückmeldungen!
Die SQL-Lösung wollte ich vermeiden, da die Datenmenge nicht gerade klein ist und ich im Data-Step noch weitere Schritte mache.
Ich müsste dann auch wieder einen Join an die bestehenden Daten machen und denke, dass das Hash-Objekt hier eine gute Lösung ist.
Außerdem wollte ich es auch einfach mal einsetzen 😉
Die Lösung von @chsc hat mich darauf gebracht, dass vermutlich nicht die Größe eines einzelnen Hash-Objektes das Problem sein dürfte.
Vielmehr vermute ich, dass SAS wie beschrieben bei jeder Iteration ein Hash-Objekt erzeugt und somit irgendwann der Speicher voll läuft.
Demzufolge hatte ich auch versucht das Hash mit h_obj.delete() zu löschen, was jedoch ohne Erfolg blieb.
Der Gedanke einfach ein (if _n_ = 1) ist natürlich ganz prima!
Herzlichen Dank für die Hinweise, ich würde das gerne als Lösung markieren, finde jedoch die Option dazu hier im Forum nicht.
Cheers,
Michael
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!