Archive for the ‘Data Step’ Category

h1

Je garde ou je jette? les variables

août 25, 2008

Pour garder ou supprimer sous SAS des variables, il y a les mots-clés KEEP (garder) et DROP (enlever). Sélectionner les variables nécessaires par la suite et seulement celles-ci est très important. Cela fait partie des outils pour améliorer la performance d’un programme tant en terme de temps d’exécution que le volume demandé pour stocker les data sets. Voici plus en détails, et avec des exemples, l’utilisation de ce vocabulaire qui s’applique au data step, aux procédures et à la syntaxe de l’ODS OUTPUT.

1. L’option dans un data step

Les mots KEEP et DROP servent principalement en tant qu’option appliquée à un data set donné. Elles sont alors listées juste après le nom du data set entre parenthèses et sont suivies du signe égal :

  • Keep= Data Set Option : nom_du_dataset (keep=nom_var1 nom_var2 etc)
  • Drop= Data Set Option : nom_du_dataset (drop=nom_var1 nom_var2 etc)

Exemple 1 : une instruction SET

data class (drop=weight: height:);
   set sashelp.class (keep=name weight height);
   weight_kg = weight*0.45359237;
   height_m  = height*0.0254;
   bmi       = weight_kg/height_m**2;
run;

Dans le cas présent, les variables NAME (nom), WEIGHT (poids) et HEIGHT (taille) sont lues dans le fichier d’origine SASHELP.CLASS et gardées. De nouvelles variables sont calculées pour avoir un poids en kilogramme (WIEGHT_KG) et une taille en mètres (HEIGHT_M). A partir du poids et de la taille, l’indicateur de masse corporelle (BMI) est calculé. Les variables, dont le nom commence par WEIGHT et HEIGHT, ne sont plus nécessaires par la suite. Elles sont donc supprimée dans le data set final appelé CLASS.

Soit le mot KEEP, soit le mot DROP est donné en option mais pas les deux afin d’éviter les confusions. Le choix entre KEEP et DROP dépend souvent du nombre de variables à lister par la suite. C’est donc un choix purement pratique.

Note, Indice de masse corporelle : L’indice de masse corporel (Body Mass Index, BMI) est égal au poids divisé par la taille au carré (poids/taille2). Le site de l’Organisation Mondiale de la Santé (World Health Organisation, WHO) donne des précisions sur le sujet.

Note, Conversion des unités de mesures (source Wikipedia) : je suppose que la taille donnée dans le fichier SASHELP.CLASS est exprimée en pouces (inches) et que le poids est exprimé en livres (pounds). Sachant qu’un pouce est égal à 2,54 cm et qu’une livre est égale à 0,45359237 kg, les tailles et poids du premier exemple ont pu être convertis en mètres et kilos.

Exemple 2 : une instruction MERGE

data age_ae;
   merge ae      (in=ref keep=name ae_id ae_sev)
         patient (keep=name age);
   by name;
   if ref;
run;

Dans ce second exemple, les patients ayant eu un effet secondaire (adverse event, AE) sont enregistrés dans le data set AE. Chaque effet secondaire est identifié de manière unique par les variables NAME et AE_ID. La sévérité de l’effet secondaire nous intéresse dans le data set AE.

A cette information, est ajouté l’âge du patient disponible dans la variable AGE du data set PATIENT.

La variable commune aux deux data sets est NAME. Il faut donc qu’elle reste dans les deux data sets. Seuls les patients ayant eu un effet secondaire sont sélectionnés grâce à l’option IN.

Pour tester l’exemple, vous trouverez en fin d’article un code créant les fichiers PATIENT et AE.

NOTE : Une variable utilisée par une autre option du data set comme RENAME ou WHERE ne pourra pas être supprimée au même moment.

2. Quelques exemples de procédures

Dans une procédure, elles suivent le nom du data set d’entrée et/ou du data set de sortie. Aucun autre mot ne doit être inséré entre le nom du data set et les options entre parenthèses.

  • proc sort data=… () out=…();
  • proc print data=… () width=min;
  • proc transpose data=…() out=…() prefix=visit;
  • proc freq data=…();
  • proc report data=…() split=’#’;
  • proc tabulate data=…()
  • proc gplot data=…()
  • proc boxplot data=…()
  • proc univariate data=…()
  • proc ttest data=…()
  • etc.

La procédure SQL liste les variables à garder après le mot SELECT. Il n’y a pas à ce stade d’option pour supprimer les variables. Par contre, on peut affiner la sélection après que le data set final soit créé. Bien sûr, le temps de lecture est augmenté puisque toutes les variables sont lues pour créer le data set et non un sous-ensemble.

proc sql;
   create table test (drop=ae_sdt ae_edt) as
      select a.*, age
      from ae a
      left join
           patient b
      on a.name=b.name;
quit;

NOTE : L’option WHERE est très pratique lorsqu’on en peut faire une sélection que sur le résultat de la fusion.

proc sql;
   create table test (where=(ae_sev=1 or age=12)) as
      select a.*, age
      from (select name, ae_id, ae_sev
            from ae) a
      left join
           (select name, age
            from patient) b
      on a.name=b.name;
quit;

3. L’option dans l’ODS OUTPUT

Les sorties générées par une procédure sont redirigeables vers un data set via l’instruction ODS OUTPUT. Le nom de la sortie est alors suivi du signe égal et du nom du data set de destination. Après ce nom les options sont ajoutables.

ods exclude all;
ods output onewayfreqs=exemple_ods (keep=age frequency percent);
proc freq data=sashelp.class;
   table age;
run;
ods output clear;
ods exclude none;

Deux articles sur l’ODS OUTPUT sont déjà à votre disposition

4. Les instructions KEEP et DROP dans un data step

En plus des options KEEP et DROP, il existe les instructions KEEP (KEEP Statement) et DROP (Drop Statement) pouvant être exécutées dans un data step. L’important ici est de se souvenir que l’instruction s’applique à la fin du data step, une fois que le data set final est créé. Ainsi il n’est pas possible de supprimer une variable en milieu de programme pour ensuite créer une autre variable du même nom.

data class (drop=i);
   do i=0 to 3;
      output;
   end;
   do i=10 to 12;
      output;
   end;
run;

data class;
   do i=0 to 3;
      output;
   end;
   drop i;
   do i=10 to 12;
      output;
   end;
run;

Les deux exemples ci-dessus font le même travail. La variable I n’apparaîtra pas dans le data set final car elle est supprimée en fin de programme.

Lectures complémentaires : Outre les options KEEP et DROP, le programmeur utilisera souvent les options RENAME et WHERE et de temps en temps les options FIRSTOBS et OBS. Pour une liste complète des options, consultez la documentation en ligne : SAS Data Set Option.

Après savoir comment supprimer les variables et donc les colonnes d’un data set SAS, vous serez peut-être intéressé de savoir comment supprimer ou garder certaines lignes d’un data set avec les mots-clés DELETE (DELETE Statement) et OUTPUT (OUTPUT Statement).

Annexe : Créer les data sets PATIENT et AE pour tester l’exemple avec MERGE.

data patient;
   set sashelp.class;
run;

data ae;
   set sashelp.class (keep=name);
   if name=‘Thomas’ then
      do;
         ae_id  = 1;
         ae_sdt = ’21MAR2007’d;
         ae_edt = ’28APR2007’d;
         ae_sev = 3;
         output;
         ae_id  = 2;
         ae_sdt = ’03JUN2007’d;
         ae_edt = ’19JUN2007’d;
         ae_sev = 1;
         output;
      end;
run;

h1

Quand compilation et exécution font la différence, un exemple

août 21, 2008

SAS effectue plusieurs lectures d’un programme. A la première lecture, c’est la compilation. A la seconde, c’est l’exécution. Connaître ces notions vous aidera à comprendre les exemples ci-dessous : pourquoi le premier code proposé ne fonctionne pas alors que les autres passent. Les fonctions PUT, VVALUE, le dictionnaire DICTIONARY.COLUMNS et le DATA _NULL_ serviront dans les exemples.

1. Un premier exemple avec la fonction PUT

Rappel sur la fonction PUT : Une fonction PUT permet de convertir une variable numérique en variable texte ou une variable texte en une autre variable texte. Elle est composée de deux paramètres. D’un côté, il y a la variable d’origine. De l’autre côté, il y a le format à appliquer sur cette variable d’origine. C’est donc la valeur sous forme formatée qui devient une valeur texte. Reportez vous à l’article « Convertir une variable caractère en numérique et inversement » pour plus de détails.

La phase de compilation : A la compilation, SAS  vérifie que la variable d’origine et le format associé sont tous les deux du même type. Il faut qu’une variable numérique est un format qui s’applique à une variable numérique. De manière identique, il faut un format caractère pour une variable texte.

La phase d’exécution : Si on ne connaît pas à l’avance le type de la variable, on peut avoir envie de définir une condition : si la variable est numérique applique tel format, sinon applique tel autre format. Hors une condition IF/THEN n’est visible par SAS qu’à la phase d’exécution.

En d’autres termes, SAS tentera d’appliquer un format numérique à une variable numérique avant de regarder si la fonction PUT est définie dans une condition.

data import_excel;
   retain type ‘CHAR’;
   study = ‘999’;
   output;
   study = ‘888’;
   output;
run;

