'Hello World' in SAS with a macro gives syntax error - sas

I am extremely new to the SAS world so naturally I was trying to write my 'Hello World'. Here is my MWE which gives me the syntax errors:
/* SAS Hello World Program */
/*Macro with date*/
%Macro datum;
Title "Hello World, today is &Sysday, &Sysdate";
%Mend datum;
/*Create Hello World Data Set */
data HelloWorld;
msg = %datum ;
run;
/*Print Hello World*/
proc print data = HelloWorld;
run;
It does not print the 'Hello Wolrd'-message instead it gives a syntax error which I don't understand. In the Log the message appears, so in principle it works - just the print step doesn't. Any ideas?

The macro you created generates a TITLE statement. So it should work fine.
But your program is using it in the wrong place. Once your macro runs and finishes generating the text of the TITLE statement your data step will look like this:
data HelloWorld;
msg = Title "Hello World, today is Thursday, 10JAN19"; ;
run;
Which will obviously give an error since the right side of the assignment statement now has two tokens, a variable named TITLE and a string constant, without any operator between them. The extra semi-colon will just generate an extra null statement and not cause any problems.
Perhaps you want to create a macro VARIABLE instead of an actual macro?
To do that your program would look more like this.
%let msg=Hello World, today is &Sysday, &Sysdate ;
data HelloWorld;
msg = "&msg." ;
run;
So when the macro variable reference is replaced this will evaluate to this SAS code to run.
data HelloWorld;
msg = "Hello World, today is Thursday, 10JAN19" ;
run;
Notice how you use & to trigger the evaluation of a macro variable. Also notice how I did not add the quotes to the value of the macro variable, but included them in the SAS code that was generated by using the value of macro variable.
NOTE that it has yesterday's day of the week and date. That is because the automatic macro variables SYSDAY and SYSDATE are set when SAS starts running and I ran this code in a SAS session that I started yesterday.

You have it as title, so you cannot use it for an assignment variable if you are beginner then do use
/* this will print in log*/
data _null_;
put "hello world";
run;
/* or use in datastep by making variable or by using macrovariable*/
%let a= Hello World, today is &Sysday, &Sysdate;
data have;
var= "Hello world";
var2= "&a";
run;
proc print data = have;
run;

Related

assign macro variable output of tranwrd in macro function

I can't figure out this seemingly trivial problem - expect macro variable to be assigned mpg_city.
%macro test(col=);
%let id = %sysfunc(tranwrd(&col, 'extra_', ''));
%put &id;
%mend test;
%test(col=extra_mpg_city);
Current output is extra_mpg_city.
Arguments listed in a function invoked through %sysfunc are implicitly text and should not be quoted. Placing quotes in a sysfunc invoked function is like nesting quotes in a DATA step invocation.
Try
%let id = %sysfunc(tranwrd(&col, extra_, %str()));
The DATA Step analog is
id = tranwrd("&col", "extra_", "");
Your original code in DATA Step analog (below) should show why the tranwrd did not operate as you expected.
id = tranwrd("&col", "'extra_'", "''");
You don't need the quotes when using string functions with %sysfunc, unless you expect to find them in the input. Try this:
%macro test(col=);
%let id = %sysfunc(tranwrd(&col, extra_, ));
%put &id;
%mend test;
%test(col=extra_mpg_city);

Compress Newline character for dynamic varaibles

