recode and add prefix to sas variables

2.1k views Asked by At

Lets's say I have a bunch of variables named the same way and I'd like to recode them and add a prefix to each (the variables are all numeric).

In Stata I would do something like (let's say the variables start with eq)

foreach var of varlist eq* {
    recode var (1/4=1) (else=0), pre(r_)
}

How can I do this in SAS? I'd like to use the %DO macros, but I'm not familiar with them (I want to avoid SQL). I'd appreciate if you could include comments explaining each step!

5

There are 5 answers

6
Tom On BEST ANSWER

SAS syntax for this would be easier if your variables are named using numeric suffix. That is, if you had ten variables with names of eq1, eq2, .... , eq10, then you could just use variable lists to define both sets of variables.

There are a number of ways to translate your recode logic. If we assume you have clean variables then we can just use a boolean expression to generate a 0/1 result. So if 4 and 5 map to 1 and the rest map to 0 you could use x in (4,5) or x > 3 as the boolean expresson.

data want;
  set have;
  array old eq1-eq10 ;
  array new r_eq1-r_eq10 ;
  do i=1 to dim(old);
    new(i) = old(i) in (4,5);
  end;
run;

If you have missing values or other complications you might want to use IF/THEN logic or a SELECT statement or you could define a format you could use to convert the values.

If your list of names is more random then you might need to use some code generation, such as macro code, to generate the new variable names.

Here is one method that use the eq: variable list syntax in SAS that is similar to the syntax of your variable selection before. Use PROC TRANSPOSE on an empty (obs=0) version of your source dataset to get a dataset with the variable names that match your name pattern.

proc transpose data=have(obs=0) out=names;
  var eq: ;
run;

Then generate two macro variables with the list of old and new names.

proc sql noprint ;
  select _name_
       , cats('r_',_name_)
    into :old_list separated by ' '
       , :new_list separated by ' '
  from names
  ;
quit;

You can then use the two macro variables in your ARRAY statements.

  array old &old_list ;
  array new &new_list ;
4
Sean On

You can do this with rename and a dash indicating which variables you want to rename. Note the following only renames the col variables, and not the other one:

data have;                                                                                                                                 
    col1=1;                                                                                                                               
    col2=2;                                                                                                                               
    col3=3;                                                                                                                               
    col5=5; 
    other=99; 
    col12=12; 
run;  


%macro recoder(dsn = , varname = , prefix = );

/*select all variables that include the string "varname"*/
/*(you can change this if you want to be more specific on the conditions that need to be met to be renamed)*/
proc sql noprint;
    select distinct name into: varnames
    separated by " "
    from dictionary.columns where memname = upcase("&dsn.") and index(name, "&varname.") > 0;
quit;

data want;
    set have;

    /*loop through that list of variables to recode*/
    %do i = 1 %to %sysfunc(countw(&varnames.)); 
    %let this_varname = %scan(&varnames., &i.);

        /*create a new variable with desired prefix based on value of old variable*/
        if &this_varname. in (1 2 3) then &prefix.&this_varname. = 0;
            else if &this_varname. in (4 5) then &prefix.&this_varname. = 1;

    %end;
run;

%mend recoder;

%recoder(dsn = have, varname = col, prefix = r_);
0
Joe On

It would be nearly trivial to write a macro to parse almost that exact syntax.

I wouldn't necessarily use this - I like both the transpose and the array methods better, both are more 'SASsy' (think 'pythonic' but for SAS) - but this is more or less exactly what you're doing above.

First set up a dataset:

data class;
  set sashelp.class;
  age_ly = age-1;
  age_ny = age+1;
run;

Then the macro:

%macro do_count(data=, out=, prefix=, condition=, recode=, else=, var_start=);
%local dsid varcount varname rc;          *declare local for safety;

%let dsid = %sysfunc(open(&data.,i));       *open the dataset;


%let varcount = %sysfunc(attrn(&dsid,nvars)); *get the count of variables to access;

  data &out.;                                 *now start the main data step;
    set &data.;                               *set the original data set;
    %do i = 1 %to &varcount;                  *iterate over the variables;
      %let varname= %sysfunc(varname(&dsid.,&i.));   *determine the variable name;
      %if %upcase(%substr(&varname.,1,%length(&var_start.))) = %upcase(&var_start.) %then %do;                   *if it matches your pattern then recode it;
        &prefix.&varname. = ifn(&varname. &condition., &recode., &else.);   *this uses IFN - only recodes numerics.  More complicated code would work if this could be character.;
      %end;
    %end;
    %let rc = %sysfunc(close(&dsid));         *clean up after yourself;
  run;

%mend do_count;

   %do_count(data=class, out=class_r, var_start=age, condition= > 14, recode=1, else=0, prefix=p_);
0
Tom On

The expression (1/4=1) means values {1,2,3,4} should be recoded into 1.

Perhaps you do not need to make new variables at all? If have variables with values 1,2,3,4,5 and you want to treat them as if they have only two groups you could do it with a format.

First define your grouping using a format.

proc format ;
  value newgrp 1-4='Group 1' 5='Group 2' ;
run;

Then you can just use a FORMAT statement in your analysis step to have SAS treat your five level variable as it if had only two levels.

proc freq ;
  tables eq: ;
  format eq: NEWGRP. ;
run;
4
data _null_ On

PROC TRANSPOSE will give you good flexibility with regards to the way your variables are named.

proc transpose data=have(obs=0) out=vars;
   var col1-numeric-col12;
   copy col1;
   run;
proc transpose data=vars out=revars(drop=_:) prefix=RE_;
   id _name_;
   run;
data recode;
   set have;
   if 0 then set revars;
   array c[*] col1-numeric-col12;
   array r[*] re_:;
   call missing(of r[*]);
   do _n_ = 1 to dim(c);
      if      c[_n_] in(1 2 3) then r[_n_] = 0;
      else if c[_n_] in(4 5)   then r[_n_] = 1;
      else                          r[_n_] = c[_n_];
      end;
   run;
proc print;
   run;