data study_new;
   set import_excel;
   if type=‘NUM’ then study_new=put(study,best.);
   else study_new=study;
run;

Le résultat : Dans l’exemple, un format numérique (BEST.) est appliqué à une variable texte (STUDY). La première partie de la condition ne s’applique pas car le type de la variable n’est pas égal à NUM. Mais SAS cherche un format $BEST. qui n’existe pas. Il est obligé de s’arrêter là.

28 data study_new;
29 set ref;
30 if upcase(type)=’NUM’ then study_new=put(study,best.);
                                                  —–
                                                  48

ERROR 48-59: The format $BEST was not found or could not be loaded.

31 else study_new=study;
32 run;

NOTE: The SAS System stopped processing this step because of errors.
WARNING: The data set WORK.STUDY_NEW may be incomplete.
When this step was stopped there were 0 observations and 3 variables
.

Un exemple où le type de la variable est inconnu : en important un fichier EXCEL (PROC IMPORT/MIXED=YES), le type de la variable sous SAS peut-être inconnu. La variable sera caractère si une cellule contient du texte. Sinon, elle sera numérique.

2. Une solution rapide

Une fonction Vxxx: la fonction VVALUE retourne une valeur texte. Elle utilise le format associé à la variable en interne pour construire la valeur sous forme formatée. La nouvelle variable aura une longueur de 200.

data study_new;
   set ref;
   if type=’NUM’ then study_new=vvalue(study);
   else study_new=study;
run;

3. Une solution plus lourde mais pouvant s’appliquer à plusieurs variables

Une autre solution est de générer le code à exécuter. Si la variable est numérique, c’est l’instruction avec PUT qui apparaîtra, sinon c’est l’autre instruction. Cette approche fait appel à la notion de dictionnaire et de DATA _NULL_.

3.1 Créer un data set contenant le nom des variables et leur type à partir du dictionnaire (dictionary) appelé COLUMNS

La première étape consiste à créer un data set, nommé DICT_REF, contenant la variable STUDY et son type.

Choix du dictionnaire : Le dictionnaire (dictionary) COLUMNS est un data set de référence, créé de manière automatique par SAS. Il répertorie toutes les variables contenues dans tous les data sets de toutes les bibliothèques actives. Il contient donc une ligne par variable.

Chaque ligne du dictionnaire contient plusieurs informations caractérisant cette variable dont :

  • la bibliothèque d’origine (variable LIBNAME)
  • le data set d’origine (variable MEMNAME)
  • le nom de la variable (variable NAME)
  • le type de la variable (variable TYPE).

Prendre un sous-ensemble du dictionnaire : Ici seule la variable STUDY est utile. Elle provient du data set EXCEL_IMPORT sauvegardé de manière temporaire dans la bibliothèque WORK.

Mettre en majuscule : Le type de la variable est soit « num » soit « char », toujours en minuscule. Le nom de la variable peut avoir un mélange de majuscules et minuscules selon la manière dont est saisi le nom lors de sa création. Pour éviter des surprises, le nom de la variable et le type sont mis en majuscule grâce à la fonction UPCASE. L’avantage des dictionnaires, c’est qu’ils peuvent lister plus d’une variable.

proc sql;
   create table dict_ref as
      select upcase(name) as name,
             upcase(type) as type
      from dictionary.columns
      where upcase(libname) = ‘WORK’ and
            upcase(memname) = ‘EXCEL_IMPORT’ and
            upcase(name)    = ‘STUDY’;
quit;

3.2 Ecrire le code et l’appeler

La deuxième étape consiste à créer un programme nommé TMP_STUD.SAS donc le nom et l’emplacement est défini dans l’instruction FILENAME. Pour écrire dans ce fichier, l’instruction FILE est ajoutée dans le DATA _NULL_. Pour plus de précisions sur l’instruction PUT, reportez-vous à l’article « Ecrire un texte avec l’instruction PUT« .

filename stud ‘C:/sasref/tmp_stud.sas’;

data _null_;
   set dict_ref;
   file stud;
   put ‘data study_new;’;
   put @3 ‘set excel_import;’;
   if type=‘NUM’ then put @3 ‘study_new=put(‘ name ‘,9.);’;
   else put @3 ‘study_new=’ name ‘;’;
   put ‘run;’;
run;

%include stud;
filename stud;

L’écriture du programme est fonction des informations contenues dans le DICT_REF. Si plus d’une variable est sélectionnée, il faudrait préciser deux choses :

  • Les instructions DATA et SET ne sont à écrire qu’une fois. Elles sont ajoutées lors de la première boucle fait en interne autour de l’étape DATA via « if _N_=1 ».
  • L’instruction RUN apparaît une fois en fin de programme. Il faut donc l’ajouter une fois le dernier record du fichier de référence atteint. L’option END= de l’instruction SET crée une variable interne prenant la valeur 0 pour tous les records sauf le dernier où elle prend la valeur 1. La condition est donc basée sur cette variable.

filename stud ‘C:/sasref/tmp_stud.sas’;

data _null_;
   set dict_ref end=eof;
   file stud;
   if _N_=1 then
      do;
         put ‘data study_new;’;
         put @3 ‘set excel_import;’;
      end;

   if type=‘NUM’ then put @3 ‘study_new=put(‘ name ‘,9.);’;
   else put @3 ‘study_new=’ name ‘;’;
   if eof then
 put ‘run;’;
run;

%include stud;
filename stud;

Le code sauvegardé dans le fichier TMP_STUD.SAS se présente donc ainsi, vu que la variable STUDY est de type caractère.

data study_new;
   set excel_import;
   study_new=STUDY ;
run;

L’instruction %INCLUDE permet l’exécution de ce code.

h1

Répéter une action sur plusieurs variables avec le langage macro

août 13, 2008

Sous SAS, deux possibilités sont envisageables pour répéter une action sur plusieurs variables. Dans le cas d’un data step, la syntaxe de l’ARRAY est tout à fait appropriée. Dans d’autres cas, le langage macro peut s’avérer plus pertinent. Voici donc une présentation de l’approche via le langage macro.

1. L’exemple

Pour illustrer le propos, le programme aura pour but de définir un PROC REPORT contenant toutes les variables du data set SASHELP.CLASS. Si on connaît le nom des variables, la syntaxe se résume de la manière suivante.

proc report data=sashelp.class;
   columns name age sex weight height;
   define name   / display;
   define age    / display;
   define sex    / display;
   define weight / display;
   define height / display;
run;

Mais si on ne connaît pas le nom des variables par avance, il faut automatiser la tâche.

2. Remplacer le nom des variables par des macros variables

Dans l’exemple suivant, le nom de chaque variable est sauvegardé dans une macro variable. Ces macros variables ont une structure particulière :

un préfixe commun + un nombre

%let class1 = name;
%let class2 = age;
%let class3 = sex;
%let class4 = weight;
%let class5 = height;

proc report data=sashelp.class;
   columns name age sex weight height;
   define &class1. / display;
   define &class2. / display;
   define &class3. / display;
   define &class4. / display;
   define &class5. / display;
run;

3. Répéter l’instruction DEFINE grâce à une boucle

Grâce à cette structure particulière, une boucle peut être envisagée. L’instruction DEFINE est alors répétée autant de fois qu’il y a de variables. La boucle est définie par une macro variable « I » qui prend des valeurs allant de 1 à 5. La partie nombre de la macro variable est donc remplacée par la valeur de la macro variable « I ».

NOTE : Pour résoudre la macro variable lors de la première boucle, SAS effectue deux lectures. A la première lecture, les deux perluètes (ampersand) && se transforment en un seul ; &i. se transforme en 1. On a donc &CLASS1. A la deuxième lecture, SAS résout la macro variable &CLASS1. comme précédemment.

%macro test;
   proc report data=sashelp.class;
   columns name age sex weight height;
   %do i=1 %to 5
      define &&class&i. / display;
   %end;
   run;
%mend test;
%test;

4. Créer les macros variables de manière automatique

Pour créer les macros variables automatiquement, il faut agir en deux étapes.

  1. Enregistrer chacune des noms de variables du data set choisi dans PROC REPORT (SASHELP.CLASS) dans un nouveau data set (LST_VAR) et plus particulière dans une variable (NAME).
  2. Associer un numéro à chaque nom de variable (compteur) et convertir l’information en macro variable (CALL SYMPUT).

proc sql;
   create table lst_var as
      select name
      from dictionary.columns
      where upcase(libname)=‘SASHELP’ and
            upcase(memname)=‘CLASS’;
quit;

data _null_;
   set lst_var;
   cnt+1;
   call symput (cats(‘CLASS’,put(cnt,best.)),name);
run;

5. Compter le nombre de variables de manière automatique

Si le nombre de variable dans le data set n’est pas connu à l’avance, il faut le retrouver. Cette information est ensuite sauvegardée dans une macro variable, disons MAX_VAR, et remplacera notre nombre 5. L’article « 3 méthodes pour construire des macro variables sans macro » vous donnera plus de précisions concernant la création d’une macro variable.

Obtenir rapidement le nombre de variables dans un data set : Un moyen pour trouver le nombre de variables est de faire appel au dictionnaire de SAS intitulé TABLES.

proc sql noprint;
   select nvar into : max_var
   from dictionary.tables
   where upcase(libname)=‘SASHELP’ and
         upcase(memname)=‘CLASS’;
quit;