Dataset: Have
F1 F2
Student Section
Name No
Dataset "Have". Data has new line character.
I need to compress the newline character from the data.
I want to do this dynamically as sometimes the "Have" dataset may contain new variables like F3,F4,F5 etc.,
I have written as macro to do this.. However it is not working as expected.
When i execute the below code, first time I am getting error as invalid reference newcnt. If i execute for second time in the same session, i am not getting error.
PFB my code:
%macro update_2(newcnt);
data HAVE;
set HAVE;
%do i= 1 %to &newcnt;
%let colname = F&i;
&colname=compress(&colname,,'c');
%end;
run;
%mend update_2;
%macro update_1();
proc sql noprint;
select count(*) into :cnt from dictionary.columns where libname="WORK" and memname="HAVE";
quit;
%update_2(&cnt)
%mend update_1;
Note: All the variables have name as F1,F2,F3,F4.,
Please tell me what is going wrong..
If there is any other procedures, please help me.
In your macro %update_1 you're creating a macro variable called &cnt, but when you call %update_2 you refer to another macro variable, &colcnt. Try fixing this reference and see if your code behaves as expected.
We created our own function to clean unwanted characters from strings using proc fcmp. In this case, our function cleans tab characters, line feeds, and carriage returns.
proc fcmp outlib=common.funcs.funcs; /* REPLACE TARGET DESTINATION AS NECESSARY */
function clean(iField $) $200;
length cleaned $200;
bad_char_list = byte(10) || byte(9) || byte(13);
cleaned = translate(iField," ",bad_char_list);
return (cleaned );
endsub;
run;
Create some test data with a new line character in the middle of it, then export it and view the results. You can see the string has been split across lines:
data x;
length employer $200;
employer = cats("blah",byte(10),"diblah");
run;
proc export data=x outfile="%sysfunc(pathname(work))\x.csv" dbms=csv replace;
run;
Run our newly created clean() function against the string and export it again. You can see it is now on a single line as desired:
data y;
set x;
employer = clean(employer);
run;
proc export data=y outfile="%sysfunc(pathname(work))\y.csv" dbms=csv replace;
run;
Now to apply this method to all character variables in our desired dataset. No need for macros, just define an array referencing all the character variables, and iterate over them applying the clean() function as we go:
data cleaned;
set x;
array a[*] _char_;
do cnt=lbound(a) to hbound(a);
a[cnt] = clean(a[cnt]);
end;
run;
EDIT : Also note that fcmp may have some performance considerations to consider. If you are working with very large amounts of data, there may be other solutions that will perform better.
EDIT 6/15/2020 : Corrected missing length statement that could result in truncated responses.
Here's an example of Robert Penridge's function, as a call routine with an array as an argument. This probably only works in 9.4+ or possibly later updates of 9.3, when permanent arrays began being allowed to be used as arguments in this way.
I'm not sure if this could be done flexibly with an array as a function; without using macros (which require recompilation of the function constantly) I don't know how one could make the right size of array be returned without doing it as a call routine.
I added 'Z' to the drop list so it's obvious that it works.
options cmplib=work.funcs;
proc fcmp outlib=work.funcs.funcs;
sub clean(iField[*] $);
outargs iField;
bad_char_list = byte(11)|| byte(10) || byte(9) || byte(13)||"Z";
do _i = 1 to dim(iField);
iField[_i] = translate(iField[_i],trimn(" "),bad_char_list);
end;
endsub;
quit;
data y;
length employer1-employer5 $20;
array employer[4] $;
do _i = 1 to dim(employer);
employer[_i] = "Hello"||byte(32)||"Z"||"Goodbye";
end;
employer5 = "Hello"||byte(32)||"Z"||"Goodbye";
call clean(employer);
run;
proc print data=y;
run;
Here is another alternative. If newline is the only thing you want to remove, then we are talking about Char only, you may leverage implicit array and Do over,
data want;
set have;
array chr _character_;
do over chr;
chr=compress(chr,,'c');
end;
run;

Running a SAS macro for every day in a range of dates

I am an intermediate user of SAS, but I have limited knowledge of arrays and macros. I have a set of code that prompts the user to enter a date range. For example, the user might enter December 1, 2015-December 5,2015. For simplicity, imagine the code looks like:
data new; set old;
if x1='December 1, 2015'd then y="TRUE";
run;
I need to run this same code for every day in the date prompt range, so for the 1st, 2nd, 3rd, 4th, and 5th. My thought was to create an array that contains the dates, but I am not sure how I would do that. My second thought was to create a macro, but I can't figure out out to feed a list through a macro.
Also, just FYI, the code is a lot longer and more complicated than just a data step.
The following macro can be used as a framework for your code:
%MACRO test(startDate, endDAte);
%DO i=&startDate %to &endate;
/* data steps go here */
/* example */
DATA test;
SET table;
IF x1 = &i THEN y = "true";
RUN;
%END;
%MEND;
Look into call execute to call your macro and a data null step using a do loop to loop through the days. Getting the string correct for the call execute can sometimes be tricky, but worth the effort overall.
data sample;
do date='01Jan2014'd to '31Jan2014'd;
output;
end;
run;
%macro print_date(date);
proc print data=sample;
where date="&date"d;
format date date9.;
run;
%mend;
%let date_start=05Jan2014;
%let date_end=11Jan2014;
data _null_;
do date1="&date_start"d to "&date_end"d by 1;
str='%print_date('||put(date1, date9.)||');';
call execute(str);
end;
run;

Text manipulation of macro list variables to stack datasets with automated names

