SAS call symput in data step - sas

I am facing this issue with sas data step. My requirement is to get a list of variables such as
total_jun2018 = sum(jun2018, dep_jun2018);
total_jul2018 = sum(jul2018, dep_jul2018);
Data final4;
set final3;
by hh_no;
do i=0 to &tot_bal_mnth.;
bal_mnth = put(intnx('month',"&min_Completed_dt."d, i-1), monyy7.);
call symputx('bal_mnth', bal_mnth);
&bal_mnth._total=sum(&bal_mnth., Dep_&bal_mnth.);
output;
end;
But I am facing error that macro variable bal_mnth not resolved. Also once it did ran successfully but I want that output must be printed sequentially but it only prints output for last loop when i=6 then it prints only Total_DEC2018=sum(DEC2018, DEP_DEC2018);
Any help will be appreciated!
Thanks,
Ajay

This is a common issue when learning SAS Macro. The problem is that the macro processor needs to resolve &bal_mnth to a value when the data step is first submitted for execution, but the CALL SYMPUT doesn't execute until the data step is actually executed, so at the time you submit the code, there is no value available for &bal_mnth.
In this case you don't need bal_mnth to be created as a variable in the data set, so you could replace the line that starts bal_mnth = put(intck(...)) with a %let bal_mnth = ... statement. The %let executes while the data step is being submitted, so that way its value will be available when you need it.
My proposed %let statement will need to wrap the functions in at least one SYSFUNC call, which is left as an exercise for the reader :-)

It looks like you want to generate a series of assignment statements like:
total_jun2018 = sum(jun2018, dep_jun2018);
total_jul2018 = sum(jul2018, dep_jul2018);
...
total_jan2019 = sum(jan2019, dep_jan2019);
What is known as wallpaper code.
If your variables names were easier, such as dep1 to dep18 then it would be easy to use arrays to process the data. With your current naming convention the problem with generating the array statements is not much different than the problem of generating a series of assignment statements.
You can create a macro so that you could use a %DO loop to generate your wallpaper code.
%local i bal_mnth;
%do i=0 %to &tot_bal_mnth.;
%let bal_mnth = %sysfunc(intnx(month,"&min_Completed_dt."d, &i-1), monyy7.);
total_&bal_mnth = sum(&bal_mnth , Dep_&bal_mnth );
%end;
Or you could just generate the code to a file with a data step.
%let tot_bal_mnth = 7;
%let min_Completed_dt=01JUN2018;
filename code temp;
data _null_;
file code;
length bal_mnth $7 ;
do i=0 to &tot_bal_mnth.;
bal_mnth = put(intnx('month',"&min_Completed_dt."d, i-1), monyy7.);
put 'total_' bal_mnth $7. ' = sum(' bal_mnth $7. ', Dep_' bal_mnth $7. ');';
end;
run;
So the generated file of code looks like this:
total_MAY2018 = sum(MAY2018, Dep_MAY2018);
total_JUN2018 = sum(JUN2018, Dep_JUN2018);
total_JUL2018 = sum(JUL2018, Dep_JUL2018);
total_AUG2018 = sum(AUG2018, Dep_AUG2018);
total_SEP2018 = sum(SEP2018, Dep_SEP2018);
total_OCT2018 = sum(OCT2018, Dep_OCT2018);
total_NOV2018 = sum(NOV2018, Dep_NOV2018);
total_DEC2018 = sum(DEC2018, Dep_DEC2018);
You can then use %include to run it in your data step.
data final4;
set final3;
by hh_no;
%include code / source2 ;
run;

I would like to offer another point of view: the difficulty you are having here results from the use of a wide data shape, with lots of columns.
Rather than working with your data in this shape, you could first transpose from wide to long, so that instead of having lots of total_xxx columns you just have 3: total, total_dep and date, with one row per month. Once it's in this format, it will be much easier to work with, potentially allowing you to avoid resorting to macros and wallpaper code.
Suggested reading:
Transpose wide to long with dynamic variables