Plus de flexibilité sur la liste des variables concernées : Un autre moyen pour compter le nombre de variables est d’agir en deux étapes.

  • Enregistrer dans un variable d’un nouveau data set chacune des noms de variables de SASHELP.CLASS.
  • Compter le nombre d’observations dans ce data set. Vous pouvez vous reporter à l’article « Combien d’observations dans mon data set » pour plus de précisions sur les différentes alternatives.

Dans notre exemple, il s’agit de créer le data set LST_VAR pour la première étape. Le code de la section 4 est tout à fait suffisant pour cela. Ensuite, CALL SYMPUTX peut servir à sauvegarder l’information dans une macro variable.

data _null_ ;
   call symputx(‘max_var’,_N_-1);
   set lst_var;
run;

Pourquoi vous ai-je proposé cette alternative ?  Ici, toutes les variables sont sélectionnées. Mais si seulement quelques une sont choisies, seule la seconde alternative marche. Voici quelques sous-sélections possibles.

  • Sélectionner toutes les variables numériques,
  • Sélectionner toutes les variables finissant pas _X,
  • etc.

En résumé : En résumé, le code se décompose en 2 étapes : créer les macros variables et utiliser ces macros variables pour définir une boucle.

*1. Créer les macros variables CLASS1 à CLASS5, MAX_VAR.;

*1.1 Créer le data set LST_VAR servant de fichier de référence.;

proc sql;
   create table lst_var as
      select name
      from dictionary.columns
      where upcase(libname)=‘SASHELP’ and
            upcase(memname)=‘CLASS’;
quit;

*1.2 Créer les macro variables CLASS1-CLASS5 en se basant sur le data set LST_VAR créé précédemment.;

data _null_;
   set lst_var;
   cnt+1;
   call symput (cats(‘CLASS’,put(cnt,best.)),name);
run;

*1.3 Créer la macro variable VAR_MAX en se basant sur le data set LST_VAR créé précédemment.;

data _null_ ;
   call symputx(‘max_var’,_N_-1);
   set lst_var;
run;

*2. Reporting : appeler les différentes macro variables pour créer la boucle autour de l’instruction DEFINE.;

%macro test;
proc report data=sashelp.class;
   columns name age sex weight height;
   %do i=1 %to &max_var.;
      define &&class&i. / display;
   %end;
run;
%mend test;
%test;

h1

2 suggestions pour grouper les valeurs d’une variable

juillet 21, 2008

Sous SAS, lorsqu’une variable contient plusieurs valeurs à regrouper pour n’en former qu’une seule, il existe plusieurs options. Voici deux suggestions : une basée sur la notion de RETAIN et FIRST/LAST, l’autre sur PROC TRANSPOSE et ARRAY.

Pour illustrer le propos un data set liste plusieurs actions pour un patient à une visite donnée. Il s’agit de regrouper ces actions par patient et visite dans un seul record.

Le data set avant

pat_id  visit_dt rec_id action

   1   02APR2007    1   RAYON X
   1   02APR2007    2   ULTRASON
   1   02APR2007    3   SCANNER
   2   15NOV2007    2   RAYON X
   2   15NOV2007    1   ULTRASON

Le data set après : une nouvelle variable caractère est créée ACTION_LST. On lui donnera une longueur de 200. Chaque action y est séparée par une barre. Les variables REC_ID (identifiant du record) et ACTION sont supprimées.

pat_id  visit_dt action_lst

   1   02APR2007 RAYON X | ULTRASON | SCANNER
   2   15NOV2007 ULTRASON | RAYON X

1. La force du RETAIN

Pour débuter une variable ACTION_LST de longueur 200 est créée. Elle ne contient à la base aucune valeur.

data final; *(drop = rec_id action);
   set orig;
   by pat_id visit_dt;
   length action_lst $200;
   retain action_lst ‘ ‘;
   if first.visit_dt then action_lst=action;
   else action_lst=catx(‘ | ‘,action_lst,action);
   *if last.visit_dt then output;
run;

Pour chaque nouvelle visite de chaque patient, ACTION_LST est initialisée. Elle prend la valeur de la variable ACTION.

Du fait de la présence de l’instruction RETAIN, cette première valeur est maintenue pour tous les records d’une même visite. A chaque nouvelle lecture d’un record, une nouvelle action est ajoutée.

La fonction CATX permet de concaténer les valeurs d’ACTION_LST et ACTION, et d’ajouter la barre comme délimiteur.

Voici donc le résultat intermédiaire, avant l’activation du code mis en commentaires.

pat_id  visit_dt action_lst

   1   02APR2007 RAYON X
   1   02APR2007 RAYON X | ULTRASON
   1   02APR2007 RAYON X | ULTRASON | SCANNER
   2   15NOV2007 ULTRASON
   2   15NOV2007 ULTRASON | RAYON X

A présent, il s’agit de garder seulement le dernier record de chaque visite par patient avec LAST.VISIT_DT et à supprimer les variables REC_ID et ACTION.

2. Rotation de données (PROC TRANSPOSE) et lecture en boucle (ARRAY)

Avec cette seconde approche, le travail est découpé en deux étapes à commencer.

Dans un premier temps, un PROC TRANSPOSE pour faire pivoter les données. Le data set n’a alors plus qu’une ligne par visite de patient. Chaque action apparaît dans une colonne donnée.

proc transpose data=orig out=final2 (drop=_name_);
   by pat_id visit_dt;
   var action;
run;

pat_id  visit_dt  COL1      COL2      COL3

   1   02APR2007  RAYON X   ULTRASON  SCANNER
   2   15NOV2007  ULTRASON  RAYON X

Dans un second temps un ARRAY nommé _ACTION est défini. Il contient toutes les variables commençant par COL. A chaque nouvelle lecture d’une variable COL, sa valeur est ajoutée à celle de la variable ACTION_LST.

data final2 (drop=i col:);
   set final2;
   length action_lst $200;
   array _action {*} col:;
   do i=1 to dim(_action);
      action_lst = catx(‘ | ‘,action_lst,_action{i});
   end;
run;

Annexe : Création du data set utilisé pour l’exemple.

data orig;
   input pat_id visit_dt date9. rec_id action $15.;
   format visit_dt date9.;
   datalines;
1 02APR2007 1 RAYON X
1 02APR2007 2 ULTRASON
1 02APR2007 3 SCANNER
2 15NOV2007 2 RAYON X
2 15NOV2007 1 ULTRASON
;
run;

proc sort data=orig;
   by pat_id visit_dt;
run;

h1

Identifier et supprimer les doublons

juillet 14, 2008

Identifier et supprimer les doublons dans un data set fait partie des compétences de base du programmeur SAS. Trois approches sont envisageables : PROC SQL, PROC SORT et un compteur dans un data step.

Qu’appelle-t-on doublon ? des lignes complètement identiques ou seulement des lignes ayant quelques variables communes ? Il est important de savoir si deux records ayant des observations communes sont considérés comme doublons ou s’il faut que toutes les observations soient identiques. Si seules quelques variables sont considérées, il faut savoir quelle ligne est conservée et lesquelles sont supprimées.

Le data set utilisé pour l’exemple est composé de trois variables CNTRY (country), PAT_ID (patient ID) et VAL (value). Le patient 2 de Chine a trois lignes d’observations dont deux strictement identiques.

cntry    pat_id    val

 CN        1        A
 CN        2        B
 CN        2        C
 CN        2        C
 HK        3        E 

1. PROC SQL

Avec la procédure SQL, il est possible de faire des calculs et de baser sa sélection d’observations sur ce calcul. En d’autres termes, il est possible de compter le nombre de fois qu’une valeur apparaît. Pour identifier les records avec doublons, on prend ceux comptés plus d’une fois.

Première présentation : Voici, tout d’abord, une présentation de PROC SQL décomposable en deux étapes.

proc sql;
   create table two (where=(cnt_pat > 1)) as
      select cntry, pat_id, val, count(*) as cnt_pat
      from one
      group by cntry, pat_id;
quit;

Dans un premier temps, une variable donne le nombre de records par patient dans un pays donné (CNT_PAT) grâce à la syntaxe GROUP BY et la fonction COUNT. A ce stade, on obtient le data set suivant :

cntry pat_id val cnt_pat

  CN     1    A     1
  CN     2    C     3
  CN     2    B     3
  CN     2    C     3
  HK     3    E     1

Dans un second temps, seules les observations ayant des doublons sont gardées, une fois le nouveau data set créé, grâce à l’option WHERE=.

Deuxième présentation : Au lieu de créer la variable CNT_PAT et de faire une sélection ultérieure, la fonction COUNT peut être  ajoutée dans une condition introduite par le mot HAVING et faisant toujours appel à la fonction COUNT.

Pour ne voir qu’une des lignes multiples, il suffit d’ajouter DISTINCT.

proc sql;
   create table two_bis as
   select /*distinct*/ cntry, pat_id, val
   from one
   group by cntry, pat_id
   having count(*) > 1;
quit;

A l’inverse, pour ne sélectionner que les observations n’apparaissant qu’une fois, « >1 » sera remplacé par « =1 ». Seules les observations ayant exactement une occurrence sont sélectionnées.

Le DISTINCT s’applique à l’intégralité des observations. Cela permet de ne garder que des lignes uniques.

Il n’est pas possible de garder la première ligne parmi les doublons définis par des variables précises (CNTRY et PAT_ID dans l’exemple) avec cette méthode.

