I have a macro variable
%let dates = (200101, 200102, 200103);
How do I create this list?
var_out = pop_200101 pop_200102 pop_200103
I can loop through and select the date:
%do i=1 %to %sysfunc(countw(%superq(dates),%str(,)));
%let this_date = %scan(&dates., &i.);
and I can concatenate:
catt(pop,_&this_date.);
but I don't know how to actually put this into a list.
Edit: additional info
I have some data
data example;
input Q40_ret_200101 Q40_ret_200102 Q40_ret_200103;
datalines;
4 6 .
. 8 9
3 7 4
2 3 .
;
run;
I am trying to do
proc summary
data = example nway;
var Q40_ret_200101 Q40_ret_200102 Q40_ret_200103;
output out = count
N = pop_200101 pop_200102 pop_200103;
run;
However in reality my dataset is much larger and it is no feasible for me to write in all the variables. I have managed to get a list of my variable names for the var statement.
PROC CONTENTS
DATA = example
OUT = VAR_NAMES (KEEP = NAME) NOPRINT;
RUN;
/*read in variable names and use substr to test if starts with q40_ret*/
DATA PARSE;
SET VAR_NAMES;
WHERE (SUBSTR(NAME, 1,8) = 'Q40_ret_');
RUN;
DATA _NULL_;
FILE 'C:';
SET PARSE END = FINIS;
IF _N_ = 1 THEN PUT '%LET VAR_LIST = ';
PUT NAME;
IF FINIS THEN DO;
CALL SYMPUT('NUM' , COMPRESS(_N_));
PUT ';';
PUT 'RUN;';
END;
RUN;
/*Next, the file ‘BUILD‘ is read back into the SAS program using a %INCLUDE statement.*/
%include 'C:\BUILD';
So far my proc summary statement looks like this:
proc summary
data = example nway;
var &var_list;
output out = pop (drop = _TYPE_ _FREQ_)
N =;
run;
But I still need to generate and state the output variable names
A doSubL side session program can compute and populate the value for the var_out macro symbol. The benefit of using %sysfunc(doSubL( is that the side program does not create a step boundary in the invoking session.
Example:
The construct of the dates macro variable value can also be used as an array initialization in the doSubL code.
%let dates = (200101, 200102, 200103);
%let rc=%sysfunc(dosubl(
data _null_;
array dates (%sysfunc(countw(&dates))) &dates;
do index = 1 to dim(dates);
length list $200;
list = catx(' ', list, cats('pop_',dates(index)));
end;
call symput('var_out',trim(list));
run;
));
%put &=var_out;
---------- LOG ----------
VAR_OUT=pop_200101 pop_200102 pop_200103
If you can change your list slightly by removing the parentheses and commas this is quite easy. Alternatively you could use COMPRESS() to remove the parenthesis and commas and then use this solution.
%macro prefix(prefix,list);
%local i bit;
%let i=1;
%let bit=%sysfunc(scanq(&list,&i,%str( )));
%do %while(%length(&bit));
&prefix.&bit
%let i=%eval(&i+1);
%let bit=%sysfunc(scanq(&list,&i,%str( )));
%end;
%mend prefix;
%let dates = (200101, 200102, 200103);
%let want = %prefix(pop_, 200101 200102 200103);
%put &want;
Via: http://www.datasavantconsulting.com/roland/Spectre/utilmacros/prefix.sas
Related
Given a SAS dataset with columns named n1,n2,..nN.
Is there a simple way to export common set of columns and unique subset of columns to a workbook, where each column is exported to the worksheet with the same name as the last column name?
Example:
For the SAS dataset above, the columns:
n1, n2, n5 -> Worksheet n5
n1, n2, n9 -> Worksheet n9
n1, n2, n13 -> Worksheets n13
are exported to a Excel workbook, with worksheets named as above.
Appreciate any suggestions.
Use the SHEET= statement in a Proc EXPORT step.
For example:
filename myxl 'c:\temp\sandbox.xlsx';
proc export replace file=myxl dbms=excel
data=sashelp.class (keep=name)
;
sheet='Name';
run;
proc export replace file=myxl dbms=excel
data=sashelp.class (keep=name age weight)
;
sheet='Weight';
run;
A macro can be coded to generate repetitive parts
%macro excel_push (file=, data=, always=, each=);
%local i n var;
%let n = %sysfunc(countw(&each));
%do i = 1 %to &n;
%let var = %scan(&each,&i);
proc export replace file=&file dbms=excel
data=&data(keep=&always &var)
;
sheet="&var";
run;
%end;
%mend;
options mprint;
filename myxl2 'c:\temp\sandbox2.xlsx';
%excel_push (
file=myxl2,
data=sashelp.class,
always=name age sex,
each=height weight
)
If you open the Excel output, leave it open and rerun the code, you will get an error, albeit slightly obscure:
ERROR: Error attempting to CREATE a DBMS table. ERROR: Execute: The Microsoft Access database
engine could not find the object ********. Make sure the object exists and that you spell
its name and the path name correctly. If ******** is not a local object, check your
network connection or contact the server administrator..
I guess, What I understood from your question is how to have sheet name with last variable of SAS dataset. One way to do this is to use dictionary.columns and find which column position (varnum in dictionary.columns)is max in a dataset which will give the last variable and you can make a macro variable out of this and use this for sheet in proc export.
/* pick up the last variable*/
proc sql ;
select name into :mysheet TRIMMED from dictionary.columns
where libname = "SASHELP"
and memname = "CLASS"
and varnum = (select max(varnum) from dictionary.columns
where libname = "SASHELP"
and memname = "CLASS");
/* use the macrovariable in your sheet statement*/
PROC EXPORT DATA= Sashelp.Class /*Sheet 1*/
outfile= "/folders/myfolders/class.xlsx "
dbms=xlsx replace;
sheet="&mysheet";
run;
I'm trying to export one SAS table into multiple Excel worksheets based on the value of a field (parent_account). I want each worksheet to be named the same as the parent_account. I'm using the following code that I found at http://www.tek-tips.com/viewthread.cfm?qid=1335588, but I'm getting these error messages:
A character operand was found in the %EVAL function or %IF condition where a numeric operand is required.
Argument 2 to macro function %SCAN is not a number.
%macro export_to_excel();
%local varlist idx var;
proc sql noprint;
select distinct parent_account into: varlist separated by '||'
from todays_activity;
quit;
%let idx = 1;
%do %while ( %scan(&varlist, &idx, %str(||)) ne %str() );
%let var=%scan(&varlist, &idx, %str(||));
proc export data=sashelp.class (where=(parent_account="&var"))
outfile='My file location\Report.xls'
dbms=excel;
sheet="&var";
quit;
%let idx = %eval(&idx + 1);
%end;
%mend export_to_excel;
%export_to_excel;
You could try using ODS EXCEL. Here is an example use SASHELP.CLASS dataset.
First make sure the data is sorted by your grouping variables.
proc sort data=sashelp.class out=class ;
by sex ;
run;
Set up ODS to point to your target file. Tell it to make a new sheet for each BY group.
ods excel file="&path/class.xlsx" ;
ods excel options
(sheet_interval="bygroup"
suppress_bylines="yes"
sheet_name='GENDER'
);
You also might want to turn off other output destinations.
Then print the file using PAGEBY option. And close the ODS EXCEL destination
proc print data=class noobs;
by sex ;
pageby sex ;
var _all_;
run;
ods excel close;
To test it you could try reading it back in as data. But watch out that it will create member names with embedded spaces.
options validmemname=extend;
libname xx xlsx "&path/class.xlsx";
proc copy inlib=xx outlib=work; run;
libname xx clear ;
From SAS log
NOTE: The data set WORK.GENDER has 9 observations and 5 variables.
NOTE: The data set WORK.'GENDER 2'n has 10 observations and 5 variables.
This might be helpful
%macro export_to_excel;
proc sql noprint;
select distinct parent_account into: varlist separated by '#' from todays_activity;
select count(distinct parent_account) into:n from todays_activity;
quit;
%do i=1 %to &n;
%let var= %scan(&varlist,&i,"#");
proc export data=sashelp.class (where=(parent_account="&var"))
outfile='Your file location\Report.xls'
dbms=excel;
sheet="&var";
run;
%end;
%mend export_to_excel;
Is there a way to dynamically change the sheet names in SAS ODS using macros? Inside a macro, I have the following code:
%DO N = 1 %TO &SQLOBS;
ODS TAGSETS.ExcelXP
OPTIONS(SHEET_NAME = &&TABLEVAR&N
EMBEDDED_TITLES = 'NO'
EMBEDDED_FOOTNOTES = 'NO'
PRINT_HEADER = 'CURRENT &&TABLEVAR&N AS OF &D';
PROC PRINT
DATA = WORK.&&TABLEVAR&N
NOOBS;
RUN;
%END;
Which basically loops through an array of table names and for each table outputs a separate Excel Sheet. And by calling a macro variable &TABLEVAR, it is supposed to dynamically change the sheet name for each table. However, I get the following error:
ERROR 22-322: Expecting a quoted string.
So SAS does resolve the macro variable, I can see the table names are right in the log, but the problem is they are not a quoted string. So I tried it like this with quotes around it:
OPTIONS(SHEET_NAME = '&&TABLEVAR&N'
But then the macro variable is not being resolved by SAS and all the sheets are named &&TABLEVAR&1, 2, etc.
Is there a way around this? (And each sheet is a separate table with a different name so I can't use a BY group.) What good macros are if they can't interact with ODS?
I just found out placing %sysfunc and quote in front of &&TABLEVAR&N does the trick.
So the final code looks like this:
%DO N = 1 %TO &SQLOBS;
ODS TAGSETS.ExcelXP
OPTIONS(SHEET_NAME = %sysfunc(quote(&&TABLEVAR&N))
EMBEDDED_TITLES = 'NO'
EMBEDDED_FOOTNOTES = 'NO'
PRINT_HEADER = 'CURRENT &&TABLEVAR&N AS OF &D';
PROC PRINT
DATA = WORK.&&TABLEVAR&N
NOOBS;
RUN;
%END;
I don't think you need all that. If you use double quotes around the macro variable expression, you should get the same result as the %SYSFUNC(QUOTE()) function:
"&&TABLEVAR&N"
That is, unless you somehow plan to have double quotes in the sheet names....
I'm somewhat new to SAS. I'm trying to update a file name to write an excel file through a loop, but am having trouble assigning the file name. Here's my code:
%MACRO loop;
%DO year1 = 1995 %TO 2008;
DATA _NULL_;
dailyret = catx(STRIP(&year1),
'''/h1/usr11/angeli/finland/haz/phreg_dailyret_', '.csv''');
*to save output to excel;
ODS TAGSETS.EXCELXP
file= %QUOTE(dailyret)
STYLE=minimal
OPTIONS ( Orientation = 'landscape'
FitToPage = 'yes'
Pages_FitWidth = '1'
Pages_FitHeight = '100' );
*a block of code that runs the program, irrelevant to my question;
ods tagsets.excelxp close;
RUN;
%END;
%MEND loop;
%loop;
I have tried many variations of this, but every time, I always get an error message along the lines of "ERROR: No logical assign for filename DAILYRET".
Is there any way I can do this so that I don't have to put physical quotes in the line with "file=" and be able to update the year?
Thank you so much!
-Angel
To reference a macro variable prefix it with an &. Make sure to use double quote characters " to quote the filename since macro triggers are not resolved inside of single quotes.
You can also optionally append a period to the macro name so that the parser knows where the macro variable name ends and regular text starts again. This means that when you want to append an extension that starts with a period you need to have two periods since the first will be used to mark the end of the macro variable reference.
%MACRO loop;
%DO year1 = 1995 %TO 2008;
ODS TAGSETS.EXCELXP
file= "/h1/usr11/angeli/finland/haz/phreg_dailyret_&year1..xml"
STYLE=minimal
OPTIONS ( Orientation = 'landscape'
FitToPage = 'yes'
Pages_FitWidth = '1'
Pages_FitHeight = '100'
)
;
*-------------------;
*a block of code that runs the program, irrelevant to my question;
*-------------------;
ods tagsets.excelxp close;
%END;
%MEND loop;
I am wondering if there is a way of defining when and where page breaks occur when using a macro to output data. I know within the various ODS tagests the "Startpage=NOW" can be used but that does not seem to work if a macro is used inside that tagset. So basically I want the two tables, and graph for each personal ID code to be on a single and the next page contains the same summary graphs, charts for that individual, and etc. Currently I can only get every table and chart to its own individual page which makes for a lengthy report! Any help/suggestions would be appreciated!
/*************************************************************************/
/* Create a macro variable of all the ID codes */
/* */
/*************************************************************************/
proc sql noprint;
select personal_id
into :varlist separated by ' ' /*Each identifier code in the list is sep. by a single space*/
from provider;
quit;
%let cntlist = &sqlobs; /*Store a count of the number of id codes*/
%put &varlist; /*Print the codes to the log to be sure our list is accurate*/
ods tagsets.rtf file="C:\USER\test.doc" style=sasdocprinter;
/* macro for generating the output table*/
%macro output(x);
proc print data=prov_&x;
run;
proc print data=prov_revCD_&x;
run;
/*Print graph to template defined earlier*/
ods graphics on / height=500px width=500px;
proc sgrender data=summary_&x template=corf_graphs;
run;
ods graphics on / reset=all;
%mend;
%macro loopit(mylist);
%let else=;
%let n = %sysfunc(countw(&mylist)); /*let n=number of codes in the list*/
data
%do I=0 %to &n;
%let val = %scan(&mylist,&I); /*Let val= the ith code in the list*/
%end;
/*Run a loop for each oscar code. Each code will enter the
%do j=0 %to &n;
%let val = %scan(&mylist,&j); /*Let val= the jth code in the list*/
/*Run the macro loop to generate the required tables*/
%runtab(&val);
%output(&val);
%end;
run;
%mend;
%loopit(&varlist)
/*Run the macro loop over the list of significant procedure code values*/
ods tagsets.rtf close;
Macros are nothing but source code generation devices. They have no impact on the effectiveness of any particular technique, except insomuch as their (mis)use may make it more difficult to see how to do it properly.
In this case, if you want the entirety of each single iteration of %output() to be on one page, then you simply need to add a startpage now before the %output(&val) call.
%runtab(&val);
ods tagsets.rtf startpage=now;
%output(&val);
%end;
That will produce a startpage instruction immediately before the output. You could just as easily include it in the %output macro, if you will always want a new page before a call of it.
I inserted the startpage=NOW where suggested and it inserted a blank page, after playing around with the placement and looking at what the code is doing this following snip it ended up working:
ods tagsets.rtf startpage=NOW;
%runtab(&val);
%output(&val);
ods tagsets.rtf startpage=no;
%end;
Beyond the use of macro, you can also consider the use of pagebywith proc print to split page for you.
See this link for PageBY