Tutorials › Biostatistics › Creating RTF Outputs with SAS ODS

SAS Reporting & Programming

Creating RTF Outputs with SAS ODS

A practical guide to producing Word-compatible RTF tables, listings, and figures with SAS ODS, including destination management, styles, titles, footnotes, pagination, PROC REPORT, ODS Graphics, multi-table documents, and validation in clinical reporting workflows.

Intermediate 18 min read

What You'll Learn

  • How ODS separates SAS analysis procedures from output destinations
  • How to open, control, and close the RTF destination
  • How to manage titles, footnotes, styles, and page breaks
  • How to build clinical-style tables with PROC REPORT
  • How to combine multiple tables in one RTF document
  • How to deliver SAS graphics into RTF output
  • How to debug unexpected ODS output
  • How to validate RTF content and presentation

Introduction

Creating polished RTF (Rich Text Format) outputs with SAS ODS is a common requirement in clinical-trial reporting, statistical programming, and regulated reporting environments. RTF files can be opened directly in Microsoft Word and are particularly useful when a table, listing, or figure needs to be reviewed, annotated, edited, or incorporated into a larger reporting package.

SAS provides the Output Delivery System (ODS) as the framework for sending procedure output to different destinations. For RTF reporting, the central destination is ODS RTF. The programmer controls the output file, page behavior, titles and footnotes, styles, column widths, and the SAS procedure that generates the actual report.

Key idea: ODS RTF does not perform the statistical analysis itself. SAS procedures such as PROC REPORT, PROC PRINT, PROC FREQ, and PROC MEANS create the output object; ODS RTF determines how that output is delivered as an RTF document.

What You'll Learn

1
Understand how the SAS Output Delivery System separates analysis procedures from output destinations.
2
Open and close the RTF destination correctly with ODS RTF FILE= and ODS RTF CLOSE.
3
Control document titles, footnotes, page breaks, and table placement.
4
Use ODS styles and procedure-specific options to create publication-ready tables.
5
Build clinical-style tables with PROC REPORT, including groups, analysis variables, statistics, and spanning headers.
6
Create multiple tables in one RTF document and control when each table begins on a new page.
7
Handle SAS graphics and understand the interaction between ODS RTF and the ODS Graphics system.
8
Validate RTF outputs in a regulated or production reporting workflow.

RTF and the SAS Output Delivery System

ODS acts as a bridge between SAS procedures and presentation formats. A procedure may generate a table or statistical result, while the ODS destination determines how that result is rendered.

LayerExamplePrimary responsibility
Analysis procedurePROC REPORTDefines rows, columns, statistics, grouping, and report logic
ODS destinationODS RTFWrites the selected output to an RTF document
ODS styleSTYLE=RTF or custom styleControls fonts, borders, spacing, colors, and other presentation attributes
ODS GraphicsSG procedures / statistical graphicsCreates graphical output for supported procedures
ValidationProgram + log + output reviewConfirms the delivered document matches specifications and source data

The Basic ODS RTF Workflow

A reliable RTF program follows a simple sequence: define the destination, generate the requested output, close the destination, and then inspect the resulting document.

1
Define the output path and RTF destination.
2
Apply a suitable ODS style or reporting template.
3
Set titles and footnotes as required by the reporting specification.
4
Run the SAS procedure that creates the table, listing, or figure.
5
Reset or replace titles and footnotes before subsequent outputs when appropriate.
6
Close the RTF destination.
7
Review the SAS log and open the RTF file for visual and content QC.

The Simplest RTF Program

The following is the minimum pattern to remember. The destination is opened before the procedure runs and closed after the desired output has been generated.

ods rtf file="c:\reports\example.rtf";

proc print data=sashelp.class;
run;

ods rtf close;
Practical rule: If ODS RTF CLOSE; is omitted, the RTF destination may remain open while later SAS statements execute. In a production program, explicitly closing the destination makes the output lifecycle easier to understand and control.

Choosing the Output File

The FILE= option identifies the RTF file to be created. Use a fully qualified path in production programs when the output location is controlled by the reporting environment.