2. Les options de la procédure PROC SORT (NODUP/NODUPRECS, NODUPKEY)

Il existe deux options dans la procédure PROC SORT pour supprimer les doublons selon qu’ils s’appliquent

  • à toute une ligne d’observation (NODUPRECS dont l’alias est NODUP) ou
  • à une liste de variables précises données dans l’instruction BY (NODUPKEY).

Au choix, le nouveau data set sans doublons remplace l’ancien ou est sauvegardé dans un dataset différent introduit par OUT=.

Les observations ayant été exclues peuvent être sauvegardées dans un nouveau data set dont le nom est défini par DUPOUT=.

proc sort data=one out=three nodupkey dupout=three_bis;
   by cntry pat_id;
run;

proc sort data=one out=four noduprecs /*nodup*/ dupout=four_bis;
   by cntry pat_id;
run;

3. Appliquer une variable compteur

Enfin, après PROC SQL et PROC SORT, une troisième possibilité pour identifier et supprimer les doublons, est d’ajouter une variable compteur.

Dans l’exemple ci-dessous, la variable compteur est nommée CNT. Pour la première observation d’un patient d’un pays donné, le compteur est initialisé à 1. Pour chaque nouveau record du patient, le compteur est incrémenté de 1. Quand la variable CNT est égale à 1, le record est ajouté dans le data set FIVE. Sinon il est ajouté dans le data set FIVE_BIS. A la fin, la variable compteur est supprimée.

Ainsi le data set FIVE contient les records sans doublons. Dans le cas du patient doublonné (patient 2), c’est la première observation qui est conservée.

Dans le data set FIVE_BIS, les records exclus de la première sélection sont conservés.

data five (drop=cnt) five_bis (drop=cnt);
   set one;
   by cntry pat_id val;
   if first.pat_id then cnt=1;
   else cnt+1;
   if cnt = 1 then output five;
   else output five_bis;
run;

Annexe :

data one;
   input cntry $ pat_id $ val $;
   datalines;
CN 1 A
CN 2 B
CN 2 C
CN 2 C
HK 3 E
;
run;

h1

Penser conditionnel (1/3) : La base du IF

juin 19, 2008

Selon que des variables aient certaines valeurs ou non, le programmeur peut décider de créer une nouvelle variable, extraire une ligne d’observation, etc.

SAS propose plusieurs syntaxes pour exprimer ces conditions.

  • Dans le data step et la procédure SQL, pour extraire un sous ensemble de records sans autre action le WHERE est probablement le plus adapté. De plus, dans d’autres cas que le data step et la PROC SQL l’option WHERE est aussi utilisable (PROC SORT, PROC FREQ, ODS OUTPUT…).
  • Sinon, dans un data step, on rencontre le IF et le SELECT et dans une procédure SQL, CASE WHEN est disponible.

Ce sujet sera divisé en 3 articles à commencer par la syntaxe du IF. Dans huit jours, le prochain rendez-vous sera consacré au SELECT. Enfin le dernier article de la série s’intéressera au CASE WHEN.

1. Le minimum ou presque

« S’il pleut, alors je prend mon parapluie, sinon, je le laisse au bureau. » Dans cette phrase, on repère trois mots : si, alors, sinon. Traduisez par IF, THEN, ELSE. C’est trois mots, version anglaise, forme la syntaxe de base.

Dans l’exemple suivant, on considère les variables AGE et SEX pour créer la variable PP_NY (per protocol population, no/yes) : « Si ma variable âge est supérieure à 13 et qu’il s’agit d’hommes alors ma variable pp_ny=1, sinon pp_ny=0. »

data class;
set sashelp.class;
if age > 13 and sex=‘M’ then pp_ny=1;
else pp_ny=0;
run;

On remarquera l’usage de l’instruction finale ELSE pour inclure tous les cas non pris en compte précédemment.

2. Multiplier les combinaisons

Dans ce second exemple, une instruction ELSE IF a été ajoutée. Ainsi tous les hommes de 13 ans ou moins pour lesquels on connaît l’âge sont sélectionnés dans cette seconde instruction. Enfin, les valeurs restantes correspondent aux lignes d’observations où l’âge est manquant ou concernant les femmes.

data class;
set sashelp.class;
if age > 13 and sex=‘M’ then pp_ny=1;
else if age > .z and sex=‘M’ then pp_ny=0;
else pp_ny=.;
run;

 

ELSE IF : il est préférable d’utiliser le ELSE IF, plutôt que le IF pour des raisons de performance et pour éviter des erreurs d’étourderie.

  • Par performance j’entends : seules les observations qui ne sont pas valides dans la première condition sont lues pour évaluer la condition suivante. C’est un temps très précieux quand on traite une grosse base de données.
  • Par erreur j’entends : en aucun cas, des observations peuvent-être sélectionnées par deux instructions. Ainsi si PP_NY=1, il n’y a aucune chance qu’il devienne PP_NY=0 après exécution de la seconde instruction.

Important : dans l’exemple précédent, j’ai volontairement choisi de lister plus d’une variable pour définir ma condition. En effet, si vous avez une seule variable en entrée et une seule variable en sortie, un informat suffit. Plusieurs avantages se cachent derrière :

  • le programme est allégé
  • l’accès aux valeurs est plus évident puisque les formats font partie des informations données en début de programme.

Voici ce que cela donnerait si on ne considérerait que la variable AGE pour définir la variable PP_NY.

proc format;
   invalue age_pp
   ._.z    = .
low-13   = 0
   13
<-high = 1;
run;

data class;
   set sashelp.class;
pp_ny=input(age,age_pp.);
run;

3. Parenthèse sur les valeurs manquantes 

On a fait très attention de ne pas inclure les âges manquants pour définir PP_NY=1/0. La valeur .z est une valeur manquante spéciale « special missing ». SAS en compte 27.

L’intérêt des valeurs manquantes spéciales : le but des special missings est d’offrir d’un éventail de valeurs pour distinguer les valeurs manquantes. Prenez le cas d’une variable indiquant le contraceptif médicamenteux pris par le patient. S’il s’agit d’une femme et que l’information n’est pas fournie, on mettra manquant (un point). Par contre, s’il s’agit d’un homme, on marquera .A pour « non applicable ». Un format permettra ensuite de représenter . par MISSING et .A par N/A.

L’ordre des valeurs manquantes : l’ordre des missings et special missings est important à connaître. Cela vous expliquera pourquoi j’ai utiliser .z et non . dans l’exemple précédent. Vous pouvez vous reporter à la Online Doc : Missing Values. En résumé cela donne :

  • ._ pour le plus petit,
  • . juste après,
  • .A
  • .Z pour le plus grand des special missings
  • valeurs négatives
  • valeurs positives

Alternative avec la fonction MISSING : sachez qu’une autre solution pour exclure toutes les valeurs manquantes (missing et special missing), c’est d’utiliser la fonction MISSING qui a pour autre avantage de s’appliquer autant aux valeurs textes qu’aux valeurs numériques. C’est très pratique quand on ne connaît pas à l’avance le type de la variable. Dans la PROC SQL, WHERE… il y a aussi « WHERE x IS MISSING ».

4. Plusieurs actions quand une condition est remplie (DO-END)

Si une condition est remplie, il peut s’avérer nécessaire de faire plusieurs choses. Par exemple, on peut à la fois créer une nouvelle variable et sortir la ligne d’observations.

Dans l’exemple ci-dessous, la variable SEX ne prend que les valeurs ‘M’ et ‘F’. Dans ce cas précis, au final, il y a plus de records à la fin. Tout d’abord, tous les records sont extraits et POP est défini comme étant égal à 1. Ensuite, pour les records masculins, les records sont sortis une deuxième fois et notre variable POP est alors égale à 2.

data class;
set sashelp.class;
pop=1;
output;
if sex=‘M’ then
do;
pop=2;
output;
end;
run;

h1

Jongler avec les records grâce aux compteurs

juin 12, 2008

Sous SAS, une variable compteur permet de numéroter les lignes d’observations. Le plus souvent, cette variable sert ensuite à sélectionner certaines lignes d’un jeu de données et d’agir en fonction. Par exemple, pour chaque patient, un fois le compteur créé, il est simple de sélectionner les 5 premiers records.

Pour développer une variable compteur, il faut maîtriser la notion de FIRST. Vous pouvez vous reporter à l’article « Repérer les 1ers/derniers records (FIRST/LAST) ».

En outre, il faut comprendre la notion de RETAIN. Celle-ci sera abordée dans cet article.

Quatre exemples serviront à illustrer le sujet. Ils sont basés sur un data set nommé LAB ayant 9 lignes observations. Il est donné en fin d’article dans le paragraphe « Annexe ». On y trouve trois patients (PAT_ID), deux types de tests médicaux (TEST) et une date pour chaque test (variable TEST_DT). La variable compteur s’appelle à chaque fois CNT.

 1. La notion de RETAIN

Un RETAIN permet d’assigner une valeur à toutes les observations d’une variable. On préfèrera donc un RETAIN à « variable=valeur; » pour assigner une valeur unique pour toutes les records d’une variable données. 

Si entre temps, la valeur d’une observation est modifiée, tous les valeurs suivantes prennent cette nouvelle valeur. Cette seconde fonctionnalité sert pour créer une variable compteur.

