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.
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:
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.
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:
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
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
This is a conceptual workflow. Organizations may use different branching, review, validation, and release procedures.
The Basic Git Workflow
A simple Git workflow is:
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.
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:
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.
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:
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.
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:
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:
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
Merge
After review, a branch can be merged into the target branch.
Conceptually:
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.
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:
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.
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:
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:
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.
Git and Reproducibility
Reproducibility can be viewed as a chain:
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()
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:
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.
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:
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.
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:
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
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:
The result can then be traced back to the relevant commit.
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:
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.
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:
A better question is:
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.
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
- Using Git only after the analysis is finished. Version control is most useful when used throughout development.
- Committing enormous unrelated changes. Small logical commits produce a more useful history.
- Using vague commit messages. "Update" provides little future information.
- Committing confidential data. A repository is not automatically an appropriate clinical-data store.
- Committing passwords or API keys. Secrets should be managed through approved mechanisms.
- Assuming .gitignore removes previously committed information. It does not rewrite existing history.
- Using force push casually. This can rewrite shared history.
- Resolving statistical merge conflicts by guessing. The governing specification should determine the correct analysis logic.
- Treating Git as a substitute for validation. Version control and validation solve different problems.
- Tracking every generated output unnecessarily. Consider whether outputs can be regenerated from controlled source artifacts.
- Ignoring package versions. Git tracks code but not the entire computational environment.
- 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:
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:
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:
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:
git status.
git diff.
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:
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.
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:
This is particularly useful when an analysis is rerun months later.
Practical Repository Checklist
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:
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:
the team can ask:
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
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:
to:
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
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.