Search code examples
sasiteratordo-loops

SAS Do Loops: How do I use a comparison operator that includes the iterator in a variable name?


I have a dataset like Cars1 (generate it with the code below), with years in the variable name.

%macro test1();
data cars1;
    set sashelp.cars (keep = make model);

    %do i = 2012 %to 2014;
        make&i. = make;
    %end;
run;
%mend test1;
%test1();

Which looks something like this...

Make    Model             make2012  make2013    make2014
Acura   MDX               Acura     Acura       Acura
Acura   RSX Type S 2dr    Acura     Acura       Acura
Acura   TSX 4dr           Acura     Acura       Acura

I want to iterate over the 3 years (2012-2014), creating a categorical dataset (make_2012_cat, etc.) for each of the 3 variables I created in Cars1, based on comparison with the respective value. So if make2012 eq 'Acura' I have a statement for that. Same for make2021 ne 'Acura'. For some reason, the comparison is not working, and I end up with the default value each time ("It's not comparable :-(")

%macro test2();
data cars2; 
    set cars1;

    %do i = 2012 %to 2014;
        %if make&i. eq "Acura" %then %do;
            make_&i._cat = "It's an Acura!";
        %end;
        %if make&i. ne "Acura" %then %do;
            make_&i._cat = "It's not an Acura.";
        %end;
        %else %do;
            make_&i._cat = "It's not comparable :-(";
        %end;
    %end;
run;
%mend test2;

%test2();

I'm ending up only with the default value. I've tried this inside and outside a macro and with lots of adjustments to the way I call the iterator, i. Also have tried declaring global variables, but at this point I'm just spinning my wheels. Any help is appreciated!!

Make   make_2012_cat        make_2013_cat        make_2014_cat 
Acura  It's not an Acura.   It's not an Acura.   It's not an Acura. 
Acura  It's not an Acura.   It's not an Acura.   It's not an Acura. 
Acura  It's not an Acura.   It's not an Acura.   It's not an Acura. 

Solution

  • So, you need to understand the difference between the macro language and the data step language. These are two separate things that don't actually do the same thing.

    %if and other macro language stuff only affect the text of the code you are compiling. They change the SAS program to some other SAS program. But they don't have anything to do with the data!

    Steps:

    1. Parse Macro language stuff
    2. Compile SAS data step program
    3. Run SAS data step program (load data one row at a time, etc)

    So you need to separate the data step (if) from the macro language (%if).

    %do i = 2012 %to 2014;
        %if make&i. eq "Acura" %then %do;
            make_&i._cat = "It's an Acura!";
        %end;
        %if make&i. ne "Acura" %then %do;
            make_&i._cat = "It's not an Acura.";
        %end;
        %else %do;
            make_&i._cat = "It's not comparable :-(";
        %end;
    %end;
    

    Turn that into

    %do i = 2012 %to 2014;
        if make&i. eq "Acura" then do;
            make_&i._cat = "It's an Acura!";
        end;
        else if make&i. ne "Acura" then do;          **note I added 'else' here;
            make_&i._cat = "It's not an Acura.";
        end;
        else do;
            make_&i._cat = "It's not comparable :-(";
        end;
    %end;
    

    The macro language is there doing its part - it's making

    if make_2012_cat = ...;
    
    if make_2013_cat = ...;
    
    if make_2014_cat = ...;
    

    But the data step language is actually what's doing the work of comparing make_2012_cat variable value to 'Acura'.


    Note one other thing; this isn't necessarily very idiomatic SAS. It's legal, and a TON of people do this, but it's not truly using SAS features to the utmost. Better would be to have make_cat and then different rows for 2012,2013,2014, if you can. Maybe you can't - maybe you have a good reason for that - but it's better when your variable name doesn't contain data (which these do). Maybe your real example is different, but I find that about 80% of the time when I see people doing things roughly like this, it could be done better in a different data structure.


    To do this entirely in the data step, you need arrays.

    data cars1;
        set sashelp.cars (keep = make model);
    
        array makes[3] make_2012-make_2014;
        do _i = 1 to dim(makes);
           makes[_i] = make;
        end;
    run;
    
    data cars2;
      set cars1;
      array makes[3] make_2012-make_2014;
      array makes_cat[3] make_cat_2012-make_cat_2014;  *slightly different, easier;
      do _i = 1 to dim(makes);
        if makes[_i] eq "Acura" then makes_cat[_i] = "It's an Acura!";
        else if makes[_i] ne "Acura" then makes_cat[_i] = "It's not an Acura.";
      end;
    run;