ods rtf file="C:\study\outputs\demographics.rtf";

On systems where SAS is running in a UNIX or Linux environment, use the corresponding path convention:

ods rtf file="/study/outputs/demographics.rtf";

A macro variable is often preferable when the same program is executed repeatedly:

%let outdir=/study/outputs;
%let outfile=&outdir./demographics.rtf;

ods rtf file="&outfile.";

proc report data=adam.adsl nowd;
run;

ods rtf close;

Why Use Macro Variables for Paths?

Hard-coded paths make programs harder to move between development, validation, and production environments. A macro variable separates the reporting logic from the physical output location.

ApproachAdvantagePotential problem
Hard-coded pathSimple for a one-off programDifficult to reuse across environments
Macro variableEasy to parameterizeRequires the macro variable to be defined correctly
Environment configurationSupports controlled production executionRequires disciplined configuration management

Controlling the Page Size and Orientation

RTF documents are ultimately intended for a page-oriented word-processing environment. SAS system options such as PAGESIZE, LINESIZE, and orientation settings can affect the generated output, although the exact appearance also depends on the RTF destination and Word's rendering behavior.

options orientation=landscape;

ods rtf file="c:\reports\wide_table.rtf";

proc report data=adam.adsl nowd;
  column trt01p age;
  define trt01p / group "Treatment";
  define age    / analysis mean "Mean Age";
run;

ods rtf close;
Important: For wide clinical tables, landscape orientation can be useful, but it should be selected deliberately. Do not assume that changing orientation alone will solve column-width or pagination problems.

ODS Styles

ODS styles provide a high-level mechanism for controlling the visual appearance of output. The style can affect fonts, table borders, cell spacing, headings, and other presentation attributes.

ods rtf file="c:\reports\styled_output.rtf"
    style=RTF;

proc means data=sashelp.class mean std min max;
  var height weight;
run;

ods rtf close;

The exact set of styles available depends on the SAS environment and installed style definitions. In a production system, use the style approved by the reporting standards rather than choosing a style solely because it looks attractive.

Titles and Footnotes

Clinical reports commonly require a figure or table number, a descriptive title, and one or more explanatory footnotes. SAS TITLE and FOOTNOTE statements can be used with ODS RTF.

title1 "Table 14.1.1";
title2 "Demographic Characteristics";

footnote1 "Source: Analysis Dataset ADSL.";
footnote2 "Percentages are based on the number of subjects in each treatment group.";

ods rtf file="c:\reports\table_14_1_1.rtf";

proc report data=adam.adsl nowd;
  column trt01p age;
  define trt01p / group "Treatment";
  define age / analysis mean "Mean Age";
run;

ods rtf close;

title;
footnote;
Watch the global state: TITLE and FOOTNOTE statements persist until changed or cleared. If one table's title is allowed to carry over into the next output, the RTF document can contain misleading or duplicated labels.

BODYTITLE and Document Layout

The BODYTITLE option places titles and footnotes in the body of the RTF output rather than relying solely on document-level header and footer behavior. This can be useful when titles need to travel with the table during Word editing or when page layout is being controlled carefully.

ods rtf file="c:\reports\bodytitle.rtf"
    bodytitle
    style=RTF;

title "Table 14.2.1. Laboratory Results";

proc print data=adam.adlb(obs=20);
run;

ods rtf close;
title;

Starting a New Page

When several tables are written into one RTF document, the programmer often needs each major table to begin on a new page. One straightforward approach is to close the first output block and use a page break before the next output. The precise technique should be aligned with the project's reporting standard and SAS version.

ods rtf file="c:\reports\multiple_tables.rtf"
    bodytitle;

title "Table 14.1.1. Demographics";

proc report data=adam.adsl nowd;
  column trt01p age;
  define trt01p / group "Treatment";
  define age / analysis mean "Mean Age";
run;

ods rtf startpage=now;

title "Table 14.2.1. Laboratory Summary";

proc means data=adam.adlb mean std;
  class trt01p;
  var aval;
run;

