Tutorials › Biostatistics › Version Control with Git for Biostatisticians

Programming & Reproducibility

Version Control with Git for Biostatisticians

A practical guide to using Git to manage statistical programs, R projects, TLFs, analysis specifications, revisions, code review, reproducibility, collaboration, and controlled clinical-trial programming workflows.

Intermediate 24 min read

What You'll Learn

  • Why version control matters in clinical biostatistics
  • Git repositories, commits, branches, merges, and tags
  • How to structure a Git repository for clinical-trial work
  • How Git integrates with R and RStudio
  • How branching and code review improve TLF development
  • How to use tags, release points, and reproducible R environments
  • How to handle merge conflicts and common Git mistakes
  • What Git does — and does not — provide for regulated work

Introduction

Clinical biostatistics is increasingly software-driven.

A modern analysis may involve R programs, statistical analysis datasets, ADaM derivations, tables, listings, figures, shells, analysis specifications, simulation programs, validation scripts, package dependencies, and automated reporting pipelines.

All of these artifacts change over time.

A statistician may revise a model specification. A programmer may correct a derivation. A table may need a formatting change. A reviewer may request an additional sensitivity analysis. A dataset may be refreshed. A sponsor may issue a revised SAP.

Without version control, these changes can become difficult to reconstruct.

Files named:

analysis_final.R
analysis_final2.R
analysis_final_revised.R
analysis_final_revised2.R
analysis_final_FINAL.R
analysis_final_FINAL_really_final.R

are a familiar symptom of a deeper problem.

Key idea: Version control replaces informal file-copying with a structured history of changes. Instead of asking which file is "the latest," a team can identify specific revisions, understand what changed, determine who made the change, review proposed changes, and return to an earlier version when necessary.

What Is Version Control?

Version control is a system for recording changes to files over time.

A version-control system allows users to answer questions such as:

  • What changed?
  • When did it change?
  • Who made the change?
  • Why was the change made?
  • What did the code look like before the change?
  • Which version produced a particular result?
  • Can an earlier version be restored?

For biostatisticians, these questions are particularly important because statistical results are generated from code and analysis specifications.

A change to one line of R code can change an entire table.

A change to a denominator definition can change an adverse-event percentage.

A change to a model specification can change an estimated treatment effect.

Version control provides a structured mechanism for recording these changes.

What Is Git?

Git is a distributed version-control system.

Unlike a simple shared folder, Git stores a history of changes in a repository. Developers and analysts can work on their own local copies and synchronize changes with a shared remote repository.

The basic conceptual model is:

1
Working directory: files currently being edited.
2
Staging area: changes selected for the next commit.
3
Local repository: committed history stored on the user's computer.
4
Remote repository: shared repository used for collaboration and backup.

This distinction is fundamental.

Editing a file does not automatically create a Git version.

A change becomes part of the repository's history when it is committed.

The Four Git Concepts You Need First

Concept Meaning
Repository The Git-controlled project and its history
Commit A recorded snapshot of selected changes
Branch An independent line of development
Remote A Git repository hosted elsewhere for collaboration

If these four concepts are understood, most of the remaining Git workflow becomes much easier.

The Repository

A Git repository is the version-controlled project.

For example, a clinical-trial repository might contain:

study-analysis/
├── R/
├── data/
├── adam/
├── tlf/
├── validation/
├── outputs/
├── specifications/
├── renv/
├── .gitignore
├── README.md
└── study-analysis.Rproj

Git tracks selected files inside the repository and maintains a history of commits.

Important: A Git repository should not automatically be treated as a location for all study data. Large datasets, confidential documents, subject-level information, and regulated records require an appropriate storage and access strategy. Version control should be designed around the organization's data-security, privacy, validation, and records-management requirements.

What Is a Commit?

A commit is a recorded change in Git history.

For example:

git add R/tlf14_01.R
git commit -m "Correct treatment-group denominator in Table 14.1"

The commit records the selected change and associates it with metadata such as the author, timestamp, and commit identifier.

A useful commit message explains what changed.

Weak commit message Better commit message
changes Correct AE denominator for safety population
update Add baseline eGFR derivation
fix Correct censoring rule in PFS derivation
final Update Table 14.2 after SAP clarification

Why Commit Messages Matter in Biostatistics

Imagine that a reviewer asks:

"Why did the number of patients in this table change between the first and second draft?"

A meaningful Git history may make the answer straightforward.

For example:

a84d912  Create baseline demographics table
b71ce03  Correct treatment assignment for randomized population
c19f6a2  Exclude screen failures from safety denominator
d30a8bc  Update table formatting

The history provides a much clearer reconstruction than a folder containing multiple copies of similarly named files.

Git Commit History

Figure 1. A Simplified Git Commit History
Each node represents a committed state of the analysis code. Branches allow different development paths before changes are integrated.
Main branch
Feature branch
Commit

Hover over commits to see an example clinical-biostatistics change associated with that point in the history.

What Is a Branch?

A branch is an independent line of development.

Suppose the main analysis code is stable. A statistician wants to add a new subgroup analysis. Instead of modifying the main branch directly, the statistician can create a branch:

git switch -c subgroup-analysis

The new work can then be developed independently.

The main branch remains unchanged while the subgroup analysis is being developed.

Why Branches Are Useful for Biostatisticians

Branches are especially useful when multiple analysis changes are occurring simultaneously.

Branch Possible Purpose
main Stable reviewed analysis code
feature/subgroup-analysis Add a prespecified subgroup analysis
fix/pfs-censoring Correct an identified derivation issue
feature/new-tlf Develop a new table or figure
validation/review Perform independent validation work

Branch naming conventions should be agreed upon by the team.

A Typical Clinical-Trial Branch Workflow