I have written a macro that accepts a list of variables, runs a proc mixed model using each variable as a predictor, and then exports the results to a dataset with the variable name appended to it. I am trying to figure out how to stack the results from all of the variables in a single data set.
Here is the macro:
%macro cogTraj(cog,varlist);
%let j = 1;
%let var = %scan(&varlist, %eval(&j));
%let solution = sol;
%let outsol = &solution.&var.;
%do %while (&var ne );
proc mixed data = datuse;
model &cog = &var &var*year /solution cl;
random int year/subject = id;
ods output SolutionF = &outsol;
run;
%let j = %eval(&j + 1);
%let var = %scan(&varlist, %eval(&j));
%let outsol = &solution.&var.;
%end;
%mend;
/* Example */
%cogTraj(mmmscore, varlist = bio1 bio2 bio3);
The result would be the creation of Solbio1, Solbio2, and Solbio3.
I have created a macro variable containing the "varlist" (Ideally, I'd like to input a macro variable list as the argument but I haven't figured out how to deal with the scoping):
%let biolist = bio1 bio2 bio3;
I want to stack Solbio1, Solbio2, and Solbio3 by using text manipulation to add "Sol" to the beginning of each variable. I tried the following, outside of any data step or macro:
%let biolistsol = %add_string( &biolist, Sol, location = prefix);
without success.
Ultimately, I want to do something like this;
data Solbio_stack;
set %biolistsol;
run;
with the result being a single dataset in which Solbio1, Solbio2, and Solbio3 are stacked, but I'm sure I don't have the right syntax.
Can anyone help me with the text string/dataset stacking issue? I would be extra happy if I could figure out how to change the macro to accept %biolist as the argument, rather than writing out the list variables as an argument for the macro.
I would approach this differently. A good approach for the problem is to drive it with a dataset; that's what SAS is good at, really, and it's very easy.
First, construct a dataset that has a row for each variable you're running this on, and a variable name that contains the variable name (one per row). You might be able to construct this using PROC CONTENTS or sashelp.vtable or dictionary.tables, if you're using a set of variables from one particular dataset. It can also come from a spreadsheet you import, or a text file, or anything else really - or just written as datalines, as below.
So your example would have this dataset:
data vars_run;
input name $ cog $;
datalines;
bio1 mmmscore
bio2 mmmscore
bio3 mmmscore
;;;;
run;
If your 'cog' is fairly consistent you don't need to put it in the data, if it is something that might change you might also have a variable for it in the data. I do in the above example include it.
Then, you write the macro so it does one pass on the PROC MIXED - ie, the inner part of the %do loop.
%macro cogTraj(cog=,var=, sol=sol);
proc mixed data = datuse;
model &cog = &var &var*year /solution cl;
random int year/subject = id;
ods output SolutionF = &sol.&var.;
run;
%mend cogTraj;
I put the default for &sol in there. Now, you generate one call to the macro from each row in your dataset. You also generate a list of the sol sets.
proc sql;
select cats('%cogTraj(cog=',cog,',var=',name,',sol=sol)')
into :callList
sepearated by ' '
from have;
select cats('sol',name') into :solList separated by ' '
from have;
quit;
Next, you run the macro:
&callList.
And then you can do this:
data sol_all;
set &solList.;
run;
All done, and a lot less macro variable parsing which is messy and annoying.

How do I invoke a macro variable inside the quoted string of a libname statement in SAS

I have a libname that varies from year to year and I wanted to make a program that automatically adjusts for this. But in order for everything to work I have to have invoke a macro inside of the quoted string in a libname statement. How do I do this?
%macro srvyr;
data work.whatever;
length srvyr $4.;
srvyr = (left(year(date()))-1);
srvyr2 = "'C:\Documents and Settings\user\Desktop\sas\d"||srvyr||"a1'";
run;
%mend;
%srvyr;
/*Everything above sets configures the pathname the way I need it*/
I want to then run this:
libname stuff &srvyr;run;
as if it were
libname stuff 'C:\Documents and Settings\user\Desktop\sas\d2010a1';
run;
How do I do this right?
Does is always have to be the previous year, or do you want to base it on a value in a dataset. You don't need macro to solve this.
The shortest method to get last year is as follows
libname stuff "C:\Documents and Settings\user\Desktop\sas\d%eval(%sysfunc(year(%sysfunc(date())))-1)a1";
and if you want to break it up to make it more readable it could be like this
%let lastyear = %eval(%sysfunc(year(%sysfunc(date())))-1);
%let libpath = C:\Documents and Settings\user\Desktop\sas\d&lastyear.a1;
libname stuff "&libpath";
call symput is your friend. Put the following inside the data step, after creating the variable srvyr2:
call symput('srvyr_path', srvyr2);
and then outside the macro,
libname stuff &srvyr_path;