ods rtf close;
title;
Concept: STARTPAGE=NOW tells ODS to begin the next output on a new page. This is especially useful when one RTF document contains a sequence of independently numbered tables.

Preventing Unwanted Page Breaks

For some reporting layouts, multiple small outputs may intentionally share a page. In other situations, automatic page breaks may be undesirable. ODS RTF provides page-control options that can be combined with procedure-level layout choices.

ods rtf file="c:\reports\combined.rtf"
    startpage=no
    bodytitle;

title "Summary Tables";

proc report data=adam.adsl nowd;
  column age;
  define age / analysis mean "Mean Age";
run;

proc report data=adam.adsl nowd;
  column weight;
  define weight / analysis mean "Mean Weight";
run;

ods rtf close;
title;

Use page-control options carefully. A layout that looks efficient on one dataset may become crowded when labels, decimal precision, or subject counts change.

PROC REPORT: The Main Workhorse for Clinical Tables

PROC REPORT is particularly powerful for RTF output because it combines data display, grouping, analysis statistics, computed columns, spanning headers, and detailed style control.

proc report data=adam.adsl nowd;
  column trt01p age weight;

  define trt01p / group "Treatment";
  define age    / analysis mean "Age (years)";
  define weight / analysis mean "Weight (kg)";
run;

The NOWD option requests non-windowing execution, which is the usual choice for batch-oriented report generation.

Group Variables and Analysis Variables

The distinction between GROUP and ANALYSIS is fundamental to PROC REPORT.

DEFINE usagePurposeTypical example
GROUPCombines observations into report groupsTreatment arm
ORDEROrders rows by a variableVisit
DISPLAYDisplays a variable without aggregationSubject ID
ANALYSISCalculates a statisticAge, laboratory value
COMPUTEDCreates a value from report columnsPercentage or derived display

Displaying Counts and Percentages

A common clinical-reporting requirement is a treatment-by-category table showing counts and percentages. One implementation uses PROC REPORT with a count statistic and carefully prepared analysis data.

proc report data=work.summary nowd;
  column category trt01p n pct;

  define category / group "Category";
  define trt01p   / group "Treatment";
  define n        / analysis sum "n";
  define pct      / analysis mean "Percent";
run;

In production, the underlying summary dataset and denominator definition should be established before the report is programmed. The RTF layer should not silently redefine the statistical population.

Spanning Headers

Clinical tables frequently use a treatment header spanning several statistics. PROC REPORT supports this through column statements containing header text and nested report columns.

proc report data=work.summary nowd;
  column category
         ("Treatment A" n_a pct_a)
         ("Treatment B" n_b pct_b);

  define category / group "Category";
  define n_a     / analysis sum "n";
  define pct_a   / analysis mean "%";
  define n_b     / analysis sum "n";
  define pct_b   / analysis mean "%";
run;
Reporting principle: Spanning headers are presentation logic, but their wording must remain consistent with the underlying analysis population and treatment definitions.

Controlling Cell Appearance with STYLE=

ODS styles establish the broad visual design, while STYLE= options can make local adjustments to specific report elements.

proc report data=work.summary nowd;
  column category n pct;

  define category / group "Category"
    style(column)={cellwidth=2.2in};

  define n / analysis sum "n"
    style(column)={just=center};

  define pct / analysis mean "%"
    style(column)={just=center};
run;

Cell-width specifications are particularly useful for controlling long category labels. However, excessive manual widths can force wrapping or push a table onto additional pages.

Fonts and Font Sizes

RTF output is often delivered to Word, so font selection matters. Font availability on the reviewer's system can influence rendering. Use the fonts specified by the reporting standard.

proc report data=work.summary nowd
  style(report)={font_face="Arial" font_size=9pt};

  column category n;

  define category / group "Category";
  define n / analysis sum "n";
run;

PROC PRINT for Listings

For patient-level listings, PROC PRINT can be a simple and effective choice.

title "Listing 16.2.1. Subject Disposition";

ods rtf file="c:\reports\listing_16_2_1.rtf"
    bodytitle;