Un RETAIN se définit le plus souvent dans une instruction RETAIN. Dans le cas des compteurs, on peut créer un RETAIN implicite prenant une valeur d’origine de zéro.

2. Pour chaque nouvelle observation incrémenter le compteur : dans ce premier exemple, à chaque nouvelle observation identifiée de manière unique par PAT_ID, TEST_DT et TEST, le compteur est incrémenté par 1. Le compteur va alors de 1 à 9 (le total d’observations). Les données sont préalablement triées par ces trois variables.

  test_dt    pat_id    test    cnt

28MAR2006      101     DBP      1
28MAR2006      101     SBP      2
29APR2006      101     DBP      3
27MAY2006      101     SBP      4
23JUN2006      101     SBP      5
13JAN2006      301     DBP      6
14FEB2006      301     SBP      7
15MAR2006      301     DBP      8
05MAR2006      401     DBP      9

Il faut donc dans un premier temps trier les données et rappeler cet ordre dans l’instruction BY du data step.

proc sort data=lab;
   by pat_id test_dt test;
run;

Puis la variable CNT prend une valeur de 0 pour chaque record.

Enfin les records sont lus les uns après les autres. A chaque nouvelle TEST pour un PAT_ID et TEST_DT donné, le compteur est incrémenté de 1.

data lab;
   retain cnt 0;
   set lab;
   by pat_id test_dt test;
   if first.test then cnt=cnt+1;
run;

Un RETAIN implicite : l’instruction RETAIN peut-être omise si un RETAIN implicite est utilisé. C’est le cas ici quand « cnt=cnt+1 » est remplacé par « cnt+1 ».

data lab;
   set lab;
   by pat_id test_dt test;
   if first.test then cnt+1;
run;

3. Pour chaque nouvelle date, incrémenter le compteur : dans le second exemple, pour chaque nouvelle date indépendamment du patient ou de type de test, le compteur est incrémenté par 1. Le compteur va de 1 à 8 (les 8 différentes dates). Les données sont triées par date au préalable.

  test_dt    pat_id    test    cnt

13JAN2006      301     DBP      1
14FEB2006      301     SBP      2
05MAR2006      401     DBP      3
15MAR2006      301     DBP      4
28MAR2006      101     SBP      5
28MAR2006      101     DBP      5
29APR2006      101     DBP      6
27MAY2006      101     SBP      7
23JUN2006      101     SBP      8

proc sort data=lab;
   by test_dt;
run;

data lab;
   set lab;
   by test_dt;
   if first.test_dt then cnt+1;
run;

4. Pour chaque nouveau patient, incrémenter le compteur : dans ce troisième exemple, chaque patient reçoit un numéro unique allant de 1 à 3 ; trois étant le nombre total de patients. Les données sont donc triées par PAT_ID.

   test_dt    pat_id    test    cnt

28MAR2006      101     SBP      1
28MAR2006      101     DBP      1
27MAY2006      101     SBP      1
23JUN2006      101     SBP      1
29APR2006      101     DBP      1
14FEB2006      301     SBP      2
13JAN2006      301     DBP      2
15MAR2006      301     DBP      2
05MAR2006      401     DBP      3

proc sort data=lab;
   by pat_id;
run;

data lab;
   set lab;
   by pat_id;
   if first.pat_id then cnt+1;
run;

5. Pour chaque nouveau date d’un patient donné, incrémenter le compteur : dans ce dernier exemple, à chaque nouvelle date, le compteur est incrémenté par un. Le premier patient ayant 4 dates différentes et 5 observations, le compteur va de 1 à 4 pour lui. Une des valeurs est doublée pour la date identique. Le second patient ayant trois dates uniques, le compteur va de 1 à 3 pour lui. Le compteur est donc réinitialisé à 1 pour chaque nouveau patient.

  test_dt    pat_id    test    cnt

28MAR2006      101     SBP      1
28MAR2006      101     DBP      1
29APR2006      101     DBP      2
27MAY2006      101     SBP      3
23JUN2006      101     SBP      4
13JAN2006      301     DBP      1
14FEB2006      301     SBP      2
15MAR2006      301     DBP      3
05MAR2006      401     DBP      1

proc sort data=lab;
   by pat_id test_dt;
run;

data one;
   set orig;
   by pat_id test_dt;
   if first.pat_id then cnt=1;
   else if first.test_dt then cnt+1;
run;

Annexe :

data orig;
   format test_dt date9.;
   input pat_id test_dt date9. test_type $;
   datalines;
101 28MAR2006 SBP
101 28MAR2006 DBP
101 27MAY2006 SBP
101 23JUN2006 SBP
301 14FEB2006 SBP
101 29APR2006 DBP
301 13JAN2006 DBP
301 15MAR2006 DBP
401 05MAR2006 DBP
;
run;

h1

Empiler des data sets

mai 22, 2008

Ajouter des lignes à un data set en utilisant celles d’un autre data set, c’est possible avec SAS. Selon les particularités du data set, une ou plusieurs méthodes sont disponibles. Trois data sets en fin d’articles sont disponibles pour tester les différentes méthodes.

1.  L’instruction SET dans un data step offre probablement le plus de flexibilité 

Groupées les données : La souplesse de l’instruction SET vient notamment de l’instruction BY. Sans cette instruction, toutes les données du premier data set sont lues et ajoutée au data set final. Puis, seulement après les données du data set suivant sont ajoutées. Si on veut que les lignes apparaissent dans un ordre précis défini par une instruction BY, les données doivent être préalablement triées dans cet ordre. 

Deux data sets et plus : en outre, plus de deux data sets peuvent êtes mis bout à bout. Les premières données qui s’afficheront seront celles du premier data set listé dans l’instruction SET.

Autorise un nombre de variables différent : A l’exception des variables listées dans une instruction BY, les data sets n’ont pas besoin d’avoir les même variables. La variable présente dans seulement certains data sets sera présente au final. Des valeurs manquantes seront ajoutées si besoin.

Même longueur et type pour les variables communes : Il est néanmoins important que certains attributs des variables présentes dans plusieurs data sets soient identiques.

  • Longueur : SAS utilisera la longueur de la première variable lue. Si la longueur de la seconde variable est plus grande, les valeurs, textes notamment, seront tronquées.
  • Type numérique ou caractère: L’attribut sur le type de la variable est aussi essentiel. SAS aura des problèmes en lisant à la fois des variables numériques et caractères du même nom.
  • Nom de variable : Si les variables ont des noms différents. Il est possible de les renommer préalablement avec l’option RENAME.

Un nom de data set au choix : par ailleurs, on peut donner un nouveau nom au data set créé

La syntaxe du data set toujours disponible : enfin, les mêmes manipulations qu’avec une instruction SET avec un seul data set peuvent êtes effectuées. Les options (IN=) sont disponibles pour établir des conditions basées sur le data set source.

data seq_all;
   set seq1 seq2 seq3;
run;

SAS Online Doc : The SET Statement

2. La procédure DATASETS (et PROC APPEND) peut s’avérer plus performante

La procédure DATASETS dispose de l’instruction APPEND. Cette procédure est plus récente que sa jumelle PROC APPEND. Elle propose d’autres instructions fortement utiles comme COPY et DELETE.

  • Nombre de data sets d’entrée: Ici, il est impératif d’avoir deux et seulement deux data sets pour une jointure donnée.
  • Structure des data sets d’entrée : Chaque data set doit avoir les mêmes variables.
  • Nom du data set de sortie : Le data set final portera le nom du data set listé dans la base.
  • Performances : APPEND pourra s’avérer plus performant si le data set défini dans la base est large. En effet, dans ce dernier cas, seul les observations du deuxième data set sont lues intégralement.

Priorité dans l’usage des bibliothèques : Dans l’exemple suivant, le data set SEQ1, SEQ2 et SEQ3 sont dans la bibliothèque WORK. Si aucune bibliothèque n’est donnée dans l’instruction APPEND, SAS utilise celle définie dans l’instruction PROC DATA SETS. Et comme aucune n’y est précisée, cela revient à utiliser la bibliothèque temporaire (WORK le plus souvent).

proc datasets;*lib=work;
   append base=seq1 data=seq2;
   *append base=work.seq1 data=work.seq2;
   append base=seq1 data=seq3;
run;

Voici la même manipulation avec PROC APPEND.

proc append base=seq1 data=seq2;
*proc append base=work.seq1 data=work.seq2;
run;
proc append base=seq1 data=seq3;
run;

Vous pouvez vous reporter à la documentation SAS :The DATASETS Procedure et consulter l’instruction APPEND. On y rappelle notamment quand le data set de la base (BASE=), les options DROP, KEEP et RENAME ne sont pas exécutées.

3. Au final pas de doublons avec l’UNION d’une PROC SQL

Important : L’UNION de deux data sets avec une PROC SQL enlèvera les doublons. Il est donc important de savoir s’il y a des doublons qui sont à enlever ou non.

Ici, on peut faire l’union de plus de deux data sets. Ils devront néanmoins tous avoir les mêmes variables.

proc sql;
   create table seq_all as
      select * from seq1
   union
      select * from seq2
   union
      select * from seq3;
quit;

4. Insérer de nouvelles observations extraites d’un autre data set