Figure 2. Feature Branch Workflow
A common pattern for developing and reviewing an analysis change without directly modifying the stable branch.

This is a conceptual workflow. Organizations may use different branching, review, validation, and release procedures.

The Basic Git Workflow

A simple Git workflow is:

1
Pull: obtain current changes from the shared repository.
2
Edit: modify the R program, specification, or other tracked file.
3
Review: inspect the differences.
4
Stage: select the changes for the next commit.
5
Commit: record the change locally.
6
Push: publish the committed changes to the remote repository.
7
Review and merge: integrate the approved branch into the shared branch.

Initializing a Repository

For an existing project, a Git repository can be initialized with:

git init

Git creates the repository metadata needed to track the project.

For a new project cloned from a remote repository, the more common command is:

git clone <repository-url>

After cloning, the user has a local working copy of the repository.

Checking Repository Status

One of the most useful Git commands is:

git status

It reports information such as:

  • Which branch is currently active
  • Which files have changed
  • Which files are staged
  • Which files are untracked
  • Whether the local branch is ahead of or behind its remote counterpart

For a beginner, git status should become a routine command.

Inspecting Changes With Git Diff

Before committing a statistical-programming change, inspect the actual difference.

git diff

For example, a programmer may discover that a seemingly small change actually altered the analysis population.

The diff provides a direct comparison between versions.

Why Diff Review Matters in TLF Programming

Consider a table program containing:

analysis_pop <- adsl |>
  dplyr::filter(SAFFL == "Y")

A later revision changes this to:

analysis_pop <- adsl |>
  dplyr::filter(ITTFL == "Y")

That is not merely a coding change.

It changes the analysis population.

A Git diff makes this change explicit.

Biostatistical principle: A code diff should be reviewed for statistical meaning, not merely programming syntax. In clinical-trial work, a one-line code change can have substantial analytical consequences.

Staging Changes

Git separates editing from committing through a staging area.

For example:

git add R/tlf14_01.R

Only the selected changes are staged.

This allows a programmer to make several edits but commit them as separate logical changes.

For example:

git add R/derive_pfs.R
git commit -m "Correct PFS censoring derivation"

git add R/tlf14_01.R
git commit -m "Update PFS summary table"

This creates a more meaningful history than one large commit containing unrelated modifications.

Committing Changes

The basic command is:

git commit -m "Correct PFS censoring derivation"

A good commit should represent a coherent change.

Examples of useful commits include:

  • Add baseline characteristic table
  • Correct handling of partial dates
  • Update treatment-emergent adverse-event definition
  • Implement subgroup analysis by disease stage
  • Correct confidence interval formatting
  • Update Kaplan-Meier figure labels

How Large Should a Commit Be?

There is no universal line-count requirement.

The more useful question is:

$$ \text{Does this commit represent one coherent logical change?} $$

A commit containing 300 lines that implement one new analysis module may be perfectly reasonable.

A commit containing five unrelated statistical corrections may be difficult to understand later.

Push: Sharing the Work

A local commit is not automatically available to collaborators.

The changes are usually pushed to a remote repository:

git push

The remote repository can then serve as a collaboration point.

Common enterprise Git hosting solutions include platforms such as GitHub, GitLab, and Bitbucket, although organizations may use other internal systems.

Pulling Changes

Before starting work, users often synchronize their local branch with the remote repository.

git pull

This retrieves changes from the remote repository and integrates them into the current local branch according to the configured Git workflow.

Do not blindly pull during an important analysis operation. If local uncommitted changes exist, understand their state before integrating remote changes. A disciplined workflow reduces the risk of accidentally mixing unrelated analysis revisions.

Git and RStudio

Git can be used from the command line, but RStudio provides graphical integration that is particularly convenient for R users.

When Git is configured for an RStudio project, users can typically:

  • See changed files
  • Review diffs
  • Stage files
  • Commit changes
  • Pull changes
  • Push changes
  • Create or switch branches

This makes Git substantially more accessible to statisticians who do not regularly work from a terminal.

An RStudio Project and Git

A typical analysis project might contain:

study-analysis/
├── study-analysis.Rproj
├── R/
│   ├── adam_adsl.R
│   ├── adam_adlb.R
│   ├── tlf14_01.R
│   └── fig14_02.R
├── specifications/
│   ├── sap.pdf
│   └── tlf-specifications.xlsx
├── validation/
├── outputs/
├── README.md
├── .gitignore
└── renv/

The exact structure should be adapted to the organization's standards.

What Should Be Version Controlled?

Many text-based analytical artifacts are excellent candidates for Git.

Artifact Git Suitability
R programs Excellent
Python programs Excellent
Shell scripts Excellent
Configuration files Excellent
Analysis specifications Usually excellent when maintained in text-based formats
README documentation Excellent
Small text-based metadata Usually excellent
Large binary datasets Requires careful strategy
Subject-level confidential data Do not place in a repository without an approved data-management strategy
Generated output files Often better regenerated from source code

The Principle of Reproducible Generation

A useful clinical-programming architecture is:

1
Controlled source data are made available to the analysis environment.
2
Analysis programs transform the data according to documented specifications.
3
Results are generated from the controlled code and inputs.
4
Outputs can be regenerated when the appropriate inputs and code version are available.

This is generally more robust than treating the final PDF or Excel file as the primary analytical artifact.

Should Generated TLFs Be Committed?

This depends on the project and organizational requirements.

In many analytical repositories, source code is version controlled while generated tables and figures are treated as build artifacts.

For example:

R/tlf14_01.R
R/tlf14_02.R
R/fig14_01.R

may be tracked while:

outputs/tlf14_01.rtf
outputs/tlf14_02.rtf
outputs/fig14_01.pdf

