%SYSFUNC() wrapped concatenation functions give an error while encountering parentheses - function

I need to write a macro program to generate a list for moving average calculation where I will need some lines to be constructed like this:
var1_ma_past_1=mean(var1, lag1(var1), lag2(var1), lag3(var1), lag4(var1), lag5(var1));
var1_ma_past_2=mean(lag1(var1), lag2(var1), lag3(var1), lag4(var1), lag5(var1), lag6(var1));
var1_ma_past_3=mean(lag2(var1), lag3(var1), lag4(var1), lag5(var1), lag6(var1), lag7(var1));
[...]
var2_ma_past_1=mean(var2, lag1(var2), lag2(var2), lag3(var2), lag4(var2), lag5(var2));
my sample program is
%macro test ;
%do i = 1 %to 5;
%let ln&i = ;
%do j = 1 %to 5;
%let dml = %str(,);
%let pos = %str(lag&i(var&j));
%let ln&j = %sysfunc(catx(&dml, &&ln&j, &pos));
%end;
%end;
/* example output */
%put &ln1;
%mend test;
%test
&j start and end values are planned to be replaced with parameters though.
the output is desired for &ln1
lag1(var1),lag2(var1),lag3(var1),lag4(var1),lag5(var1)
but for &ln2 &ln3 etc it's not (lag1(varn) is missing)
lag2(var4),lag3(var4),lag4(var4),lag5(var4)
lag2(var3),lag3(var3),lag4(var3),lag5(var3)
Besides, I got flooding log output saying ERROR: Required operator not found in expression: which is because of the parentheses inside of cats(), which is inside of %sysfunc(), an example macro to replicate this is
%macro test2;
%let x=meow;
%put %sysfunc(cats(x,lag()));
%mend;
%test2
I tried to mask the parentheses with %str, %superq, %bquote but none worked.
I'd like to learn
the reason for incorrect output for &ln2, &ln3 and so on
the reason for ERROR: Required operator not found in expression: and how to fix it (or a workaround, or even to suppress the error if it's not critical)
Thanks in advance.

There is no need to use CAT...() functions in macro code.
In macro code to concatenate values you just expand them next to each other.
Also it looks like your logic is mixing up the I and J counters.
%macro test ;
%do i = 1 %to 5;
%let list = ;
%let dlm = ;
%do j = 1 %to 5;
%let list = &list.&dlm.lag&j(var&i) ;
%let dlm = ,;
%end;
%put &=i &=list;
%end;
%mend test;
%test
Results:
I=1 LIST=lag1(var1),lag2(var1),lag3(var1),lag4(var1),lag5(var1)
I=2 LIST=lag1(var2),lag2(var2),lag3(var2),lag4(var2),lag5(var2)
I=3 LIST=lag1(var3),lag2(var3),lag3(var3),lag4(var3),lag5(var3)
I=4 LIST=lag1(var4),lag2(var4),lag3(var4),lag4(var4),lag5(var4)
I=5 LIST=lag1(var5),lag2(var5),lag3(var5),lag4(var5),lag5(var5)
For your actual problem you might want to create a macro that only returns the comma delimited list as the result of the macro call.
%macro lags(varname,first,last);
%local lag dlm;
%do lag= &first %to &last ;
%if (&lag > 0) %then %*;&dlm.lag&lag(&varname);
%else %*;&dlm.&varname;
%let dlm=,;
%end;
%mend lags;
%put var1_ma_past_1=mean(%lags(var1,0,5));
%put var1_ma_past_2=mean(%lags(var1,1,6));
%put var1_ma_past_3=mean(%lags(var1,2,7));
%put var2_ma_past_1=mean(%lags(var2,0,5));
Why you are getting those error messages:
The %sysfunc() macro function needs to try to figure out whether each argument is character or numeric for a function like CATX() that can operate on either type of input. That is why the () in the argument values is confusing it since it looks like you are trying to pass a numeric expression.
18 %put %sysfunc(catx(|,a(b),b));
ERROR: Required operator not found in expression: a(b)
a(b)|B
19 %put %sysfunc(catx(|,(1+2),b));
3|B
You could force in quotes around the values and then remove them later (if your values don't actually contain quotes).
%let left=A(b);
%let right=b;
%let intermediate=%sysfunc(catx(|,"&left","&right"));
%let want=%sysfunc(compress(&intermediate,%str(%"));
%put &=want;

Related

How to handle an apostrophe in an href in SAS

I have a SAS program that sends out an email using HTML, but one of the folders I am trying to reference as a link contains an apostrophe:
%let body1 = %nrquote(
<ul>
<li><a href='\\server\Studies\Alzheimer's\Documents'>Alzheimer's Documents</a>
</ul>
) ;
This macro variable will be used in the following macro:
%macro sas_email(to=, subject=, body1=, body2=, body3=) ;
options
emailsys=smtp
emailhost=("smtp.gmail.com" port=465) ;
filename alert email to=(&to.)
subject="&subject."
content_type="text/html"
debug ;
data _null_ ;
file alert ;
put "&body1." ;
%if %length(&body2.) > 0 %then %do ;
put "&body2." ;
%end ;
%if %length(&body3.) > 0 %then %do ;
put "&body3." ;
%end ;
run ;
%mend sas_email ;
As you can imagine, the apostrophe in \Alzheimer's causes issues. Using double quotes instead of single gives me the error:
ERROR: A character operand was found in the %EVAL function or %IF condition where a numeric
operand is required. The condition was: %length(&body1.) > 0
HTML doesn't care if you use double quotes or single quotes. So your generated HTML tag could look like this:
<a href="\\server\Studies\Alzheimer's\Documents">
If you want to add single quotes into a string enclosed in single quotes then encode it.
<a href='\\server\Studies\Alzheimer%27s\Documents'>
Since you are going to use the macro variable in a data step try just using %BQUOTE() to add macro quoting when creating the value. This should let you create a string that would look to SAS like unbalanced quotes.
%let body1 = %bquote(
<ul>
<li>Alzheimer's Documents
</ul>
) ;
%let body2=;
%let body3=;
Then when using it avoid trying to expand the macro variable by using symget() function to pull the value of the macro variable into an actual variable that you can then write using the PUT statement.
data _null_;
file alert ;
length str $32767;
do i=1 to 3;
str=symget(cats('body',i));
put str ;
end;
run;

MYSQL run data based on loop date range

I am using MYSQL. Basically I am trying to generate a dataset based on my own defined date range. Can someone share how I am able to do a looping like in SAS? example:
%macro date_loop(start,end);
/*converts the dates to SAS dates*/
%let start=%sysfunc(inputn(&start,anydtdte9.));
%let end=%sysfunc(inputn(&end,anydtdte9.));
/*determines the number of months between the two dates*/
%let dif=%sysfunc(intck(month,&start,&end));
%do i=0 %to &dif;
/*advances the date i months from the start date and applys the DATE9. format*/
%let date=%sysfunc(putn(%sysfunc(intnx(month,&start,&i,b)),date9.));
%put &date;
%end;
%mend;
%date_loop(2017-01-31,2019-01-31)
Part of my MySQL code: The st_date is self defined.
SET #st_date = '2019-01-31'
DATE_FORMAT(#st_date,'%d/%m/%Y') as POSITION_DATE,

create utf-8 text file from a sas datastep

I have this macro that creates a json file,
but even though I have specified encoding='utf-8' bom I don't get an utf-8 file.
%macro json4datatables_useformat(ds,path,file,charvars,numvars)
/
DES="json4datatables(ds,path,file,charvars,numvars)";
/* creates a json with no headers
* a bit like a csv without the first line
* it takes thus less space
* but you have to know which column is what
*/
data _null_;
length line $300;
set &ds nobs=nobs end=end;
file "&path.&file." encoding='utf-8' bom/**/ ;
line = '[';
%if &charvars ne %then %do;
%do i=1 %to %sysfunc(countw(&charvars));
%let charvar = %scan(&charvars, &i);
%if &i ne 1 %then %do;
line = cats(line,',');
%end;
line = cats(line,'"',vvalue(&charvar),'"');
%end;
%end;
%if &numvars ne %then %do;
%do i=1 %to %sysfunc(countw(&numvars));
%let numvar = %scan(&numvars, &i);
line = catx(',',line,vvalue(&numvar));
%end;
%end;
line = cats(line,']');
if _n_=1 then put '{"data": [';
if not end then put line +(-1) ',';
else do;
put line;
put ']}';
end;
run;
%mend json4datatables_useformat;
I noticed because accents appear as strange charactes on the web page where I display the json file.
And the issue is solved simply by opening the json file in sublime text and doing File>Save with Encoding>UTF-8.
(with BOM isn't necessary.)
Is there another way to force utf-8 encoding?
EDIT: I'm using SAS EG 7.1 , SAS 9.3 on windows.
Your browser doesn't have the appropriate character set and / or fonts to render your data.
To add the language you want to browse:
Control Panel -> Region and Language -> Keyboards and Languages -> Display language -> Install/uninstall languages

SAS proc export to CSV: how to add double quotes

New to this, so apologies. I have a file in SAS that I need to export as a CSV and I need to add double quotes to all fields. How can I accomplish this? Thanks in advance.
There are many ways to create a CSV file from SAS. Using proc export won't wrap every field in double-quotes, so the easiest one that will do this is the %ds2csv() macro. This assumes you have SAS 9.2 or later. The documentation for it can be found here:
http://support.sas.com/documentation/cdl/en/lrdict/64316/HTML/default/viewer.htm#a002683390.htm
An example of running it:
%ds2csv(data=sashelp.retail, runmode=b, csvfile=c:\class.csv);
Produces:
"Retail sales in millions of $","DATE","YEAR","MONTH","DAY"
"$220","80Q1","1980","1","1"
"$257","80Q2","1980","4","1"
"$258","80Q3","1980","7","1"
Here is another one using data step
data _null_;
file 'data_want.csv' dsd dlm = ',';
set data_have;
if _n_ = 1 then put #1 "variables name to input"; /* Add variable names */
put (_all_) (~);
run;
Here is a way to get the same result with headers included
proc contents data=sashelp.class out=vars(keep=name varnum);run;
proc sql noprint;
select quote(strip(name)) into :vars separated by '|' from vars
order by varnum;
quit;
data _null_;
file 'test.txt' dsd dlm='|';
set sashelp.class;
if _n_ = 1 then put %sysfunc(quote(&vars.));
put (_all_) (~);
run;
I know it's an old question, but my 2 pence...
The macro below does not quote numeric values that are formatted to a character value, but it's easy to implement.
%macro csvQuoteChars(lib=,dset=,outPath=);
proc contents data=&lib..&dset out=c(keep=name type) noprint;
run;
%let dsid = %sysfunc(open(c));
%let nobs = %sysfunc(attrn(&dsid,nlobs));
data _null_;
set &lib..&dset;
file "&outPath\&lib..&dset..csv" dsd;
%do i=1 %to &nobs;
%let rc = %sysfunc(fetchobs(&dsid,&i));
%let vName = %sysfunc(getvarc(&dsid,1));
%let vType = %sysfunc(getvarn(&dsid,2));
%put &=vName &=vType;
%if &vType = 2 %then %do;
put (&vName) (~) #;
%end;
%else %if &vType = 1 %then %do;
put &vName #;
%end;
%if &i = &nobs %then %do;
put;
%end;
%end;
run;
%let dsid = %sysfunc(close(&dsid));
%mend csvQuoteChars;
%csvQuoteChars(lib=sashelp,dset=class,outPath=d:\temp);

Reading multiple csv files into SAS datasets

I have the following csv files and I need to read them into SAS datasets.
Also, I need to assign column names.
In addition, I need the column to be numberic but some columns have both number and character values.
folder aa: abc1.csv, abc2.csv, abc3.csv,......
folder bb: abc1.csv, abc2.csv, abc3.csv,......
folder cc: abc1.csv, abc2.csv, abc3.csv,......
You could do it in following way also.
Keep all your files in one folder.
Name all of them in a csv file with only one column.
Import the file (csv) with file names into SAS.
Create a macro to keep their name with "into" clause.
proc sql;
select name_list into :name separated by '*' from work.name;
%let count2 = &sqlobs;
quit;
Create a macro like below.
%macro yy;
%do i = 1 %to &count2;
%let j = %scan(&name,&i,*);
proc import out = &j datafile="folderwhereallcsvfilesarekept\&j..csv"
dbms=csv replace;
getnames = yes;
run;
%end;
%mend;
This isn't a complete answer, but it will get you started. You'll have to add an outer loop to go through the different directories you want to get files from.
/*List the files in a directory for use in a data step. This is written for Windows. Other operating systems will be slightly different. */
filename fnames pipe 'dir c:\temp\* /b';
/* Create a data set with one observation for each file name */
data fnames;
infile fnames pad missover;
input #1 filename $255.;
n=_n_;
run;
/* Store the number of files in a macro variable "num" */
proc sql noprint; select count(filename) into :num; quit;
/* Create a macro to iterate over the filenames, read them in, and append to a data set. */
%macro doit;
%do i=1 %to &num;
proc sql noprint;
select filename into :filename from fnames where n=&i;
quit;
data ds;
infile &filename;
input ...list of variable names...;
...other statements...;
run;
proc append data=ds base=final; run;
%end;
%mend;
%doit;