I am trying to dynamically generate and export bar charts to and an excel workbook. My Macro pulls certain distinct Identifier codes and creates two summary tables (prov_&x table and the prov_revcd_&x) and populates to single excel sheet, for each respective ID. I have been unable however to successfully generate bar charts for the data and export to excel. Below is a condensed version of code. I have removed the steps for creating the prov_&x table and the prov_revcd_&x table to help keep the post as concise as possible. I have tried using the GOUT function and NAME function and then explicitly calling those but that does not seem to work. Any suggestions are welcomed and I understand my macro code is a little sloppy but it generates the tables so I will clean up once I can get the bar charts to generate.
Also, I can see in my results viewer that the graphs are generating, so I'm assuming the problem is in how I am trying to reference them to the workbook. THANKS!
%macro runtab(x);
/*Create summary chart for generating graph of codes billed per month*/
proc sql;
CREATE TABLE summary_&x AS
select DISTINCT month, COUNT (CH_ICN) AS ICN_Count, CLI_Revenue_Cd_Category_Cd
FROM corf_data1_sorted
WHERE BP_Billing_Prov_Num_OSCAR=&x
group by month ,CLI_Revenue_Cd_Category_Cd;
run;
/*Create a graph of Services Per Month and group by the Revenue Code*/
proc sgplot data=summary_&x NAME= 'graph_&x';
title 'Provider Revenue Analysis';
vbar month / response=ICN_count group=CLI_Revenue_Cd_Category_Cd stat=sum
datalabel datalabelattrs=(weight=bold);
yaxis grid label='Month';
run;
%mend runtab;
/*Create a macro variable of all the codes */
proc sql noprint;
select BP_Billing_Prov_Num_OSCAR
into :varlist separated by ' ' /*Each code in the list is sep. by a single space*/
from provider;
quit;
%let cntlist = &sqlobs; /*Store a count of the number of oscar codes*/
%put &varlist; /*Print the codes to the log to be sure our list is accurate*/
/*write a macro to generate the output tables*/
%macro output(x);
ods tagsets.excelxp options(sheet_interval='none' sheet_name="&x");
proc print data=prov_&x;
run;
proc print data=prov_revcd_&x;
run;
proc print data=graph_&x;
run;
%mend;
/*Run a loop for each oscar code. Each code will enter the document generation loop*/
%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;
%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;
/*Run the macro loop over the list of significant procedure code values*/
ods tagsets.excelxp file="W:\user\test_wkbk.xml";
%loopit(&varlist)
ods tagsets.excelxp close;
You can't export charts with ODS TAGSETS.EXCELXP, unfortunately.
You have a few options if you need to export charts.
Use ODS Excel, available in the more recent maintenance releases of SAS 9.4. See Chris H's blog post for more information on that. It is fairly similar to Tagsets.ExcelXP, but not identical. It does generate a "Real" excel file (.xlsx).
Create an HTML file that Excel can read, using TAGSETS.MSOFFICE2K or regular HTML. Chevell Parker, a SAS tech support analyst, has a few papers like this one on the different options.
Use DDE to write your image to the excel file. Not a preferred option but included for completeness.
There is also a new proc - proc mschart - that is enabled in SAS 9.4 TS1M3 due out in a month or two, that will generate Excel charts in ODS EXCEL (ie, not an image, but telling Excel to make a chart here please).
Related
I have a macro which looks like this:
%macro mac_name (st, en=);
%do j=1 %to &en.;
%let k=%eval(&j.+1);
proc freq data=data_name;
tables status&j. * status&k. / nocol norow nopercent missing;
run;
%end;
%mend;
%mac_name (st=1, en=%sysfunc(week(%sysfunc(today()), u)));
The output produces multiple proc freq tables with the same title.
I need this output put into a excel spreadsheet. Ideally all proc freqs in one sheet, one above the other or separate sheets.
Is this possible?
Thanks in advance!!!
The easiest way to do this is to use ODS EXCEL, if you have SAS 9.4.
ods excel file="yourfilename.xlsx";
proc freq data=sashelp.class;
tables age;
run;
proc freq data=sashelp.class;
tables sex;
run;
ods excel close;
You have options for whether they're all on one sheet or separate sheets. You can use ODS TAGSETS.EXCELXP if you have an earlier version of SAS, though they're less "true excel" files. You can also make CSV files or various other things with ODS.
In your case you'd put the opening ODS EXCEL line before the first call of the macro (doesn't have to precede the definition of the macro) and then the ODS EXCEL CLOSE line after the last call.
I am trying to prepare a report in Excel using ODS Excel in SAS. The report contains two sheets with three tables place side by side. I used Panelcol option but it is not available in ODS Excel. Also, the Start_at= option does not seem to be working?
Is there anyway it can be done in ODS Excel? Please let me know.
Any help will be very much appreciated.
Thank you,
Shankar
Shankar:
The ODS EXCEL docs for 9.4 state the start_at= options can not be changed mid-sheet. Link
(START_AT='string') specifies a starting cell for the report. The
default is to start at column 1 and row 1. Default 1,1 Tip This option
cannot be changed in the middle of a sheet. Example ods excel
options(start_at="2,2");
So this example does not work as you expect. The first start_at is honored, and subsequent output on the same page is stacked while still honoring the first start at column.
ods _all_ close;
ods excel file='%temp%\3-tables-in-first-tab-sample.xlsx'
options (sheet_interval='none')
;
ods excel options (start_at="2,2");
* top of output is upper left corner;
proc print noobs data=sashelp.class(keep=name age);run;
* top of output is to the right and down three rows from the first output;
ods excel options (start_at="5,5");
proc print noobs data=sashelp.class(keep=name weight height);run;
ods excel options (start_at="1,9");
proc print noobs data=sashelp.class(keep=name sex);run;
ods excel close;
One way to put multiple tables together is to create a faux table that appends data in a sidewise manner.
Example:
%macro RHS_append (out=, data=);
%if not %sysfunc(exist(&out)) %then %do;
data &out; set &data; run;
%return;
%end;
%local nout names labels;
proc contents noprint data=&out out=outvar(keep=name varnum label);
proc contents noprint data=&data out=datavar(keep=name varnum label);
proc sql noprint; select count(*) into :nout trimmed from outvar;
select
cats(name,'=v',&nout+varnum+1)
, case
when label=''
then cats('v',&nout+varnum+1,'=',quote(trim(name)))
else ''
end
into
:names separated by ' '
, :labels separated by ' '
from datavar order by varnum
;
create table gap (v%eval(&nout+1) char(1) label='a0'x);
quit;
data &out;
merge &out gap &data(rename=(&names)); %* rare use of merge without by;
label &labels;
run;
%mend;
options mprint;
proc delete data=foo;
run;
%RHS_append (out=foo, data=sashelp.class);
%RHS_append (out=foo, data=sashelp.gas);
%RHS_append (out=foo, data=sashelp.cars);
ods excel file='%temp%\3-tables-in-first-tab-sample.xlsx';
proc print label data=foo;
run;
ods excel close;
I have some data set. (name - table, field - Field1 label="current.field")
When I do
proc export data=work.table label;
outfile = 'bla bla';
DBMS=Excelcs;
run;
I get an error:
CLI execute error: [Microsoft][ODBC Excel
Driver] 'current.field' is not a valid name. Make sure that it does not include
invalid characters or punctuation and that it is not too long..
I know that the problem is in label - it contains ".". But I need this label fro my need. Do anyone knows how to solve this problem? Than you.
If you need your SAS labels with periods (.) in Excel (row 2) you can try the approach outlined in this blog post.
Simply add the label keyword to the proc export step as follows:
/* send data */
PROC EXPORT DATA=&libds OUTFILE=_webout DBMS=&type REPLACE LABEL;
run;
Then, when setting up the web query, ensure your target is a cell in row 2 (eg A2). This worked fine for me, as follows:
One thing you can do in SAS, is to modify those labels so that they will play nicely with the Excel driver. The following xl_label() macro will scan your table and remove any periods (.) from your labels.
/* sample data */
data work.table;
label x ='test1.invalid' y='test2.invalid';
x=1; y='age';
run;
%macro xl_label(lib=,ds=);
/* get labels */
ods output variables=vars(keep=variable label);
proc contents data=&lib..ds; run;
data _null_; set vars;
/* modify labels to make them valid for excel */
label=tranwrd(label,'.','_');
/* send to macro variable array */
call symputx(cats('label',_n_)
,"label "!!variable!!'="'!!label!!'";'
,'l');
run;
/* update table metadata */
proc datasets nolist library=&lib;
modify &ds;
%local x;
%let x=1;
%do %while (%symexist(label&x));
&&label&x /* label statements */
%let x=%eval(&x+1);
%end;
%mend;
%xl_label(lib=work, ds=table);
I am new to SAS and am having some issues exporting data. I have written a macro to generate some summary tables based on a certain ID. The macro creates two tables for each ID identified in a particular proc sql query. I can write out the last two tables but it overwrites all tables. I was wondering if there is a way to generate one sheet, containing the two summary tables, for each ID identified in my query. Below is the code I have to date for exporting data:
%macro output(x);
ods tagsets.excelxp file="W:\user\test.xls" options(sheet_interval='none');
proc print data=prov_&x;
run;
proc print data=prov_revcd_&x;
run;
ods tagsets.excelxp close;
%mend;
/*Run a loop for each IDcode. Each code will enter the document generation loop*/
%macro loopit(mylist);
%let else=;
%let n = %sysfunc(countw(&mylist)); /*let n=number of codes in the list*/
data
%do I=1 %to &n;
%let val = %scan(&mylist,&I); /*Let val= the ith code in the list*/
%end;
%do j=1 %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;
/*Run the macro loop over the list of significant procedure code values*/
%loopit(&varlist);
Any help for correcting this issue would be greatly appreciated! Thanks!
I would rewrite %output like so.
%macro output(x);
ods tagsets.excelxp options(sheet_interval='none' sheet_name="&x");
proc print data=prov_&x;
run;
proc print data=prov_revcd_&x;
run;
%mend;
Then as Reeza suggests put the original ods tagsets.excelxp file= ... and close outside the whole macro.
ods tagsets.excelxp file="c:\temp\test.xlsx";
%loopit(&varlist)
ods tagsets.excelxp close;
If you use PROC EXPORT, that does allow apending to a workbook without this step (and no ODS at al).
%macro output(x);
proc export data=prov_&x outfile="c:\temp\test.xlsx" dbms=excel replace;
sheet="&x._prov";
run;
%mend;
However, this only allows one dataset per sheet - so either you append them together first as a single dataset, or you use 2 sheets in this solution.
Move the ods tagsets.excelxp file= and ods tagsets.excelxp close to outside of the macro otherwise you're recreating the file each time.
You may want to explicitly name the sheets as well.
I'm working with a rather large several dataset that are provided to me as a CSV files. When I attempt to import one of the files the data will come in fine but, the number of variables in the file is too large for SAS, so it stops reading the variable names and starts assigning them sequential numbers. In order to maintain the variable names off of the data set I read in the file with the data row starting on 1 so it did not read the first row as variable names -
proc import file="X:\xxx\xxx\xxx\Extract\Live\Live.xlsx" out=raw_names dbms=xlsx replace;
SHEET="live";
GETNAMES=no;
DATAROW=1;
run;
I then run a macro to start breaking down the dataset and rename the variables based on the first observations in each variable -
%macro raw_sas_datasets(lib,output,start,end);
data raw_names2;
raw_names;
if _n_ ne 1 then delete;
keep A -- E &start. -- &end.;
run;
proc transpose data=raw_names2 out=raw_names2;
var A -- &end.;
run;
data raw_names2;
set raw_names2;
col1=compress(col1);
run;
data raw_values;
set raw;
keep A -- E &start. -- &end.;
run;
%macro rename(old,new);
data raw_values;
set raw_values;
rename &old.=&new.;
run;
%mend rename;
data _null_;
set raw_names2;
call execute('%rename('||_name_||","||col1||")");
run;
%macro freq(var);
proc freq data=raw_values noprint;
tables &var. / out=&var.;
run;
%mend freq;
data raw_names3;
set raw_names2;
if _n_ < 6 then delete;
run;
data _null_;
set raw_names3;
call execute('%freq('||col1||")");
run;
proc sort data=raw_values;
by StudySubjectID;
run;
data &lib..&output.;
set raw_values;
run;
%mend raw_sas_datasets;
The problem I'm running into is that the variable names are now all set properly and the data is lined up correctly, but the labels are still the original SAS assigned sequential numbers. Is there any way to set all of the labels equal to the variable names?
If you just want to remove the variable labels (at which point they default to the variable name), that's easy. From the SAS Documentation:
proc datasets lib=&lib.;
modify &output.;
attrib _all_ label=' ';
run;
I suspect you have a simpler solution than the above, though.
The actual renaming step needs to be done differently. Right now it's rewriting the entire dataset over and over again - for a lot of variables that is a terrible idea. Get your rename statements all into one datastep, or into a PROC DATASETS, or something else. Look up 'list processing SAS' for details on how to do that; on this site or on google you will find lots of solutions.
You likely can get SAS to read in the whole first line. The number of variables isn't the problem; it is probably the length of the line. There's another question that I'll find if I can on this site from a few months ago that deals with this exact problem.
My preferred option is not to use PROC IMPORT for CSVs anyway; I would suggest writing a metadata table that stores the variable names and the length/types for the variables, then using that to write import code. A little more work at first, but only has to be done once per study and you guarantee PROC IMPORT isn't making silly decisions for you.
In the library sashelp is a table vcolumn. vcolumn contains all the names of your variables for each library by table. You could write a macro that puts all your variable names into macro variables and then from there set the label.
Here's some code that I put together (not very pretty) but it does what you're looking for:
data test.label_var;
x=1;
y=1;
label x = 'xx';
label y = 'yy';
run;
proc sql noprint;
select count(*) into: cnt
from sashelp.vcolumn
where memname = 'LABEL_VAR';quit;
%let cnt = &cnt;
proc sql noprint;
select name into: name1 - :name&cnt
from sashelp.vcolumn
where memname = 'LABEL_VAR';quit;
%macro test;
%do i = 1 %to &cnt;
proc datasets library=test nolist;
modify label_var;
label &&name&i=&&name&i;
quit;
%end;
%mend test;
%test;