Care and Feeding of Your Biostats Team: Scaling best practices in a large hybrid SAS/R team
Watch on YouTubeVideo summary
The video introduces John Erlinger from Cleveland Clinic's Cardiovascular Outcomes Registries and Research (CORE) team, detailing their evolution from a small SAS-only group in 1997 to a large hybrid organization utilizing both SAS and R. Guided by the principle of seamless handoffs between analysts, the team has grown significantly, with nineteen staff members now handling hundreds of data requests annually that contribute to over one thousand peer-reviewed publications each year. As the scale increased, it became evident that discipline alone was insufficient to maintain efficiency; instead, the focus shifted toward creating a robust pipeline where every step is reliable and ready for transfer between team members, ensuring business continuity even when individuals are absent or leave the organization.
To achieve this scalability, the presentation outlines three critical areas where reproducibility often fails: data ingestion, model building environments, and code-result connectivity. The speaker advocates for replacing mental overhead with automation by implementing standardized file naming conventions that include creation dates and using checksums to verify data integrity against accidental overwrites. For software dependencies, which are notoriously volatile in shared server environments like SAS or R Studio, the team utilizes virtual environment tools such as `renv` to lock package versions and create snapshots that travel with each project, ensuring jobs can be picked up months later without breaking due to missing libraries or updated functions.
The final major challenge addressed is connecting code directly to results using unified reporting frameworks like Quarto, which eliminates the need for manual copy-pasting between scripts and output files found in traditional list reports. The team also standardizes their visual outputs by developing internal R packages that enforce consistent themes and statistical plotting methods across manuscripts and presentations, thereby reducing the time analysts spend on decoration rather than analysis. Ultimately, Erlinger argues that adopting these best practices is not merely about academic rigor but a practical necessity for production reliability; standards must be easier to follow than workarounds so that team members never feel stranded when collaborating or handing off critical research tasks.
Read the full video transcript
My name is John Erlinger. I work in the
cardiovascular outcomes registries and
research. We're part of the Heart,
Vascular, and Thoracic Institute at the
Cleveland Clinic.
CORE was started in 1997
with the arrival of our director, Dr.
Eugene Blackstone.
We started as a pure SAS shop with a
small team.
Guiding principle of CORE from day one
has been to have the ability to hand off
any study easily.
These hand-offs require a social
contract of practices. These are held
together by the discipline within the
team.
Our primary in-house method started with
Proc Hazard, a C program with tight SAS
bindings. Over time, R entered by the
methodological evolution.
Dr. Hemant Ishwaran developed random
forest SRC in R and CORE in 2007.
Random forest for survival, regression,
and classification.
I wrote the GG random forest package to
simplify plotting these forest outputs
using GG plot.
As we've grown, we found that discipline
alone doesn't scale easily.
We are concerned with the scale that
this social contract has to support. 19
staff driving 150 data requests
delivered directly to researchers for
their publications. We also directly
produce about 100 presentations and 50
manuscripts a year in collaborations.
All of this feeds into the 1,100 HTI
peer-reviewed publications produced last
year. Every inefficiency in our system
multiplies across that. This talk, I'll
be directly addressing what operating at
scale means.
These 19 staff are split across three
disciplines. Data engineers focus on
executing data requests and then getting
data from the medical systems into our
data warehouse, primarily using Python.
Stat programmers assemble analysis data
sets primarily using SAS.
Biostatisticians build an analysis
report using SAS and R. Our researchers
collaborator convert the analysis report
into manuscripts and presentations. Data
flows through the systems. People
transform it at each step. The
biostatistician sits at the core taking
the analysis data set and producing the
analysis report that the researchers can
use to construct the manuscript.
Since this is an R conference,
we can focus on the biostats component
of this pipeline.
The main deliverable of the team is the
analysis report. The analysis done in
SAS and R jobs are an implementation
detail. What our customer cares about is
the report that comes out of the other
side. Researchers bring the questions
formed as hypotheses. This drives the
process. What data is required to answer
these questions? Core determines the
statistical approaches in collaboration
with the researchers, refining the
questions, figures, and tables
communicate the results to the end
audience.
Scaling the team means making every step
of that pipeline reliable and handoff
ready.
We still maintain the goal of any
analyst can pick up any job. We call
this bus insurance. Can another analyst
step in and pick up wherever you are and
continue the work?
There are three areas where
reproducibility breaks down in practice.
We'll address each one of these areas
with a Here's the failure mode, and
here's what we're doing about it.
Make the safe path the easy path. We
currently do a lot of mental work to
compensate for gaps in each of these
areas, remembering which data set is
current, keeping track of which version
of a function ran, manually assembling
figures into a report. The goal isn't to
add process on top of that, it's to
replace that mental overhead with
automation. Using checksums instead of
your memory, package versions instead of
tribal knowledge, port or cut it instead
of copy and paste.
Long-term division is an audit process
that runs automatically, not a checklist
someone fills out, but a system that
tells you whether the analysis is
reproducible before you have to ask.
We're not there yet, but every piece we
put in place moves us closer.
Starting with data ingestion, making the
safe path the easy path.
We have a standard folder structure.
That isn't the problem. That works. We
But we've all seen these naming
strategies.
Data file naming might work.
We also use
the system the file system modified
dates implicitly, but those are fragile,
changing uncontrollable ways.
We want to standardize our data with a
simple automatic approach. Start by
making the file date part of that
standard.
Adding the date to the file name when
the file is created means it won't
change if the file is moved.
That doesn't solve the whole problem. We
still need to track what happens inside
the file. What if the file gets
overwritten? What if a data extract gets
updated? Naming and modified dates
weren't designed for that job.
The folder structure is already there
and it works. Jobs go in the analysis
folder, reading data sets and writing
models and results to an estimates
folder, and then generating graphs. The
documents folder contains our
deliverables. Inside the data sets
folder, file names can now include the
date they were written programmatically.
A data set manifest can be created with
a checksum registration before we first
use the data file. This must be separate
from data set creation and done before
we build the individual analysis jobs.
We are building a data audit trail
checked inside the analysis jobs.
Importantly, this can be done regardless
of the analysis platform.
Checksum is stored in the manifest at
registration. This is the deliberately
separate from file creation. If the file
is overwritten accidentally, say a data
extract is rerun without a date change,
or the data corrected in place, the old
checksum stays in the manifest and the
job should fail till someone consciously
reregisters or recreates a new data set.
Social contract now is never overwrite a
dated data set.
This makes rebuilds fail loudly by
default. The date stamped file name
tells you when the extract was pulled.
The checksum tells you whether the file
you're reading now is the same one that
was registered. It doesn't tell you if a
new data set file exists. It only
indicates that the registered one hasn't
changed.
Model building, making the safe path the
easy path.
In model building, we have a few
problems to address. First, regardless
of the platform, SAS and R versions are
not locked into the study.
We work on a shared disk with our studio
server and a shared SAS instance.
Neither platform captures the
environment well.
SAS's shared server setup makes this
invisible when you run a job. R studio
can help with projects if we use them.
Global package library in R makes using
package versions fragile over time.
Failure modes look different, but the
root cause is the same. No one picked
the envi- pinned the environment to that
study.
Can you pick up a job 6 months from now?
Will it still run?
The key here is we want the handoff.
Python has virtual environments. In R,
we can use the RENV package to track the
environment.
On day one, we initialize RENV and then
take a snapshot of the package library
and R version we are currently using.
This makes the jobs transferable, not
just reproducible on your own machine.
The lock file travels with the project.
The library itself gets rebuilt locally
when we create the
invoke the RENV restore command.
We update packages intentionally with
RENV install whenever we need the new
versions, and then create a new snapshot
so that those are also stored with the
study.
SAS has no equivalent that I can find.
It is less volatile than R, but the
environments are still an open problem.
Our strategy for SAS and R shared
functions surface two different failure
modes, both equally bad.
We use a SAS standard centralized macro
library, but we don't control when
chances changes land. Every study gets
every update whether you're ready or
not.
We also use a master template folder.
This copies the current version of R and
SAS shared functions into the study at
creation.
This isolates studies from each other,
but also from improvements. Fixes
typically stay local to the study until
they're copied into the master folder,
if they're copied into the master
folder. And then these functions often
diverge over time because we rarely
propagate changes back from the master
folder into an existing study.
Neither platform has a deliberate
version release process.
Version packages are better than
scattered copies, but the governance
problems shift over time rather than
just disappear. Combined with RENV, the
version choices become explicit and
deliberate, which is exactly the point.
There is still a maintenance issue.
Anyone can currently edit the map to SAS
macro library. The package development
and
takes a different skill set. There are
new disciplines around release
management and communication. Still,
controlled versioning helps maintain
stability of a study over time.
The next standard problem is the code
and results are not connected. This
again transcends the platform. SAS, R,
and even Python all have interactive
execution modes.
In SAS, we solve this by mandating that
you have to run the whole job before
leaving it. Does everyone do that every
night? Maybe.
Probably not.
In SAS, the list file is the output.
Python has Jupyter notebooks.
R scripts have no explicit equivalent,
so we turn to Quarto.
Quarto can be seen as being analogous to
a SAS job and the list files. Single
unified report that includes R code and
the output. The report has the advantage
of including real figures in line and
renders to a deliverable report in HTML,
PDF, or even Word.
The pitch to a SAS analyst isn't just
learn this new tool. I tried that. It
didn't work.
It's this gets what you already have in
a list file, but with real figures in
line and a structure everyone agrees on.
We're working on a four-section template
structure as a standard. Short enough
chunks to be understandable, consistent
enough to navigate, flexible enough to
cover every study type.
We have data ingestion report that
passes clean data to the methods for
model building.
The model is then passed to the
explainability and the results reports
for communicating with researchers.
Longer term, we're working on a book
down approach to possibly replace the
manually assembled analysis report
deliverable that we rely on.
We still have an underlying copy-paste
problem. Both SAS jobs and R scripts are
copied from the central templates
folder, meaning in-flight studies don't
implicitly get the updates. R 2 and R
packages are the foundation we're
building first before tackling that
propagation problem.
Figures and tables, making the safe path
the easy path.
We spent an extraordinary amount of time
and effort to make SAS graphics behave
with our SAS
plot.sas macro. We bend SAS graphics
into publication quality for graph for
manuscripts, but we never solved the
presentation problem.
The methods we used to use we used to
use broke over time, so every
presentation now requires a lot of
manual work.
ggplot is more flexible, but customizing
the output for manuscripts and
presentations still requires a bit of
effort. Can we capture that effort and
make our figures consistent?
Our internal HPTI plot R package
addresses this. Capturing the effort in
a package and enforcing shared standards
through an S3 pattern.
The S3 pattern matters because it
separates concerns cleanly. The
transform functions do the statistical
work and return typed objects. The plot
method knows how to visualize each of
the typed objects and returns a ggplot
object.
The analyst's job is then decoration,
choosing a scale, adding annotations,
not rebuilding the figure from scratch.
We provide different themes to target
different
end targets.
One for manuscripts, a couple for
presentations if you're using a dark
background or a light background.
Formatting is all controlled within the
included themes.
The package examples end up being the
documentation and the quality standard.
If your figure matches the example, it's
correct. This is what we will cover
and improve consistency in how we
deliver publication quality graphics in
R.
GGRandomForest was a proof of concept
born from that internal need, built as a
package, and eventually adapted
externally in other organizations. It
was removed from CRAN because of a
dependency drift, not a design failure.
The pattern still holds. I hope to get
it back on CRAN soon, but there's still
a bit of work to make to bridge the gap.
HVTIR plotR applies the same model to a
core specific plot types. These packages
are always in development. Science keeps
moving, and the package has have to keep
up. That's a governance problem as much
as a technical one.
This is the type of output that
motivated GGRandomForest. Random Forest
SRC produces individual predicted
survival curves for every patient in the
cohort. What you're seeing here is full
distributional picture of survival
across two groups, not just the mean.
GGRandomForest turned that forest object
into this figure with very few function
calls. Handle The package handles the
extraction and the scaling. And ggplot
scaffolding, the analyst then controls
labels and themes.
We're not done.
I've used R table packages when
communicating with collaborators, but we
need to tame the GT and GT summary
outputs to align with HVTI publication
standards. We have SAS table macros, so
the framework's there. HVTI R tables
package is on a long to-do list.
This table is the honest answer to where
we currently are. Folder structure and
naming conventions are long-standing and
solid. That's the foundation everything
else builds on. R and V is the easy win
that we're rolling out now. Methodology
and graphics packages are in use, but
actively changing. I'm also actively
working on in development items. And the
aspirational items are real and planned,
but not yet standard practice.
This is a real production environment
with real constraints, not a clean
academic case study.
To wrap up, I've been working on these
pain points in core for a long time. The
reproducibility framing named failed.
Reproducibility framing failed. It reads
as an academic virtue.
Something you do for reviewers when
asked.
The production reliability framing lands
differently with the team. It's about
whether someone else can pick up your
job on Monday morning when you're out
sick. Same tools, same practices, but
the reason to adopt them shifts from
good science hygiene to your teammates
won't be stranded. The second framing
can help with buying.
Make the safe path the easy path means
the standards have to be lower friction
than the workaround, or people will
always take the workaround.
Being transparent, it still takes work
for people to change their ways. Even if
the path is hypothetically easier,
rollout is still a learning experience,
and it's being done under production
pressure.
I'm hoping that this talk was
informative and might start some
conversations. I want to know, what's
your experience? And can we all benefit
from the knowledge as a community?
Tell me, please, what have you done?
My slides and other resources are all
available on GitHub. Additionally, I'm
hiring, so please reach out. Thank you.