Tutorials › Biostatistics › SAS PROC FREQ for Categorical Data Analysis

Categorical Data Analysis

SAS PROC FREQ for Categorical Data Analysis

A practical guide to using PROC FREQ for frequency tables, cross-tabulations, chi-square tests, Fisher's exact test, odds ratios, risk measures, trend tests, stratified analyses, McNemar's test, and clinical-trial reporting.

Intermediate 24 min read

What You'll Learn

  • How PROC FREQ analyzes categorical variables
  • How to create one-way and two-way frequency tables
  • How to perform chi-square and Fisher's exact tests
  • How to calculate odds ratios and risk measures
  • How to analyze ordered categories and matched data
  • How PROC FREQ is used in clinical-trial TLF development

Introduction

Categorical variables appear throughout clinical research. Treatment group, sex, race, geographic region, smoking status, disease stage, response category, adverse-event severity, treatment-emergent adverse events, and laboratory toxicity grades are all examples of categorical data.

For many of these variables, the first analytical question is deceptively simple:

How many observations fall into each category?

The next question is often whether the distribution differs between groups. For example, does the proportion of patients experiencing an adverse event differ between treatment arms? Is objective response associated with treatment? Are disease-control rates different between groups?

In SAS, one of the most important procedures for answering these questions is PROC FREQ.

PROC FREQ is a flexible procedure for producing frequency distributions, cross-tabulations, measures of association, statistical tests, and exact analyses for categorical data.

What Is PROC FREQ?

PROC FREQ counts observations according to the levels of one or more categorical variables.

At its simplest:

proc freq data=adsl;
    tables sex;
run;

This produces a one-way frequency table for SEX.

For each category, SAS typically reports the frequency and percentage.

SEX Frequency Percent
F 48 48.0%
M 52 52.0%
Total 100 100.0%

This simple output is the foundation of a large amount of clinical-trial reporting.

Why PROC FREQ Matters in Clinical Programming

Categorical analyses occur in many common clinical-trial tables and listings.

Clinical Question Typical Categorical Analysis
How many patients are female? One-way frequency
How many patients discontinued treatment? Frequency and percentage
How many patients had a treatment-emergent adverse event? Two-way frequency
Does response differ by treatment? Chi-square or Fisher's exact test
What is the association between treatment and response? Odds ratio / risk measures
Is there a trend across toxicity grades? Cochran-Armitage trend test
Did paired observations change? McNemar's test

Basic PROC FREQ Syntax

The general structure is:

proc freq data=dataset;
    tables variable-list / options;
run;

The DATA= option identifies the input dataset. The TABLES statement identifies the variables or table combinations to analyze.

Example

proc freq data=adsl;
    tables sex race agegr1;
run;

This produces separate frequency tables for the three variables.

One-Way Frequency Tables

A one-way table describes the distribution of a single categorical variable.

proc freq data=adsl;
    tables trt01p;
run;

If the treatment variable contains two treatment arms, the output might look conceptually like:

Treatment N Percent
Placebo 98 49.0%
Drug A 102 51.0%
Total 200 100.0%

One-way frequency tables are particularly useful for validating population counts before more complicated analyses are performed.

Programming habit: Before creating a clinical-trial table, verify the number of observations and the distribution of the variables being used. PROC FREQ is one of the fastest ways to detect unexpected categories, coding errors, and missing values.

Frequency Versus Percentage

A frequency is the number of observations in a category.

A percentage is calculated relative to the relevant denominator.

For a simple one-way table:

Percentage = category count divided by the total nonmissing count, multiplied by 100.

Suppose 30 of 120 patients are in a category:

30 / 120 × 100 = 25%

The distinction between numerator and denominator becomes much more important when PROC FREQ is used for cross-tabulations.

Two-Way Frequency Tables

A two-way table cross-classifies two categorical variables.

proc freq data=adsl;
    tables trt01p*sex;
run;

This creates a treatment-by-sex contingency table.

Conceptually:

Treatment
Female
Male
Placebo
46
52
Drug A
55
47

The raw counts are useful, but clinical reporting frequently requires row percentages, column percentages, or both.

Understanding TABLES Options

PROC FREQ provides several options for controlling what appears in a cross-tabulation.

Common options include:

  • NOROW — suppress row percentages.
  • NOCOL — suppress column percentages.
  • NOPERCENT — suppress overall percentages.
  • CHISQ — request chi-square statistics.
  • FISHER — request Fisher's exact test.
  • OR — request odds ratios for appropriate tables.
  • RELRISK — request relative risk measures.
  • AGREE — request agreement statistics for matched data.
  • TREND — request trend statistics.

Row Percentages

Row percentages answer a question such as:

Within each treatment group, what percentage of patients are female?

Use:

proc freq data=adsl;
    tables trt01p*sex / norow;
run;

Be careful: suppressing row percentages is different from requesting only row percentages. PROC FREQ normally displays multiple percentage types unless specific options are used.

For many clinical tables, programmers calculate or structure the output explicitly so that the denominator matches the table shell.

Column Percentages

Column percentages answer:

Within each sex category, what percentage of patients received each treatment?

Column percentages are especially useful when the categorical variable in the columns defines the analysis population.

Overall Percentages

Overall percentages use the total number of nonmissing observations represented in the table.

For example, if 25 of 100 patients experienced an event:

25 / 100 = 25%

However, in a treatment-by-event table, an overall percentage may be less clinically informative than the treatment-specific percentage.

Clinical Trial Example: Adverse Events

Suppose a study has 200 patients divided between two treatment groups. We want to determine whether the incidence of a particular adverse event differs between treatment arms.

proc freq data=adae;
    tables trt01p*ae_flag / chisq fisher;
run;

The two variables might be:

Variable Meaning
TRT01P Planned treatment
AE_FLAG Whether the patient experienced the event

The resulting contingency table forms the basis for a comparison of event rates.

Binary Outcomes

Many clinical-trial categorical analyses reduce an outcome to two categories. For example:

  • Response / No response
  • Event / No event
  • Death / Alive
  • Discontinued / Completed
  • Progression / No progression
  • Responder / Nonresponder