Il est possible aussi d’ajouter les observations avec l’instruction INSERT INTO. Le data set d’origine est alors actualisé. Dans l’exemple ci-dessous on ajoute seulement les observations des data sets SEQ2 et SEQ3 s’il si le test mesure la pression systolique (Systolic Blood Pressure, SBP).

NOTE : Il est important de sélectionner les observations à ajouter d’un data set différent de celui à mettre à jour. Sinon, SAS risque de rencontrer des problèmes. Voir la SAS Online Doc: INSERT Statement pour plus de précisions.

proc sql;
  insert into seq1
     select *
     from seq2
     where test_nom=‘SBP’;
  insert into seq1
     select *
     from seq3
     where test_nom=‘SBP’;
quit;

Annexe :

data seq1;
   length test_nom $3 test_unit $4;
   input test_nom $ test_seq test_val test_unit $;
   datalines;
SBP 1 120 mmHg
DBP 1 80 mmHg
DBP 1 80 mmHg
;
run;

data seq2;
   length test_nom $3 test_unit $4;
   input test_nom $ test_seq test_val test_unit $;
   datalines;
SBP 2 115 mmHg
DBP 2 85 mmHg
;
run;

data seq3;
   length test_nom $3 test_unit $4;
   input test_nom $ test_seq test_val test_unit $;
   datalines;
SBP 3 117 mmHg
DBP 3 81 mmHg
;
run;

h1

Repérer les 1ers/derniers records (FIRST/LAST)

mai 6, 2008

Repérer la première et/ou la dernière observation d’un jeu de données ou d’un sous-ensemble de ce jeu, c’est possible sous SAS avec les mots-clés FIRST et LAST dans un data step. On se sert de cette information sous forme de condition. Si la première observation est rencontrée, on fait ceci, sinon on fait cela. Cela sert pour créer une variable compteur ou pour générer plusieurs programmes via un DATA _NULL_, programmes variant par quelques valeurs listées dans un fichier de référence.

1. Le raisonnement FIRST/LAST en langage humain

Dans l’exemple ci-dessous, on a tout d’abord deux variables MEMNAME et NAME qui sont triées. Ensuite sont ajoutées plusieurs variables.

Les variables FRST_DSN/LST_DSN

  • S’il s’agit de la première fois que l’on lit la valeur de la variable MEMNAME, alors on donne une value de 1 à FRST_DSN, sinon on donne une valeur de 0.
  • Si au contraire, il s’agit de la dernière valeur avant de changer, LST_DSN prend la valeur 1, sinon il prend la valeur 0.

Dans l’exemple, on remarque que FRST_DSN et LST_DSN sont toutes les deux égale à 1 quand MEMNAME=DSN2, car il n’y a qu’une observation pour ce MEMNAME. La première observation est donc également la dernière,

memname  name  frst_dsn lst_dsn frst_var lst_var
    dsn1       var1          1          0           1          0
    dsn1       var1          0          0           0          0
    dsn1       var1          0          0           0          1
    dsn1       var2          0          0           1          1
    dsn1       var3          0          1           1          1    
    dsn2       var1          1          1           1          1    

    dsn3       var1          1          0           1          1
    dsn3       var2          0          0           1          1
    dsn3       var3          0          0           1          1
    dsn3       var4          0          1           1          1    
    dsn4       var1          1          0           1          1
    dsn4       var2          0          1           1          1       

FRST_VAR/LST_VAR : une fois dans un groupe (DSN1, DSN2, DSN3 ou DSN4), on regarde la seconde variable NAME.

  • Si on a la première fois la valeur dans ce groupe, FRST_VAR=1 sinon FRST_VAR=0.
  • Si au contraire, il s’agit de la dernière fois qu’on l’observe dans ce group, LST_VAR=1, 0 autrement.

Dans l’exemple, seul le DSN1 a plusieurs fois une VAR1 associée. C’est donc le seul moment où FRST_VAR n’est pas égal à LST_VAR.

NOTE, choix de l’auteur : entendez FRST pour rappeler le mot FIRST (premier), LST le mot LAST (dernier) et DSN le mot DATA SET NAME (nom du jeu de données).

2. Le raisonnement FIRST/LAST en langage SAS

SAS lie les données d’un jeu de données ligne par ligne. On rassemble les données par groupe en les triant. On rappelle cet ordre avec une instruction BY.

Ici les variables MEMNAME et NAME sont extraites de la bibliothèque SASHELP grâce au dictionnaire COLUMN.

proc sql;
   create table lst_dsn_var as
   select memname, name
   from dictionary.columns
   where upcase(libname)=’SASHELP’;
quit;

Puis, chacune des variables FRST_DSN, LST_DSN, FRST_VAR et LST_VAR sont crées. Ces variables prennent une valeur de 1, si la condition est vrai (s’il s’agit bien de la première ou de la dernière observation), 0 sinon. Bien sûr, on peut choisir de leur donner la valeur que l’on veut.

data _null_;
   set lst_dsn_var;
   by memname name;
   if first.memname then frst_dsn=1;
   else frst_dsn=0;
   if last.memname then lst_dsn=1;
   else lst_dsn=0;
   if first.name then frst_var=1;
   else frst_var=0;
   if last.name then lst_var=1;
   else lst_var=0;
run;

NOTE : SAS se base sur les données d’origine pour dire si oui ou non, il s’agit de la première/dernière observation. Dès lors, si le jeu d’origine est altéré (suppression de lignes), SAS ne redéfinira pas une première/dernière observation parmi celles restantes. On peut donc ne plus avoir l’observation considérée par SAS comme première/dernière. Il choisira si besoin de faire les deux opérations dans des data steps distincts.

3. La première et la dernière observation d’un data set

Vous n’aurez pas toujours une variable prenant la même valeur pour toutes les observations et ainsi retrouver la première et la dernière observation. On peut soit en créer une avec un RETAIN par exemple ou plus simplement utiliser

  • la variable automatique _N_ pour la première observation et
  • la variable assignée avec l’option END= dans l’instruction SET pour la dernière observation.

data _null_;
   set lst_dsn_var end=eof;
   if _N_=1 then …;
   if eof then…;
run;

NOTE : Par habitude, on donne ici le nom EOF (End Of File) comme nom à la variable qui prend une valeur 1 s’il s’agit de la dernière observation, 0 autrement. Comme la variable automatique _N_, EOF n’apparaît pas dans le data set final, s’il est créé.

h1

Combien d’observations dans mon data set ?

avril 14, 2008

Savoir trouver le nombre d’observations dans un data set SAS : tel est le sujet d’aujourd’hui. Connaître le nombre d’observations dans un jeu de données présente plusieurs avantages. En voici deux :

  • Définir une condition pour qu’un code soit exécuté.
  • Définir une boucle pour exécuter un code autant de fois qu’il y a d’observations dans le data set d’origine.

Dans les deux cas, on choisi ici de sauvegarder ce nombre dans une macro variable. Quelles sont les méthodes à disposition pour trouver ce nombre ? Je vous en propose d’en détailler six. Celles-ci fonctionnent également lorsque le data set est vide d’observations.

Exemples illustrés avec le data set ORIG : pour illustrer les différentes méthodes, j’utilise un jeu de données nommé ORIG ne contenant pas d’observations.

data orig;
x=1;

*if x=1 then output;
if x=1 then delete;
run;

L’instruction %PUT permet de voir la valeur de mes macro variables dans la LOG.

1. La fonction COUNT dans PROC SQL : la procédure SQL et sa fonction COUNT permettent de retrouver le nombre total d’observations dans le data set lu, si on n’utilise pas de GROUP BY. L’étoile signifie « TOUTES LES OBSERVATIONS », indépendamment de la variable. Si le nombre d’observations est stocké dans une variable, la valeur s’affiche pour chaque observation. Comme cette valeur est la même pour toutes les observations, on peut n’en afficher qu’une seule via DISTINCT. On peut sauvegarder cette information dans une macro variable via INTO:. Si le DISTINCT n’est pas utilisé, seule la première valeur sera sauvegardé dans la macro variable. DISTINCT est donc optionnel dans ce cas précis.

proc sql noprint;
select distinct count(*) into: methode1
from orig;
quit;

%put METHOD 1 = &methode1. ;


2. SQLOBS, une macro variable automatique à utiliser avec précaution : lorsqu’un data set est créé avec une procédure SQL, on peut, juste après, retrouver l’information avec la macro variable automatique &SQLOBS. Il faut bien faire attention de ne pas inclure d’autres data sets entre temps, lors d’une mise à jour par exemple. SQLOBS récupère le nombre d’observations du dernier jeu de données quelque soit son nom.

proc sql ;
create table methode2 as
select *
from orig;
quit;

%put METHODE 2 = &sqlobs. ;

3. Les métadonnées des dictionnaires SAS : le dictionnaire TABLES liste tous les data sets de toutes les bibliothèques et des informations supplémentaires les caractérisant. Ainsi la variable NOBS contient le nombre d’observations dans le data set. Ici on choisi INTO: pour sauvegarder ce nombre dans une macro variable.

proc sql noprint;
select nobs into: methode3
from dictionary.tables
where upcase(libname)=’WORK’ and
upcase(memname)=’ORIG’;
quit;

%put METHODE 3 = &methode3. ;

