22S:172 The SAS Output Delivery System Lab 10 July 25, 2005 Kellie Poulin Bach kellie-poulin@uiowa.edu The SAS Output Delivery System (ODS) was created to increase the number of ways we can view SAS output and interact with it. The most common uses are: o To automatically create output that can be directly passed to clients o Take part of all of the output and send it to an external program (such as Word or Excel) o To take parts of the output and use them as input into future SAS programs or SAS datasets SAS Output typically shows up in the output window. It can also go into o HTML files o CSV files o RTF files (for Word) o PDF files o PS files o SAS datasets o WML (wireless markup language – I have not used this new option yet) o Latex and Colorlatex (experimental meaning it will usually work, but it is not yet fully working or supported by SAS) We will go through examples it illustrate how to use ODS. You can copy and paste the code from this document into your SAS program editor to follow along. Note you will need to change the location of the input data in your editor. /* 1. CREATING OUTPUT THAT CAN BE DIRECTLY PASSED TO CLIENTS */ /* Read in the patient dataset from earlier this semester */ DATA PATIENTS; INFILE "D:\s172\patients.dat" PAD; *you will need to change this infile statement; INPUT @1 PATNO $3. @4 GENDER $1. @5 VISIT MMDDYY10. @15 HR 3. @18 SBP 3. @21 DBP 3. @24 DX $3. @27 AE $1.; LABEL PATNO GENDER VISIT HR SBP DBP DX AE = = = = = = = = "Patient Number" "Gender" "Visit Date" "Heart Rate" "Systolic Blood Pressure" "Diastolic Blood Pressure" "Diagnosis Code" "Adverse Event?"; FORMAT VISIT MMDDYY10.; RUN; /* The standard way to produce a table */ proc tabulate data=patients format=7.3; title "statitics for numeric variables"; var hr sbp dbp; tables hr sbp dbp, N*F=7.0 NMISS*F=7.0 MEAN MIN MAX /RTSPACE=18; keylabel N ='Number' NMISS='Missing' MEAN='Mean' MIN='Lowest' MAX='Highest'; run; /* Producing the same table in HTML */ ods html ; proc tabulate data=patients format=7.3; title "statitics for numeric variables"; var hr sbp dbp; tables hr sbp dbp, N*F=7.0 NMISS*F=7.0 MEAN MIN MAX /RTSPACE=18; keylabel N ='Number' NMISS='Missing' MEAN='Mean' MIN='Lowest' MAX='Highest'; run; ods html close; /* Saving the HTML file so that you can send it to a client */ ods html file = 'Pat_Stat.html' path = 'c:\temp\' (url=none) ; * (url=none) makes it so SAS does not hard code the URL. This is important if you will pass this file to others via email. If you will post the file on a web site then you can put the url of the output here ; proc tabulate data=patients format=7.3; title "statitics for numeric variables"; var hr sbp dbp; tables hr sbp dbp, N*F=7.0 NMISS*F=7.0 MEAN MIN MAX /RTSPACE=18; keylabel N ='Number' NMISS='Missing' MEAN='Mean' MIN='Lowest' MAX='Highest'; run; ods html close; /* You can send the output from several procedures to the same HTML file. To make it easier to navigate, use a contents tab on the left of the output */ ods html file = 'Pat_Stat.html' path = 'c:\temp\' (url=none) contents='contents.html' frame='frame.html' style = default ; proc tabulate data=patients format=7.3; title "statitics for numeric variables"; var hr sbp dbp; tables hr sbp dbp, N*F=7.0 NMISS*F=7.0 MEAN MIN MAX /RTSPACE=18; keylabel N ='Number' NMISS='Missing' MEAN='Mean' MIN='Lowest' MAX='Highest'; run; proc univariate data=patients ; var hr sbp dbp; run; ods html close; /* Go to the c:\temp directory and double click on the frame.html file to see how the table of contents works */ /* NOTE: AVAILABLE SAS STYLES ARE BarretsBlue, Beige, Brick, Brown, D3D, Default, fancyPrinter, Minimal, Printer, RTF, SASWeb, Theme and more.... You can also create your own using proc template You can also create your own css style sheets and use them*/ ods html file = 'Pat_Stat.html' path = 'c:\temp\' (url=none) contents='contents.html' frame='frame.html' style = Brown ; proc tabulate data=patients format=7.3; title "statitics for numeric variables"; var hr sbp dbp; tables hr sbp dbp, N*F=7.0 NMISS*F=7.0 MEAN MIN MAX /RTSPACE=18; keylabel N ='Number' NMISS='Missing' MEAN='Mean' MIN='Lowest' MAX='Highest'; run; proc univariate data=patients ; var hr sbp dbp; run; ods html close; /* The ODS htmlcss statement will output a css file that you can edit in notepad to make custom output */ ods htmlcss file = 'Pat_Stat2.html' path = 'c:\temp\' (url=none) contents='contents2.html' frame='frame2.html' style = Brown STYLESHEET= 'Style.css' ; proc tabulate data=patients format=7.3; title "statitics for numeric variables"; var hr sbp dbp; tables hr sbp dbp, N*F=7.0 NMISS*F=7.0 MEAN MIN MAX /RTSPACE=18; keylabel N ='Number' NMISS='Missing' MEAN='Mean' MIN='Lowest' MAX='Highest'; run; proc univariate data=patients ; var hr sbp dbp; run; ods htmlcss close; /* You can find the color codes here http://www.immigration-usa.com/html_colors.html and http://jeff-lab.queensu.ca/stat/sas/sasman/sashtml/gref/zgscheme.htm The code below shows how to reference your new style sheet */ ods html file = 'Pat_Stat3.html' path = 'c:\temp\' (url=none) contents='contents3.html' frame='frame3.html' STYLESHEET= (url='Style.css') ; proc tabulate data=patients format=7.3; title "statitics for numeric variables"; var hr sbp dbp; tables hr sbp dbp, N*F=7.0 NMISS*F=7.0 MEAN MIN MAX /RTSPACE=18; keylabel N ='Number' NMISS='Missing' MEAN='Mean' MIN='Lowest' MAX='Highest'; run; proc univariate data=patients ; var hr sbp dbp; run; ods html close; /* modifying templates using proc template is beyond the scope of this talk, but to view the templates you have to get an idea of the syntax you can right click on the word results. Then select templates, scroll down to styles and view the code that makes the different styles. If you want to edit a style you should make a copy first. Do not edit any of the styles on UIOWA computers, use CSS instead */ /* Clients may prefer PDS files. Here is example code for creating a PDF file */ %let PDFReport= c:\temp\report.pdf; ods pdf file="&PDFReport." uniform startpage=no; *startpage=no stops SAS from starting a new page at the beginning of each proc Uniform provides uniformity from page to page within a single table when tables span pages. ; proc tabulate data=patients format=7.3; title "statitics for numeric variables"; var hr sbp dbp; tables hr sbp dbp, N*F=7.0 NMISS*F=7.0 MEAN MIN MAX /RTSPACE=18; keylabel N ='Number' NMISS='Missing' MEAN='Mean' MIN='Lowest' MAX='Highest'; run; proc univariate data=patients ; var hr sbp dbp; run; ods pdf close; /* Suppose your client does not want all of the Univariate output. You can select what parts to include in the file you send them. You first need to find out the name of the output section you want. To do that you use ODS trace */ ods trace on /label; proc univariate data=patients plots; var hr sbp dbp; run; ods trace off; /* the log window will show you the names of the output sections */ /* Suppose the client only wants to see the extreme obs for data checking purposes */ ods html file = 'Pat_Stat3.html' path = 'c:\temp\' (url=none) contents='contents3.html' frame='frame3.html' style = beige ; ods html select extremeobs; proc univariate data=patients plots; var hr sbp dbp; run; ods html close; /* Suppose the client only wants to see the extreme obs for hr */ ods html file = 'Pat_Stat3.html' path = 'c:\temp\' (url=none) contents='contents3.html' frame='frame3.html' style = beige ; ods html select hr.extremeobs; proc univariate data=patients plots; var hr sbp dbp; run; ods html close; /* Lastly this code removes all of the SAS language from the output and creates a report for the client to review invalid records. We use the ods proclabel statement to name our proc and the contents statement to change what is shown on the table of contents */ ods html file = ‘report.html’ path = ‘c:\temp\’(url=none) contents=’contents.html’ frame=’frame.html’ style = d3d; ods proclabel="Patient Data Table"; proc print data=patients split='*' NOOBS contents="Invalid Records"; ID PATNO; VAR GENDER DX AE; label gender='Gender' dx='Diagnosis Code' ae='Indicator of*Adverse Event'; WHERE GENDER NOT IN ('M' 'F' ' ') VERIFY(DX,' 0123456789') NE 0 AE NOT IN ('0' '1' ' '); OR OR TITLE "LISTING OF INVALID CHARACTER VALUES"; run; ods html close ; /* note this whole time we have also been sending the default output to the output window. You can turn that off using ODS listing close; And turn it back on just submit ODS listing; */ /* 2. Taking part of the output and sending it to Word or Excel. This may also be for a client or for your own review and manipulation. */ /* here is an example of sending the extreme observations output to a word doc. */ ods rtf file = 'Pat_Stat3.doc' path = 'c:\temp\' (url=none) style = beige ; ods rtf select hr.extremeobs; proc univariate data=patients plots; var hr sbp dbp; run; ods rtf close; /* here is an example of sending the extreme obs output to an excel file. */ ods csvall file = 'Pat_Stat_all.csv' path = 'c:\temp\' (url=none) ; ods csvall select hr.extremeobs; proc univariate data=patients ; var hr sbp dbp; run; ods csvall close; ********************************; ods csv file = 'Pat_Stat.csv' path = 'c:\temp\' (url=none) ; ods csv select hr.extremeobs; proc univariate data=patients ; var hr sbp dbp; run; ods csv close; /* the first set of code will also put the headings and titles in the excel file. The second set of code will not */ /* For the next few examples we will use the dataset you used for Lab 8 */ data bpd ; infile 'd:\s172\bpd.dat' firstobs = 2 ; input bpd birthwt gestage toxemia @50 steroid 1. ; run ; /* Comparing models in Excel */ ods csvall path="c:\temp\" body='logistic.csv' ; ods select LackFitPartition LackFitChiSq; proc logistic data= bpd; model bpd = birthwt /lackfit; ods select LackFitChiSq; LackFitPartition proc logistic data= bpd; model bpd = gestage /lackfit; ods select LackFitPartition LackFitChiSq; proc logistic data= bpd; model bpd = steroid /lackfit; ods select LackFitPartition LackFitChiSq; proc logistic data= bpd; model bpd = birthwt gestage ods select LackFitPartition /lackfit; LackFitChiSq; proc logistic data= bpd; model bpd = birthwt steroid /lackfit; ods select LackFitPartition LackFitChiSq; proc logistic data= bpd; model bpd = gestage steroid /lackfit; ods select LackFitPartition LackFitChiSq; proc logistic data= bpd; model bpd = birthwt gestage steroid run ; ods csvall close; /lackfit; /* note this code could be greatly improved and generalized by creating a macro and using macro variables. Also adding titles will help us keep the models straight in the output */ /* 3. Taking part of the output and using it as a program input or putting into a SAS dataset. */ /* A simple example use ODS output to put the output in a dataset. Note you have to tell SAS what part of the output to put in each file. */ /* This puts the extreme obs from each variable into different datasets */ ods output extremeobs(match_all)=extobs; proc univariate data=patients; var hr sbp dbp; run; /* This puts the extreme obs from each variable into a single dataset */ ods output extremeobs=extobs_all; proc univariate data=patients; var hr sbp dbp; run; /* Now a more complicated example. Since we probably will not have time to cover this in detail, I refer you to CC05-Warner.pdf which covers Jen Warner Freeman’s code. The basic steps are to use the ODS output statement to select parts of output and send them to datasets (see highlighted sections below). Then if you need to use a value from the output in a program, you can create a macro variable from the data set using the call symput function. You can then use these macro variable in later SAS code. A very good example of this is a program written by a colleague of mine to reformat the proc ttest output. We needed to do this because we often had thousands of variables to examine and this code made it easy for us to find the ones that were most significant. FOR THIS PROGRAM DOWNLOAD CAMPAIGN_DATA.ZIP and unzip into your SAS library. */ libname s172 'd:\s172'; /**START OF JEN WARNER FREEMANS CODE */ %let lib1 =work; %let lib2 = s172; %let fileorg =campaign_sample; %let pre = temp; %let buyind=buy_ind; ods output "Statistics" = &lib1..&pre.stats "T-Tests" = &lib1..&pre.ttests "Equality of Variances" = &lib1..&pre.vars; proc ttest data=&lib2..&fileorg; class &buyind; var _numeric_; run; ods output close; data &lib1..&pre.stats1 (keep = variable class mean_nonbuyers mean_buyers); set &lib1..&pre.stats (rename=(mean=avg)); if class = ' 0' then mean_nonbuyers = avg; if class = ' 1' then mean_buyers = avg; run; data &lib1..&pre.buyers(drop = class mean_nonbuyers) &lib1..&pre.nonbuyers(drop = class mean_buyers); set &lib1..&pre.stats1; if class = ' 0' then output &lib1..&pre.nonbuyers; if class = ' 1' then output &lib1..&pre.buyers; run; proc sort data = &lib1..&pre.buyers; by variable; run; proc sort data = &lib1..&pre.nonbuyers; by variable; run; data &lib1..&pre.statsfinal; merge &lib1..&pre.buyers &lib1..&pre.nonbuyers; by variable; run; data &lib1..&pre.ttests1; set &lib1..&pre.ttests; length difference $10.; if probt le .0001 then difference = 'highly'; else if probt le .005 then difference = 'somewhat'; else if probt le .05 then difference = 'weak'; else difference = 'not at all'; run; proc freq data = &lib..&pre.ttests1; tables difference; run; data &lib1..&pre.vars; set &lib1..&pre.vars (rename=(probf=probv)); run; proc sort data = &lib1..&pre.vars; by variable; run; proc sort data = &lib1..&pre.ttests1; by variable difference; run; data &lib1..&pre.merged; merge &lib1..&pre.ttests1 (in=a) &lib1..&pre.vars (in=b keep = variable probv); by variable; if a; run; data &lib1..&pre.diffsame; set &lib1..&pre.merged; if probv le .0050 and variances = 'Unequal' then output; if probv gt .0050 and variances = 'Equal' then output; run; proc sort data = &lib1..&pre.diffsame; by variable; run; data &lib2..&pre.ttest; merge &lib1..&pre.statsfinal &lib1..&pre.diffsame (drop = method variances df probv); by variable; run; proc sort data = &lib2..&pre.ttest; by probt; run; proc print data = &lib2..&pre.ttest; run; /**END OF JEN WARNER FREEMANS CODE */ /* START OF MY CODE TO DEMONSTRATE USING ODS TO CREATE MACRO VARIABLES */ /* This macro will create plots of each predictor variable with the target variable of a logistic regression. This will help us determine if the predictor is linear in the logit. We need to run this code for each possible predictor on our file. Sometimes we have hundreds of predictors. */ %macro varplot(var); proc rank data=&lib2..&fileorg. groups=10 out=ranks; var &var.; ranks rank; proc means data=ranks; class rank; var &buyind. &var.; output out=&lib1..temp sum(&buyind.)=buyers mean(&var.)=mean_&var.; data &lib1..&var.; set &lib1..temp; if _type_=1; percent=buyers/_freq_; lnmean=log(mean_&var.) ; logit=log((buyers+1)/(_freq_-buyers+1)); meansq=mean_&var.**2; sqrtx=sqrt(mean_&var.); proc plot data=&lib1..&var.; plot percent*mean_&var.; plot percent*lnmean; plot logit*mean_&var.; plot percent*meansq; plot percent*sqrtx; plot logit*lnmean; plot logit*sqrtx; plot logit*meansq; run; %mend; /* But, I do not want to type the names of all of my possible predictor variables into macro calls. So I wrote this code to pull the names of the numeric variables out of the dataset created by the ods output statement (and later manipulated by Jen’s code). I then use a little do loop to call the macro once for each numeric variable. */ data _null_; set &lib1..&pre.statsfinal end=last; num+1; call symput("var"!!left(num),variable); if last then call symput("NUM",left(num)); run; %put _user_; * this is so I can make sure I made the variables correctly. run; %macro temp; Look in the log; %do i = 1 %to &NUM ; %Varplot( &&VAR&i ); %end; %mend; %Temp; /* 4. Another cool thing I want to mention A new experimental module is now available in SAS. It is called ODS Graphics. It creates a lot of new cool graphics in many of the SAS Proceedures. */ /* Example an ROC Curve is now displayed in the html output. Note this is not available in the standard output without calling proc gplot. */ ods html path="c:\temp\" body='logistic.html'; ods graphics on; proc logistic data= bpd; model bpd = birthwt gestage steroid /lackfit outroc=temp selection=stepwise slentry=.1 slstay=.1; run ; ods html close; ods graphics off; /* Many cool diagnostic plots come out of proc timeseries. */ /*Read in the soccho dataset */ data soccho ; infile 'd:\s172\soccho2002.dat' delimiter = ',' ; /* data file has commas rather than spaces betw cols */ input acct $ unitval date $10. ; if mod(_n_, 7) > 1 ; /* eliminate weekend days */ run ; data soccho_num; set soccho; * convert the date to a numeric variable from character; num_date=input(date,mmddyy10.); format num_date mmddyy10.; *example of converting numeric to character; char_date_with_fmt=put(num_date,mmddyy10.); char_date_no_fmt=put(num_date,$10.); run; *goptions device=activex; goptions device=java; ods html path="c:\temp\" body='timeseries'; ods graphics on; proc timeseries data= soccho_num print=(descstats trends decomp) plot=(series corr acf pacf iacf wn tc sc ic cc); var unitval; id num_Date interval=weekday accumulate=none; run; ods html close; ods graphics off; ods listing; /* lets see what the univariate output would look like with this option */ ods html file = 'Pat_Stat3.html' path = 'c:\temp\' (url=none) contents='contents3.html' frame='frame3.html' style = beige ; ods graphics on; proc univariate data=patients plots; var hr sbp dbp; run; ods graphics off; ods html close;