These are naturally represented using a 2 × 2 contingency table.

The 2 × 2 Table

Consider treatment versus response:

Treatment
Response = Yes
Response = No
Drug A
40
60
Placebo
20
80

The response rate in Drug A is:

40 / (40 + 60) = 40%

The response rate in placebo is:

20 / (20 + 80) = 20%

This table can support several different analyses.

Chi-Square Test

The Pearson chi-square test evaluates whether two categorical variables are independent.

In SAS:

proc freq data=analysis;
    tables treatment*response / chisq;
run;

The chi-square test compares the observed cell counts with the counts expected under independence.

The general Pearson statistic is:

χ² = Σ (Observed − Expected)² / Expected

A small p-value provides evidence against the null hypothesis of independence.

What Does the Chi-Square Test Actually Test?

Suppose:

  • Variable A = treatment
  • Variable B = response

The null hypothesis is that treatment and response are independent.

The alternative hypothesis is that the distribution of response differs by treatment.

Important: A statistically significant chi-square test does not tell you by itself which treatment is better, how large the treatment effect is, or whether the effect is clinically meaningful. Those questions require effect estimates and clinical context.

Expected Cell Counts

The Pearson chi-square approximation depends on expected cell counts being sufficiently large.

If several expected counts are small, the asymptotic chi-square approximation may be unreliable.

This is one reason Fisher's exact test is important.

Fisher's Exact Test

Fisher's exact test is commonly used for 2 × 2 tables when sample sizes or cell counts are small.

proc freq data=analysis;
    tables treatment*response / fisher;
run;

Fisher's exact test calculates an exact probability rather than relying on the large-sample chi-square approximation.

Example

Treatment
Event
No Event
Drug A
2
18
Placebo
0
20

With such sparse data, Fisher's exact test may be preferable to relying solely on Pearson's chi-square approximation.

Chi-Square Versus Fisher's Exact Test

Feature Chi-Square Fisher's Exact
Large samples Excellent Valid
Small samples May be unreliable Excellent
Sparse cells Potential concern Useful
Computational burden Low Higher
Exact calculation No Yes

In clinical programming, it is common to request both when appropriate:

proc freq data=analysis;
    tables trt01p*response / chisq fisher;
run;

The statistical analysis plan or table shell should determine which result is reported as the primary test.

Odds Ratios

For a 2 × 2 table, PROC FREQ can calculate odds ratios.

proc freq data=analysis;
    tables treatment*response / or;
run;

Suppose:

Treatment
Response
No Response
Drug A
40
60
Placebo
20
80

The odds of response under Drug A are:

40 / 60 = 0.667

The odds under placebo are:

20 / 80 = 0.250

The odds ratio is therefore:

0.667 / 0.250 ≈ 2.67

An odds ratio greater than 1 indicates greater odds of response in the first group, assuming the table orientation is defined that way.

Orientation matters. Changing the order of rows or columns can invert the reported odds ratio. Always confirm which treatment is the numerator/reference group before interpreting the estimate.

Risk Ratio and Relative Risk

For a cohort-style 2 × 2 table, PROC FREQ can request relative risk measures.

proc freq data=analysis;
    tables treatment*response / relrisk;
run;

The risk ratio compares probabilities rather than odds.

Using the previous example:

Risk in Drug A = 40 / 100 = 0.40

Risk in Placebo = 20 / 100 = 0.20

Risk Ratio = 0.40 / 0.20 = 2.00

Thus, the response probability is twice as high in the Drug A group in this illustrative dataset.

Odds Ratio Versus Risk Ratio

Measure Definition Interpretation
Risk ratio Risk₁ / Risk₀ Relative probability of the outcome
Odds ratio Odds₁ / Odds₀ Relative odds of the outcome

When outcomes are uncommon, odds ratios and risk ratios may be numerically similar.

When outcomes are common, they can differ substantially.

Confidence Intervals

Effect estimates should generally be accompanied by confidence intervals.

For example:

Odds Ratio = 2.67 (95% CI: 1.35–5.29)

The confidence interval describes the uncertainty associated with the estimated association.

If a confidence interval for an odds ratio excludes 1, that corresponds to a two-sided 0.05-level significance result under the corresponding assumptions.

ORDER= and Table Orientation

The ordering of categorical levels can affect the presentation and interpretation of results.

PROC FREQ provides ordering controls such as:

proc freq data=analysis order=formatted;
    tables treatment*response;
run;

Common ordering approaches include internal order, formatted order, data-set order, and frequency-based ordering.

For clinical reporting, formatted order is often useful because it allows the programmer to control the displayed sequence through formats.

Using Formats With PROC FREQ

Formats are particularly valuable in clinical-trial programming.

proc format;
    value $trtf
        "A" = "Drug A"
        "P" = "Placebo";

    value $sexf
        "F" = "Female"
        "M" = "Male";
run;

proc freq data=adsl order=formatted;
    format trt01p $trtf. sex $sexf.;
    tables trt01p*sex;
run;

The underlying data remain coded while the output becomes human-readable.

Best practice: Use controlled formats rather than changing source values simply to make a table readable. This preserves traceability between the analysis dataset and the reported output.

Suppressing Unwanted Output

Sometimes PROC FREQ is being used primarily for a statistical calculation or intermediate validation rather than for presentation.

The NOPRINT option can suppress displayed output.

proc freq data=analysis noprint;
    tables treatment*response / chisq;
run;

This is useful when PROC FREQ is part of a larger programming workflow.

ODS OUTPUT With PROC FREQ

Clinical programmers frequently need to capture PROC FREQ results into SAS datasets for downstream reporting.

ods output CrossTabFreqs = crosstab
           ChiSq         = chisq;

proc freq data=analysis;
    tables treatment*response / chisq;
run;

ods output close;

The exact ODS table names should be confirmed for the specific PROC FREQ statement and SAS environment being used.

This approach is powerful because it separates:

  • Statistical calculation
  • Result extraction
  • Formatting
  • Final table presentation

Why ODS OUTPUT Matters

A production clinical-programming workflow often should not depend on manually copying numbers from the SAS Results Viewer or listing output.