are regenerated as needed.

However, there are situations where retaining generated artifacts is appropriate or required.

Best practice: Define whether generated outputs are source artifacts, build artifacts, or controlled records. Do not assume that Git alone determines the correct records-management strategy.

The .gitignore File

The .gitignore file specifies files Git should not normally track.

A clinical analysis project might contain:

.Rhistory
.Rproj.user/
.RData
.Ruserdata
renv/local/
*.log
*.tmp

For example:

# RStudio
.Rproj.user/

# R session files
.Rhistory
.RData
.Ruserdata

# Temporary files
*.tmp
*.log

# Local package library
renv/local/

The exact rules should reflect the project.

What Should Never Be Solved With .gitignore Alone?

A critical misconception is:

".gitignore protects confidential data."

It does not.

A .gitignore rule tells Git not to automatically track specified files. It is not a security boundary.

If confidential information is accidentally committed, adding the file to .gitignore later does not erase the information from existing Git history.

Sensitive-data incidents should be handled according to the organization's security and incident-response procedures.

Never Commit Secrets

Git repositories should not contain credentials such as:

  • Passwords
  • API keys
  • Private tokens
  • Cloud credentials
  • Database passwords
  • Authentication secrets

For example, this is a bad practice:

api_key <- "MY_SECRET_API_KEY"

Use approved secret-management mechanisms instead.

Clinical Data Require Special Care

Clinical-trial data can contain sensitive information.

A Git repository should therefore be designed around the principle:

$$ \text{Source code} \neq \text{clinical database} $$

The repository may contain the code used to analyze the data without containing the underlying subject-level data.

For example:

analysis/
├── R/
├── specifications/
├── validation/
├── README.md
└── .gitignore

controlled-data/
└── maintained outside Git repository

The precise architecture depends on the organization's infrastructure, security controls, and regulatory procedures.

Git Branching Strategy

A team should establish a branching strategy rather than creating branches arbitrarily.

A simple model might use:

Branch Purpose
main Stable, reviewed code
feature/* New analysis functionality
fix/* Corrections to existing code
validation/* Validation-specific work where appropriate

For example:

git switch main
git pull

git switch -c feature/efficacy-subgroup-analysis

Feature Branches for TLF Development

Suppose a programmer needs to add Table 14.3.

The programmer might create:

feature/tlf14-03

The branch could contain:

R/tlf14_03.R
R/derive_subgroup.R
tests/testthat/test_tlf14_03.R

The programmer can then run the analysis and validate the result without changing the stable branch.

Pull Requests and Merge Requests

A pull request, often called a merge request on some platforms, is a request to integrate changes from one branch into another.

For example:

feature/tlf14-03
        |
        |  Pull Request
        v
      main

A review can occur before the change is merged.

Why Code Review Matters

Code review is particularly valuable in clinical statistics because the reviewer can examine both:

  • Programming correctness
  • Statistical meaning

For example, a reviewer might notice:

filter(SAFFL == "Y")

where the specification requires:

filter(ANL01FL == "Y")

The programming syntax is valid in both cases.

The statistical meaning is different.

A Practical Code-Review Checklist

1
Does the code implement the analysis specification?
2
Is the analysis population correct?
3
Are baseline and post-baseline definitions correct?
4
Are missing values handled correctly?
5
Are denominators correct?
6
Are statistical methods implemented correctly?
7
Are labels and treatment groups correct?
8
Are tests or validation checks included where appropriate?
9
Is the code understandable and maintainable?

Merge

After review, a branch can be merged into the target branch.

Conceptually:

$$ \text{Feature Branch} \rightarrow \text{Review} \rightarrow \text{Approval} \rightarrow \text{Merge} $$

The result is that the changes become part of the target branch's history.

Merge Conflicts

A merge conflict occurs when Git cannot automatically reconcile competing changes.

For example, two programmers might modify the same line:

analysis_pop <- adsl |>
  filter(SAFFL == "Y")

One branch changes it to:

analysis_pop <- adsl |>
  filter(ITTFL == "Y")

while another changes it to:

analysis_pop <- adsl |>
  filter(ANL01FL == "Y")

Git cannot determine which statistical definition is correct.

It therefore reports a conflict.

What a Conflict Looks Like

<<<<<<< HEAD
analysis_pop <- adsl |>
  filter(ITTFL == "Y")
=======
analysis_pop <- adsl |>
  filter(ANL01FL == "Y")
>>>>>>> feature/subgroup-analysis

The programmer must resolve the conflict deliberately.

Important: A merge conflict in clinical programming is not merely a technical problem. The correct resolution may require consulting the SAP, analysis specification, statistical programmer, statistician, or other responsible reviewer.

Never Resolve a Statistical Conflict by Guessing

Suppose two branches disagree about the definition of an analysis population.

Do not simply select the version that makes Git happy.

Instead:

1
Identify the conflicting analysis definition.
2
Check the governing specification.
3
Consult the responsible statistician if necessary.
4
Resolve the code according to the approved analysis definition.
5
Re-run appropriate validation and review.

Rebase vs. Merge

Git provides multiple ways to integrate changes.

Two important concepts are merge and rebase.

A merge combines histories while preserving the branching structure.

A rebase replays commits onto another base and can produce a more linear history.

For example:

git switch feature/tlf14-03
git rebase main

Rebase can be useful, but it changes commit ancestry.

Clinical-workflow principle: Teams should establish clear rules for rebasing and history rewriting. Do not rewrite shared history casually, particularly for branches that other people depend on or for controlled workflows where the original history needs to remain stable.

Git Reset

The git reset command can move the current branch or modify staging state.

For example:

git reset HEAD~1

may move the branch back one commit under the relevant reset mode.

Because reset can alter local history, beginners should use it cautiously.

Git Revert

For a committed change that should be undone while preserving history, git revert is often more appropriate.

git revert <commit>

This creates a new commit that reverses the effect of the selected commit.

Conceptually:

$$ C_1 \rightarrow C_2 \rightarrow C_3 \rightarrow C_4 $$

If C_3 introduced an incorrect derivation, a revert creates another commit rather than simply pretending that C_3 never existed.

Tags

A Git tag provides a named reference to a specific point in repository history.

Tags are useful for identifying meaningful release points.

For example:

git tag CSR-database-lock
git push origin CSR-database-lock

A project might establish tags such as:

Tag Possible Meaning
analysis-v1.0 First approved analysis-code release
analysis-v1.1 Approved update
csr-production Code state used for a defined production run
validation-release-01 Defined validation release point

The exact naming convention should be established by the project.

Why Tags Are Valuable

Imagine that a clinical team asks:

"Which version of the analysis code produced the CSR tables?"

A tag can provide a precise pointer to a defined Git state.

Instead of saying:

"It was probably the code in the analysis-final folder."

the team can identify a specific repository state.

Git Log

The command:

git log

displays repository history.

A compact version is:

git log --oneline --decorate --graph --all

This can provide a useful overview of branches, commits, and tags.

Comparing Versions

Git can compare two versions of a file or two points in repository history.

git diff analysis-v1.0 analysis-v1.1

This is useful when investigating what changed between defined analysis releases.

Finding the Version of a Program That Produced a Result

This is one of the most useful ideas in reproducible statistical programming.

Suppose a figure was generated from a program whose relevant commit was:

8f4b9c2

That commit identifies a specific repository state.

If the corresponding input data, package environment, configuration, and execution conditions are also controlled, the team has a much stronger basis for reproducing the result.

Important distinction: A Git commit identifies the state of version-controlled files. It does not by itself guarantee that the result can be reproduced. Data, package versions, operating-system dependencies, random seeds, external services, and execution conditions may also matter.

Git and Reproducibility

Reproducibility can be viewed as a chain:

$$ \text{Data} + \text{Code} + \text{Dependencies} + \text{Configuration} + \text{Execution} \rightarrow \text{Result} $$

Git primarily addresses the code and configuration history portion of this chain.

Additional tools and procedures are needed for the other components.

Using renv With Git

R package dependencies can change over time.

An analysis that works today may produce different results or fail after a package is upgraded.

The R package renv can help create a project-level R package environment.

A typical workflow begins with:

install.packages("renv")
renv::init()

After installing or updating packages, the project environment can be recorded with:

renv::snapshot()

The resulting lockfile can then be version controlled.

A collaborator can restore the recorded environment with:

renv::restore()
Why this matters: Git can tell you which R code was used. A package-management tool such as renv can additionally help document the R package versions associated with that project state.

Git + renv: A Powerful Combination

Component Question It Helps Answer
Git What source code changed?
Git commit What specific code state was recorded?
Git tag Which named release point was used?
renv.lock Which R package versions were associated with the environment?
Analysis data Which controlled dataset was analyzed?
Execution record How was the analysis run?

Random Numbers and Git

Simulation studies frequently depend on random-number generation.

For example:

set.seed(12345)

x <- rnorm(1000)

The seed can make a simulation reproducible under the same computational conditions.

But the seed alone does not guarantee reproducibility if the code or package implementation changes.

Therefore:

$$ \text{Reproducible simulation} \neq \text{seed alone} $$

Git for Statistical Simulation

Suppose a simulation study compares two estimators.

The repository might contain:

simulation/
├── R/
│   ├── generate_data.R
│   ├── estimator_a.R
│   ├── estimator_b.R
│   └── summarize_results.R
├── scenarios/
│   └── scenarios.csv
├── renv.lock
└── README.md

A commit can then record changes to the estimator or simulation design.

Versioning Analysis Specifications

Version control should not be restricted to R programs.

Analysis specifications can be equally important.

For example, a specification might define:

  • Analysis population
  • Endpoint derivation
  • Baseline definition
  • Censoring rules
  • Statistical model
  • Multiplicity adjustment
  • Missing-data handling
  • Output formatting

If the specification changes, the corresponding analytical code may also need to change.

Keep specification changes synchronized with programming changes. A repository containing a current program alongside an outdated analysis specification can create a misleading record even if every individual file is version controlled.

Example: SAP Change

Suppose an SAP amendment changes the primary analysis from:

ANCOVA adjusted for baseline value

to:

MMRM with treatment, visit, treatment-by-visit,
baseline value, and appropriate covariance structure

This is a major analytical change.

The Git workflow might include:

1
Commit or otherwise control the updated analysis specification.
2
Create a dedicated analysis-code branch.
3
Update the statistical programs.
4
Run validation.
5
Perform statistical programming and statistician review.
6
Merge the approved change.
7
Create an appropriately documented release point.

Git and Double Programming

Independent programming remains an important validation strategy where required by the study or organization.

Git does not replace independent validation.

For example, a primary programmer may create:

R/tlf14_01.R

while an independent programmer creates:

validation/tlf14_01_validate.R

The validation code can be developed separately and compared against the primary result.

Key point: Version control manages the history of programming artifacts. It does not, by itself, establish statistical independence, validate an analysis, or prove that a result is correct.

Git and Automated Testing

Git becomes even more useful when combined with automated tests.

For example, a derivation function might have tests checking:

  • Expected values
  • Boundary conditions
  • Missing values
  • Invalid inputs
  • Known clinical scenarios

Using the R package testthat, a simple test might look like:

test_that("PFS event date is correctly derived", {

  result <- derive_pfs_event(
    progression_date = as.Date("2026-01-15"),
    death_date = NA
  )

  expect_equal(
    result,
    as.Date("2026-01-15")
  )

})

Tests can then be stored and version controlled alongside the code.

Continuous Integration

Continuous integration, or CI, allows automated checks to run when code changes.

A CI pipeline might:

1
Detect a new commit or pull request.
2
Install the required software environment.
3
Run automated tests.
4
Check code quality or formatting.
5
Report failures before the change is merged.

In a clinical environment, the exact CI implementation should be qualified and governed according to organizational requirements where applicable.

GitHub, GitLab, and Similar Platforms

Git is the version-control system.

Platforms such as GitHub or GitLab provide additional collaboration functionality around Git repositories.

Depending on the platform and organizational configuration, these systems may provide:

  • Pull or merge requests
  • Code review
  • Issue tracking
  • Access controls
  • Protected branches
  • Automated workflows
  • CI/CD
  • Release management

It is useful to distinguish the underlying Git technology from the surrounding platform.

Protected Branches

A protected main branch can help enforce team workflow.

For example, an organization may require:

  • No direct pushes to the main branch
  • At least one reviewer
  • Successful automated tests
  • Resolution of review comments
  • Successful validation checks

These controls can reduce the risk of unreviewed changes entering the stable analysis branch.

A Practical Clinical Programming Review Workflow

Figure 3. Controlled Analysis-Code Review Workflow
A conceptual workflow connecting statistical specification, programming, review, validation, and release.

Git Commit Granularity for TLFs

Suppose a programmer is developing Table 14.1.

One possible history is:

Create Table 14.1 shell
Implement treatment-group summaries
Add baseline laboratory summaries
Correct denominator calculation
Add confidence intervals
Fix table footnote
Finalize formatting

This is much easier to understand than:

Update everything for Table 14.1

The first approach creates a useful analytical narrative.

Commit Messages as Analytical Documentation

A well-maintained Git history can provide a concise narrative of development.

For example:

3a8d112  Create ADTTE derivation framework
61cf290  Add PFS event definition
7c8d5ab  Implement censoring rules
9af102e  Add sensitivity analysis
c92d611  Correct handling of same-day progression
a1e44bc  Update PFS summary table

This history can be extremely useful during development and troubleshooting.

Git Blame

The command:

git blame R/derive_pfs.R

can show which commit last changed each line.

This can help answer:

"Why does this line look different from the version I remember?"

The result can then be traced back to the relevant commit.

Do not interpret Git blame as assigning responsibility for an error. It identifies historical changes, not clinical accountability or scientific ownership. A line may have been changed because of a specification decision, review comment, or contribution from another team member.

Git Bisect

Git also provides tools for locating the commit that introduced a problem.

One example is:

git bisect start
git bisect bad
git bisect good analysis-v1.0

Git can then help narrow the history between a known-good and known-bad state.

This can be useful when:

  • A TLF suddenly changes unexpectedly
  • A derivation begins producing incorrect values
  • A test starts failing
  • A simulation result changes unexpectedly

Example: Finding a Regression

Suppose Table 14.2 produced 100 subjects last week but now produces 98.

The team knows:

$$ \text{Version A}=\text{correct} \qquad \text{Version B}=\text{incorrect} $$

Git history can help identify the commit where the behavior changed.

The programmer can then inspect the relevant diff and determine whether the change was:

  • Intentional
  • Unintentional
  • Required by a specification change
  • Caused by a programming error

Git and Analysis Outputs

Suppose an R program produces:

tlf14_01.rtf
tlf14_02.rtf
fig14_01.pdf
fig14_02.pdf

A reproducible project should make it possible to identify:

  • The source code version
  • The input dataset version
  • The analysis specification
  • The R/package environment
  • The execution process

Git contributes strongly to the first component and can help connect the others, but it does not automatically manage all of them.

Why Git Is Better Than File Names

File-Copy Approach Git Approach
analysis_final.R Commit history
analysis_final2.R Diff between commits
analysis_final_revised.R Branch and pull request
final_approved.R Tag or release point
"Who changed this?" Commit metadata and history
"What changed?" Git diff
"Can we go back?" Historical commit or tag

Git Does Not Mean "Save Everything Forever"

A common misconception is that Git is an immutable audit trail.

It is not.

Git history can be rewritten through operations such as rebasing, resetting, and force-pushing.

Remote platforms can also have administrative capabilities that affect repository history.

Regulatory distinction: Git provides powerful version-control capabilities, but Git alone should not automatically be represented as a validated electronic audit trail or complete regulatory records-management system. Regulated clinical-trial environments require appropriate procedures, access controls, validation, documentation, retention, and governance.

Git and Regulatory Submission Work

Git can support a regulated statistical-programming environment by helping organizations control:

  • Source-code revisions
  • Analysis-program development
  • Code review
  • Release points
  • Change history
  • Reproducibility
  • Collaboration

However, regulatory acceptability depends on the complete system and organizational controls, not simply on the presence of Git.

What a Controlled Environment Might Add

An enterprise clinical-programming environment may add controls such as:

  • Role-based access
  • Protected branches
  • Required reviews
  • Electronic approvals
  • Change-control procedures
  • Validation procedures
  • Release management
  • Retention policies
  • Controlled execution environments
  • Documented procedures

These controls sit around Git rather than being supplied automatically by Git.

Git and GxP Thinking

When Git is used in a regulated environment, the key question is not simply:

$$ \text{"Does this system use Git?"} $$

A better question is:

$$ \text{"Does the overall process adequately control the computerized system and its records?"} $$

The answer depends on the organization's intended use, risk assessment, procedures, controls, validation approach, and applicable requirements.

A Recommended Repository Structure for Biostatisticians

One possible structure is:

clinical-analysis/
│
├── README.md
├── .gitignore
├── study-analysis.Rproj
│
├── R/
│   ├── setup.R
│   ├── adam/
│   ├── tlf/
│   ├── figures/
│   └── utilities/
│
├── specifications/
│   ├── analysis-specification.md
│   └── tlf-specifications/
│
├── validation/
│   ├── independent/
│   └── tests/
│
├── tests/
│   └── testthat/
│
├── renv/
│
└── config/
    └── analysis-config.yml

This is an example, not a universal clinical-trial repository standard.

The README File

Every substantial analytical repository should have documentation explaining how it works.

A useful README might contain:

  • Study or project description
  • Repository purpose
  • Directory structure
  • Required software
  • How to restore the R environment
  • How to run the analysis
  • Expected inputs
  • Expected outputs
  • Validation instructions
  • Branching conventions
  • Contact or ownership information

For example:

# Clinical Trial Analysis

## Purpose

Programs for production and validation of efficacy TLFs.

## Environment

R package dependencies are managed using renv.

## Setup

renv::restore()

## Analysis

Run:

R/setup.R

## Validation

Validation programs are located in validation/.

## Repository workflow

Changes to main require review before merge.

Git Workflow for a New TLF

Suppose a statistician requests a new figure showing change from baseline in a biomarker.

1
Review the statistical specification.
2
Create a feature branch.
3
Develop the R program.
4
Commit logical changes.
5
Run tests and validation.
6
Open a pull or merge request.
7
Obtain review.
8
Resolve comments.
9
Merge the approved branch.
10
Create or update the appropriate release documentation.

Example Git Commands for a New Analysis

git switch main
git pull

git switch -c feature/biomarker-figure

# Edit R/fig14_03.R

git status
git diff

git add R/fig14_03.R
git commit -m "Add biomarker change-from-baseline figure"

git push -u origin feature/biomarker-figure

The branch can then be reviewed through the team's approved collaboration platform.

Example: Correcting a Statistical Programming Error

Suppose a programmer discovers that a table incorrectly includes post-treatment events in a baseline summary.

A disciplined workflow might be:

git switch -c fix/baseline-event-window

# Correct the derivation

git diff

git add R/derive_baseline_events.R
git commit -m "Restrict baseline events to prespecified window"

git push -u origin fix/baseline-event-window

The pull request can explain:

  • What was incorrect
  • What was changed
  • Which specification governs the correction
  • Which tables or figures are affected
  • What validation was performed

Git and Change Control

Git history can support change-control processes.

For example, a change-control record might reference:

Change ID: CR-2026-017
Git branch: fix/pfs-censoring
Merge commit: 8af31c2
Release tag: analysis-v2.1

The exact process depends on the organization's quality system.

Do Not Put the Entire Change-Control System Inside Git

Git can provide useful technical evidence about source-code changes.

But a clinical change-control process may require additional information such as:

  • Reason for the change
  • Impact assessment
  • Approval
  • Testing
  • Validation
  • Implementation date
  • Affected outputs
  • Release decision

Git is one component of that broader process.

Common Git Mistakes for Biostatisticians

  1. Using Git only after the analysis is finished. Version control is most useful when used throughout development.
  2. Committing enormous unrelated changes. Small logical commits produce a more useful history.
  3. Using vague commit messages. "Update" provides little future information.
  4. Committing confidential data. A repository is not automatically an appropriate clinical-data store.
  5. Committing passwords or API keys. Secrets should be managed through approved mechanisms.
  6. Assuming .gitignore removes previously committed information. It does not rewrite existing history.
  7. Using force push casually. This can rewrite shared history.
  8. Resolving statistical merge conflicts by guessing. The governing specification should determine the correct analysis logic.
  9. Treating Git as a substitute for validation. Version control and validation solve different problems.
  10. Tracking every generated output unnecessarily. Consider whether outputs can be regenerated from controlled source artifacts.
  11. Ignoring package versions. Git tracks code but not the entire computational environment.
  12. Failing to tag important release points. Meaningful release identifiers make historical reconstruction easier.

Git for Clinical Study Reports

Consider the final production of a clinical study report.

The analysis repository might contain:

R/
├── table_demographics.R
├── table_efficacy.R
├── table_safety.R
├── figure_km.R
└── figure_waterfall.R

specifications/
├── efficacy.md
└── safety.md

renv.lock

README.md

A defined release point can identify the code state used for production.

The production process can then document:

  • Repository
  • Commit or tag
  • Data snapshot
  • Software environment
  • Execution date
  • Output package
  • Validation status

Git and ADaM Programming

Git is particularly useful when analysis datasets are generated from layered derivations.

For example:

$$ SDTM \rightarrow ADSL \rightarrow ADAE \rightarrow ADTTE \rightarrow TLFs $$

Changes to an upstream derivation can propagate downstream.

Git history can help identify when an upstream transformation changed.

Example: ADSL Treatment Assignment

Suppose the treatment variable is incorrectly derived.

ADSL
  |
  +-- Treatment assignment
          |
          +-- Efficacy TLFs
          |
          +-- Safety TLFs
          |
          +-- Exposure TLFs
          |
          +-- Figures

A single correction can therefore affect many outputs.

Version control makes the source-code change visible and traceable.

Git and Dependency Management

An analysis depends on more than its own code.

For example:

  • R version
  • R packages
  • Operating system
  • Database drivers
  • External software
  • Configuration files
  • Input data

Therefore:

$$ \text{Git reproducibility} < \text{complete computational reproducibility} $$

Git is foundational, but not sufficient by itself.

Git and Documentation

Documentation should evolve with the code.

For example, if a new TLF is added, the README or analysis documentation should be updated in the same development cycle when appropriate.

This creates alignment between:

A
Analysis specification
B
Statistical programming
C
Validation
D
Documentation
E
Production release

Git Workflow for a Statistical Team

Role Typical Git Activity
Biostatistician Reviews analysis changes and statistical intent
Statistical programmer Develops and commits analysis code
Independent programmer Performs independent validation where required
Team lead Reviews and approves controlled changes
Quality/validation role Assesses applicable process and validation requirements

The exact responsibilities vary by organization.

A Minimal Git Workflow for an Individual Biostatistician

Even a statistician working alone can benefit from Git.

git init

git add .
git commit -m "Initial analysis project"

# Later

git status
git diff

git add R/
git commit -m "Add adjusted treatment-effect model"

No remote repository or complicated branching strategy is required to obtain basic local version-control benefits.

A Minimal Team Workflow

A small team might use:

main
 |
 +-- feature/new-tlf
 |
 +-- fix/derivation
 |
 +-- feature/sensitivity-analysis

Each branch is reviewed before being merged into main.

A More Mature Clinical Programming Workflow

A larger organization may use:

main
 |
 +-- feature/*
 |
 +-- bugfix/*
 |
 +-- validation/*
 |
 +-- release/*

Combined with:

  • Protected branches
  • Mandatory code review
  • Automated testing
  • Package locking
  • Release tags
  • Change-control records
  • Documented validation procedures

Git Commands Worth Memorizing

Command Purpose
git status Show current repository state
git diff Show unstaged changes
git add Stage changes
git commit Record a commit
git log View history
git switch Switch branches
git switch -c Create and switch to a branch
git pull Retrieve and integrate remote changes
git push Publish commits to a remote
git merge Integrate one branch into another
git revert Create a commit that reverses an earlier commit
git tag Create a named reference to a repository state

Commands to Use With More Caution

Command Why It Requires Care
git reset Can change local branch history or staging state
git rebase Rewrites commit ancestry
git push --force Can overwrite remote branch history
git clean Can remove untracked files

A Good First-Day Git Workflow

A new biostatistician does not need to master every Git command.

Start with:

1
Create or clone the repository.
2
Learn git status.
3
Learn git diff.
4
Commit logical changes with useful messages.
5
Learn the team's branch workflow.
6
Learn how to open and review pull requests.

A Good Git Workflow for TLF Generation

For clinical TLF development, a practical pattern is:

Specification
      |
      v
Feature Branch
      |
      v
R Program
      |
      v
Local Testing
      |
      v
Commit
      |
      v
Push
      |
      v
Code Review
      |
      v
Validation
      |
      v
Merge
      |
      v
Release / Tag
      |
      v
Production Output

This workflow connects programming activity to the statistical analysis process rather than treating Git as a separate technical activity.

What Git Changes About Collaboration

Without version control, collaboration often looks like:

Aaron_final.R
Jane_final.R
Aaron_final_revised.R
Jane_final2.R
combined_final.R

With Git, collaboration becomes:

main
 |
 +-- feature/aaron-analysis
 |
 +-- feature/jane-validation
 |
 +-- review
 |
 +-- merge

The second model provides a much clearer relationship between development, review, and integration.

Git and Statistical Reproducibility

A reproducible analysis should ideally answer:

$$ \text{Which data?} \quad+\quad \text{Which code?} \quad+\quad \text{Which environment?} \quad+\quad \text{Which specification?} $$

Git addresses the code history exceptionally well.

Additional controls are needed for data, environments, specifications, and execution records.

Git Does Not Guarantee Scientific Correctness

A perfectly version-controlled program can still be statistically wrong.

For example:

model <- lm(
  response ~ treatment,
  data = analysis_data
)

Git can record exactly when this model was introduced.

It cannot determine whether the model is appropriate for the prespecified analysis.

Remember: Version control answers "what changed?" It does not automatically answer "was the statistical method correct?"

Git and Good Statistical Programming Practices

Git works particularly well when combined with:

  • Modular functions
  • Meaningful variable names
  • Unit tests
  • Code review
  • Analysis specifications
  • Automated reporting
  • Package management
  • Independent validation
  • Documented workflows

Version control therefore becomes part of a broader reproducible-programming framework.

A Complete Example

Suppose a clinical trial requires a new progression-free survival analysis.

The workflow might be:

# 1. Synchronize
git switch main
git pull

# 2. Create development branch
git switch -c feature/pfs-analysis

# 3. Develop code
# R/adtte_pfs.R
# R/tlf_pfs.R
# R/fig_pfs_km.R

# 4. Inspect changes
git status
git diff

# 5. Commit derivation
git add R/adtte_pfs.R
git commit -m "Implement PFS event and censoring derivation"

# 6. Commit outputs
git add R/tlf_pfs.R R/fig_pfs_km.R
git commit -m "Add PFS summary table and Kaplan-Meier figure"

# 7. Publish branch
git push -u origin feature/pfs-analysis

The team can then review the changes.

What the Reviewer Should Examine

The reviewer should inspect:

  • The specification
  • The Git diff
  • Population definitions
  • Event definitions
  • Censoring rules
  • Statistical model
  • Confidence intervals
  • Labels
  • Output checks
  • Validation evidence

This is where Git becomes especially valuable to biostatisticians.

The reviewer is not merely reviewing source code.

The reviewer is reviewing a controlled change to an analysis.

Release Management

Once an analysis is ready for production, the team can identify the relevant Git state.

For example:

git tag -a efficacy-release-2026-09 -m \
"Production release for efficacy TLFs"

The release process can then associate the tag with:

  • Analysis dataset version
  • Analysis specification version
  • R environment
  • Validation status
  • Production execution

Why Release Points Matter

During a long clinical study, the repository may contain hundreds or thousands of commits.

A release identifier makes it possible to distinguish:

$$ \text{development history} \quad\text{from}\quad \text{defined production state} $$

This is particularly useful when an analysis is rerun months later.

Practical Repository Checklist

1
Repository has a clear README.
2
Sensitive information is excluded and handled through approved systems.
3
.gitignore is configured appropriately.
4
Branching conventions are documented.
5
Commit messages describe meaningful changes.
6
Code review is performed according to team procedures.
7
Validation evidence is maintained appropriately.
8
Package dependencies are controlled.
9
Production release points are identifiable.
10
The Git process is integrated with the organization's quality system.

Common Questions

Do I need to know the command line?

No.

RStudio and Git-hosting platforms provide graphical interfaces for many common operations.

However, learning the basic Git command line is highly recommended because it makes troubleshooting and advanced workflows much easier.

Should every statistician learn Git?

Anyone who regularly develops statistical code should strongly consider it.

The value increases substantially when working on:

  • Clinical trials
  • Shared analysis projects
  • R packages
  • Simulation studies
  • Automated TLF pipelines
  • Regulatory submissions

Can Git replace file servers?

No.

Git is a version-control system, not a universal file-storage platform.

Can Git replace validation?

No.

Git records changes. Validation determines whether the software or analysis performs as intended according to the applicable requirements.

Can Git replace an audit trail?

Not automatically.

Git history can be valuable evidence of software development activity, but regulated environments may require additional controls and validated systems.

Should I put patient-level data in Git?

Not unless the organization's approved architecture specifically permits it and the necessary controls are in place.

For many clinical-trial environments, the safer design is to keep controlled data outside the source-code repository and version the code used to analyze that data.

Git Vocabulary for Biostatisticians

Git Term Biostatistics Analogy
Repository Controlled project containing analysis source and history
Commit Recorded logical change to analysis artifacts
Branch Independent development line for an analysis change
Merge Integration of reviewed development into another branch
Tag Named reference to a defined analysis-code state
Diff Detailed comparison between versions
Remote Shared repository location
Pull request Formal request to review and integrate changes
Revert New change that reverses an earlier change
Rebase Reconstruction of commit ancestry onto another base

The Mental Model to Remember

The easiest way for a biostatistician to think about Git is:

$$ \boxed{ \text{Edit} \rightarrow \text{Review} \rightarrow \text{Commit} \rightarrow \text{Share} \rightarrow \text{Review} \rightarrow \text{Merge} \rightarrow \text{Release} } $$

The individual commands are less important than understanding this lifecycle.

From "Final Files" to "Controlled History"

Traditional file-based analysis development often focuses on the final artifact.

Git changes the emphasis.

Instead of asking:

$$ \text{"Which file is final?"} $$

the team can ask:

$$ \text{"Which controlled repository state represents the analysis release?"} $$

That is a much more powerful question.

The Most Important Git Concepts for Clinical Biostatistics

Concept Why It Matters
Commit history Provides a structured history of source-code changes
Branches Allow development without destabilizing the main code line
Code review Allows statistical and programming review before integration
Diffs Make specific code changes visible
Tags Identify meaningful release states
renv Helps control R package environments
Tests Provide automated checks for expected behavior
Protected branches Can reduce uncontrolled changes to stable code
Documentation Connects code history with analytical intent
Governance Places Git within the broader regulated-process framework

A Practical Git Checklist for Biostatisticians

1
Use Git from the beginning of the analysis project.
2
Keep source code in a well-structured repository.
3
Do not commit confidential information or secrets.
4
Use meaningful commit messages.
5
Use branches for substantive development.
6
Review diffs before committing.
7
Use pull or merge requests for team review.
8
Resolve statistical conflicts using the approved specification.
9
Control R package versions with an appropriate dependency-management strategy.
10
Tag or otherwise identify important production states.
11
Integrate Git into validation and change-control procedures.
12
Remember that version control supports reproducibility but does not replace statistical validation.

Bottom Line

Git is much more than a programming tool.

For biostatisticians, it provides a framework for managing the evolving relationship between statistical specifications, analysis programs, validation, collaboration, and production outputs.

A well-designed Git workflow allows a team to move from:

$$ \text{multiple "final" files} $$

to:

$$ \text{controlled history} \rightarrow \text{reviewed changes} \rightarrow \text{defined releases} $$

The most valuable Git features for clinical biostatistics are therefore not necessarily the most technically sophisticated ones.

They are the ability to:

  • See what changed
  • Understand why it changed
  • Review the change
  • Identify who made it
  • Return to an earlier code state
  • Develop changes independently
  • Merge approved work
  • Identify production release points
  • Support reproducible analysis
Final takeaway: Git gives biostatisticians a structured history of their analytical code. When combined with disciplined branching, code review, testing, package management, documentation, validation, and appropriate clinical-data controls, it becomes a powerful foundation for reproducible and collaborative statistical programming. The critical distinction is that Git controls source-code history; it does not by itself validate statistical analyses, secure clinical data, or constitute a complete regulated audit-trail system.

References

Chacon, S. & Straub, B. Pro Git. A comprehensive introduction to Git concepts, branching, merging, and repository workflows.

Git Documentation. Git Reference and User Documentation. Official documentation for Git commands, concepts, and workflows.

RStudio / Posit Documentation. Version Control with Git. Documentation describing Git integration with RStudio projects and common version-control workflows.

Posit. renv: Project Environments for R. Documentation describing reproducible R package environments and lockfiles.

Wickham, H., Çetinkaya-Rundel, M., & Grolemund, G. R for Data Science. Discussion of project organization, reproducibility, and software practices for R-based analytical work.