proc print data=adam.adsl noobs label;
  var usubjid trt01p age sex;
run;

ods rtf close;
title;

For very large listings, page behavior, variable widths, sorting, and readability should be reviewed carefully. A listing that technically fits the RTF page may still be difficult to review.

PROC FREQ and RTF Tables

ODS RTF can capture output from many statistical procedures without requiring PROC REPORT. For example, PROC FREQ can produce frequency tables directly.

ods rtf file="c:\reports\sex_by_treatment.rtf"
    bodytitle;

title "Sex by Treatment";

proc freq data=adam.adsl;
  tables trt01p*sex / missing;
run;

ods rtf close;
title;
Do not confuse convenience with specification compliance. A procedure's default output may contain statistics, labels, spacing, or ordering that do not match the approved TLF shell. In regulated reporting, explicitly control the output that is required.

ODS SELECT and ODS EXCLUDE

Some SAS procedures generate several output objects. If only one is required in the RTF document, ODS selection statements can reduce unwanted material.

ods rtf file="c:\reports\selected_output.rtf";

ods select Moments;

proc univariate data=adam.adsl;
  var age;
run;

ods rtf close;

Similarly, ODS EXCLUDE can suppress output objects that are not required. The exact object names depend on the procedure and SAS release, so inspect the SAS log or use ODS tracing when necessary.

Finding Output Object Names with ODS TRACE

ODS TRACE is useful when a procedure produces several output objects and you need to identify which object should be selected or excluded.

ods trace on;

proc means data=adam.adsl mean std min max;
  var age;
run;

ods trace off;

The SAS log will report information about the generated ODS objects. This is a valuable diagnostic technique when building production programs around procedure output.

Creating an RTF Document with Multiple Tables

A single RTF document can contain a sequence of tables. This is useful for reports where related outputs are intended to be reviewed together.

ods rtf file="c:\reports\efficacy_tables.rtf"
    bodytitle
    style=RTF;

title "Table 14.1.1. Demographics";
proc report data=work.demog nowd;
  column trt01p n;
  define trt01p / group "Treatment";
  define n / analysis sum "Subjects";
run;

ods rtf startpage=now;

title "Table 14.2.1. Primary Endpoint";
proc report data=work.endpoint nowd;
  column parameter trt01p estimate;
  define parameter / group "Parameter";
  define trt01p / group "Treatment";
  define estimate / analysis mean "Estimate";
run;

ods rtf startpage=now;

title "Table 14.3.1. Safety Summary";
proc report data=work.safety nowd;
  column aesoc trt01p n;
  define aesoc / group "System Organ Class";
  define trt01p / group "Treatment";
  define n / analysis sum "n";
run;

ods rtf close;
title;

Managing Titles Between Tables

Because titles persist, a multi-table document should deliberately update them before each output. A clean pattern is:

1
Set the title for Table 1.
2
Run the procedure for Table 1.
3
Start a new page if required.
4
Replace the title with the Table 2 title.
5
Run the procedure for Table 2.
6
Clear titles after the final table.

RTF Output and ODS Graphics

SAS graphics can also be delivered through ODS. When a supported procedure or graphing procedure generates an image, ODS RTF can place the resulting graphic in the document.

ods graphics on;

ods rtf file="c:\reports\survival_plot.rtf"
    bodytitle;

title "Figure 14.2.1. Kaplan-Meier Estimate";

proc lifetest data=work.survival plots=survival;
  time aval*censor(1);
run;

ods rtf close;

ods graphics off;
title;

For production figures, graphical dimensions, image quality, fonts, labels, and page placement should be controlled according to the figure specification.

Controlling Graphic Dimensions

ODS Graphics options such as width and height can be used to establish a consistent figure size.

ods graphics on /
  width=6.5in
  height=4.5in;

ods rtf file="c:\reports\figure.rtf";

proc sgplot data=work.plotdata;
  series x=visit y=mean / group=trt01p;
run;

ods rtf close;

ods graphics off;
Consistency matters: If several figures appear in the same report, use a common dimension convention unless the figure type requires a different aspect ratio.