Instead:

1
Run the statistical procedure.
2
Capture relevant ODS tables.
3
Transform results into reporting-ready datasets.
4
Apply table-shell formatting.
5
Generate the final TLF.

Frequency Counts and Missing Values

Missing values require special attention in PROC FREQ.

By default, missing values are generally excluded from percentage calculations unless explicitly requested.

The MISSING option can include missing values as categories.

proc freq data=adsl;
    tables sex / missing;
run;

This can be useful when missingness itself is relevant to the descriptive summary.

Clinical-trial caution: Do not automatically include missing values merely because PROC FREQ makes it possible. The denominator and missing-data treatment should follow the statistical analysis plan and table specification.

Missing Values in Two-Way Tables

proc freq data=analysis;
    tables treatment*response / missing;
run;

Including missing values can materially change both counts and percentages. Therefore, programmers should explicitly determine whether missing categories belong in the analysis.

BY-Group Analysis

PROC FREQ can perform separate analyses for groups using a BY statement.

proc sort data=analysis;
    by visit;
run;

proc freq data=analysis;
    by visit;
    tables treatment*response / chisq;
run;

This produces separate analyses for each visit.

The dataset must be sorted by the BY variable unless an appropriate alternative data structure is being used.

WHERE Conditions

A WHERE statement can restrict the analysis population.

proc freq data=adsl;
    where saffl = "Y";
    tables trt01p*sex;
run;

This is common in clinical programming when a table is based on a specific analysis population.

For example:

  • Safety population
  • Intent-to-treat population
  • Full analysis set
  • Per-protocol population
  • Responders only
Always validate the denominator. A correct PROC FREQ statement applied to the wrong population still produces the wrong clinical-trial result.

Using a DATA= Subset Versus WHERE

Both approaches can restrict observations, but they are not always equivalent in how they interact with SAS processing and variables.

For straightforward analysis filtering, a WHERE statement is often clear:

proc freq data=adae;
    where saffl = "Y";
    tables trtemfl*severity;
run;

If a permanent subset is needed for multiple downstream procedures, creating a dedicated analysis dataset may improve traceability.

Multiple Tables in One PROC FREQ

Several tables can be requested in one procedure call.

proc freq data=adsl;

    tables sex;
    tables race;
    tables trt01p*sex;
    tables trt01p*race;

run;

Alternatively, multiple requests can be placed in a single TABLES statement.

proc freq data=adsl;
    tables sex race trt01p*sex trt01p*race;
run;

For large production programs, separate TABLES statements can sometimes make the code easier to audit.

Three-Way Tables

PROC FREQ also supports higher-dimensional contingency tables.

proc freq data=analysis;
    tables treatment*response*sex;
run;

This can be useful for exploratory analyses.

However, three-way tables can become difficult to interpret and report. For formal adjusted analyses, regression models or stratified methods may be more appropriate.

Stratified Analysis

A stratified analysis evaluates the treatment-outcome association within levels of a third variable.

For example:

  • Treatment
  • Response
  • Disease stage as the stratification variable
proc freq data=analysis;
    tables stage*treatment*response / cmh;
run;

The CMH option requests Cochran-Mantel-Haenszel statistics.

Cochran-Mantel-Haenszel Analysis

The Cochran-Mantel-Haenszel framework provides tests and estimates for associations across strata.

For example, suppose response is associated with treatment, but disease stage is also relevant.

Rather than simply pooling all patients, a stratified analysis can account for the specified strata.

proc freq data=analysis;
    tables stage*treatment*response / cmh;
run;

This is particularly useful when the statistical analysis plan specifies a stratified categorical analysis.

Important: The Cochran-Mantel-Haenszel analysis is not a generic replacement for a multivariable model. The stratification variables, estimand, and interpretation must be defined in the statistical analysis plan.

Trend Tests

Some categorical variables are ordinal.

Examples include:

  • Mild / Moderate / Severe
  • Grade 1 / Grade 2 / Grade 3 / Grade 4
  • Stage I / Stage II / Stage III / Stage IV
  • Low / Medium / High

When the categories have a meaningful order, a trend test may be appropriate.

proc freq data=analysis;
    tables treatment*severity / trend;
run;

The Cochran-Armitage trend test can evaluate whether there is a systematic trend across ordered categories.

Ordinal Categories Are Not Just Nominal Categories

Consider adverse-event severity:

Grade 1
Grade 2
Grade 3
Grade 4

These categories have an inherent order.

Treating them purely as unrelated nominal categories can discard information about that ordering.

However, an ordinal analysis should only be used when the ordering has a meaningful statistical interpretation and is consistent with the prespecified analysis.

McNemar's Test

PROC FREQ can analyze paired binary data using McNemar's test.

This is useful when the same subjects are classified twice.

Examples include:

  • Before versus after treatment
  • Baseline positive versus Week 12 positive
  • Two diagnostic methods applied to the same subjects
  • Paired assessment classifications
proc freq data=paired;
    tables before*after / agree;
run;

The AGREE option provides agreement statistics and McNemar-related analyses for appropriate paired categorical data.

Example of McNemar's Test

Before
After: Positive
After: Negative
Positive
35
15
Negative
5
45

The key information for McNemar's test is the pair of discordant cells:

  • Positive → Negative = 15
  • Negative → Positive = 5

The test focuses on whether these discordant changes are symmetric.

Agreement Statistics

PROC FREQ can also calculate agreement statistics for paired categorical ratings.

proc freq data=ratings;
    tables rater1*rater2 / agree;
run;

Depending on the analysis, statistics such as Cohen's kappa may be useful for quantifying agreement beyond what would be expected by chance.

WEIGHT Statement

Sometimes the dataset contains aggregated counts rather than one observation per subject.

PROC FREQ can use a WEIGHT statement.

proc freq data=summary_counts;
    tables treatment*response;
    weight count;
run;

For example, the input might contain:

Treatment Response COUNT
Drug A Yes 40
Drug A No 60
Placebo Yes 20
Placebo No 80

The WEIGHT statement tells PROC FREQ that the value in COUNT represents the number of observations represented by each row.

