SAS ODS RTF - Best option to embed Title/Footnote into Table - sas

Looking for advice to embed title/footnote as part of the table (see below - making it easier to copy & paste into the different document)
Options explored so far
1) PROC REPORT - COMPUTE PAGE BEFORE (COMPUTE doesn't support justification option and did not find any reliable option to right-align "page x of y" text in title1 e.g. calculating and inserting BLANK space. In addition, I have a need to center align the title)
2) ODS RTF - BODYTITLE and BODYTITLE_AUX option (displays title/footnote as part of the body but not exactly as part of the table - not easy to select as one object)

The SAS ODS inline styling directive ^{PAGEOF} will produce Page x of y output in the output file. Since the output is a Word field you might need to Ctrl-A, F9 to compute the field values when the document is opened.
The RTF destination renders TITLE and FOOTNOTE in the header and footer part of the documents, so tricks are needed to produce per-table 'titles' and 'footers'. From my perspective as a document reader, the best place for PAGEOF would be in the header section and not as part of a table header.
ods escapechar = '^';
title justify=right '^{PAGEOF}';
ODS TEXT= can be used to add paragraphs before and after a Proc TABULATE that does statistical reporting. ODS TEXT= will process inline ODS formatting directives (including PAGEOF). See SAS® 9.4 Output Delivery System: User’s Guide, Fifth Edition, ODS ESCAPECHAR Statement for more info.
ods text='
Narrative before tabular output via ODS TEXT=
^{NEWLINE} Inline styling directives will be processed.
^{NEWLINE} ^{STYLE [color=green]Mix and match}
^{NEWLINE} Let''s see why.
';
ods text='Here you are at ^{STYLE ^{PAGEOF}}';
* This might require Word field computation or print preview to get proper numbers;
Depending on the destination STYLE=, such as ods rtf style=plateau … ODS TEXT might have some outlining or other styling artifacts.
If you are rtf hardcore, ODS TEXT= can also inject rtf codes directly into the destination stream to produce any rtf possible production. In the following example:
legacy style function ^S{attr-name=attr-value} is used to force the text container to be 100% of the page width.
current style function ^{RAW function is used to introduce raw rtf coding. Each { of the actual rtf is introduced using {RAW. Note how the RAWs can be (and are) nested.
ods text = '^S={outputwidth=100% just=c} ^{RAW \rtf1\ansi\deff0^{RAW \fonttbl^{RAW \f0 Arial;}}
\qc\f0\fs20\i\b
This output created with ODS TEXT=\line
Injecting raw RTF coding\line
Such as ^{RAW \cf11\ul colored and underlined}\line
Not for the casual coder.
}';
Some procedures, such as Proc PRINT have style options such as style(table)=[ … ] in which a pretext= and posttext= can be specified, and such texts will be rendered before and after the rtf table -- the texts are not part of the table and would not be 'picked up' in a single click of Word's 'table select' icon (). Also, unfortunately, the pretext= and posttext= values are not processed for ODS styling directives. However, the values can be raw rtf!
PRETEXT demonstrating inline styling is not honored:
proc print
data=sashelp.class (obs=3)
noobs
style(table)=[
pretext='^{STYLE [color=Red]Above 1 ^{NEWLINE}Above 2 - Pretext= is unprocessed ODS directives}'
posttext='^{STYLE [color=Green] Below}'
]
;
run;
PRETEXT demonstrating as raw rtf passed through (when first character is {)
proc print
data=sashelp.class (obs=3)
noobs
style(table)=[
pretext='{\rtf1\ansi\deff0{\fonttbl{\f0 Arial;}}
\qc\f0\fs20\i\b This output created with SAS PRETEXT=
\line Injecting raw RTF coding
\line Not for the casual coder.
}'
posttext='{\rtf1\ansi\deff0{\fonttbl{\f0 Arial;}}
\qc\f0\fs20\i\b This output created with SAS POSTTEXT=
\line Injecting raw RTF coding
\line Not for the casual coder.
}'
]
;
run;
Proc TABULATE does not have a style(table) option, but the TABLE statement does have options:
/ CAPTION= (rendered when OPTION ACCESSIBLETABLE; active)
Note: Caption value is rendered in the page dimension container, so the caption value be overwritten if your TABLE statement has a page dimension in it's crossings.
/ STYLE=[PRETEXT='...' POSTTEXT='...']
Same caveats as mentioned earlier
TABULATE does not:
have any feature that will let you annotate a column header with a row statistic (in this case your (N=###) as part of the category value). A precomputation step that summarizes the crossings to be tabulated will allow you to place a statistic there.
provide any mechanism for inserting a blank or label row that spans the table (such as the LINE statement in Proc REPORT)
Consider this tabulate example with accessibletable on for some medical data. Some of the variables are for:
categorical demographics (such as sex),
continuous measures (such as age cost),
binary flags about state.
data have;
call streaminit(123);
do _n_ = 1 to 1e3 - 300 + rand('uniform',600);
patientId + 1;
category = ceil(rand('uniform',4));
age = 69 + floor(rand('uniform',20));
cost = 500 + floor(rand('uniform',250));
length sex $1;
sex = substr('MF', 1+rand('uniform',2));
array flags flag1-flag3; * some flags asserting some medical state is present;
do over flags; flags = rand('uniform', 4) < _i_; end;
output;
end;
label age = 'Age' cost = 'Cost' sex = 'Sex';
run;
* Precomputation step;
* Use SQL to compute the N= crossings and make that part of a
* new variable that will be in the tabulation column dimension;
proc sql;
create table for_tabulate as
select
*
, catx(' ', 'Category', category, '( n =', count(*), ')')
as category_counted_columnlabel
from have
group by category
;
quit;
Tabulation report
options accessibletable;
proc tabulate data=for_tabulate ;
class
category_counted_columnlabel
sex
/
missing style=[fontsize=18pt]
;
var age cost flag1-flag3;
table
/* row dimension */
age * ( mean std min max median n*f=8.) * [style=[cellwidth=0.75in]]
N='Sex' * sex
cost * ( mean std min max median n*f=8. )
(flag1 - flag3) * mean = '%' * f=percent5.1
,
/* column dimension */
category_counted_columnlabel = ''
/
/* options */
nocellmerge
caption = "This is caption text is ^{STYLE [color=green]from Mars}^{NEWLINE}Next line"
;
run;
For SAS versions before 9.4M6 you can add a page dimension variable (whose value is inlined ODS stylized text to be the caption) to the output of the precomputation step and specify that variable in the table statement. Something like
, '^{NEWLINE}title1' /* SQL step */
|| '^{NEWLINE}title2'
|| '^{NEWLINE}title3'
|| '^{NEWLINE}title4'
as pagedim_title
and
/* tabulate step */
class pagedim_title / style = [background=lightgray fontfamily=Arial textalign=right];
table pagedim_title, …, category_counted_columnlabel = '' … ;

It might be easier to edit the RTF.
read the file and count the occurrences of cf1{Page
{\field{*\fldinst { PAGE }}} of {\field{*\fldinst { NUMPAGES
}}}\cell}
then read again and write modify those lines as you write the new
file.

Related

SAS: prevent proc report from applying comma format to display header title

I'm generating tables with rolling weeks of data, so my columns have to be named in the yyyymmdd format like 20161107. I need to apply a comma format to these columns to display counts, but the format is also being applied to the column name so 20161107 turns into 20,161,107. Below is example code that shows the error:
data fish; set sashelp.fish;
TEST = WIDTH*1000;
run;
ods tagsets.excelxp file = "C:\User\Desktop\test.xls" style=minimal
options(embedded_titles="yes" autofit_height="yes" autofilter="all");
proc report data = fish spanrows nowd &header_style.;
column SPECIES TEST;
define SPECIES / display;
define TEST / display "20161107"
f=comma12. style={tagattr='format:###,###,###'}; /* ERROR OCCURS WITH THIS STYLE */
title1 bold "sashelp.fish title";
run; title1;
ods tagsets.excelxp close;
It looks like I can fix this error by padding the display name with spaces like " 20161107 " but I'm not hardcoding these names, so I'd like to try to fix it in the proc report syntax first if possible. Any insight?
You should tell SAS to only apply that style to the column, then:
define TEST / display "20161107"
f=comma12. style(column)={tagattr='format:###,###,###'};
Then it should work as you expect.
Styles in PROC REPORT typically have multiple things they can apply to, and if you don't specify which they apply to all. style(header), style(report), etc. are all options - you can see the full list, plus a good explanation, in the SAS paper Using Style Elements in the REPORT and TABULATE procedures.

SAS Proc Report changing header style attributes for an across variable

A sample SAS Proc report code is below. I want to change the color of the header background and foreground for one value of the across variable. I used a compute block to change the column background to light gray using the absolute column references c4 and c5. How do I change the header style attributes for c4 and c5 to background=gainboro and foreground=black?
data test;
length name $ 10 disease $ 10.;
infile datalines dsd;
input name $ disease cases rate;
datalines;
State,Fever,4847,25.16
State,Cold,25632,131.5
State,Flu,103825,535.82
Lincoln,Fever,3920,44.17
Lincoln,Cold,16913,190.18
Lincoln,Flu,62965,735.39
Washington,Fever,827,56.56
Washington,Cold,3609,234.26
Washington,Flu,16610,1078.8
Kings,Fever,1026,37.45
Kings,Cold,4984,181.85
Kings,Flu,18388,694.33
Sussex,Fever,1411,78.38
Sussex,Cold,5515,300.46
Sussex,Flu,13881,813.11
Queens,Fever,616,26.03
Queens,Cold,2496,107.75
Queens,Flu,12518,558.09
;
run;
proc report data=test nowd headline headskip
STYLE(Header)={background=charcoal foreground=white }
style(column)={background=gray foreground=black}
style(report)=[rules=rows bordercolor=white];
columns (name disease,(cases rate));
define name/group order=data 'County' style(column)={background=lighttgray} style(header)=[bordertopcolor=gainsboro background=gainsboro foreground=black];
define disease/across '' order=data ;
define cases/'Cases' format=comma9. ;
define rate/'Rate' format=comma12.1 ;
compute cases;
call define('_c4_','style','style={background=lighttgray}');
call define('_c5_','style','style={background=lighttgray}');
endcomp;
run;
quit;
run;
You can use formats to do something close to what you're asking, but I'm not sure it's possible to do what you're asking - maybe Cynthia Zender on communities.sas.com might?
data test;
length name $ 10 disease $ 10.;
infile datalines dsd;
input name $ disease cases rate;
datalines;
State,Fever,4847,25.16
State,Cold,25632,131.5
State,Flu,103825,535.82
Lincoln,Fever,3920,44.17
Lincoln,Cold,16913,190.18
Lincoln,Flu,62965,735.39
Washington,Fever,827,56.56
Washington,Cold,3609,234.26
Washington,Flu,16610,1078.8
Kings,Fever,1026,37.45
Kings,Cold,4984,181.85
Kings,Flu,18388,694.33
Sussex,Fever,1411,78.38
Sussex,Cold,5515,300.46
Sussex,Flu,13881,813.11
Queens,Fever,616,26.03
Queens,Cold,2496,107.75
Queens,Flu,12518,558.09
;
run;
proc format;
value $headerbackf
'Cold' = 'gainsboro'
other = 'charcoal';
value $headerforef
'Cold' = 'black'
other = 'white'
;
quit;
proc report data=test nowd headline headskip
STYLE(Header)={background=charcoal foreground=white }
style(column)={background=gray foreground=black}
style(report)=[rules=rows bordercolor=white];
columns (name disease,(cases rate));
define name/group order=data 'County' style(column)={background=lightgray} style(header)=[bordertopcolor=gainsboro background=gainsboro foreground=black];
define disease/across '' order=data style(header)={background=$HEADERBACKF. foreground=$HEADERFOREF.};
define cases/'Cases' format=comma9. style(header)=inherit;
define rate/'Rate' format=comma12.1 ;
compute cases;
call define('_c4_','style','style={background=lighttgray}');
call define('_c5_','style','style={background=lighttgray}');
endcomp;
run;
That gets that top row formatted, but, doesn't actually get the row you're asking for. I'm not sure it's possible to.
It's possible as #ChrisJ noted that you might be able to do this with CSS styles and nth child selection. It's also possible you can't, unfortunately, due to how SAS does things with PROC REPORT - in particular, in PROC REPORT everything gets shoved inside <tr>s including the header rows, so nth-child and sibling selectors are impossible due to the headers not being children or siblings of each other.
Here's an example of a kludgey version of this, using sashelp.cars as an example.
CSS: (save in a .css file on your drive somewhere, say "c:\temp\test.css"):
#import 'base.css';
/* Red the second (really third) column header value */
.table thead tr:nth-child(2) th:nth-child(3) {
color:red
}
/* Yellow background for the mpg headers under Europe */
.table thead tr:nth-child(3) th:nth-child(4),
.table thead tr:nth-child(3) th:nth-child(5)
{
background-color:yellow
}
/* Green the mpg-city values */
.table thead tr:nth-child(3) th:nth-child(even) {
color:green
}
SAS program: (assumes the above-saved CSS file)
ods html file='example.html' cssstyle='c:\temp\test.css'(html);
ods pdf file='example.pdf' cssstyle='c:\temp\test.css'(print);
proc sort data=sashelp.cars out=cars; by origin;
run;
proc report data=cars nowd;
columns type origin,(mpg_city mpg_highway);
define origin/across;
define type/group;
define mpg_City / analysis mean;
define mpg_highway / analysis mean;
run;
ods _all_ close;
This is partially based on Kevin Smith's Unveiling the power of Cascading Style Sheets (CSS) in ODS.
Unfortunately, we can't in any way identify a cell that has "MPG(City)" in it except by knowing they'll be even column numbers. We similarly can't identify a cell under a "Europe" except by knowing what cells those will be.
Try adding a dummy column _c to the end of your columns statement, and add a define & compute to go with it.
Also, ensure your colour names are actually valid, e.g. lighttgray is invalid and will not work.
columns ... _c ;
define _c / computed noprint ;
compute _c ;
call define('_c4_','style','style={background=lightgray}');
call define('_c5_','style','style={background=lightgray}');
endcomp ;

Break After & R Break

I am creating a report using proc report. My syntax runs fine but it doesnot shows the results of R break & After break in the output report. Thanks in advance
ods pdf file = "D:\New folder (2)\Assignment\Case_Study_1\Detail_Report.pdf";
proc report data = Cs1.Detailed_Report headline nowd ls = 256 ps = 765;
Title 'Olympic Pipeline (LONDON) - by Probability As of 17th November 2012';
column Probability Account_Name Opportunity_Owner Last_Modified_Date Total_Media_Value Digital_Total_Media_Value Deal_Comments;
where Probability > 0;
define Probability/group Descending 'Probability';
define Account_Name/order 'Client';
define Opportunity_Owner/order 'Champ';
define Last_Modified_Date/order format = MMDDYY. 'Modified';
define Total_Media_Value/order format = dollar25. 'Tot_Budget';
define Digital_Total_Media_Value/order format = dollar25. 'Digital_Bugt';
define Deal_Comments/order 'Deal_Comments';
break after Probability/ summarize suppress ol ul;
rbreak after / summarize ol ul;
run;
ods listing close;
ods pdf close;
Your main problem is that you don't have anything for the summarization to do. All of your columns are "ORDER" columns, which is probably not what you want. This is a common confusion in PROC REPORT; ORDER actually can be used in two different ways.
ORDER column type (vs. ANALYSIS, GROUP, ACROSS, COMPUTED, etc.)
ORDER= instruction for how to order data in a column (ORDER=DATA, ORDER=FORMATTED, etc.)
You can instruct SAS how to order a column without having to make it an ORDER column (which is basically similar to GROUP except it doesn't condense extra copies of a value if there are more than one).
If you want RBREAK or BREAK to do anything, you need to have an ANALYSIS variable(s); those are the variables that you want summaries (and other math) to work on.
Here is an example of this working correctly, with analysis variables. You need to tell SAS what to do, also, when summarizing them; mean, sum, etc., depending on what your desired result is.
ods pdf file = "c:\temp\test.pdf";
proc report data = sashelp.cars headline nowd ls = 256 ps = 765;
column cylinders make model invoice mpg_highway mpg_city;
where cylinders > 6;
define cylinders/group Descending;
define make/order;
define model/order;
define invoice/analysis sum;
define mpg_highway/analysis mean;
define mpg_city/analysis mean;
break after cylinders/ summarize suppress ol ul;
rbreak after / summarize ol ul;
run;
ods pdf close;

Suppress "Frequency" text in PROC FREQ

How can I get rid of the box inside the red circle
code:
data mydata;
input x y;
datalines;
1 2
3 4
1 5
3 2
;
run;
proc freq data=mydata;
tables x*y /nopercent norow nocol;
run;
As always with SAS there are multiple paths to the desirred result.
If you are willing to step away from proc freq you can achieve the required output using proc tabulate. This provides a simpler (and more concise) solution and does not require digging through the long and often confusing proc template documents.
proc tabulate data = mydata;
/* Specify the variables to investigate */
class x y;
table
/* Put x in the rows suppress its title and add a total row */
x = "" all = "Total",
/* Put y in the columns, request a total, request frequency count*/
(y all = "Total") * n = "" /
/* Put "x" in the top left box */
box = "x"
/* Put 0 instead of missing */
misstext = "0";
run;
data mydata;
input x y;
datalines;
1 2
3 4
1 5
3 2
;
run;
/*Write new template to work folder */
ods path work.templat(update) sashelp.tmplmst(read);
/*Remove the "Frequency" header from the template */
proc template;
edit Base.Freq.CrossTabFreqs;
edit frequency;
header="";
end;
end;
run;
proc freq data=mydata;
tables x*y /nopercent norow nocol;
run;
The only way I know how to do this is to edit the PROC FREQ template. Since 9.3 or so, PROC FREQ has been rewritten as a basic table via PROC TEMPLATE, which is nice because it lets us edit nearly all aspects of it.
What I do is right click on the Results tree header in display manager - like so:
Then select "Templates". This opens up the template list, which includes all templates you have installed (both style templates and table templates - most of the PROCs have a table template, though msot do not give you as much control as PROC FREQ does).
Then navigate to Sashelp.Tmplmst (Which is the major template store for most preinstalled templates), and open up Base, and then Freq. This gives you a list of table templates that make up PROC FREQ.
In your case, you want to open CrossTabFreqs, which is what you're producing (A crosstab). You could identify this by running ods trace on; then running your code; it will tell you in the log what the name of the template it's using is.
Open that and copy the contents of the template browser to the clipboard, then paste in a new window. We will modify it, save it in a temporary location, and use it.
Add to the top of the program this line:
ods path work.templat(update) sashelp.tmplmst(read);
This tells SAS two things: first, when we update or write to the template store, do that in work; and two, to look in work first then in sashelp for templates. This way when we create our new CrossTabFreqs template, it will come from work.
Now, look in the program for the location where it defines the Frequency block. This is what you want to modify, since this is the instructions for SAS telling it what to print when it is showing frequencies (as opposed to percents or whatnot). It will read as so:
define Frequency;
header = "Frequency";
format = BEST7.;
label = "Frequency Count";
print;
data_format_override;
end;
You want to remove the header. You can do that one of two ways: either remove the header line entirely, or my preferred option, change it to
define Frequency;
header = " ";
format = BEST7.;
label = "Frequency Count";
print;
data_format_override;
end;
Then run the entire program (including the ods path statement). Now, when you use PROC FREQ in this SAS session, it will have no text in the frequency table header.

How do I insert vertical space into a PDF document using ODS PDF?

I'm using ODS PDF to create a simple PDF report, but I'm having trouble inserting space between the tables into the PDF file. This is the code so far:
ODS PDF FILE = "test.pdf" STARTPAGE = NEVER;
DATA CLINIC;
INPUT ID $ 1-3
GENDER $ 4
RACE $ 5
HR 6-8
SBP 9-11
DBP 12-14
N_PROC 15-16;
AVE_BP = DBP + (SBP - DBP)/3;
DATALINES;
001MW08013008010
002FW08811007205
003MB05018810002
004FB 10806801
005MW06812208204
006FB101 07404
007FW07810406603
008MW04811207006
009FB07719011009
010FB06616410610
;
ODS PDF TEXT = "MEANS PROCEDURE FOR EVERYONE";
PROC MEANS DATA=CLINIC N STD MEAN;
VAR SBP DBP;
RUN;
ODS PDF TEXT = "TEXT FOR ANALYSIS GOES HERE";
* Vertical space should be inserted here;
ODS PDF TEXT = "MEANS PROCEDURE FOR MEN ONLY";
PROC MEANS DATA=CLINIC N STD MEAN;
WHERE GENDER = "M";
VAR SBP DBP;
RUN;
ODS PDF TEXT = "TEXT FOR ANALYSIS GOES HERE";
ODS PDF CLOSE;
I know that if I remove STARTPAGE = NEVER; the tables will appear on separate pages, but since these are short tables, it doesn't make sense to have each small table on a separate page.
I'm just trying to insert some vertical space into the file where the comments indicate (between the text after the first table and the text before the first table). How can I do this?
You could just add some newlines like:
ods pdf text="^{newline 1}";
The 1 can be replaced with how many lines of white space you want added.
This is assuming your escape character is the same as mine. If not, set it like:
ods escapechar="^";