Page Layout for Wide Tables

RTF tables can become difficult to read when there are many treatment columns. Typical solutions include:

  • Landscape orientation.
  • Smaller but still readable fonts.
  • Carefully chosen column widths.
  • Short, standardized column labels.
  • Spanning headers.
  • Splitting an oversized table into logically related sections.
Avoid simply shrinking everything. A table that technically fits a page but requires a reviewer to zoom substantially is not necessarily a better table. Readability and specification compliance should take precedence over squeezing every column onto one page.

Using ODS RTF in a Clinical TLF Workflow

In a clinical reporting environment, RTF generation is only one part of the workflow. The statistical program should begin from controlled analysis datasets and an approved table, listing, or figure specification.

1
Read the approved TLF shell and identify every required row, column, statistic, label, and footnote.
2
Confirm the analysis dataset and population definitions.
3
Prepare or derive the report-ready data.
4
Program the SAS procedure and ODS RTF destination.
5
Generate the RTF output.
6
Review the SAS log for errors, warnings, and unexpected notes.
7
Perform content QC against the source analysis dataset.
8
Perform independent or validated program QC where required.
9
Perform visual QC of the final RTF document.
10
Archive the program, input versions, output, log, and QC evidence according to the project process.

RTF Versus Other ODS Destinations

RTF is one of several ODS destinations. Choosing the destination depends on the intended consumer and downstream workflow.

DestinationTypical useStrength
RTFWord-compatible review and reportingEditable, familiar document format
PDFFixed-layout deliveryStable pagination and appearance
HTMLBrowser-based reviewInteractive and web-friendly
EXCELSpreadsheet-oriented deliveryUseful for data-oriented review and manipulation
LISTINGTraditional SAS outputSimple diagnostic or text-oriented output

RTF Versus PDF for Clinical Reporting

RTF

Useful when reviewers need a Word-compatible document that can be edited, annotated, or incorporated into another report.

PDF

Useful when stable pagination and fixed visual presentation are more important than editability.

The same statistical result may be delivered to multiple destinations during development. The destination should not change the underlying statistical derivation.

Separating Data Preparation From Presentation

A robust reporting program usually separates the calculation of analysis values from the presentation of those values. For example, a data step or summary procedure can create a report-ready dataset, while PROC REPORT focuses on arranging that dataset into the approved table.

data work.tlf14_1_1;
  set adam.adsl;

  /* Example presentation variables only.
     Production derivations should follow the SAP and dataset specifications. */
  length age_display $20;
  age_display = strip(put(age, 8.));
run;

ods rtf file="c:\reports\tlf_14_1_1.rtf"
    bodytitle;

title "Table 14.1.1. Demographic Characteristics";

proc report data=work.tlf14_1_1 nowd;
  column trt01p age_display;

  define trt01p     / group "Treatment";
  define age_display / display "Age (years)";
run;

ods rtf close;
title;
Why this helps: When the report-ready dataset contains clearly defined display variables, the RTF-generation layer becomes easier to read, debug, validate, and reuse.

Using Labels and Formats

SAS labels and formats can improve report readability, but production programs should distinguish between analysis values and display values.

proc format;
  value trtf
    1 = "Treatment A"
    2 = "Treatment B";
run;

proc report data=work.adsl nowd;
  column trt01p age;

  define trt01p / group "Treatment"
                  format=trtf.;
  define age / analysis mean "Mean Age";
run;

For controlled clinical reporting, format definitions should be maintained consistently across tables and should not introduce a label that conflicts with the approved terminology.

Decimal Precision

RTF output preserves the values delivered by SAS, but the report programmer controls how many decimals are displayed. The display decision should be specified rather than chosen ad hoc.

proc report data=work.summary nowd;
  column parameter estimate;

  define parameter / group "Parameter";
  define estimate / analysis mean "Estimate"
                   format=8.2;
run;

Handling Missing Values

Missing values need an explicit reporting decision. Depending on the table, a missing value may be displayed as blank, a symbol, or a prespecified text such as NE or NA.