Validation warning: A WEIGHT statement changes the interpretation of the input dataset. Always confirm whether the input represents subject-level records or aggregated counts before using WEIGHT.

Zero-Frequency Categories

A common issue in clinical reporting occurs when a category exists in the controlled terminology but has zero observations.

A formatted variable may have levels such as:

Grade 1
Grade 2
Grade 3
Grade 4
Grade 5

If no patient has Grade 5, PROC FREQ may not display the zero-frequency category by default.

The SPARSE option can be useful for displaying zero-frequency combinations in contingency tables.

proc freq data=analysis;
    tables treatment*severity / sparse;
run;

This can be important when the final table shell expects all predefined categories to appear.

ORDER=FREQ

Sometimes categories are displayed according to their observed frequency.

proc freq data=analysis order=freq;
    tables response;
run;

This can be useful for exploratory summaries.

For standardized clinical tables, however, a logical or protocol-defined ordering is often preferable.

ORDER=DATA

The DATA ordering option can preserve the order in which levels first appear in the dataset.

proc freq data=analysis order=data;
    tables severity;
run;

This can be useful when the dataset has intentionally been constructed in the desired category order.

ORDER=FORMATTED

Formatted ordering is often particularly convenient for clinical reporting.

proc freq data=analysis order=formatted;
    format severity severityf.;
    tables severity;
run;

This allows the programmer to control both the displayed labels and ordering through a SAS format.

PROC FREQ for Demographic Tables

One of the most common uses of PROC FREQ in a clinical trial is the demographic and baseline characteristics section.

Examples include:

proc freq data=adsl;
    tables
        sex
        race
        ethnicity
        region
        smoking_status
        disease_stage;
run;

When stratified by treatment:

proc freq data=adsl;
    tables
        trt01p*sex
        trt01p*race
        trt01p*ethnicity
        trt01p*region;
run;

PROC FREQ for Adverse Events

Adverse-event reporting frequently involves categorical variables.

Examples include:

  • Presence or absence of an event
  • Severity
  • Relationship to study treatment
  • Seriousness
  • Action taken
  • Outcome

A simple severity table might begin with:

proc freq data=adae;
    tables trt01p*severity;
run;

However, clinical AE tables often require patient-level counting rather than raw event-level counting.

Critical AE programming issue: If a patient experiences the same adverse event multiple times, simply running PROC FREQ on event-level ADAE records may count events rather than unique patients. Many clinical-trial AE tables require each patient to contribute at most once to a category.

Why Subject-Level Deduplication Matters

Suppose three patients experience an adverse event:

Patient Event Records
001 2
002 1
003 3

There are six event records but only three patients.

If the clinical table reports the percentage of patients with an event, PROC FREQ must operate on appropriately derived patient-level records.

Typical AE Patient-Level Workflow

1
Start with the event-level adverse-event dataset.
2
Apply the analysis population.
3
Derive the required patient-level indicator.
4
Deduplicate appropriately.
5
Run PROC FREQ on the reporting-ready data.
6
Validate counts against independent patient-level calculations.

PROC FREQ and Treatment-Emergent Events

A common pattern is:

proc freq data=adae;
    where trtemfl = "Y";
    tables trt01p*ae_flag;
run;

But the correct analysis depends on how AE_FLAG was derived and whether the table is intended to count patients or events.

PROC FREQ for Laboratory Toxicity Grades

Categorical toxicity grades are another common application.

proc freq data=adtte;
    tables trt01p*toxgrade;
run;

For example:

Treatment Grade 0 Grade 1 Grade 2 Grade 3+
Placebo 62% 22% 11% 5%
Drug A 48% 27% 16% 9%

Depending on the analysis, the table may summarize worst post-baseline grade, shift from baseline, or another prespecified derivation.

Shift Tables

A shift table compares a categorical baseline classification with a post-baseline classification.

For example:

proc freq data=shift;
    tables baseline_grade*worst_grade;
run;

The resulting table shows how patients moved between categories.

Baseline \ Worst Normal Grade 1 Grade 2 Grade 3+
Normal 42 15 8 3
Grade 1 4 18 9 4
Grade 2 1 3 12 7

Shift tables are particularly useful for laboratory, ECG, vital-sign, and other categorical safety analyses.

PROC FREQ and Response Categories

In oncology trials, response categories may include:

  • Complete response
  • Partial response
  • Stable disease
  • Progressive disease
  • Not evaluable

A simple frequency analysis is:

proc freq data=efficacy;
    tables bor;
run;

To compare response categories by treatment:

proc freq data=efficacy;
    tables trt01p*bor / chisq;
run;

For formal oncology efficacy analyses, however, the appropriate statistical method depends on the endpoint definition and SAP.

Visualization of a Frequency Distribution

The basic idea behind a frequency table can be visualized simply:

Figure 1. Example Frequency Distribution
Illustrative distribution of a binary categorical outcome across 100 patients.
60
No Event
40
Event

Cross-Tabulation as a Statistical Building Block

A contingency table is more than a display. It is the foundation for many categorical-data methods.

For example:

proc freq data=analysis;
    tables treatment*response / chisq fisher or relrisk;
run;

One procedure invocation can therefore provide:

  • Cell counts
  • Percentages
  • Chi-square tests
  • Exact tests
  • Odds ratios
  • Risk measures

Contingency Tables and the Analysis Question

Question Useful PROC FREQ Feature
How many? Basic TABLES statement
What percentage? Frequency percentages
Are variables associated? CHISQ
Are counts sparse? FISHER
How strong is association? OR / RELRISK
Is there an ordered trend? TREND
Is there agreement? AGREE
Is association adjusted across strata? CMH

Statistical Significance Versus Clinical Importance

Suppose a study contains 5,000 patients and the response rate is:

Drug A:   40.0%
Placebo:  38.0%

A chi-square test might produce a statistically significant result because the sample is very large.

But the absolute difference is only 2 percentage points.

Conversely, a small study might show:

Drug A:   60%
Placebo:  40%

without reaching statistical significance because the sample is too small.

Interpretation principle: PROC FREQ provides statistical evidence and descriptive summaries. Clinical importance should be evaluated using the magnitude of the effect, confidence intervals, clinical context, and the prespecified estimand.

