Reproducibility & Reporting with Jupyter and Quarto¶
Tools: Python, Jupyter, Quarto, pandas, matplotlib, Git
Learning objectives¶
By the end of this tutorial, you should be able to:
evaluate whether a notebook is reproducible;
eliminate hidden execution-state dependencies;
create a computational report;
combine Markdown, Python, tables, and figures;
render a Quarto document;
generate HTML and other outputs from one source;
dynamically compute reported quantities;
import reusable functions from
src/;record Python dependencies;
control randomness;
separate raw, processed, and report outputs;
perform a final reproducibility test.
Part 0 — Project setup¶
Extend your existing STA 556 project:
project/
├── README.md
├── data/
│ ├── raw/
│ └── processed/
├── notebooks/
├── src/
├── figures/
├── reports/
└── tests/Create:
reports/week08_report.qmdAlso create:
notebooks/week08_reproducibility.ipynbCreate a small Quarto project configuration at the repository root:
_quarto.ymlwith:
project:
type: default
execute-dir: projectexecute-dir: project tells Quarto to execute report code from the project root. This keeps paths such as data/processed/week08_data.csv and imports such as from src... consistent with the rest of the course.
Part 1 — Verify the provided computational environment¶
From the terminal:
python --versionCheck:
quarto --versionIf quarto --version fails in the official course Codespace, treat that as a course-environment issue rather than installing Quarto manually.
Then:
python -m pip show pandasand:
python -m pip show matplotlibReflection¶
Why could package versions matter if the same source code is run several years later?
Part 2 — Create a deliberately fragile notebook¶
In:
notebooks/week08_reproducibility.ipynbcreate cells in this order.
Cell 1¶
import pandas as pd
import numpy as npCell 2¶
mean_scoreCell 3¶
rng = np.random.default_rng(556)
df = pd.DataFrame({
"group": rng.choice(
["A", "B"],
size=50
),
"score": rng.normal(
80,
10,
size=50
)
})Cell 4¶
mean_score = df["score"].mean()Now execute:
Cell 1
Cell 3
Cell 4
Cell 2The notebook appears to work.
Part 3 — Restart and run all¶
Restart the kernel.
Then run all cells from top to bottom.
Question¶
What fails?
Why?
Fix the notebook by moving computations before anything that depends on them.
Repeat:
Restart kernel
Run alluntil the notebook succeeds.
Part 4 — Add a reproducible random generator¶
Compare:
x = np.random.normal(
size=5
)with:
rng = np.random.default_rng(556)
x = rng.normal(
size=5
)Restart the kernel and rerun.
Question¶
Which one reproduces the same values?
Why will this matter during future simulation modules?
Part 5 — Build a small processed dataset¶
Create:
rng = np.random.default_rng(556)
n = 100
df = pd.DataFrame({
"id": range(1, n + 1),
"group": rng.choice(
["A", "B"],
size=n
),
"age": rng.normal(
35,
9,
size=n
),
"score": rng.normal(
80,
10,
size=n
)
})Then:
df["age"] = df["age"].round(1)Save:
df.to_csv(
"data/processed/week08_data.csv",
index=False
)Part 6 — Create a reusable summary function¶
In:
src/week08_analysis.pywrite:
import pandas as pdThen:
def summarize_groups(
data: pd.DataFrame
) -> pd.DataFrame:
return (
data.groupby(
"group"
)["score"]
.agg(
n="count",
mean="mean",
sd="std"
)
.reset_index()
)Save.
Part 7 — Use your module¶
In the notebook:
from src.week08_analysis import (
summarize_groups
)Then:
summary = summarize_groups(df)
summaryReflection¶
Why is this preferable to copying the summary logic into every report?
Part 8 — Create a basic Quarto document¶
Open:
reports/week08_report.qmdAdd:
---
title: "STA 556 Week 8 Reproducible Report"
author: "Your Name"
format: html
jupyter: python3
---Then:
# Introduction
This report demonstrates a reproducible statistical
workflow using Python and Quarto.Part 9 — Add setup code¶
Add a Python block:
```{python}
import pandas as pd
import matplotlib.pyplot as plt
from src.week08_analysis import summarize_groups
```Depending on where your report is located, you may need to ensure the project root is the working directory.
Question¶
Why should path assumptions be explicit?
Part 10 — Load the data¶
Add:
```{python}
df = pd.read_csv(
"data/processed/week08_data.csv"
)
df.head()
```Important¶
Do not manually recreate the DataFrame inside the report.
The report should consume the processed analytical data.
Part 11 — Add a data-validation block¶
Add:
```{python}
assert len(df) == 100
assert df["id"].is_unique
assert df["score"].notna().all()
```Question¶
Why include checks in a report-generation workflow?
What happens if the upstream data unexpectedly change?
Part 12 — Generate a summary table¶
Add:
```{python}
summary = summarize_groups(df)
summary
```Add narrative:
## Descriptive statistics
The table below summarizes assessment scores by group.Reflection¶
If the data change, what must you manually update?
Ideally: nothing.
Part 13 — Generate a dynamic value¶
Calculate:
overall_mean = df["score"].mean()Use the result in your narrative.
If your chosen Quarto/Jupyter workflow supports inline execution, insert it dynamically.
If not, generate a compact results object in a code block and reference the reported value immediately below it.
Principle¶
Do not manually copy a statistic if the document can generate it.
Part 14 — Create a figure in the report¶
Add:
```{python}
fig, ax = plt.subplots(
figsize=(7, 5)
)
ax.hist(
df["score"],
bins=15
)
ax.set(
title="Distribution of assessment scores",
xlabel="Assessment score",
ylabel="Frequency"
)
plt.show()
```Render later and verify the figure appears.
Part 15 — Add a figure caption¶
Use Quarto cell options:
```{python}
#| label: fig-score-distribution
#| fig-cap: "Distribution of assessment scores."
fig, ax = plt.subplots()
ax.hist(
df["score"],
bins=15
)
ax.set(
xlabel="Assessment score",
ylabel="Frequency"
)
plt.show()
```Now reference the figure in your prose according to Quarto’s cross-reference syntax.
Part 16 — Render the report¶
From the project root:
quarto render reports/week08_report.qmdLocate the resulting HTML file.
Open it.
Questions¶
Did the Python code execute?
Did the table appear?
Did the figure appear?
Did the narrative render correctly?
Part 17 — Preview during development¶
Run:
quarto preview reports/week08_report.qmdIn GitHub Codespaces, Quarto runs the preview server inside the Codespace, not on your laptop. Codespaces detects the preview server and exposes it through a forwarded port. Use the Open in Browser notification or the VS Code Ports panel rather than manually typing a localhost address.
Edit the report and observe the preview update.
Reflection¶
When is preview useful?
When would you use render instead?
Part 18 — Control code visibility¶
At the top of the document, experiment with:
execute:
echo: falseRender.
What changed?
Now remove it and instead hide only one code block:
```{python}
#| echo: false
summary
```Question¶
Why might a report hide code from one audience but show it to another?
Part 19 — Code folding¶
For HTML, experiment with:
format:
html:
code-fold: trueRender.
Reflection¶
How does code folding balance readability and transparency?
Part 20 — Generate a Word version¶
Change:
format:
html: default
docx: defaultThen:
quarto render reports/week08_report.qmdInspect both outputs.
Question¶
What benefit comes from generating both from one source?
Part 21 — Optional PDF output¶
If your environment has the required PDF tooling, add:
pdf: defaultand render.
If PDF rendering fails because a TeX dependency is missing, document the dependency rather than treating it as an analysis error.
Part 22 — Add meaningful labels¶
Review all figures and tables.
Replace:
score
age
groupwith reader-friendly labels where appropriate:
Assessment score
Age (years)
Study groupPrinciple¶
The report is for a reader, not for the DataFrame.
Part 23 — Build a proper report structure¶
Organize:
# Introduction
# Data
# Methods
# Results
## Descriptive statistics
## Visualization
# DiscussionChallenge¶
Write 1–3 sentences for each section.
Keep the report concise.
Part 24 — Separate computation from interpretation¶
Example:
## Results
[computed table]
Group A had a higher mean score than Group B.The table is computational output.
The sentence is statistical interpretation.
Question¶
Why should these remain conceptually distinct?
Part 25 — Make the report fail deliberately¶
Rename:
data/processed/week08_data.csvtemporarily.
Render the report.
Question¶
What happens?
Why is an obvious failure preferable to silently using old manually copied results?
Restore the file afterward.
Part 26 — Record the environment¶
From the terminal:
python -m pip freeze > requirements.txtOpen:
requirements.txtInspect several entries.
Question¶
What does this file record?
What does it not record?
Part 27 — Reproducibility test in a fresh environment¶
Conceptually, another user should be able to run:
python -m pip install -r requirements.txtThis command documents how an environment can be recreated, but students normally do not need to run it in STA 556 Codespaces because the course dependencies are installed automatically when the Codespace is created. Treat requirements.txt as part of the reproducibility record rather than as a weekly installation step.
and then:
quarto render reports/week08_report.qmdYou do not need to destroy your current environment.
Instead, explain the steps another researcher would follow.
Part 28 — Make a clean execution test¶
Before the final render:
restart the Jupyter kernel;
run the notebook from beginning to end;
save it;
render the Quarto report;
inspect warnings/errors;
verify the reported numbers.
Question¶
Why is this stronger than simply checking whether the final HTML file exists?
Part 29 — Remove manual values¶
Search your report for manually typed statistical quantities.
For example:
The mean score was 81.37.Ask:
Could this value be generated computationally?
If yes, replace the manual workflow.
Part 30 — One-source-many-outputs challenge¶
Configure the report to produce at least:
HTML
DOCXfrom the same .qmd.
Render both.
Compare:
tables;
figures;
headings;
code;
overall readability.
Reflection¶
What formatting differences exist despite sharing one source?
Part 31 — Parameterized-report concept¶
Imagine you need one report per group.
Instead of:
report_A.qmd
report_B.qmdthink:
report.qmd
+
group parameterSketch how your functions would need to work:
def prepare_group_report(
data,
group
):
...Question¶
What advantages does this have over copying and editing reports?
Part 32 — Automation concept¶
Create a small shell script conceptually containing:
python src/prepare_data.py
quarto render reports/week08_report.qmdYou do not need sophisticated automation yet.
The point is to see the report as the final step of a computational pipeline.
Part 33 — Cache discussion¶
Suppose your analysis takes two hours.
Would you want Quarto to recompute it every time you change one sentence?
Discuss:
benefits of caching;
risk of stale computation;
when recomputation is preferable.
Part 34 — Audit the report¶
Use this checklist.
Data¶
Is the data source clear?
Is the processed file generated reproducibly?
Are raw data preserved?
Code¶
Are transformations in code?
Are reusable functions in
src/?Are assumptions checked?
Environment¶
Is
requirements.txtpresent?
Randomness¶
Are seeds controlled?
Report¶
Are statistics generated dynamically?
Are figures generated from code?
Are tables generated from code?
Can the report render from beginning to end?
Part 35 — Peer reproducibility test¶
If possible, exchange repositories with a classmate.
Without verbal instructions, attempt to determine:
Which file is the report source?
Which data file does it use?
Which Python module does it import?
How should the report be rendered?
What dependencies are required?
Important¶
If your partner must ask you where everything is, improve the project documentation.
Part 36 — Update the README¶
Add:
## Week 8 report
To render the report:
```bash
quarto render reports/week08_report.qmdRequired Python dependencies are listed in:
requirements.txt
Also document where input data are located.
---
# Part 37 — Git checkpoint
Run:
```bash
git statusReview carefully.
Do you want to commit:
.qmd source?
requirements.txt?
src module?
processed data?
rendered HTML?
rendered Word document?Your choice should be deliberate.
Then:
git add .
git commit -m "Complete Week 8 reproducible reporting workflow"
git pushPart 38 — Final reflection¶
Answer in Markdown.
1. Reproducibility¶
What information is required to reproduce a computational analysis?
2. Jupyter¶
What is hidden state, and why can it be dangerous?
3. Literate programming¶
What does it mean to combine narrative and computation?
4. Quarto¶
What role does Quarto play in the workflow?
5. Dynamic reporting¶
Why are dynamically generated statistics preferable to manually copied values?
6. Environments¶
Why should package dependencies be recorded?
7. Randomness¶
Why should random seeds be explicit?
8. Rendered outputs¶
Why should rendered Word/HTML/PDF files usually not become the primary source document?
9. Modularity¶
How did the Week 7 function/module work improve this week’s report?
10. Scientific validity¶
Why can a workflow be reproducible but still statistically wrong?
Completion checklist¶
Created Week 8 notebook
Created a Quarto
.qmdreportDemonstrated hidden notebook state
Restarted and ran a notebook from top to bottom
Controlled randomness with a generator/seed
Created processed data
Created a reusable analysis module
Imported module functions into the workflow
Added Quarto YAML metadata
Loaded data from the report
Added validation checks
Generated a dynamic summary table
Generated a figure from code
Added a figure caption
Rendered an HTML report
Used
quarto previewControlled code visibility
Experimented with code folding
Generated a Word report
Explored PDF rendering if available
Added reader-friendly labels
Structured the report into narrative sections
Deliberately triggered a reproducibility failure
Created
requirements.txtDocumented how to restore dependencies
Performed a clean execution test
Removed manually copied statistical values
Produced multiple output formats from one source
Explored parameterized-report design
Explored automated rendering
Audited the project for reproducibility
Attempted a peer reproducibility test
Updated the README
Committed Week 8 work to Git
Pushed work to GitHub
What you should now understand¶
Raw data
↓
Documented environment
↓
Modular code
↓
Computational document
↓
Execute from beginning to end
↓
Generate tables + figures + values
↓
Render
↓
HTML / Word / PDF
↓
Another person can reproduce itNext week we move into Matrix Computation & Linear Algebra, including broadcasting, vectorized operations, SVD, and QR decomposition for statistical computing.