data work.report;
  set work.analysis;

  length estimate_display $20;

  if missing(estimate) then
    estimate_display = "NE";
  else
    estimate_display = strip(put(estimate, 8.2));
run;
Do not hide missingness unintentionally. A display transformation should preserve the intended meaning of the analysis result. The reason for a missing value and the convention used to display it should be consistent with the table specification.

Footnotes for Statistical Conventions

RTF makes it easy to attach explanatory notes to a table, but footnotes should describe the output precisely. Typical topics include denominators, rounding, analysis populations, censoring rules, and abbreviations.

title "Table 14.2.1. Summary of the Primary Endpoint";

footnote1 "Analysis population: Full Analysis Set.";
footnote2 "Values are based on the prespecified analysis population.";
footnote3 "CI = confidence interval.";

ods rtf file="c:\reports\primary_endpoint.rtf"
    bodytitle;

proc report data=work.endpoint nowd;
  column parameter estimate lower upper;
  define parameter / group "Parameter";
  define estimate  / analysis mean "Estimate";
  define lower     / analysis mean "Lower 95% CI";
  define upper     / analysis mean "Upper 95% CI";
run;

ods rtf close;

title;
footnote;

Common RTF Problems

ProblemLikely causePractical response
RTF file is not createdInvalid path or destination errorCheck the log and confirm the output directory exists
Output appears in the wrong placeIncorrect destination stateReview which ODS destinations are open
Title appears on the next tableTITLE statement persistedReset or replace titles explicitly
Table is too wideToo many columns or excessive widthsReview orientation, widths, labels, and font size
Unexpected extra outputProcedure created multiple ODS objectsUse ODS SELECT/EXCLUDE or revise the procedure
Graphic is too largeODS Graphics dimensions not controlledSet an appropriate width and height
Pagination changes after data updateContent length changedReview page-break logic and avoid fragile layout assumptions
Formatting differs between environmentsFont/style/version differencesUse controlled styles and validate in the target environment

Debugging the ODS Destination State

When output does not appear where expected, first determine which ODS destinations are open.

ods _all_ show;

This is useful during development because multiple destinations may be active simultaneously. A production program should nevertheless manage its ODS state deliberately rather than relying on whatever destinations happen to be open from an interactive SAS session.

Closing the Destination Safely

A clean program usually follows this pattern:

ods rtf file="c:\reports\final.rtf";

/* report-generating statements */

ods rtf close;

If a program creates several independent RTF documents, close each destination before opening the next document unless there is a specific reason to keep multiple destinations active.

One Table Per RTF File vs. One Report Per RTF File

StrategyAdvantagesConsiderations
One RTF per tableEasy table-level QC and distributionCreates many files and requires file-management discipline
One RTF per report sectionBalances manageability and review convenienceRequires deliberate page and title management
One RTF for the entire reportConvenient for end-to-end reviewPagination and document size can become complex

RTF Output in Regulated Environments

In regulated environments, the RTF document is a controlled deliverable rather than merely a visual by-product. The programming process should provide traceability from the final table back to the approved specification and source data.

Validation principle: Validate both content and presentation. A table can contain statistically correct values and still fail its reporting specification because of incorrect labels, missing footnotes, wrong ordering, incorrect pagination, or an incomplete population.

Program-Level QC

  • Confirm the correct analysis dataset and version are used.
  • Confirm the expected variables exist and have the expected attributes.
  • Check that sorting and grouping are deterministic.
  • Review all log errors and investigate unexpected warnings.
  • Confirm the intended ODS destination is open.
  • Confirm the destination is closed after output generation.
  • Check that no unintended output objects are included.

Content QC

  • Compare key counts with an independent derivation or source table.
  • Verify treatment groups and analysis populations.
  • Verify denominators for percentages.
  • Verify rounding and decimal precision.
  • Verify missing-value handling.
  • Verify statistical labels and confidence-interval wording.
  • Verify all required footnotes and abbreviations.