4. ATTRN comme « SAS Component Language » ou SCL : le SCL nommé ATTRN permet d’accéder au nombre d’observations. Pour cela, le nom du data set en question et le mot clé NOBS sont données. Ce SCL est accessible via %SYSFUNC. Pour lire le data set et extraire cette information, il faut au préalable, l’ouvrir et ensuite le fermer pour éviter des bugs, via les SCL OPEN et CLOSE. On réfère donc au data set via la macro variable DSID ouvrant le data set.

%let dsid     = %sysfunc(open(work.orig,in));
%let methode4 = %sysfunc(attrn(&dsid,nobs));
%if &dsid. > 0 %then %let rc = %sysfunc(close(&dsid));

%put METHODE 4 = &methode4. ;

5. La variable automatique _N_ : dans un data step, une variable nommée _N_ est créée automatique. Elle contient le nombre total d’itérations effectuées par SAS. Ainsi, si aucune donnée n’est lue, _N_=1. Une fois la lecture d’une observation faite par exemple, SAS revient juste après l’instruction DATA et _N_ est incrémenté de 1. Le nombre d’observations dans le data set est donc _N_-1. Ce nombre est sauvegardé ici dans une macro variable appelée METHODE5 créée via une instruction CALL SYMPUTX.

data_null_;
call symputx(‘methode5’,_N_-1);
set orig;
run;

%put METHODE 5 = &methode5. ;

6.  L’option NOBS de l’instruction SET : en précisant un nom après l’option NOBS= de l’instruction SET, la valeur de NOBS est sauvegardée dans une variable. Ici, elle s’appelle METHODE6. L’instruction CALL SYMPUTX permet de sauvegarder cette valeur dans une macro variable. La manière dont SAS traite le code est très importante ici.  Les explications d’Alain vous sont données plus bas.

data _null_;
if 0 then set orig nobs=methode6;
call symputx(‘methode6’,methode6);
stop;
run;

Le « IF 0 » est une instruction toujours fausse, donc l’exécution de l’instruction
conditionnée ne sera jamais réalisée. La récupération du nombre d’observations
depuis le dictionnaire du data set se fait au moment de la compilation par le SAS DATA
COMPILER, qui stocke dans une variable qui est forcément temporaire citée avec
l’option « NOBS= ».

L’instruction STOP ne permet pas d’écourter le temps de lecture des données car il
n’y pas d’exécution de lecture. Alors que se passe-t-il ?

L’étape DATA étant une boucle automatique dès qu’une instruction de lecture (Set ,
Merge, Modify, Update, …) de data set est détectée par le Superviseur SAS et par
compilateur de l’étape DATA , l’exécution de la boucle est automatique à cause de la
détection de l’instruction « SET » et SAS va vérifier si le test
induit par l’instruction « IF 0 » (comprendre if 0 =1) n’est pas devenu vrai !
D’où la nécessité de poser l’instruction SAS « STOP », pour éviter la boucle de
l’étape DATA.

 

(SUPERVISEUR SAS : Agent pivot qui distribue le code soumis par PARSING, soit au
DATASTEP COMPILER, PROCEDURE PARSER ou au Compilateur MACRO)

h1

Changer la séquence d’affichage des variables

avril 7, 2008

Pour changer l’ordre des variables dans un jeu de données, il faut demander à SAS de relire l’intégralité des données dans l’ordre souhaité. Deux méthodes sont disponibles : un data step ou une procédure SQL. Dans le cas particulier où les variables sont à afficher par ordre alphabétique, on pourra extraire la liste des variables des dictionnaires SAS et la sauvegarder dans une macro variable.

Je vous propose donc de voir l’approche avec le data step, puis celle avec la PROC SQL et enfin d’ajouter un exemple particulier utilisant en plus les dictionnaires.

1. Définir la séquence des variables dans un data step : l’ordre de lecture des noms de variables déterminera l’ordre d’apparition dans le jeu de données.

1.1 Une instruction INPUT : Dans l’exemple ci-dessous, où seule l’instruction INPUT est utilisée, la variable YR02 apparaît en premier suivie de YR00, YR99 et YR01. 

data one;
  input yr02 19 date9. yr00 1120 date9. yr99 yr01 $;
  datalines;
02APR2002 21FEB2001 3.52 C
;
run;

1.2 Les instructions RETAIN, FORMAT, INFORMAT, LABEL, ATTRIB : maintenant, on ajoute avant l’instruction INPUT des instructions RETAIN, FORMAT, INFORMAT, LABEL et ATTRIB. Ainsi, on voit que ces instructions affectent l’ordre d’apparition des variables : YR98, YR99,… YR02.

data one;
  retain yr98 ‘REF’;
  format yr99 best.;
  informat yr00 date9.;
  label yr01=‘Annee 2001’;
  attrib yr02 format=date9.
              informat=date9.
              label=‘Annee 2002’;
  input yr02 yr00 yr99 yr01 $;
  datalines;
02APR2002 21FEB2001 3.52 C
;
run;

1.3 L’instruction SET : de la même manière, l’instruction SET défini l’ordre d’apparition des variables pas encore mentionnées.

2. Lister les variables dans une procédure SQL.

2.1 Lister les variables manuellement : les variables du jeu de données SASHELP.CLASS apparaissent dans l’ordre suivant : NOM, SEX, AGE, HEIGHT et WEIGHT. Pour changer cet ordre, avec une procédure SQL, il faut les lister individuellement. Dans l’exemple qui suit, les variables apparaissent par ordre alphabétique.

proc sql;
  create table class as
  select age, height, name, sex, weight
  from sashelp.class;
quit;

2.2 Automatiser la démarche : Si vous avez plus de 300 variables à lister, cela devient vite très contraignant. Pour rendre la tâche plus simple, on sauvegarde la liste des variables par ordre alphabétique dans une macro variable. Le nom de toutes les variables est disponible dans le dictionnaire TABLES.

proc sql noprint;
  select name into : var_lst separated by ‘, ‘
     from dictionary.columns
     where upcase(libname)=‘SASHELP’ and
           upcase(memname)=‘CLASS’
     order by name;
  create table class as
     select &
var_lst.
     from sashelp.class;
quit;

h1

Retrouver l’information précédente avec la fonction LAG

mars 4, 2008

Lorsqu’un test médical est conduit plusieurs fois sur un patient, les résultats pourront être sauvegardés dans une variable par ordre chronologique. Pour étudier le changement entre deux examens, on peut créer une nouvelle variable contenant l’information précédente. Ceci est un exemple d’application de la fonction LAG qui récupère la valeur de l’observation précédente.

Ici je vous propose d’aborder cette fonction. Les exemples vont utiliser un jeu de donnée, nommé ‘test’, qui contient une variable identifiant chaque ‘patient’ et une variable date ‘dt’.  On peut donc avoir plusieurs dates par patient. L’objectif est de créer une nouvelle variable contenant la date précédente ou suivante.

1. La fonction LAG retrouve l’observation précédente : si une variable x à cinq valeurs (1, 2, 3, 4, 5) et qu’une nouvelle variable y contenant le LAG de x est créée, cette nouvelle variable aura (., 1, 2, 3, 4) comme valeurs. Ceci veut aussi dire qu’il y aura autant d’observations y que d’observations x. Les données devront être triées au préalable.

2. Obtenir la valeur précédente par sous groupe : la fonction LAG est indépendante d’une BY variable (ex : par patient). La première observation d’un patient devra pourtant être toujours manquante, puisqu’il n’y a pas de valeur avant pour ce patient. Pour que cette observation ne vienne pas du patient précédent, on la remplace par une valeur manquante grâce à FIRST. Mais la condition sur cette première observation sera faite après le LAG et non avant.

proc sort;
   data=test;
   by patient dt;
run;

data next_dt;
   set test;
   by patient;
   next_dt=lag(dt);
   if first.patient then next_dt=.;
run;

 

3. Retrouver une information précédente mais pas la dernière3.1 Sauter plusieurs observations : la fonction LAG peut être agrémentée d’un nombre pour préciser l’écart avec l’observation actuelle. LAG(x) est équivalent de LAG1(x). Ainsi dans notre exemple précédant (1, 2, 3, 4, 5), LAG2(x) donne (., ., 1, 2, 3).

3.2 Par sous-groupe : pour trouver l’information par sous-groupe, il faut ajouter un compteur. Celui-ci permet de définir une condition pour remplacer les dernières observations de chaque patient par des valeurs manquantes.

data next_dt2 (drop=cnt);
   set test;
   by patient;
  next_dt2=lag1(dt);
   cnt+1;
   if first.patient then cnt=1;
   if cnt in (1,2) then next_dt2=.;
run;

En résumé, la fonction LAG ‘fait descendre’ de z steps les données. On peut récupérer l’observation précédente contigue ou une information plus lointaine.

h1

Faire des petits avec l’instruction OUTPUT

mars 1, 2008

bouchons_fr.jpg 

Imaginez devoir créer plusieurs fois le même tableau avec des fréquences, des tests statistiques, etc. Pourquoi plusieurs fois ? Parce que chaque tableau inclus une population différente.

1. Une observation par tableau

Dans le cas le plus simple, chaque observation n’apparaît que dans un seul des tableaux à produire. Une variable identifie le tableau auquel l’observation est allouée et l’instruction BY utilise cette variable avec la procédure report ou autre.

Voici un exemple où les données sont publiées par pays dans des tableaux distincts. L’utilisation d’une valeur numérique pour le pays est optionnelle mais ajoute de la flexibilité pour trier les données.