Absolute Risk Difference

For binary outcomes, the absolute risk difference can be especially informative.

If:

Risk₁ = 0.40
Risk₀ = 0.20

then:

Risk Difference = 0.40 − 0.20 = 0.20

or a 20-percentage-point difference.

The appropriate PROC FREQ output option should be selected according to the specific measure required by the analysis plan.

Relative Versus Absolute Measures

Measure Example Question
Risk 40% How common is the outcome?
Risk difference 20% How much higher is the risk?
Risk ratio 2.0 How many times greater is the risk?
Odds ratio 2.67 How many times greater are the odds?

Reference Categories Matter

Categorical effect measures require a clearly defined reference group.

For example:

Drug A versus Placebo

is not interchangeable with:

Placebo versus Drug A

The reciprocal relationship affects odds ratios and relative measures.

Programming check: Whenever an odds ratio, relative risk, or other directional measure appears in a report, confirm the row and column ordering and the intended reference group.

Using Formats to Control Reference Levels

A controlled format can help make the intended ordering obvious.

proc format;
    value $trtord
        "P" = "Placebo"
        "A" = "Drug A";
run;

proc freq data=analysis;
    format trt01p $trtord.;
    tables trt01p*response / or;
run;

The format controls display, while the underlying analysis coding remains traceable.

PROC FREQ Versus PROC LOGISTIC

Both procedures can analyze categorical outcomes, but they serve different purposes.

Feature PROC FREQ PROC LOGISTIC
Simple frequency tables Excellent No
Cross-tabulation Excellent No
Chi-square test Excellent Not primary purpose
Fisher's exact test Excellent Different functionality
Simple odds ratio Excellent Excellent
Multiple covariates Limited Excellent
Adjusted logistic model No Yes

PROC FREQ is often the natural first tool for descriptive and unadjusted categorical analyses.

PROC FREQ Versus PROC GENMOD

For more complex modeling of binary outcomes, PROC GENMOD may be appropriate.

For example, generalized linear models can estimate adjusted effects under specific link and distribution choices.

The choice depends on the estimand and analysis plan rather than simply on the type of outcome variable.

PROC FREQ Versus PROC SURVEYFREQ

If the data arise from a complex survey design, ordinary PROC FREQ may not appropriately account for sampling weights, clusters, or strata.

PROC SURVEYFREQ is designed for complex survey frequency analyses.

Important distinction: The correct procedure depends not only on whether the variable is categorical, but also on how the observations were sampled and what inferential framework is required.

Common PROC FREQ Options

Option Purpose
CHISQ Requests chi-square statistics
FISHER Requests Fisher's exact test
OR Requests odds ratios
RELRISK Requests relative risk measures
CMH Requests Cochran-Mantel-Haenszel statistics
TREND Requests trend analysis
AGREE Requests agreement statistics
MISSING Includes missing values as categories
SPARSE Displays zero-frequency table combinations
NOPRINT Suppresses printed output

Complete Example: Treatment and Response

Consider a hypothetical oncology trial.

data efficacy;
    input usubjid $ treatment $ response $;
    datalines;
001 DrugA Yes
002 DrugA Yes
003 DrugA No
004 DrugA Yes
005 DrugA No
006 DrugA No
007 Placebo Yes
008 Placebo No
009 Placebo No
010 Placebo No
;
run;

The first analysis is a simple cross-tabulation:

proc freq data=efficacy;
    tables treatment*response;
run;

Then request the chi-square test:

proc freq data=efficacy;
    tables treatment*response / chisq;
run;

Because this example is intentionally small, Fisher's exact test can also be requested:

proc freq data=efficacy;
    tables treatment*response / chisq fisher;
run;

For association measures:

proc freq data=efficacy;
    tables treatment*response / chisq fisher or relrisk;
run;

Using a WHERE Clause in the Example

Suppose only patients in the full analysis set should be included.

proc freq data=efficacy;
    where fasfl = "Y";
    tables treatment*response / chisq fisher;
run;

The population definition must be consistent with the SAP and table shell.

Using a Weight Variable

Suppose the data are already aggregated:

data counts;
    input treatment $ response $ count;
    datalines;
DrugA    Yes 40
DrugA    No 60
Placebo  Yes 20
Placebo  No 80
;
run;

proc freq data=counts;
    tables treatment*response / chisq fisher or relrisk;
    weight count;
run;

This produces the same conceptual contingency table without requiring one row per patient.

PROC FREQ and Clinical Study Report Tables

PROC FREQ often contributes to tables such as:

  • Demographic characteristics
  • Baseline disease characteristics
  • Prior therapies
  • Discontinuations
  • Adverse events
  • Serious adverse events
  • Laboratory shifts
  • Response categories
  • Patient disposition
  • Protocol deviations

However, PROC FREQ does not automatically produce a final CSR table.

A production TLF generally requires additional programming to:

  • Define the analysis population
  • Derive analysis variables
  • Control denominators
  • Deduplicate patient-level events
  • Format categories
  • Combine counts and percentages
  • Arrange treatment columns
  • Apply table-shell labels
  • Produce the final report

Denominator Control

Denominators are among the most important aspects of categorical clinical reporting.

Consider:

Drug A:  40 responders / 100 patients
Placebo: 20 responders / 100 patients

The response rates are 40% and 20%.

But if five Drug A patients have no evaluable response assessment, the denominator might instead be 95 depending on the prespecified endpoint.

Therefore, a programmer should never assume that PROC FREQ's default percentage denominator is automatically the correct clinical denominator.

Key rule: The statistical denominator comes from the analysis definition, not from the convenience of the programming procedure.

Patient-Level Versus Event-Level Data

This distinction is especially important in safety analyses.

Data Structure Typical Unit Example
ADSL Patient One row per subject
ADAE Adverse-event record Potentially many rows per patient
ADLB Laboratory assessment Multiple assessments per patient
ADRS Response assessment Multiple assessments per patient

PROC FREQ does not know which unit of analysis you intended.

The programmer must provide the correct input dataset.

