sas2r helps statistical programmers and biostatisticians translate SAS programs for SDTM, ADaM, tables, listings and figures into R. It combines rule-based translation with separate AI translation, review and repair roles, then runs the R programs and reports the checks that passed or need attention.
The package does not require SAS to translate or run generated R. Comparing results with SAS requires matching reference outputs. AI review does not replace independent programming or your organization’s statistical QC procedures.
Release status: 0.5.8 is being prepared for CRAN
resubmission; it has not yet been accepted. main is the
release branch. Ongoing development and the complete test suite live on
develop.
See the release
workflow for validation and branch roles.
Quickstart · AI models · Parallel translation · FAQ · Guides
flowchart TD
source["Preflight: check sources, inputs and dependencies"] --> coordinator["Coordinator: assign programs when dependencies are ready"]
coordinator --> translator
subgraph program["Each program or called macro"]
translator["AI translator: write R from SAS"] --> checks["Automated code checks"]
checks --> reviewer["AI reviewer: independently check R against SAS"]
reviewer --> execution["Run R and check execution"]
checks -->|"Code problem"| fixer["AI fixer: repair source-supported problems"]
reviewer -->|"Review finding"| fixer
execution -->|"Execution problem"| fixer
fixer -->|"Recheck revised code"| checks
end
execution -->|"Initial processing finished"| final_review["Complete reviews for the selected code"]
final_review --> study["Run the full study pipeline and check outputs / supplied references"]
study -->|"Source-supported repair; allowance remains"| fixer
study --> report["Save R, outputs, findings and run status"]
The translator, reviewer and fixer are separate AI roles. The coordinator is package logic that assigns work and controls program order, repair limits and spending. Repairs return through the checks; unresolved problems are reported rather than treated as successful translations. A reference difference prompts investigation against the SAS source before repair.
Dependencies come from the SAS sources. For example, a summary program waits for the program that creates its analysis dataset. Independent translation and review can overlap; execution tests, repairs and the full study run happen one at a time. See the workflow guide for review timing and how earlier passing results are protected.
AI translation requires ellmer 0.4.2 or newer. We recommend ellmer 0.5.0 for new installations; both versions pass our offline compatibility tests. Deterministic workflows can run without ellmer.
# install.packages("remotes")
install.packages("ellmer")
remotes::install_github("songyue-chen/sas2r")For unreleased development changes, install
songyue-chen/sas2r@develop instead.
Installed examples are available without a GitHub checkout. Locate
them with system.file("examples", package = "sas2r"); its
README explains how to copy the migration demo into a writable directory
and prepare its synthetic input. The annotated YAML is a configuration
reference to adapt to your own study.
_sas2r.ymlCreate one small file, _sas2r.yml, in your project
directory. It says where your data lives, which outputs matter, and
which AI model to use. The folder of SAS programs to translate is not
part of it: you pass that folder, or a single .sas file, as
the path argument in steps 3 and 4, with
recursive = TRUE to include subfolders. Called macro
folders are named under macros.
project: my_clinical_study
libraries: # your SAS librefs and where their data files live
sdtm:
path: data/sdtm
engine: xpt # how to read members: xpt, sas7bdat, or rds
adam:
path: data/adam
engine: xpt
write: rds # how translated programs save results: rds or xpt
outputs: # what the migration must produce
datasets:
- adam.adsl
- adam.adae
references: # optional: gold-standard SAS outputs to compare against
adam.adsl: data/reference/adsl.xpt
adam.adae: data/reference/adae.xpt
llm: # a recommended Flash starting profile
provider: deepseek
auth_mode: api_key
model: deepseek-flash
max_output_tokens: 393216
capabilities:
structured_output: fallback
tool_calling: native
reasoning_effort: unsupported # connector cannot set effort; server thinking stays on
timeout_seconds: 1800
max_tries: 1Make DEEPSEEK_API_KEY available to your R session before
translation. For Gemini Flash or another provider, replace the
llm: block with its complete provider
profile.
Every budget: limit is commented out by default: observe
mode records usage without stopping requests, so translation finishes
without interruption. Uncomment a limit only if you want the run to stop
when it is reached. The code style keys dialect and
allowlist are described in code
style and packages.
library(sas2r)
migration_dir <- tempfile("sas2r-migration-")
check <- sas_preflight(
"programs/", # the folder of SAS programs to translate
config = "_sas2r.yml", out_dir = migration_dir, diagnose = "off"
)
print(check)
check$inputs # availability and producer-order status
check$budget # effective limits; preflight makes no model callsWith diagnose = "off", preflight checks the source setup
without model calls or reading dataset contents. The default
diagnose = "auto" reports missing LLM configuration or uses
one bounded request to suggest corrections for findings. Advice never
clears static findings. To specify a shared-session program sequence,
set migration.execution_order; see execution
order. Review the findings: missing resources usually allow
translation with warnings. See the preflight
guide for library paths, output requirements and QC profiles.
These examples write under the R session temporary directory. Supply
your own migration_dir and export_dir when you
want to retain the files.
library(sas2r)
result <- sas_translate(
path = check$project, # reuse the unchanged source scan
out_dir = migration_dir,
max_parallel_translations = 1, # programs or macros translated at once; see below
execute = TRUE, # actually run the translated programs
max_program_repair_rounds = 1, # immediate repair attempts per program
max_bundle_repairs_per_component = 2, # bundle repairs per component
max_bundle_repair_rounds = NULL, # optional overall cap on bundle fixer calls
agent_evidence = "code_only" # what repair evidence the AI may see
# Optional limits, commented out by default. Uncomment to enforce them; the
# run stops admitting requests when a limit is reached. Requests and tool calls per program
# vary by model and study: size ceilings from a completed run's usage summary.
# Catalog costs are estimates; an in-flight request may exceed the threshold.
# , budget_usd = 10, budget_mode = "soft"
# , usage_limits = list(max_calls = 700, max_tool_calls = 700, max_wall_time = 14400)
)
# What you get back
result$status # one of the four statuses below
result$bundle_dir # all available selected programs and macros, including failed code
result$outputs_dir # saved deliverables: datasets/<library>/ and tlf/ (NULL if none)
result$report_path # a readable report of everything that happened
# Read a translated program
cat(sas_code(result, 1))
# Export the selected code, generated outputs, reports, and run guide
export_dir <- tempfile("sas2r-export-")
sas_write(result, export_dir)Every limit is commented out by default: a run records usage in
observe mode and finishes without interruption. Uncomment
budget_usd or usage_limits to enforce a limit,
knowing that the run stops when it is reached. How many requests and
tool calls a program or macro needs varies by model, reasoning setting
and study, so size ceilings from the usage summary of a completed run
rather than from a rule of thumb. As one labelled example, a
17-component study with GPT-5.6 Luna at high reasoning used about 340
requests, 390 tool executions and an hour with four workers.
usage_limits = list(max_calls = 0) remains the way to
forbid provider calls entirely.
Open
file.path(result$out_dir, result$run_id, "START_HERE.html")
first. It links the code, outputs, diagnostics and comparison reports,
and states what ran, what was skipped, and why a run was blocked or
failed. Saved code can be incomplete or unvalidated; file existence
alone does not establish success.
| Status | Meaning |
|---|---|
blocked |
A required part failed: for example, execution, an output check or a dependency needed to continue. |
needs_review |
Required review or other evidence is incomplete. Some programs may have run successfully. |
migration_ready |
Required checks passed, but no required output has a passing SAS reference comparison. |
validated |
Required checks passed and at least one required output matched its reference within the configured tolerances. Other outputs may have no reference. |
This is not proof of SAS equivalence. Check which outputs were compared and which remain unverified. Matching a reference does not establish that the original SAS or the analysis specification is correct.
For a blocked run, inspect the named problem before changing code. A reference mismatch alone does not justify a repair: first confirm that both results use the same source programs, inputs and analysis definitions.
Use resume = TRUE to reuse compatible saved work; repair
counts and recorded spending are retained.
options(sas2r.progress = FALSE) hides progress updates.
Wrap the call in suppressMessages() to hide all routine
messages, including progress, the final summary and report locations.
Returned results, saved reports and execution error logs remain
available. Source parsing caches use session-temporary storage and do
not modify input projects. See logs,
repairs and resume.
The default is one program or called macro at a time. To allow up to
two, pass the argument to sas_translate():
result <- sas_translate(path = check$project, out_dir = migration_dir,
max_parallel_translations = 2)A worker is an R process handling an assigned translation or review task. The limit covers whole workflows, including their review and repair steps; it does not create that many of each AI role. All work shares the same run budgets and quality checks. The count is not a CPU count: remote AI requests spend time waiting, but memory and provider quotas still limit useful concurrency.
Parallel mode requires ellmer 0.5.0 or newer and
llm.max_tries: 1. If both
max_parallel_translations and max_tries exceed
1, the run stops with instructions to change one. Older ellmer uses one
workflow and reports why. Missing inputs or source dependencies produce
warnings while available code keeps translating. A failed component does
not stop other programs. Reports distinguish saved code from execution
that could not run. Use execute = FALSE for code only;
omitting SAS references still permits execution. Unusable configuration
and pipeline omissions remain errors.
Start with one, then compare two on representative programs. Equal live-model quality and a particular speedup are not established by the offline tests. The speed FAQ explains why more workers do not give proportional time savings. See parallel details and provider settings.
Start with Gemini Flash or DeepSeek Flash. They offer a practical balance of speed, cost and coding capability, making them our recommended first choice for translation trials. Confirm accuracy on representative study programs with the same code review, execution and output checks you would use with a frontier model. Use a frontier model when the first choice leaves unresolved translation problems. See the current Gemini model documentation and DeepSeek model and pricing information.
For OpenAI, start with GPT-5.6 Luna, which is designed for cost-sensitive, high-volume work. Apply the same study-level accuracy checks. See the official Luna model documentation.
These are suggested profiles, not automatic package defaults. Model availability and settings were checked on September 18, 2026. Use the documented maximum output allowance when your endpoint accepts it and the request fits the context window, to give reasoning and complete R code room to finish.
| Starting choice | Model | Reasoning | Output token ceiling | Request timeout |
|---|---|---|---|---|
| Gemini Flash | gemini-3.8-flash |
high |
65536 | 900 seconds |
| DeepSeek Flash | deepseek-flash |
Keep server thinking default | 393216 | 1800 seconds |
| OpenAI Luna | gpt-5.6-luna |
high |
128000 | 900 seconds |
| Claude Sonnet 5 | claude-sonnet-5 |
high with adaptive thinking |
128000 (see below) | 900 seconds |
llm.max_output_tokens and
llm.timeout_seconds to the values above. Unused
output allowance is not billed. A higher ceiling permits more
generation; actual usage, including billable reasoning, determines token
cost. Timeouts are starting values per request, not a promise that the
full output ceiling can be generated in that time.max_tries: 1 and
capabilities.tool_calling: native. Set
capabilities.structured_output: fallback for Gemini,
DeepSeek and Anthropic, or native for OpenAI. Leave
temperature and top_p unset.reasoning_effort and use
capabilities.reasoning_effort: unsupported, as in the
quickstart. This retains server-default thinking; it does not switch
reasoning off.max_parallel_translations = 1 in
sas_translate(), then try 2 with the same
programs and checks. Increase further only when provider quotas and
available memory permit.budget.max_output_tokens, keep it at least
as large as llm.max_output_tokens. Strict dollar budgets
reserve worst-case costs before requests; larger ceilings may need more
budget, especially with parallel work.Claude qualification: Sonnet 5 replaces the older Sonnet 4.6 recommendation. Its 128,000-token maximum is conditional guidance for this package: sas2r currently uses non-streaming requests, and very long responses need transport validation. Anthropic recommends streaming or batch processing for long requests; increasing the token ceiling or timeout does not enable either. See the Claude profile before using its full allowance.
Use the provider
guide for complete connection profiles, model availability checks,
output allowances, timeouts and tuning guidance. The full configuration template
lists the available study and model settings. The program folder itself
is the path argument of sas_preflight() and
sas_translate(), not a configuration key. Called macro
folders are configured through macros.search_path; see macro
setup and limitations.
Dataset processing and comparisons run on your infrastructure. AI
requests contain SAS/R code, comments, paths and structured technical
errors. Dataset row numbers, subject identifiers, key values, cell
values, previews and raw runtime messages stay local under both
code_only and the legacy bounded policy.
Source code and comments are not automatically de-identified.
Focused mismatch investigation can receive relative sizes, column/type differences and categorical patterns. Fixers and candidate acceptance reviews remain reference-blind: SAS is the source of truth. Human report explanations never drive code repairs. Use an organization-approved endpoint and confirm its data residency and retention terms. Read the full privacy guidance before using confidential study material; generated R is not a filesystem sandbox.
Provide the SAS programs, called macros and include files, the input
data locations, and the list of required outputs. Add matching SAS
reference outputs if available. Use _sas2r.yml to describe
the study, then run sas_preflight(..., diagnose = "off") to
find setup problems before AI calls. See the quickstart.
SAS is not required to translate code or run the generated R. Without
reference outputs, the package can still review code, test execution and
apply configured output checks, but it cannot compare results with SAS.
A migration_ready result is different from
validated; see the four
statuses. For comparisons, use SAS outputs produced from the same
programs, inputs, macros and parameters.
No. It reviews translated R against the SAS source; it does not independently implement the analysis from the protocol, SAP or programming specifications. Your team’s independent review and QC procedures still apply. The saved code, findings and comparison reports help reviewers inspect the work.
validated mean every dataset and analysis is correct?No. It means the configured reference comparisons passed within the declared tolerances, alongside the package’s required checks. An output without a reference has not received that comparison. Read the report’s coverage and unresolved findings; the status does not establish that the original SAS, the analysis specification or every statistical result is correct.
The translation follows the supplied SAS. It does not independently decide whether a population definition, baseline rule, imputation or analysis method is appropriate for the study. Review those decisions against the SAP and dataset specifications, and perform your usual CDISC checks separately. A problem in the original SAS can remain a problem in a faithful translation.
SAS graphics can be translated into native R plotting and PDF output; an R copy of the SAS graphics engine is not required. The translation must preserve the requested statistics, labels, summary tables and pagination. Fonts and spacing may need refinement.
Dataset comparisons check the configured reference data and tolerances. A readable table or figure file alone does not establish that its content is correct. Review the analysis populations, denominators, statistics, rounding, labels and presentation, as applicable. See the output-evidence guide for comparison coverage.
A program waits for its required upstream programs to finish initial translation and checks. For example, a summary program waits for the derivation it reads. Unrelated programs or macros can use the other available workers. The full study run still follows dependency order. Parallel mode retains the existing checks, but unchanged live-model quality and speedup have not yet been established by a paired study run. See parallel translation.
No. Two workers do not guarantee 50% of the one-worker time, and four do not guarantee 25%. The setting allows up to that many workflows at once; it does not divide every step of a study evenly among them:
For illustration, suppose a one-worker run takes 60 minutes: 20 minutes must run one at a time and 40 minutes can be shared perfectly. Even with no extra overhead, two workers would take 40 minutes, and four 30 minutes. This is an example, not a measured sas2r benchmark. Compare elapsed time and output checks on the same representative study before increasing the setting. One observed point: a 17-component study took 79 minutes with four workers on sas2r 0.5.1 and 59 minutes on 0.5.2 with GPT-5.6 Luna at high reasoning; other models and studies differ.
Start with the console message and the saved
START_HERE.html report, when available. They identify the
reason, what ran and what still needs attention. Check input paths,
missing programs or macros, and the reported code error. For comparison
failures, first confirm that the SAS reference uses the same inputs and
specifications. Do not change R code merely to match a reference that
represents a different analysis. See reference
differences.
Use resume = TRUE to reuse compatible saved work.
Changed source code, inputs or other relevant settings can require fresh
translation or checks. Repair allowances and recorded spending are
retained; resume does not reset them. See resume
and limit provider calls.
Data processing runs locally. Agents cannot retrieve dataset records, comparison cells, previews or raw runtime logs. Source code, comments and project paths are still sent to the configured provider and may themselves contain confidential information. Approved aggregate comparison patterns go only to focused investigation and human-report diagnosis. Review the privacy details before using confidential study material.
Yes. Export with sas_write() and keep the complete
exported set of scripts and supporting files. Its guide lists required R
packages and input paths; run.R launches the programs in
order without sas2r or an AI key. Human edits and manual reruns need new
QC and do not automatically update the saved migration report.
| I want to… | Read |
|---|---|
| Set up libraries, required outputs and QC checks | Preflight and QC |
| Understand logs, repairs, resume, macros or exported files | Running migrations |
| Choose a provider and configure model settings | AI providers |
| Compare saved datasets and investigate differences | Output comparisons |
| Understand acceptance rules and recorded evidence | Migration evidence |
| Understand what can be sent to an AI provider | Privacy |
For regulated work, review migrated code and outputs under your organization’s SOPs. Automated translation does not replace required independent review or QC.
For quick browser-based code translation, see the separate sas2r.ai companion site. Its workflow differs from this R package.
Apache License 2.0. See LICENSE.md.