LesezeichenAbonnierenRSS-Feed abonnieren
mfab
Quartz | Level 8

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 Smiley (zwinkernd)

Vorerst werde ich das wohl klassisch mit einem Array lösen.

 

Beste Grüße

Michael

3 ANTWORTEN 3
jh_ti_bw
Obsidian | Level 7

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

chsc
Calcite | Level 5

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

mfab
Quartz | Level 8

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

SAS Innovate 2025: Save the Date

 SAS Innovate 2025 is scheduled for May 6-9 in Orlando, FL. Sign up to be first to learn about the agenda and registration!

Save the date!

Diskussionsstatistiken
  • 3 Antworten
  • 1856 Aufrufe
  • 2 Kudos
  • 3 in Unterhaltung