Visual QC

  • Open the actual RTF file in the target review environment.
  • Inspect every page, not only the first page.
  • Check for clipped or wrapped labels.
  • Check table borders and header alignment.
  • Check page breaks and repeated headers.
  • Check titles and footnotes on each page where applicable.
  • Inspect figures for resolution, dimensions, and placement.
  • Check that no blank or nearly blank pages were introduced.

A Practical RTF Validation Checklist

1
Correct output file name and location.
2
Correct table, listing, or figure number.
3
Correct title and subtitle.
4
Correct analysis population.
5
Correct treatment groups and ordering.
6
Correct row and column labels.
7
Correct values, statistics, and rounding.
8
Correct missing-value representation.
9
Correct footnotes and abbreviations.
10
Correct page orientation and table width.
11
Correct page breaks and continuation behavior.
12
Correct graphics size and placement.
13
No unexpected SAS output.
14
No SAS errors or unexplained warnings.
15
Final RTF opens successfully in the approved review environment.

Advanced Pattern: Macro-Driven RTF Generation

When a project contains many similarly structured outputs, a macro can standardize the destination and common options while allowing each table to supply its own data and title.

%macro make_rtf(
    outfile=,
    title_text=,
    data=
);

  ods rtf file="&outfile."
      bodytitle
      style=RTF;

  title "&title_text.";

  proc report data=&data. nowd;
  run;

  ods rtf close;

  title;
%mend;

%make_rtf(
  outfile=c:\reports\table1.rtf,
  title_text=Table 14.1.1. Demographics,
  data=work.demog
);
Use macros for consistency, not obscurity. A macro should make repeated reporting patterns easier to maintain. It should not hide critical analysis logic that reviewers or validators need to understand.

Advanced Pattern: Separate Analysis and Display Macros

A stronger architecture is to keep statistical derivations in one layer and RTF rendering in another. This makes it possible to test the analysis dataset independently of Word-oriented formatting.

/* Layer 1: analysis-ready data */
data work.tlf_data;
  set adam.adsl;
  /* prespecified derivations */
run;

/* Layer 2: presentation */
ods rtf file="c:\reports\demographics.rtf"
    bodytitle;

title "Table 14.1.1. Demographic Characteristics";

proc report data=work.tlf_data nowd;
  column trt01p age;
  define trt01p / group "Treatment";
  define age / analysis mean "Mean Age";
run;

ods rtf close;
title;

Reproducibility and File Naming

Consistent output naming is especially important when a program generates dozens or hundreds of RTF files. A useful naming convention encodes the report identifier and, where appropriate, the analysis version.

%let study=ABC123;
%let tlf=14_1_1;
%let version=v01;

%let outfile=/study/outputs/&study._tlf_&tlf._&version..rtf;

ods rtf file="&outfile.";
/* report code */
ods rtf close;

The exact convention should follow the project's document-management and submission requirements.

What ODS RTF Does Not Do

ODS RTF is a delivery mechanism, not a replacement for statistical programming standards. It does not by itself determine whether an analysis is correct, whether a denominator is appropriate, or whether a subject belongs in the analysis population.

QuestionWhere it should be controlled
Who is included in the analysis?Analysis dataset and SAP-defined population logic
How is a derived endpoint calculated?Prespecified analysis derivation
What statistic is required?Table/figure specification and statistical analysis plan
How is the result arranged in Word?ODS RTF and procedure/reporting code
Whether the final result is acceptableProgramming QC, independent QC, and clinical/statistical review as applicable

Common Mistakes

  1. Forgetting to close the RTF destination. Explicitly close it after the intended output.
  2. Allowing titles or footnotes to persist. Reset global statements after each logical output block.
  3. Using default procedure output without checking the TLF shell. Defaults rarely represent the full reporting specification.
  4. Hard-coding environment-specific paths. Use controlled macro variables or configuration.
  5. Overusing manual column widths. Widths that work for one dataset may fail when labels or values change.
  6. Ignoring page breaks. A table can be numerically correct but visually unacceptable.
  7. Failing to inspect the actual RTF. Successful SAS execution does not guarantee acceptable presentation.
  8. Relying on visual review alone. Content validation must trace output values back to the analysis data.
  9. Leaving unrelated ODS destinations open. Interactive-session state can produce confusing output during development.
  10. Mixing analysis logic and formatting logic excessively. Separate derivation from presentation where practical.

