Having some problems with Do Loop Concepts. I have a static date (can be any date for that matter) defined with -
%LET DATE = %SYSFUNC(TODAY());
%PUT &DATE;
I need to create a series of macro variables that hold values of that date (&DATE) incremented by 10 days, so I used a simple data step to achieve this -
DATA _NULL_;
CALL SYMPUT('DATE10',&DATE+10);
CALL SYMPUT('DATE20',&DATE+20);
CALL SYMPUT('DATE30',&DATE+30);
RUN;
This method is OK for increments of 10 up to 30 days after the initial value of &DATE. I am now tasked with extending the report to execute on dates that extend to 250 days (incremented by 10 days) from the value of &DATE. Assuming a DO LOOP would be the most efficient execution method, I am having trouble understanding how the loop would "create" a new macro var (ex. &Date150) within the loop. Assuming the syntax below is correct, I am not sure what the next/correct step would be :
DATA _NULL_;
DO I=10 TO 150 BY 10;
CALL SYMPUT('DATE10',&DATE);
END;
RUN;
How would I "increment" the actual name of the macro var (&DATE10,&Date20...&Date150) in the loop while executing the creation of a macro var based on 10 day increments?
Use the I variable as part of your variable name, via a concatenation function, cats() is probably appropriate. Also, a personal preference, but I prefer Call SymputX as it removes any extra spaces.
DATA _NULL_;
DO I=10 TO 150 BY 10;
CALL SYMPUTX(cats('DATE', i), &DATE+i);
END;
RUN;
Consider placing the values into a single macro variable. As long as the list is not longer than maximum length of a macro variable.
DATA _NULL_;
length dates $32767 ;
date=today();
DO I=10 TO 150 BY 10;
dates=catx(' ',dates,date+i);
end;
CALL SYMPUTx('dates',dates);
RUN;
Then in your reporting code you can either just use the list of dates.
proc report ;
where date in (&dates);
...
run;
Or if you have macro you can use a %DO loop.
%do i=1 %to %sysfunc(countw(&dates));
%let date=%scan(&dates,&i);
proc report;
where date=&date;
....
%end;
Easy enough - pass in a variable as the first argument to call symput that contains the name of the macro variable you want to create.
Related
I need to store a value from a table into a variable. I've tried let, symputx, and select into. In the current version I try to use symputx, but the variable is not being updated. The products table contains type, price_floor, price_tier1, price_tier2.
%global price1;
%global price2;
%macro container();
DATA _null_;
SET products;
IF type = "Single" THEN DO;
CALL SYMPUTX('price1', price_floor,'g');
END;
IF type = "Multi" THEN DO;
CALL SYMPUTX('price1', price_tier1,'g');
CALL SYMPUTX('price2', price_tier2,'g');
END;
%PUT &=price1;
%PUT &=price2;
%mend;
Both price1 and price2 are null.
SYMBOLGEN: MACRO variable PRICE1 resolves to PRICE1=
SYMBOLGEN: MACRO variable PRICE2 resolves to PRICE2=
You don't have a run statement on your datastep, so your %put statement is being written to the log before the data step executes - so, the variables don't exist yet. It won't be run until you do provide a run statement or step boundary, or SAS might do that politely for you when the program is finished, but either way it's not being run before the %put.
Usually, this kind of program is an anti-pattern in SAS; you don't provide sufficient context, so maybe it's okay, but this will only work if you have only one row in the dataset - otherwise it probably won't do anything useful. The key word that's triggering me to write this is that you called this a "variable" - not a "macro variable" - in your question; SAS macro variables are not really "variables" and not meant to be used like a C variable or Python variable.
FYI to anyone interest, I decided to restructure the program as a whole. The original plan was to pass elements of a user defined array into %container, then use the assigned macro variables price1 and price2 as parameter in another macro call.
Instead of imbedding this data step in a macro, I created a table to contain all of the inputs I planned to pass into %container. Then I just used executes with the table variables concatenated within instead of direct macro calls.
data _null_;
SET products;
IF type = "Single" THEN DO;
CALL execute('%split_prod('||price_floor||');');
END;
IF type = "Multi" THEN DO;
CALL execute('%select_prod('||price1||','||price2||');');
END;
run;
There isn't anything wrong with the code besides the RUN.
%global price1;
%global price2;
data products;
infile cards dlm=',';
input Type $ price_floor price_tier1 price_tier2;
cards;
Multi, 40, 20, 60
;;;;;
%macro container();
DATA _null_;
SET products;
IF type = "Single" THEN DO;
CALL SYMPUTX('price1', price_floor,'g');
END;
IF type = "Multi" THEN DO;
CALL SYMPUTX('price1', price_tier1,'g');
CALL SYMPUTX('price2', price_tier2,'g');
END;
RUN;
%mend;
%container();
%PUT &=price1;
%PUT &=price2;
LOG:
96 %PUT &=price1;
PRICE1=20
97 %PUT &=price2;
PRICE2=60
You never showed your call for %container() so not sure what the underlying issue is, but CALL EXECUTE is a better method as debugging macros is painful.
I'm trying to create a custom transformation within SAS DI Studio to do some complicated processing which I will want to reuse often. In order to achieve this, as a first step, I am trying to replicate the functionality of a simple APPEND transformation.
To this end, I've enabled multiple inputs (max of 10) and am trying to leverage the &_INPUTn and &_INPUT_count macro variables referenced here. I would like to simply use the code
data work.APPEND_DATA / view=work.APPEND_DATA;
%let max_input_index = %sysevalf(&_INPUT_count - 1,int);
set &_INPUT0 - &&_INPUT&max_input_index;
keep col1 col2 col3;
run;
However, I receive the following error:
ERROR: Missing numeric suffix on a numbered data set list (WORK.SOME_INPUT_TABLE-WORK.ANOTHER_INPUT_TABLE)
because the macro variables are resolved to the names of the datasets they refer to, whose names do not conform to the format required for the
SET dataset1 - dataset9;
statement. How can I get around this?
Much gratitude.
You need to create a macro that loops through your list and resolves the variables. Something like
%macro list_tables(n);
%do i=1 %to &n;
&&_INPUT&i
%end;
%mend;
data work.APPEND_DATA / view=work.APPEND_DATA;
%let max_input_index = %sysevalf(&_INPUT_count - 1,int);
set %list_tables(&max_input_index);
keep col1 col2 col3;
run;
The SET statement will need a list of the actual dataset names since they might not form a sequence of numeric suffixed names.
You could use a macro %DO loop if are already running a macro. Make sure to not generate any semi-colons inside the %DO loop.
set
%do i=1 %to &_inputcount ; &&_input&i %end;
;
But you could also use a data step to concatenate the names into a single macro variable that you could then use in the SET statement.
data _null_;
call symputx('_input1',symget('_input'));
length str $500 ;
do i=1 to &_inputcount;
str=catx(' ',str,symget(cats('_input',i)));
end;
call symputx('_input',str);
run;
data .... ;
set &_input ;
...
The extra CALL SYMPUTX() at the top of the data step will handle the case when count is one and SAS only creates the _INPUT macro variable instead of creating the series of macro variables with the numeric suffix. This will set _INPUT1 to the value of _INPUT so that the DO loop will still function.
So I have this macro: stab_index(yearmonth,period).
Let's say that I have to run it 5 times (maybe more) with different parameters like this
%stab_index(201601,01/2016);
%stab_index(201602,02/2016);
%stab_index(201603,03/2016);
%stab_index(201604,04/2016);
%stab_index(201605,05/2016);
in order to generate a adequate dataset to run another macro: Stab_Ind_DYNAMICS.
But I don't want to run 6 times to get the result, I would like to run all of them at once without having to fill the parameters every time.
Can someone point me in the direction of how I would set this up?
Thanks!
This assumes your parameter values always exist within your data. If you can get your dataset down to every unique combination of yearmonth and period (how my unique dataset looks below), then you don't need to input anything, just let the data do the work which can accommodate changing data:
** create test data **;
data have0;
year = 2016;
do i=1 to 12;
temp=i;
output;
end;
run;
data have; set have0;
temp1 = strip(put(year,best4.))||strip(put(temp,z2.));
yearmonth=intnx('month', input(put(temp1,6.), yymmn6.), 1)-1;
period=yearmonth;
format yearmonth yymmn6. period mmyys7.;
run;
** get data down to every unique combination of yearmonth and period **;
proc sort data = have out=unique(keep=yearmonth period) nodupkey;
by yearmonth period;
run;
** create a macro string dynamically using data **;
data create_macro_string; set unique;
macro_str=%nrstr("%stab_index")||"("||strip(put(yearmonth,yymmn6.))||","||strip(put(period,mmyys7.))||");";
keep yearmonth period macro_str;
run;
** put all your macros into a list **;
proc sql noprint;
select macro_str
into: macro_list separated by " "
from create_macro_string;
quit;
** call your macros **;
%put ¯o_list.;
You can achieve this with another macro which loops trough your list of parameters.
%let param1 = 201601 201602 201603 201604 201605;
%let param2 = 01/2016 02/2016 03/2016 04/2016 05/2016;
%macro loop();
%do i=1 %to %sysfunc(countw(¶m1,%str( )));
%let thisparam1=%scan(¶m1,&i,%str( ));
%let thisparam2=%scan(¶m2,&i,%str( ));
%put &thisparam1 &thisparam2;
%stab_index(&thisparam1,&thisparam2);
%end;
%mend loop;
%loop;
You first need to define your lists of parameters (I called them param1 & param2 here).
Then you can loop from 1 to the number of words and retrieve the i'th paramter from the list and use it in your stab_index macro.
Just in case you parameters contains spaces, you can use another separator than spaces for your lists and define it with a 2nd argument in the countw function (%sysfunc(countw(¶m1,'-'))) and a third parameter in the scan function (%scan(¶m1,&i,'-')).
I have a set of input macro variables in SAS. They are dynamic and generated based on the user selection in a sas stored process.
For example:There are 10 input values 1 to 10.
The name of the macro variable is VAR_. If a user selects 2,5,7 then 4 macro variables are created.
&VAR_0=3;
&VAR_=2;
&VAR_1=5;
&VAR_2=7;
The first one with suffix 0 provides the count. The next 3 provides the values.
Note:If a user select only one value then only one macro variable is created. For example If a user selects 9 then &var_=9; will be created. There will not be any count macro variable.
I am trying to create a sas table using these variables.
It should be like this
OBS VAR
-----------
1 2
2 5
3 7
-----------
This is what I tried. Not sure if this is the right way to do approach it.
It doesn't give me a final solution but I can atleast get the name of the macro variables in a table. How can I get their values ?
data tbl1;
do I=1 to &var_0;
VAR=CAT('&VAR_',I-1);
OUTPUT;
END;
RUN;
PROC SQL;
CREATE TABLE TBL2 AS
SELECT I,
CASE WHEN VAR= '&VAR_0' THEN '&VAR_' ELSE VAR END AS VAR
from TBL1;
QUIT;
Thank You for your help.
Jay
SAS helpfully stores them in a table for you already, you just need to parse out the ones you want. The table is called SASHELP.VMACRO or DICTIONARY.MACROS
Here's an example:
%let var=1;
%let var2=3;
%let var4=5;
proc sql;
create table want as
select * from sashelp.vmacro
where name like 'VAR%';
quit;
proc print data=want;
run;
I think the real issue is the inconsistent behavior of the stored process. It only creates the 0 and 1 variable when there are multiple selections. I think that your example is a little off. If the value of VAR_0 is three then their should be a VAR_3 macro variable. Also the value of VAR_ and VAR_1 should be set to the same thing.
To fix this in the past I have done something like this. First let's assign the parameter name a macro variable so that the code is reusable for other programs.
%let name=VAR_;
Then first make sure the minimal macro variables exist.
%global &name &name.0 &name.1 ;
Then make sure that you have a count by setting the 0 variable to 1 when it is empty.
%let &name.0 = %scan(&&&name.0 1,1);
Then make sure that you have a 1 variable. Since it should have the same value as the macro variable without a suffix just re-assign it.
%let &name.1 = &&&name ;
Now your data step is easier.
data want ;
length var $32 value $200 ;
do i=1 to &&&name.0 ;
var=cats(symget('name'),i);
value=symget(var);
output;
end;
run;
I don't understand your numbering scheme and recommend changing it, if you can; the &var_ variable is very confusing.
Anyway, the easiest way to do this is SYMGET. That returns a value from the macro symbol table which you can specify at runtime.
%let VAR_0=3;
%let VAR_=2;
%let VAR_1=5;
%let VAR_2=7;
data want;
do obs = 1 to &var_0.;
var = input(symget(cats('VAR_',ifc(obs=1,'',put(obs-1,2.)))),2.);
output;
end;
run;
I am trying to create categorical variables in sas. I have written the following macro, but I get an error: "Invalid symbolic variable name xxx" when I try to run. I am not sure this is even the correct way to accomplish my goal.
Here is my code:
%macro addvars;
proc sql noprint;
select distinct coverageid
into :coverageid1 - :coverageid9999999
from save.test;
%do i=1 %to &sqlobs;
%let n=coverageid&i;
%let v=%superq(&n);
%let f=coverageid_&v;
%put &f;
data save.test;
set save.test;
%if coverageid eq %superq(&v)
%then &f=1;
%else &f=0;
run;
%end;
%mend addvars;
%addvars;
You're combining macro code with data step code in a way that isn't correct. %if = macro language, meaning you are actually evaluating whether the text "coverageid" is equal to the text that %superq(&v) evaluates to, not whether the contents of the coverageid variable equal the value in &v. You could just convert %if to if, but even if you got that to work properly it would be hideously inefficient (you're rewriting the dataset N times, so if you have 1500 values for coverageID you rewrite the entire 500MB dataset or whatnot 1500 times, instead of just once).
If what you want to do is take the variable 'coverageid' and convert it to a set of variables that consist of all possible values of coverageid, 1/0 binary, for each, there are a nubmer of ways to do it. I'm fairly sure the ETS module has a procedure that just does this, but I don't recall it off the top of my head - if you were to post this to the SAS mailing list, one of the guys there would undoubtedly have it quickly.
The simple way for me, is to do this with entirely datastep code. First determine how many potential values there are for COVERAGEID, then assign each to a direct value, then assign the value to the correct variable.
If the COVERAGEID values are consecutive (ie, 1 to some number, no skips, or you don't mind skipping) then this is easy - set up an array and iterate over it. I will assume they are NOT consecutive.
*First, get the distinct values of coverageID. There are a dozen ways to do this, this works as well as any;
proc freq data=save.test;
tables coverageid/out=coverage_values(keep=coverageid);
run;
*Then save them into a format. This converts each value to a consecutive number (so the lowest value becomes 1, the next lowest 2, etc.) This is not only useful for this step, but it can be useful in the future in converting back.;
data coverage_values_fmt;
set coverage_values;
start=coverageid;
label=_n_;
fmtname='COVERAGEF';
type='i';
call symputx('CoverageCount',_n_);
run;
*Import the created format;
proc format cntlin=coverage_values_fmt;
quit;
*Now use the created format. If you had already-consecutive values, you could skip to this step and skip the input statement - just use the value itself;
data save.test_fin;
set save.test;
array coverageids coverageid1-coverageid&coveragecount.;
do _t = 1 to &coveragecount.;
if input(coverageid,COVERAGEF.) = _t then coverageids[_t]=1;
else coverageids[_t]=0;
end;
drop _t;
run;
Here's another way that doesn't use formats, and may be easier to follow.
First, just make some test data:
data test;
input coverageid ##;
cards;
3 27 99 105
;
run;
Next, create a data set with no observations but one variable for each level of coverageid. Note that this approach allows arbitrary values here.
proc transpose data=test out=wide(drop=_name_);
id coverageid;
run;
Finally, create a new data set that combines the initial data set and the wide one. Then, for each level of x, look at each categorical variable and decide whether to turn it "on".
data want;
set test wide;
array vars{*} _:;
do i=1 to dim(vars);
vars{i} = (coverageid = substr(vname(vars{i}),2,1));
end;
drop i;
run;
The line
vars{i} = (coverageid = substr(vname(vars{i}),2));
may require more explanation. vname returns the name of the variable, and since we didn't specify a prefix in proc transpose, all variables are named something like _1, _2, etc. So we take the substring of the variable name that starts in the second position, and compare it to coverageid; if they're the same, we set the variable to 1; otherwise it evaluates to 0.