Chapter 10 — Communicating your pipeline
Reproducible reports, dashboards, and uncertainty views
Chapter 10 turns the analysis into something another person can inspect, run, and read. This page carries starter scaffolding for the integrative artifact (§10.3.4) and pointers for the environment-freezing exercise (§10.1.2).
What you will produce. One artifact for an audience outside your field, carrying at least three coordinated views and an explicit uncertainty layer, built as a reproducible report, an interactive dashboard, or an annotated notebook.
Materials for this chapter
Starter scaffolding
Quarto report and Streamlit dashboard skeletons, below on this page.
Communication tools
Reporting, dashboard, and visualization tools, maintained as software changes.
Worked notebooks
Read code alongside saved outputs, then assess which parts you can inspect and what you would need to rerun the analysis.
Studies on communication
Forty-four published studies annotated for how inspectable and reproducible their pipelines are. Useful models before you build your own.
The standards this artifact is judged against (inspectability, reproducibility, interpretability) are developed in Chapter 9.
Freezing the environment (§10.1.2)
In your Python environment, record the installed packages:
python -m pip freeze > requirements.txtFor R, run these commands in an R session:
renv::init()
renv::snapshot() # writes renv.lockA Python requirements file records installed packages; it is not a complete environment lock. Keep the language version, operating system, and other dependencies with it. Then create a clean environment, install from your records, and rerun the analysis. Record random seeds and any differences in output. See the pip freeze documentation for its scope.
Choosing the form of the artifact (§10.3.4)
Choose a form based on how your audience will use the result:
| Form | Best when | Tools |
|---|---|---|
| Reproducible report | The audience reads linearly and you want code and narrative in one artifact | Quarto, R Markdown, Jupyter Book |
| Interactive dashboard | The audience wants to filter and drill into the data themselves | Streamlit, Shiny, Dash, Observable |
| Annotated notebook | The audience is technical and wants to see the pipeline | Jupyter, Quarto notebook |
Reproducible report skeleton (Quarto)
---
title: "Collaboration and topical framing in <your field>"
author: "<you>"
date: today
format:
html:
toc: true
code-fold: true # code available, but not in the reader's way
execute:
warning: false
---code-fold: true lets readers expand the code when they want to inspect it.
Dashboard skeleton (Streamlit)
import streamlit as st, pandas as pd
st.title("Research collaboration in <field>")
df = pd.read_parquet("data/processed/analysis_table.parquet")
year_min, year_max = int(df.year.min()), int(df.year.max())
year = (year_min, year_max)
if year_min < year_max:
year = st.slider("Publication year", year_min, year_max, year)
sub = df[df.year.between(*year)]
st.caption(f"{len(sub):,} works, {year[0]}–{year[1]}. "
"Excludes records without abstracts (n = ...).") # document the filtering
col1, col2 = st.columns(2)
col1.subheader("Publications by year"); col1.line_chart(sub.groupby("year").size())
col2.subheader("Topic prevalence"); col2.bar_chart(sub.groupby("topic").size())Replace the caption’s placeholder with your exclusion count so readers can see what was left out. Add a third view that answers another question about the same selected records.
The uncertainty layer (§10.3.5)
Community detection can assign different numeric labels to the same groups on different runs. Compare the groups’ membership, rather than the label numbers. This example measures how consistently each node shares a community with the same peers across pairs of runs:
from itertools import combinations
import community as community_louvain # pip install python-louvain
def membership_stability(runs):
"""Mean Jaccard overlap of each node's co-members across pairs of runs."""
if len(runs) < 2:
raise ValueError("Compare at least two runs.")
nodes = set(runs[0])
if any(set(run) != nodes for run in runs):
raise ValueError("All runs must contain the same nodes.")
groups = []
for run in runs:
members = {}
for node, label in run.items():
members.setdefault(label, set()).add(node)
groups.append({node: members[label] for node, label in run.items()})
pairs = list(combinations(groups, 2))
stability = {}
for node in nodes:
overlaps = []
for left, right in pairs:
a, b = left[node] - {node}, right[node] - {node}
union = a | b
overlaps.append(len(a & b) / len(union) if union else 1.0)
stability[node] = sum(overlaps) / len(overlaps)
return stability
runs = [community_louvain.best_partition(G, random_state=s) for s in range(20)]
stability = membership_stability(runs)
# Darker nodes: more consistent co-membership; lighter nodes: less consistent.A score of 1 means the node has the same community peers in every run; lower scores mean membership varies. Two singleton assignments count as agreement. Use color and a clear legend to show this variation, and inspect the less stable nodes before interpreting their position. This is a measure of sensitivity to the algorithm’s seed, not a probability that a community is correct. The pairwise calculation is intended for small teaching graphs.
Graceful degradation and maintenance
Provide a static fallback for readers without JavaScript, with a text summary or accessible table that explains the views. A PNG alone does not make a chart accessible to a screen reader. Check keyboard navigation as well. Then write the maintenance memo the exercise asks for: what breaks this artifact in five years, and which parts of the analysis are portable across whatever replaces today’s tools.
Where to go next
- Communication tools
- About this site: how these URLs stay stable