A Complete Example: Clinical-Style RTF Table

The following example combines several concepts: macro-based output naming, title and footnote control, landscape orientation, a report-ready dataset, PROC REPORT, and explicit destination closure.

%let outdir=C:\study\outputs;
%let outfile=&outdir.\table_14_1_1_demographics.rtf;

options orientation=landscape;

data work.tlf_14_1_1;
  set adam.adsl;

  length sex_display $20;
  sex_display = sex;
run;

ods rtf file="&outfile."
    bodytitle
    style=RTF;

title1 "Table 14.1.1";
title2 "Demographic Characteristics";

footnote1 "Analysis Population: All Randomized Subjects.";
footnote2 "Source: ADSL.";

proc report data=work.tlf_14_1_1 nowd
    style(report)={font_face="Arial" font_size=9pt};

  column trt01p age sex_display;

  define trt01p / group
    "Treatment";

  define age / analysis mean
    "Age (years)"
    format=8.1;

  define sex_display / display
    "Sex";
run;

ods rtf close;

title;
footnote;

How to Think About RTF Programming

A useful mental model is:

\[\text{Analysis data} \;\longrightarrow\; \text{SAS procedure} \;\longrightarrow\; \text{ODS object} \;\longrightarrow\; \text{RTF document}\]

Each stage has a different responsibility. The analysis dataset provides the values. The procedure determines what is calculated or displayed. ODS manages the output destination and style. The final RTF document is the artifact reviewed by users.

RTF and Reproducible Reporting

The main advantage of SAS ODS RTF is not simply that SAS can create a Word-compatible file. The deeper advantage is that the same program can regenerate the document when the controlled input data change.

If a statistic is represented by the generic calculation

\[\hat{\theta}=f(X_1,\ldots,X_n)\]

then a reproducible reporting workflow keeps the function that produces θ̂ in the analysis layer and uses ODS to render the resulting value consistently.

Bottom line: Use ODS RTF to control delivery and presentation, while keeping statistical definitions, population selection, derivations, and validation in the appropriate analysis and reporting layers.

Final Checklist

AreaQuestion to ask
DestinationIs the correct RTF path and file name being used?
ODS stateIs the intended destination open, and is it explicitly closed?
AnalysisDoes the report use the correct controlled analysis dataset?
ProcedureDoes the procedure produce exactly the required output?
LabelsDo titles, headers, and labels match the specification?
StatisticsAre values, denominators, precision, and missing-value rules correct?
LayoutDo orientation, widths, page breaks, and headers work on every page?
GraphicsAre figures correctly sized and legible?
FootnotesAre all required explanatory notes present and accurate?
QCHas the actual RTF been reviewed, not merely the SAS log?

Summary

Creating RTF outputs with SAS ODS is fundamentally a process of separating analysis from presentation. The analysis program establishes the correct population, variables, and statistical results. SAS procedures generate the output objects. ODS RTF delivers those objects as a Word-compatible document and provides controls for style, titles, footnotes, page behavior, and graphics.

For clinical reporting, the strongest approach is to combine ODS RTF with a well-specified report procedure such as PROC REPORT, controlled data preparation, explicit page and title management, and systematic content and visual QC.

Key takeaway: A reliable RTF program should be reproducible, specification-driven, readable, and validated. The goal is not merely to produce an RTF file; it is to produce the correct report artifact every time the controlled program is executed.

References

SAS Institute Inc. SAS Output Delivery System: User's Guide. SAS documentation.
SAS Institute Inc. SAS/STAT User's Guide. SAS documentation for procedures that generate ODS output.
SAS Institute Inc. SAS 9.4 Procedures Guide: PROC REPORT. SAS documentation.
SAS Institute Inc. SAS 9.4 Output Delivery System: User's Guide. SAS documentation for ODS destinations, styles, output objects, and graphics.