Frequency Tables as QC Tools

PROC FREQ is also extremely useful for quality control.

For example:

proc freq data=adsl;
    tables trt01p sex race agegr1 / missing;
run;

This can quickly reveal:

  • Unexpected treatment codes
  • Unexpected missing values
  • Spelling differences
  • Unexpected category levels
  • Incorrect formats
  • Population-count discrepancies

Checking Treatment Assignment

A simple treatment-frequency check might be:

proc freq data=adsl;
    tables trt01p*trt01pn / list;
run;

This can help identify inconsistencies between character and numeric treatment variables.

Checking Derived Flags

Suppose a programmer derives:

RESPFL = "Y"

for responders.

A simple check is:

proc freq data=efficacy;
    tables respfl;
run;

A treatment-by-response check is:

proc freq data=efficacy;
    tables trt01p*respfl / missing;
run;

These quick checks often catch derivation problems before final TLF generation.

List Output for Detailed Investigation

The LIST option can display the combinations of levels in a multiway table.

proc freq data=analysis;
    tables treatment*response*sex / list;
run;

This can be useful during debugging when a standard formatted table obscures the underlying combinations.

Zero Counts and Clinical Tables

Suppose no patients experienced Grade 5 toxicity.

The final table may nevertheless require:

Grade 1
Grade 2
Grade 3
Grade 4
Grade 5

with Grade 5 displayed as:

0 (0.0%)

This usually requires deliberate data preparation and/or use of formats and sparse combinations.

Category Formats

A typical format definition might be:

proc format;

    value toxgrpf
        0 = "Normal"
        1 = "Grade 1"
        2 = "Grade 2"
        3 = "Grade 3"
        4 = "Grade 4"
        5 = "Grade 5";

run;

Then:

proc freq data=analysis order=formatted;
    format toxgrade toxgrpf.;
    tables toxgrade;
run;

Formats make production programs more maintainable because category labels can be managed separately from the analysis logic.

Character Versus Numeric Variables

PROC FREQ works with both character and numeric categorical variables.

For example:

proc freq data=analysis;
    tables sex;
    tables sexn;
run;

where:

SEX  = "F" / "M"
SEXN = 1 / 2

Both can be analyzed, but formatted numeric variables are often convenient in standardized clinical datasets.

PROC FREQ and CDISC-Oriented Programming

In CDISC-oriented clinical programming, categorical analyses often draw from analysis datasets such as:

  • ADSL for subject-level characteristics
  • ADAE for adverse events
  • ADLB for laboratory analyses
  • ADRS for response analyses
  • ADVS for vital signs

The precise dataset and derivations depend on the study.

PROC FREQ generally operates on the analysis-ready variables rather than performing all clinical derivations itself.

Example: Demographic TLF Programming

proc freq data=adsl noprint;

    where saffl = "Y";

    tables trt01p*sex /
        out=sex_freq
        missing;

run;

The resulting dataset can then be transformed into the exact layout required by the table shell.

OUT= Data Sets

PROC FREQ can write frequency results to an output dataset.

proc freq data=adsl noprint;

    tables sex / out=sex_freq;

run;

This can produce variables containing the category, frequency, and percentage.

For production reporting, the output dataset can then be merged, transposed, formatted, or otherwise transformed.

Why OUT= Is Useful

A typical reporting pipeline is:

1
PROC FREQ calculates counts and percentages.
2
OUT= captures the results in a SAS dataset.
3
DATA step processing creates report-ready records.
4
PROC REPORT, PROC TABULATE, ODS RTF, ODS PDF, or another reporting layer produces the final output.

Building a Count-and-Percentage String

A common clinical-reporting requirement is:

40 (40.0%)

Rather than:

Frequency = 40
Percent = 40.0

The reporting layer can combine the two values.

length result $30;

result =
    cats(
        put(count, 3.),
        " (",
        put(percent, 5.1),
        "%)"
    );

The exact formatting should follow the table shell and sponsor standards.

Creating a Treatment-by-Category Table

A common workflow uses treatment as one dimension and the categorical variable as the other.

proc freq data=adsl noprint;

    tables trt01p*sex /
        out=sex_by_trt;

run;

The resulting data can then be reshaped for reporting.

PROC FREQ and PROC REPORT

PROC FREQ is often the statistical engine while PROC REPORT is the presentation engine.

For example:

proc freq data=adsl noprint;

    tables trt01p*sex /
        out=sex_by_trt;

run;

proc report data=sex_by_trt nowd;
    columns sex trt01p count percent;
run;

In a production environment, additional processing is generally required to produce a polished clinical table.

Statistical Tests Should Match the Design

PROC FREQ provides many categorical-data tests, but the existence of an option does not automatically make it appropriate for a particular study.

Before selecting a test, consider:

  • Study design
  • Independent versus paired observations
  • Nominal versus ordinal categories
  • Sample size
  • Expected cell counts
  • Stratification
  • Prespecified estimand
  • Multiplicity
  • Missing-data rules

Independent Versus Paired Data

Data Structure Potential Method
Two independent categorical groups Chi-square / Fisher's exact
Paired binary observations McNemar's test
Ordinal categories Trend methods where appropriate
Stratified 2 × 2 tables Cochran-Mantel-Haenszel methods
Multiple covariates Regression modeling

Common Mistake: Using Chi-Square for Paired Data

Suppose the same patients are classified before and after treatment.

These observations are not independent.

A simple Pearson chi-square test can ignore the pairing.

A paired analysis such as McNemar's test may instead be appropriate.

Always identify the observational unit and dependency structure before selecting a categorical-data test.

Common Mistake: Treating Ordinal Data as Nominal

Severity grades contain order.

A test that ignores that order may answer a different question than the one the investigator intended.

The correct approach depends on the SAP and scientific objective.

Common Mistake: Ignoring Sparse Cells

A 2 × 2 table containing cells with very small counts may not support a reliable large-sample chi-square approximation.

Fisher's exact test may be appropriate.

proc freq data=analysis;
    tables treatment*response / chisq fisher;
run;

Common Mistake: Misinterpreting the P-Value

