Dateien trennen und zusammenfügen

Ich hoffe, die Überschrift verwirrt nicht zu sehr, aber mir ist spontan keine bessere für folgendes Problem eingefallen:

Ich habe eine Datei mit einer ID-Spalte und mehreren numerischen Werten, sagen wir 4 (könnten auch bis zu 20 werden):

ID Zahl_A Zahl_B Zahl_C Zahl_D

Ich möchte nun daraus 4 neue Dateien machen (PoolA-D), die alle Spalten behalten, aber nur die Einträge beinhalten, wo die Variable Zahl_i z.B. den zweithöchsten Wert aller Zahl_n annimmt. Ich verallgemeinere das ein wenig, da ich aus der Original-Datei auch Pools bilden will, in denen sich z.B. immer nur der dritthöchste Wert befindet.

Erklärungsbeispiel für zweithöchsten Wert: Wenn

ID Zahl_A Zahl_B Zahl_C Zahl_D
1 1 2 3 4

, dann kommt diese ID in den Pool_C.

Weiteres Beispiel:
ID Zahl_A Zahl_B Zahl_C Zahl_D
2 9 12 6 3

kommt in Pool A.

Insgesamt können bis zu 20.000.000 Einträge in den Dateien sein, aber mir geht es nicht um performance, sondern das es überhaupt läuft. :)

Nur den höchsten Wert auslesen kriege ich sogar hin, aber mit einen komplizierten Zählschleife, die ich zwecks Verwirrung hier weglasse. Was nicht klappt, ist eben der zweit-, dritt-, viert-, usw. -höchste Wert.

Gibt's da Ideen? Scheint mir etwas für Knobler zu sein. :)

Knobler

Wie so oft liegt die einfache Lösung bei PROC TRANSPOSE:

/* Beispieldaten */
data zahlen;
   
input id zahl_a zahl_b zahl_c zahl_d;
datalines;
1  1  2  3  4
2  9 12  6  3
3  4  2  3  1
4  0 99 66 77
;

/* muss nach Id sortiert sein, hier unnötig */
proc sort data=zahlen;
   
by id;
run;

/* transponieren,
   damit man nach den Zahlen sortieren kann */
proc transpose data=zahlen out=trans;
   
by id;
   
var zahl_a zahl_b zahl_c zahl_d;
run;

/* nach den Zahlen pro Id sortieren */
proc sort data=trans;
   
by id descending col1;
run;

/* nimm die zweitgrößte Zahl pro Id */
data zuord (keep=id _name_);
   
set trans;
   
by id;
   
if first.id then count=0;
   count+
1;
   
if count=2;/* die zweitgrößte */
run;

/* die richtigen Ids raussuchen und wegschreiben */
data pool_a pool_b pool_c pool_d;
   
merge zahlen zuord;
   
by id;
   
drop _name_;
   
select (upcase(_name_));
      
when ('ZAHL_A') output pool_a;
      
when ('ZAHL_B') output pool_b;
      
when ('ZAHL_C') output pool_c;
      
when ('ZAHL_D') output pool_d;
   
end;
run;

Anmerkung zur Performance

Das ist vielleicht eine der einfachsten Lösungen, aber sicher nicht die performanteste. Vielleicht gibt es ein Problem bei 20Mio Datensätzen, ich denke aber nicht. Die performante Lösung ist komplizierter, aber man kann alles in einem Data-Step erledigen, indem man mit Hilfe einer Array-Schleife den zweithöchsten Wert heraussucht.

Performance

ist zwar etwas komplizierter, aber für meinen Geschmack nicht einmal unübersichtlich. Hier noch die kompliziertere Lösung mit einem Bubblesort (in Anlehnung an das Sortieren einer Makrovariablenliste hier, nur diesmal nicht in Makrosprache)

data zahlen;
   
input id zahl_a zahl_b zahl_c zahl_d;
datalines;
1  1  2  3  4
2  9 12  6  3
3  4  2  3  1
4  0 99 66 77
;

/* muss nach Id sortiert sein, hier unnötig */
proc sort data=zahlen;
   
by id;
run;

data
  
zahl_a(keep=id zahl_a zahl_b zahl_c zahl_d)
  zahl_b(keep=id zahl_a zahl_b zahl_c zahl_d)
  zahl_c(keep=id zahl_a zahl_b zahl_c zahl_d)
  zahl_d(keep=id zahl_a zahl_b zahl_c zahl_d)
  ;
set zahlen;

array _zahl(4) zahl_a zahl_b zahl_c zahl_d;

/* Schreibe die Zahlen in ein zu sortierendes Array _sort(i) */
/* Merke die Zugehörigkeit 1 ^= zahl_a, etc. ebenfalls       */
/* in einem Array _index(i)                                  */
array _index(4) _temporary_;
array _sort(4)  _temporary_;

do i= 1 to 4;
  _sort(i)=_zahl(i);
  _index(i)=i;
end;
/* Sortiere parallel das Indexarray und die ursprüng-    */
/* lichen Zahlen (nun in dem Array _sort(i)) mit einem   */
/* Bubblesort (Quicksort geht nicht, da dort ein ver-    */
/* schachteltes Aufrufen erforderlich ist), ist aber bei */
/* der kleinen Anzahl zu sortierender Zahlen unkritisch. */
do until(sortiert);
  sortiert=
1;
  
do j = 1 to 3;
    k=j+
1;
    
if _sort(j)>_sort(k) then do;
      tmp_sort=_sort(j);
      _sort(j)=_sort(k);
      _sort(k)=tmp_sort;
      tmp_index=_index(j);
      _index(j)=_index(k);
      _index(k)=tmp_index;
      sortiert=
0;
    
end;
  
end;
end;
/* Ausgabe in die entsprechende Datei, je nachdem, wo der */
/* gelandet ist. Da aufsteigend sortiert wurde steht an   */
/* der Stelle, die in _index(1) steht die kleinste Zahl,  */
/* an der Position von _index(2) steht die zweitkleinste  */
/* etc.                                                   */
     
if _index(3)=1 then output zahl_a;
else if _index(3)=2 then output zahl_b;
else if _index(3)=3 then output zahl_c;
else                     output zahl_d;
run;

Gruß
Wolfgang Hornung

P.S.: Wahrscheinlich gibt es selbst hierfür noch eine SAS-Funktion.

Sortieren von Variablenwerten

Auf diesen Beitrag von Ihnen, Herr Hornung, hatte ich gehofft ;-)

Es gibt übrigens ein SUGI-Paper Quicksorting an Array, das sich ganz dem Thema des effizienten Sortierens von Arrays widmet.

Danke für den Link

ich werde das Paper zu meiner Urlaubslektüre legen ;-)

array

Hallo Leute,

was wird genau in den Array geschrieben mit _temporary_ ? Ich kenne nur _char_ oder _num_.

array _sort(4) _temporary_;

Viele Grüsse
Dirk Engfer

_temporary_

Hallo Dirk,

hier ein Link auf die Online-Doc, in Kürze:
Der Zusatz _temporary_ bedeutet, wie der Name schon aussagt, dass das Array eben temporär ist. Die einzelnen Arrayelemente benötigen keinen Variablennamen, sie kommen nicht in die Ausgabedatei und sie sind automatisch retained.

Gruß
Wolfgang Hornung