Related

Using SAS finance XIRR function on multiple rows with different numbers of variables

I am trying to use the SAS XIRR function on a dataset. The syntax is:
finance('XIRR',value1, value2, value3...valuen,date1,date2,date3...daten);
My problem is that the data has different numbers of values/dates on each row. There could be up to 122 values/dates per row.
Where there are missing values the XIRR function fails, so I set all missing values to 0. Now the function fails as the 'missing' dates are now Jan1960. Anyone got any ideas?
in the code below cf1-cf122 are the cash flow values and ed1-ed122 are the dates.
/* remove blanks */
data irrtable3;
set irrtable2;
array change _numeric_;
do over change;
if change=. then change=0;
end;
run;
/* create irr */
data irrtable4;
set irrtable3;
IRR=finance('XIRR',OF CF1-CF122,OF ED1-ED122);
run;```
You can use codegen to construct a dynamic FINANCE(..) call, with a variable number of arguments, that is resolved by the macro system at DATA step run-time.
Using RESOLVE to compute the result in macro environment for many, many rows will likely have a noticeable slowness compared to plain DATA step.
Example:
data have;
v1=−10000; d1=mdy(1, 1, 2008);
v2=2750; d2=mdy(3, 1, 2008);
v3=4250; d3=mdy(10, 30, 2008);
v4=3250; d4=mdy(2, 15, 2009);
v5=2750; d5=mdy(4, 1, 2009);
output;
call missing(v5,d5); output;
call missing(v4,d4); output;
call missing(v3,d3); output;
call missing(v2,d2); output;
run;
options missing=' ';
data want;
set have;
args = catx(',', of v1-v5, of d1-d5);
result = resolve( cats (
'%sysfunc(FINANCE(XIRR,', args, '))'
));
run;
options missing='.';
From what I can tell (And I don't work with Finance functions, so I'm not an expert), if you have all of the 'filled' arguments prior to the 'unfilled', you are okay to just set everything to zero that's missing (both on the 'value' and 'date' side). Using the example Richard provides (which is the one from the SAS documentation):
data want2;
set have;
array v v1-v5;
array d d1-d5;
do _i_ = 1 to dim(v);
if missing(v[_i_]) then do;
v[_i_]=0; d[_i_]=0;
end;
end;
args = catx(',', of v1-v5, of d1-d5);
result =FINANCE('XIRR',of v1-v5, of d1-d5);
run;
That works and gets the same result as Richard's, and is probably faster.
This does require the 0s to all be at the end - if they're interspersed, and you can't use CALL SORTN to get them put all on one end - and your data is too big to use with RESOLVE, then I would construct this entirely in the macro language. You could do a few things, all of which are too long for this answer, but the simplest is probably to create code for every line, and put them behind if _n_ = 5 then do; &row5code.; end; for each row. This would be very long, certainly, but should be faster than the resolve (just a lot less maintainable). You could also do a CALL EXECUTE for each line, also slow but a possibility, or even DOSUBL.

Calling macros from within a datastep

Maintaining somebody else's SAS project, I found some code snippet which creates a table input-stats within a data step. The variable &all. contains a list of tables to examine. The data step is rather long which I shortened here with /* more code */:
%let all = "work.table1*mywork.table2";
data input-stats;
i = 1;
do while (scan(&all., i, '*') ne '');
name = scan(&all., i, '*');
/* more code */
output;
i = i + 1;
end;
run;
I want to expand the table input-stat with yet another column giving me the number of lines of each table. I found the following macro within the project to do exactly this:
%macro count_rows(ds);
%let DSID=%sysfunc(OPEN(&DS.,IN));
%let NOBS=%sysfunc(ATTRN(&DSID.,NOBS));
%let RC=%sysfunc(CLOSE(&DSID.));
&nobs
%mend;
I would like to now integrate this call within the above mentioned data step, but obviously, I cannot just simply add a rows=%count_rows(name) (e.g. instead of /* more code */) as I would in other programming languages.
How would you solve this issue with minimal code modifications? Is there a way without making a huge %while loop?
The functionality of the macro code can be replicated in DATA Step scope by invoking the same functions. No need for macro and intermingling scopes that can be confusing when using RESOLVE or CALL EXECUTE.
...
name = scan(&all., i, '*');
/* more code */
* add row counting code here;
_dsid = open (name,'IN');
nobs = attrn(_dsid,'NOBS');
_dsid = close (_dsid);
drop _:;
output;
...

Remove single quotes in list of values in macro variable

I have a project with multiple programs. Each program has a proc SQL statement which will use the same list of values for a condition in the WHERE clause; however, the column type of one database table needed is a character type while the column type of the other is numeric.
So I have a list of "Client ID" values I'd like to put into a macro variable as these IDs can change, and I would like to change them once in the variable instead of in multiple programs.
For example, I have this macro variable set up like so and it works in the proc SQL which queries the character column:
%let CLNT_ID_STR = ('179966', '200829', '201104', '211828', '264138');
Proc SQL part:
...IN &CLNT_ID_STR.
I would like to create another macro variable, say CLNT_ID_NUM, which takes the first variable (CLNT_ID_STR) but removes the quotes.
Desired output: (179966, 200829, 201104, 211828, 264138)
Proc SQL part: ...IN &CLNT_ID_NUM.
I've tried using the sysfunc, dequote and translate functions but have not figured it out.
TRANSLATE doesn't seem to want to allow a null string as the replacement.
Below uses TRANSTRN, which has no problem translating single quote into null:
1 %let CLNT_ID_STR = ('179966', '200829', '201104', '211828', '264138');
2 %let want=%sysfunc(transtrn(&clnt_id_str,%str(%'),%str())) ;
3 %put &want ;
(179966, 200829, 201104, 211828, 264138)
It uses the macro quoting function %str() to mask the meaning of a single quote.
Three other ways to remove single quotes are COMPRESS, TRANSLATE and PRXCHANGE
%let CLNT_ID_STR = ('179966', '200829', '201104', '211828', '264138');
%let id_list_1 = %sysfunc(compress (&CLNT_ID_STR, %str(%')));
%let id_list_2 = %sysfunc(translate(&CLNT_ID_STR, %str( ), %str(%')));
%let id_list_3 = %sysfunc(prxchange(%str(s/%'//), -1, &CLNT_ID_STR));
%put &=id_list_1;
%put &=id_list_2;
%put &=id_list_3;
----- LOG -----
ID_LIST_1=(179966, 200829, 201104, 211828, 264138)
ID_LIST_2=( 179966 , 200829 , 201104 , 211828 , 264138 )
ID_LIST_3=(179966, 200829, 201104, 211828, 264138)
It really doesn't matter that TRANSLATE replaces the ' with a single blank () because the context for interpretation is numeric.

JCL: How to read file name and find the specific string

I need to read the file name in JCL and find specific string in that. If string present then i need to set the flag variable.
Example:
000063 //SETVARS SET RUNMODE=AY,
000064 // MGRTMODE=M,
000065 // PARMFILE=BASXXXX.T1.XXXX.JIRA.T011746
If "PARMFILE" contains "JIRA", then set JIRAFLAG = Y else JIRAFLAG = N. I need to pass the JIRAFLAG to another JCL/SAS job to process further.
Thanks!
Bharathi
SAS:
000083 /**/
000084 %LET CHKFILE = %SCAN(&SYSPARM,1,+);
000085 %put &CHKFILE ;
000086 %GLOBAL JIRAFLG ;
000087 %MACRO CHK ;
000088 %LET TSTVAL = %SCAN(&CHKFILE,4,.) ;
000089 %PUT &TSTVAL;
000090 %IF &TESTVAL EQ 'JIRA' %THEN %LET JIRAFLG = 'Y' ;
000091 %ELSE %LET JIRAFLG = 'N' ;
000092 %PUT &JIRAFLG ;
000093 %MEND CHK;
000094 %CHK ;
JCL does not provide a scripting language per se although it is intuitive to think of it that way because of the JCL Symbols and conditionals.
I'm not a SAS buy but it seems like your SAS sample does the scanning. I'd suggest that you have a STEP where you parse the PARMFILE statement and set a known return code of your choice. For instance, choose a return code of 1.
Follow that with another step that when the return code is 1 executes the program you want to process when JIRA is in the PARMFILE. Since you mentioned a seperate job you could submit another job through INTRDR.
There are ways to accomplish what you want you just need to be creative. As I said, the conditionals and JCL Symbols lure many people into thinking scripting which is unfortunately not how it works.
SRCHFOR 'YOURSTRING',W (https://www.ibm.com/support/knowledgecenter/en/SSLTBW_2.1.0/com.ibm.zos.v2r1.f54u200/sfcmdfgrr.htm)
and then check here for conditional processing
https://www.ibm.com/support/knowledgecenter/en/SSLTBW_2.1.0/com.ibm.zos.v2r1.ieab600/iea3b6_Examples_of_IF_THEN_ELSE_ENDIF_statement_constructs.htm

Stop SAS from trying to resolve & reference within a macro string

I'm running a process that lists jobs I want to check the modification date on. I list the jobs in a dataset and then pass these to macro variables with a number.
e.g.
Data List_Prep;
Format Folder
Code $100.;
Folder = 'C:\FilePath\Job ABC'; Code = '01 Job Name.sas'; Output;
Folder = 'C:\FilePath\Job X&Y'; Code = '01 Another Job.sas'; Output;
Run;
%Macro List_Check();
Data List;
Set List_Prep;
Job + 1;
Call Symput (Cats("Folder", Job), Strip(Folder));
Call Symput (Cats("Code", Job), Strip(Code));
Run;
%Put Folder1 = &Folder1;
%Put Folder2 = &Folder2;
%MEnd;
%List_Check;
It prints the %Put statement just fine for foler 1, but folder 2 doesn't work right.
Folder1 = C:\FilePath\Job ABC
WARNING: Apparent symbolic reference Y not resolved.
Folder2 = C:\FilePath\Job X&Y
When I then go in to a loop to check the datasets, again, it work, so looks for Folder1, Code1 etc, but I still get the warnings.
How can I stop these warnings? I've tried %Str("&") instead, but still get the issue.
The %superq() macro function is a great way to mask macro triggers that are already in a macro variable. You could either remember to quote the values when using them,
%put Folder1 = %superq(Folder1) ;
or you could adjust your process to quote them right after creating them.
data List_Prep;
length Folder Code $100;
Folder = 'C:\FilePath\Job ABC'; Code = '01 Job Name.sas'; Output;
Folder = 'C:\FilePath\Job X&Y'; Code = '01 Another Job.sas'; Output;
run;
data List;
set List_Prep;
Job + 1;
length dummy $200 ;
call symputx(cats("Folder", Job), Folder);
dummy = resolve(catx(' ','%let',cats("Folder", Job),'=%superq(',cats("Folder", Job),');'));
call symputx(cats("Code", Job), Code);
dummy = resolve(catx(' ','%let',cats("Code", Job),'=%superq(',cats("Code", Job),');'));
drop dummy;
run;
P.S. Don't use FORMAT to define variables. Use statements like LENGTH or ATTRIB that are designed for defining variables. FORMAT is for attaching formats to variable, not for defining them. The only reason that using FORMAT worked is that it had the side effect of SAS defining the variable's type and length to match the format that you attached to it because it was the first place you referenced the variable in the data step.
You can prevent SAS from trying to resolve the ampersand in the value by using the %superq function
%put Folder2 = %superq(Folder2);