A p-value is not:

  • The probability that the null hypothesis is true
  • The probability that the treatment works
  • The magnitude of the treatment effect
  • A measure of clinical relevance

It quantifies the compatibility of the observed data with the null hypothesis under the specified statistical procedure.

Common Mistake: Forgetting the Reference Group

An odds ratio of 2.5 has no complete directional interpretation without knowing which group is being compared with which.

Always verify:

  • Row order
  • Column order
  • Reference category
  • Event category

Common Mistake: Wrong Analysis Population

A frequency table can be mathematically perfect while being clinically wrong because the wrong population was selected.

For example, a safety table might accidentally use all randomized patients rather than all treated patients.

The resulting frequencies would still be internally consistent.

They would simply answer the wrong question.

Common Mistake: Counting Records Instead of Patients

This is one of the most important issues in clinical safety programming.

If a patient has multiple event records, raw PROC FREQ counts records unless the dataset has first been reduced to the intended unit of analysis.

Always ask:

What exactly does one row represent?

Validation Strategy

A production PROC FREQ program should be independently validated.

At minimum, verify:

  • Input dataset
  • Analysis population
  • Category definitions
  • Missing-data treatment
  • Frequency counts
  • Percentages
  • Denominators
  • Reference categories
  • Statistical test
  • Effect estimates
  • Confidence intervals
  • Final displayed values

Manual 2 × 2 Validation

Suppose the analysis table is:

Treatment
Response
No Response
Drug A
40
60
Placebo
20
80

The Drug A response percentage should be:

40 / 100 × 100 = 40.0%

The placebo response percentage should be:

20 / 100 × 100 = 20.0%

The odds ratio should be approximately:

(40 × 80) / (60 × 20) = 2.67

These independent calculations provide useful validation of PROC FREQ output.

Validation Using PROC SQL

A second programming approach can provide an independent check.

proc sql;

    select
        treatment,
        response,
        count(*) as n
    from analysis
    group by treatment, response;

quit;

Comparing these results against PROC FREQ can reveal unexpected discrepancies.

Validation Using DATA Step Logic

For simple binary outcomes, independent flags can also be summarized.

data qc;
    set analysis;

    event_n = (response = "Yes");
run;

proc means data=qc sum n;
    var event_n;
run;

The resulting event count should agree with the corresponding PROC FREQ frequency when the same population and unit of analysis are used.

Testing the Test

Validation should not stop at frequencies.

If a table reports a chi-square p-value, verify that:

  • The same observations are included.
  • The same categories are used.
  • The same missing-data rules apply.
  • The same test is being reported.
  • The same continuity correction convention is used where relevant.

Fisher's Exact Test in Validation

For small tables, the exact p-value should be independently checked whenever it is a critical reported result.

proc freq data=analysis;
    tables treatment*response / fisher;
run;

The validation program should use an independently constructed input or independent programming logic rather than simply copying the production code.

Clinical-Trial Table Specification

Before writing production code, define the table specification.

Specification Example
Population Safety analysis set
Unit Patient
Treatment variable TRT01P
Outcome AE_FLAG
Categories Yes / No
Denominator Patients in each treatment arm
Primary test Fisher's exact test
Effect measure Odds ratio
Confidence interval 95%

This specification should be established before the statistical programming begins.

Production PROC FREQ Template

proc freq data=analysis noprint;

    where analysis_flag = "Y";

    tables
        treatment*outcome
        / chisq
          fisher
          or
          relrisk;

    ods output
        CrossTabFreqs = crosstab
        ChiSq         = chisq
        OddsRatios    = oddsratio;

run;

The exact ODS objects and requested statistics should be confirmed against the specific SAS release and procedure statement being used.

Separating Analysis From Presentation

A robust clinical-programming architecture often separates the statistical calculation from the final display.

A
Analysis dataset: validated subject-level or assessment-level data.
B
PROC FREQ: categorical counts and statistical results.
C
QC dataset: independent verification of key results.
D
Reporting dataset: shell-ready counts, percentages, and statistics.
E
Output: final table, listing, or statistical report.

When PROC FREQ Is the Right Tool

PROC FREQ is particularly well suited to:

  • Descriptive categorical summaries
  • Contingency tables
  • Unadjusted association tests
  • Small 2 × 2 tables
  • Exact tests
  • Simple odds ratios
  • Simple relative risks
  • Trend tests
  • Matched categorical data
  • Stratified categorical analyses

When PROC FREQ Is Not Enough

More complex questions may require other methods.

Examples include:

  • Adjusted binary regression
  • Repeated categorical outcomes
  • Longitudinal categorical models
  • Generalized estimating equations
  • Mixed-effects categorical models
  • Time-to-event analyses
  • Competing-risk analyses
  • Complex survey designs

PROC FREQ should therefore be viewed as a powerful categorical-analysis procedure, not a universal solution for every categorical endpoint.

PROC FREQ and Reproducibility

One advantage of SAS programming is that the entire analysis can be reproduced from code.

A good PROC FREQ program should make explicit:

  • Which dataset was analyzed
  • Which observations were included
  • Which categorical variables were analyzed
  • Which statistical options were requested
  • Which output datasets were created
  • How results were transformed

Good Programming Style

A concise program is not necessarily a good program.

For regulated clinical programming, readability and traceability are often more important than minimizing the number of lines of code.

Prefer:

proc freq data=analysis;

    where saffl = "Y";

    tables
        trt01p*response
        / chisq
          fisher;

run;

over a highly compressed statement that hides the analysis intent.

Recommended PROC FREQ Checklist

1
Confirm the analysis population.
2
Confirm the unit of analysis.
3
Confirm category definitions.
4
Confirm missing-value handling.
5
Confirm denominator definitions.
6
Confirm table orientation.
7
Select the appropriate statistical test.
8
Request effect estimates where appropriate.
9
Capture ODS output when needed.
10
Independently validate the final results.

Practical Decision Framework

Situation Starting Point
One categorical variable PROC FREQ TABLES variable
Two independent categorical variables PROC FREQ TABLES var1*var2
Large-sample association test CHISQ
Small/sparse 2 × 2 table FISHER
Odds ratio required OR
Relative risk required RELRISK
Ordered categories TREND where appropriate
Paired binary outcomes AGREE / McNemar framework
Stratified categorical association CMH
Adjusted multivariable analysis Consider PROC LOGISTIC or another modeling procedure

