Hi there,
I'm trying to implement a simple functionality in a more complez macro and I'm having a hard time making it work properly.
The input is a variable containing several numbers concatenated in a string and separated by commas. I want to separate the numbers, assign their value with certain format to a specific column and then concatenate all the non-missing column values.
I'm first counting the number of elements of my input variable value and then scaning each of them in a loop so I can assign their value to a specific array element. Then, in a different loop (to be sure of following a certain order while the input could be disordered) I check non-missing values and concatenate them. Here is the code:
proc format;
value charformat
1 = 'AAA'
2 = 'BBB'
3 = 'CCC'
4 = 'DDD'
5 = 'EEE'
6 = 'FFF'
7 = 'GGG'
8 = 'HHH'
9 = 'III'
10 = 'JJJ'
11 = 'KKK'
12 = 'LLL'
13 = 'MMM'
14 = 'NNN'
15 = 'OOO'
;
run;
data test1;
infile datalines delimiter='';
length testvariable $100;
input testvariable $;
datalines;
5,8,15
1
1,3
1,2,3
2,3
3,1,2
2,15
11,5
7,1,9
13,2
14
;
run;
options mlogic mprint symbolgen;
%macro multiple(var);
data test2;
set test1;
length all_options $200;
array options [15] $100;
/* We count the number of elements on the list */
elements = count(&var,',')+1;
call symputx('endloop', elements);
%if "&var" ne ' ' %then %do;
/*From 1 to the number of elements*/
%do i=1 %to &endloop;
/* We scan the i-th element of the list and turn it into a numeric value*/
item = trim(left(scan(&var,&i,',')))*1;
/* With the scaned element we give value to the item-th element of the array options and apply an specific format */
if item ne . then options[item] =trim(left (put(item,charformat.)));
%end;
/* We concatenate all non-zero values from the array */
%do j=1 %to 15;
if all_options ne ' ' and options[&j] ne ' ' then all_options = trim(left(all_options)) ||'-'|| trim(left(options[&j]));
if all_options eq ' ' then all_options = options[&j];
%end;
%end;
run;
%mend;
%multiple(testvariable);
However, the execution finishes without errors and only the first element is scanned ('item' value should be the last element of the list as it is overwritten at every loop step and it remains equal to the first element when the execution finishes, and only this element is saved in the array). See the obtained output (click to enlarge):
Do you have an idea on what may be causing this issue? Something obvious in the code that I might change?
Thanks for any suggestions!!
Part of your issue is attempting to use the value of macro variable in the same step it is created. The way you are attempting doesn't do what you want (I think).
You create a data step variable ELEMENTS, stuff it into a macro variable, then attempt to use that macro variable as a loop counter.
I don't think there is any real reason to not to use Elements as the limit on the loop counter.
example:
i=1 to elements; /* We scan the i-th element of the list and turn it into a numeric value*/ item =input(scan(&var,ii,','),best.); /* With the scaned element we give value to the item-th element of the array options and apply an specific format */ if item ne . then options[item] =strip(put(item,charformat.)); end;
The STRIP function, which combines Trim (left()) has been around for 15(?) or more years. I don't know why everyone keeps using trim(left( resulting ugly-ish code)
Similar with the second loop,
do j=1 to 15; if all_options ne ' ' and options[j] ne ' ' then all_options = catx('-',all_options,options[j]); Else all_options = options[j]; end;
There are several concatenation functions, CAT CATT CATS CATX CATQ that are generally much cleaner than the venerable || and in the case of CATT CATS and CATX remove trailing and/or leading spaces so Strip or Left(trim()) are seldom needed. CATX places a
separator, the first parameter between each value (and can be any string not just a single character)
And I think the last loop isn't actually needed. Consider the result of
%let var=testvariable ; data test2; set test1; length all_options $200; array options [15] $100; /* We count the number of elements on the list */ elements = count(&var,',')+1; do i=1 to elements; /* We scan the i-th element of the list and turn it into a numeric value*/ item =input(scan(&var,i,','),best.); /* With the scaned element we give value to the item-th element of the array options and apply an specific format */ if item ne . then options[item] =strip(put(item,charformat.)); end; all_options = catx('-',of options(*)); drop i; run;
Which gives me
Obs all_options 1 EEE-HHH-OOO 2 AAA 3 AAA-CCC 4 AAA-BBB-CCC 5 BBB-CCC 6 AAA-BBB-CCC 7 BBB-OOO 8 EEE-KKK 9 AAA-GGG-III 10 BBB-MMM 11 NNN
BTW, you should test
%if "&var" ne ' ' %then %do;
outside of the data step and not even enter the data step the variable is unassigned.
Can you post exactly what you want out. That screenshot you show makes no sense to me, and that code is a lot of faff. Why not just simply concatenate each item:
data want; set test1; length all_string $2000; do i=1 to countw(testvariable,","); all_string=catx(",",all_string,put(input(scan(testvariable,i,","),best.),charformat.)); end; run;
Part of your issue is attempting to use the value of macro variable in the same step it is created. The way you are attempting doesn't do what you want (I think).
You create a data step variable ELEMENTS, stuff it into a macro variable, then attempt to use that macro variable as a loop counter.
I don't think there is any real reason to not to use Elements as the limit on the loop counter.
example:
i=1 to elements; /* We scan the i-th element of the list and turn it into a numeric value*/ item =input(scan(&var,ii,','),best.); /* With the scaned element we give value to the item-th element of the array options and apply an specific format */ if item ne . then options[item] =strip(put(item,charformat.)); end;
The STRIP function, which combines Trim (left()) has been around for 15(?) or more years. I don't know why everyone keeps using trim(left( resulting ugly-ish code)
Similar with the second loop,
do j=1 to 15; if all_options ne ' ' and options[j] ne ' ' then all_options = catx('-',all_options,options[j]); Else all_options = options[j]; end;
There are several concatenation functions, CAT CATT CATS CATX CATQ that are generally much cleaner than the venerable || and in the case of CATT CATS and CATX remove trailing and/or leading spaces so Strip or Left(trim()) are seldom needed. CATX places a
separator, the first parameter between each value (and can be any string not just a single character)
And I think the last loop isn't actually needed. Consider the result of
%let var=testvariable ; data test2; set test1; length all_options $200; array options [15] $100; /* We count the number of elements on the list */ elements = count(&var,',')+1; do i=1 to elements; /* We scan the i-th element of the list and turn it into a numeric value*/ item =input(scan(&var,i,','),best.); /* With the scaned element we give value to the item-th element of the array options and apply an specific format */ if item ne . then options[item] =strip(put(item,charformat.)); end; all_options = catx('-',of options(*)); drop i; run;
Which gives me
Obs all_options 1 EEE-HHH-OOO 2 AAA 3 AAA-CCC 4 AAA-BBB-CCC 5 BBB-CCC 6 AAA-BBB-CCC 7 BBB-OOO 8 EEE-KKK 9 AAA-GGG-III 10 BBB-MMM 11 NNN
BTW, you should test
%if "&var" ne ' ' %then %do;
outside of the data step and not even enter the data step the variable is unassigned.
Thank you @ballardw,
Not only you solved my problem, but you also taught me a bunch of very useful tricks that I intend to apply from now on.
Thanks a lot again!
Best regards
Registration is now open for SAS Innovate 2025 , our biggest and most exciting global event of the year! Join us in Orlando, FL, May 6-9.
Sign up by Dec. 31 to get the 2024 rate of just $495.
Register now!
Learn how use the CAT functions in SAS to join values from multiple variables into a single value.
Find more tutorials on the SAS Users YouTube channel.
Ready to level-up your skills? Choose your own adventure.