proc format;
value cntry
1=’Chine’
2=’Malaisie’;
run;

proc report data=lab;
by cntry;
columns test cnt;

format cntry cntry.;

run;

2. Plusieurs tableaux pour une observation

Exemple : maintenant voici deux exemples des essais cliniques pour lesquels une observation peut servir à plusieurs tableaux.

  • Construire un tableau pour la population ‘per protocol’, un pour ‘intent-to-treat’ et un pour ‘safety’ : un patient valide par protocole est aussi pour l’analyse de sûreté mais tous les patients valides pour la sûreté ne répondent pas forcément aux critères du protocole.
  • créer un tableau par zone géographique : un tableau pour tous les pays couverts par l’étude clinique, un tableau par pays et un par centre dans un pays donné.

Dans ces deux cas, une observation peut être utilisée par plusieurs tableaux. La méthode précédente n’est plus suffisante. Voici la solution que je vous propose :

  • 1er tableau : extraire les observations correspondant au premier tableau en utilisant une condition listant le critère de sélection si besoin.
  • rendre ces observations uniques en ajoutant une nouvelle variable avec un numéro unique
  • sauvegarder ces observations dans un jeu de données
  • 2ème tableau : extraire les observations pour le second tableau
  • donner un numéro différent à ces observations
  • ajouter ces observations à celles précédemment sélectionnées
  • 3ème tableau : etc.

Dans l’exemple qui suit, quatre tableaux différents sont à produits. On intercalera le code suivant entre les procédures format et report mentionnées précédemment.

data lab;
set lab;
grp=1;
output;
if cntry=’CN’ then
do;
grp=2;
output;
end;
else if cntry=’MY’ then
do;
grp=3;
output;
end;
if cntr=1 and cntry=’CN’ then
do;
grp=4;
output;
end;
run;

3. Créer plusieurs jeux de données

L’instruction OUTPUT dirige, par défaut, les données vers le jeu de données nommé dans l’instruction DATA. Parfois, il faut créer plusieurs data sets à partir à partir d’un seul jeu de données. Par exemple, on créera un data set contenant les patients inclus dans l’étude et un autre ceux exclus. Pour cela, il suffit de nommer les nouveaux jeux de données dans l’instruction DATA et de rappeler leur nom dans chaque instruction OUTPUT.

data incl excl;
set pat_lst;
if missing (randomno) then output excl;
else output incl;
run;

4. Créer une variable numérique avec des valeurs continues

En combinaison avec une boucle, l’instruction OUTPUT permet de générer des nombres. Par exemple, j’ai eu besoin de tester une macro via un data set listant toutes les valeurs ASCII imprimables. Ces valeurs ont un numéro allant de 32 à 126. La fonction BYTE retrouve la valeur ASCII avec ces numéros.

data ascii (drop=i);
do i=31 to 126;
ascii=byte(i);
output;
end;
run;

Autres lectures : vous pouvez étendre votre lecture sur l’instruction DO et DELETE. L’instruction DO dispose d’un mot-clé BY. L’instruction DELETE est, quant à elle, l’inverse de l’OUTPUT.

h1

Ecrire un texte avec l’instruction PUT

février 24, 2008

pen_fr.jpg 

Faire apparaître un message d’erreur dans la log ; afficher la valeur de variables dans la log lors du développement d’un programme ; générer plusieurs programmes similaires : voici autant d’applications de l’instruction PUT, propre au data step, qui affiche, par défaut, le texte dans la log.

1. Taper du texte brut : en mettant votre texte entre guillemets dans l’instruction PUT, celui-ci apparaîtra dans la log.

Couper l’instruction PUT : lorsqu’une instruction PUT devient longue, on doit se déplacer sur plusieurs écrans pour voir l’intégralité du programme. En coupant le texte, la lisibilité du programme et le confort du programmeur se trouvent améliorés et le texte reste continue dans la sortie LOG ou autre.

Ecrire des guillemets : pour afficher une guillemet simple, celle-ci devra apparaître dans des guillemets doubles et inversement. Pour afficher deux guillemets simples, il faudra les séparer dans deux jeux de guillemets doubles. Par défaut, on privilégiera les guillemets simples pour alléger le programme.

Ajouter un saut de ligne : à chaque nouvelle instruction PUT, SAS crée un saut de ligne. Les barres inclinées (slash /) ajoutent autant de sauts de ligne supplémentaires que désirés.

Ajouter des espaces : en écrivant une procédure SQL, on fait utile des indentations améliorant la lisibilité du code. Soit ces espaces de début de ligne sont ajoutés manuellement entre les guillemets, soit un arobas suivi du nombre d’espaces précède les guillemets (@6 crée une indentation de six espaces). 

Enlever l’espace créé par défaut avant un début de guillemets : plusieurs jeux de guillemets sont fréquemment utilisés pour insérer entre les deux le nom d’une variable. Au final, il est parfois important que la valeur de la variable et le texte qui suit se suivent sans espace. Pour cela, il faut faire reculer le curseur d’un espace en ajoutant  +(-1) avant le début de la guillemet.

2. Afficher les valeurs des variables d’un jeu de données

Afficher la valeur de la variable : pour voir la valeur des variables, il faut lister leur nom dans l’instruction PUT sans guillemet. C’est très pratique pour générer un code variant uniquement par la valeur de variables.

Afficher la valeur d’une variable et son nom : lorsqu’il s’agira de débugger un programme, vous repérerez plus rapidement dans la log vos valeurs si le nom de la variable apparaît également. Pour ce faire, vous avez deux options plus ou moins rapide.

  • Utiliser les guillemets vous obligera à écrire le nom de la variable deux fois : une fois entre guillemets et une fois sans guillemet.
  • Plus simplement, le nom de la variable est suivi du signe égal.

Lectures complémentaires : l’instruction PUT trouve tout son potentiel en combinaison avec d’autres instructions : 

  • Pas de jeu de données (DATA _NULL) : l’instruction PUT est propre au data step. Quand vous ne voudrez pas créer un jeu de données SAS dans ce data step, vous aurez besoin du DATA _NULL_.
  • Changer de destination (FILE/FILENAME) : Pour diriger votre texte vers une autre sortie que la log, vous aurez besoin de l’instruction FILE. Selon que cette sortie sera vers un fichier externe ou vers la fenêtre OUTPUT de SAS, vous aurez besoin de l’instruction globale FILENAME ou non.
  • Alterner selon les valeurs des variables (BY/FIRST/LAST) : pour générer des codes variant selon la valeur de variables, il y a l’instruction BY en combinaison avec FIRST et LAST.
  • Début et fin de texte (_N_/END=) : pour afficher un texte unique en début et en fin, on peut faire référence à la variable automatique _N_ (if _N_=1) et à une variable définie avec l’option END= de l’instruction SET.
h1

Envoyez un email via SAS

février 11, 2008

Envoyer un email via SAS

Lorsque vous écrivez un programme devant être exécuté de manière régulière, disons une routine pour faire un transfert de base de données, vous pouvez communiquer par email à votre collègue, en charge de la base, toute incohérence détectée. Je vous invite à essayer l’exemple détaillé plus loin, qui fait appel au jeu de données CLASS, disponible dans la bibliothèque SASHELP de SAS. Avec lui, un email est envoyé si un élève répertorié a 15 ans. Mais auparavant, je vous propose de comprendre le mécanisme pour envoyer un email.

1. Ecrire son message : vous écrivez via un DATA _NULL_ et des instructions (statement) PUT votre message.

2. Préciser la destination du message : puis vous indiquez vers quelle la destination votre message doit être envoyé, via le FILE statement. Si aucune incohérence n‘est détectée, vous ferez une sortie classique vers le listing (file print;). Sinon vous l’adresserez vers une référence extérieure, qu’on appellera dans notre exemple MEMO (file memo;), définie dans le FILENAME statement.

3. Créer un data set de référence : pour savoir si une incohérence existe, vous vous référerez à un data set, qui contiendra la liste des incohérences, via l’instruction SET. Lorsqu’il est vide, aucun message ne sera envoyé.

4. Alternez entre vos deux sorties via une macro variable : par défaut, il n’y aura pas d’email envoyé (file print;). Pour alterner entre les deux sorties possibles du message, on utilisera une macro variable définie dans un premier temps égal à PRINT (%let email_out=print;). Puis on changera cette valeur si besoin dans une procédure SQL avec INTO :.

5. Définir l’entête de votre email via deux mots-clés : le FILENAME statement contiendra un nom de référence. J’ai choisi MEMO ici. Puis l’email du destinataire après le mot-clé EMAIL sera listé entre guillemets. Et enfin, on ajoutera l’objet du mail via SUBJECT=’ ‘.

%let email_out=print ; 

proc sql noprint;
   create table incoherences as
      select *, 'memo' as outp
      from sashelp.class
      where age=15
      order by name;
   select distinct outp into :ema_out
      from incoherence;
quit; 

filename memo email 'veronique.bourcier@sasreference.fr'
              subject = 'Exemple de programme '; 

data _null_ ;
   set incoherences ;
   file &email_out. ;
   if _N_=1 then
      do;
         put 'Dear Administrator ';
         put // 'The SASHELP.CLASS table has ages equal to 15.';
         put // 'Records are listed below. ';
         put // 'Kind Regards, ';
         put /;
      end;
  put / NAME=;
run;