Final Example: A Production-Oriented Program

/*-----------------------------------------------------------
  Example: Treatment by Response
  Population: Full Analysis Set
  Unit: One record per patient
-----------------------------------------------------------*/

proc freq
    data=adrs
    order=formatted
    noprint;

    where fasfl = "Y";

    tables
        trt01p*response
        / chisq
          fisher
          or
          relrisk;

    ods output
        CrossTabFreqs = qc_crosstab
        ChiSq         = qc_chisq
        OddsRatios    = qc_or;

run;

/* Independent frequency check */

proc freq
    data=adrs
    order=formatted;

    where fasfl = "Y";

    tables trt01p*response / missing;

run;

This illustrates an important production principle: the statistical procedure and the validation procedure should have clearly identifiable purposes.

Interpreting a PROC FREQ Output

When reviewing PROC FREQ results, work from the bottom up:

1
Counts: Do the frequencies match the expected patients?
2
Denominators: Are the percentages calculated from the correct population?
3
Category ordering: Are the levels displayed correctly?
4
Statistical test: Is the selected test appropriate?
5
Effect estimate: Is the direction/reference group correct?
6
Clinical interpretation: Does the result answer the intended question?

The Most Important PROC FREQ Concept

The most important concept is that PROC FREQ does exactly what you ask it to do on the data you provide.

It does not know:

  • Which patients should be included
  • Whether duplicate records represent repeated events
  • Which denominator the clinical endpoint requires
  • Which treatment should be the reference
  • Whether categories are nominal or ordinal
  • Whether a test is prespecified

Those decisions belong to the statistical analysis specification and the programmer.

Think of PROC FREQ as the categorical-data engine. The procedure provides reliable frequency calculations, contingency tables, statistical tests, and association measures. The programmer's responsibility is to ensure that the input data, population, denominator, category definitions, and requested statistical method correctly represent the clinical question.

Summary

SAS PROC FREQ is one of the most useful procedures for categorical data analysis.

At the simplest level, it produces counts and percentages:

proc freq data=analysis;
    tables sex;
run;

It can cross-classify categorical variables:

proc freq data=analysis;
    tables treatment*response;
run;

It can evaluate association:

proc freq data=analysis;
    tables treatment*response / chisq;
run;

It can handle sparse 2 × 2 tables:

proc freq data=analysis;
    tables treatment*response / fisher;
run;

It can calculate association measures:

proc freq data=analysis;
    tables treatment*response / or relrisk;
run;

It can support stratified analysis:

proc freq data=analysis;
    tables stratum*treatment*response / cmh;
run;

And it can support paired categorical analyses:

proc freq data=analysis;
    tables before*after / agree;
run;

In clinical-trial programming, its greatest value is not simply producing a frequency table. It provides a reproducible framework for converting analysis-ready categorical data into validated descriptive and inferential results.

Bottom line: PROC FREQ is the fundamental SAS procedure for categorical data analysis. Mastering its TABLES statement, percentages, contingency tables, CHISQ, FISHER, OR, RELRISK, CMH, TREND, AGREE, MISSING, SPARSE, WEIGHT, OUT=, and ODS OUTPUT capabilities provides a strong foundation for clinical-trial demographic, safety, efficacy, and categorical endpoint programming. The key to high-quality results is not merely knowing the syntax—it is ensuring that the analysis population, unit of analysis, denominator, category ordering, reference group, missing-data rules, and statistical method all match the prespecified analysis.

References

SAS Institute Inc. SAS/STAT User's Guide: The FREQ Procedure. SAS Institute Inc., Cary, NC.

Agresti, A. Categorical Data Analysis. 3rd ed. Wiley, 2013.

Agresti, A. An Introduction to Categorical Data Analysis. 3rd ed. Wiley, 2018.

Fisher, R.A. On the Interpretation of χ² from Contingency Tables, and the Calculation of P. Journal of the Royal Statistical Society, 1922.

McNemar, Q. Note on the Sampling Error of the Difference Between Correlated Proportions or Percentages. Psychometrika, 1947.

Cochran, W.G. Some Methods for Strengthening the Common χ² Tests. Biometrics, 1954.

Mantel, N. and Haenszel, W. Statistical Aspects of the Analysis of Data From Retrospective Studies of Disease. Journal of the National Cancer Institute, 1959.

Clinical Trials

See Cochran-Mantel-Haenszel test in real clinical trials

See the method applied to published trial results, with the estimates, confidence intervals and interpretation explained.

ORIGIN
Complete statistical analysis of the ORIGIN phase 3 trial, including its factorial design, cardiovascular and diabetes endpoints, Cox and log-rank methods, odds…
Phase 3 · n = 12,537
ACTG A5279
Independent statistical analysis of ACTG A5279 (NCT01404312), a randomized phase 3 trial comparing a rifapentine-plus-isoniazid regimen with an isoniazid regimen for tuberculosis…
Phase 3 · n = 3,000
TBTC Study 31
Complete statistical analysis of TBTC Study 31, a randomized phase 3 trial of rifapentine-containing tuberculosis treatment-shortening regimens, including non-inferiority methodology, disease-free survival,…
Phase 3 · n = 2,516
PREVAIL
Independent statistical analysis of the phase 3 PREVAIL trial of enzalutamide versus placebo in chemotherapy-naive patients with progressive metastatic prostate cancer, including…
Phase 3 · n = 1,717
PROSPER
Independent statistical analysis of the phase 3 PROSPER trial of enzalutamide versus placebo in nonmetastatic castration-resistant prostate cancer, including metastasis-free survival, secondary…
Phase 3 · n = 1,401
GRIPHON
Independent statistical analysis of the GRIPHON phase 3 trial of selexipag in pulmonary arterial hypertension, covering its randomized time-to-event endpoint, secondary analyses,…
Phase 3 · n = 1,156
See all 89 trials using Cochran-Mantel-Haenszel test →