PUBHLT 0411 · Python for Public Health Data Analysis
Dept. of Biostatistics and Health Data Science, University of Pittsburgh
October 6, 2026
The opioid overdose epidemic is the worst public health crisis in Pennsylvania, and the nation, in almost a generation.
Figure 1. Each dot is one Pennsylvania county’s overdose death rate in 2023, plotted on a single axis. The dots spread from Forest County at 0.0 per 100,000 to Philadelphia at 84.8, with most counties clustered between 15 and 40 and the state rate of 36.7 marked by a dashed line.
Today we will discuss….
A state total describes no one. The counties above differ by more than an order of magnitude, and the state figure conceals that variation entirely.
This session establishes what that number means for a single county. The question is answered at the end, from data, using code we will write together.
Prerequisite assumed
Six weeks of R programming experience, but NOTHING else!
Every dataset in this course traces to a public agency, and every limitation is stated.
| Source | Pennsylvania Department of Health, Estimated Drug Overdose Deaths |
| Coverage | 67 counties, 2019–2024 |
| Definition | Accidental and undetermined drug overdose deaths |
| Known limitation | PA DOH marks 2024 provisional — death certificates lag three to six months. This session therefore works in 2023, the most recent complete year of data available |
| Second limitation | Counts of 1–5 are suppressed, not published. Seven counties are blank in 2023 |
Numbers are reductive! In public health we must remember the human beings behind the numbers.
4703 is a quantity admitting arithmetic, and will form the basis of several calculations we do this semester. It is also 4,703 deaths.
Seven questions to learn about public health in Pennsylvania using Python!
Each question is answered with code that we will write
| Module | Area | Skill | Question answered |
|---|---|---|---|
| 0 | Substance use | Notebook basics and variables | What is Schuylkill County’s overdose death rate? |
| 1 | Substance use | Interrogating values, and acting on the answer | Has Centre County met the national overdose target of 13.1 per 100,000? |
| 2 | Infectious disease | Handling many values at once, and code reuse | Which month of the COVID-19 pandemic killed the most Pennsylvanians? |
| 3 | Environmental health | Whole-dataset arithmetic | How far above the EPA fine-particulate standard is the worst county’s air? |
| 4 | Newborn health | Reshaping and combining tables to fit a question | Why do some counties not publish an infant mortality rate? |
| 5 | Chronic disease | Constructing figures, and describing what they show | Does adult asthma prevalence move with smoking across all 67 counties? |
| 6 | Injury & violence | Conducting an analysis end to end, reproducibly | Is the rural firearm death rate really lower than the urban one? |
Every dataset originates with the PA Department of Health, the CDC, or the EPA.
The objective is analytic, not vocational
The purpose of this course is not programming proficiency as an end in itself.
The purpose is to pose a question about a population and obtain a defensible answer from data. Code is instrumental.
Sessions meet Tuesday and Thursday, 9:30–10:45, in 105 Lawrence Hall.
| Module | Area | Lecture | Lab — due 11:59pm | Homework — due 9:00am |
|---|---|---|---|---|
| 0 | Substance use | Tue 10/6 | Tue 10/6 — not graded | — |
| 1 | Substance use | Thu 10/8 | Tue 10/13 | Thu 10/15 |
| 2 | Infectious disease | Thu 10/15 | Tue 10/20 | Thu 10/22 |
| 3 | Environmental health | Thu 10/22 | Tue 10/27 | Thu 10/29 |
| 4 | Newborn health | Thu 10/29 | Thu 11/5 | Tue 11/10 |
| 5 | Chronic disease | Tue 11/10 | Thu 11/12 | Tue 11/17 |
| 6 | Injury & violence | Tue 11/17 | Tue 11/17 | — |
Labs are due the day of the lab session at 11:59pm. Homework is due at 9:00am the morning the next module begins — half an hour before that lecture.
Module 0’s lab is the exception: module0_lab.ipynb is a systems check, not an assessment. It is run after class to confirm the environment works, and there is nothing to write and nothing to submit.
The Tuesday–Thursday rhythm breaks twice
Module 4’s lab falls six days after its lecture, spanning Election Day, when the University does not meet.
Module 5 then inverts — lecture Tuesday, lab Thursday. Module 6 is a single combined session.
| Component | Weight | Detail |
|---|---|---|
| Labs (in class) | 40% | Six per module, submitted to Canvas by 11:59pm the day of the lab. No make-ups; lowest per module dropped. |
| Weekly homework | 25% | Five per module, completed on DataCamp, due 9:00am the day the next module starts. Graded on completion. Nothing is dropped. |
| Final project | 30% | In pairs. Teaching session 12/1 or 12/3 (60%), lab packet due exam week (40%). |
| Participation | 5% | Regular attendance and consistent participation. |
Office hours: Thursdays, 1–2pm, A740 Crabtree Hall.
Generative AI is not permitted in this course
The prohibition covers labs, homework, and the final project. It is important to have strong coding skills to understand where AI can produce errors/bugs, verbose code that can be made more efficient, or even hallucinates.
This includes inline code completion. JupyterLab as installed for this course ships no assistant and requires no action. The prohibition applies to any that is added — a Copilot extension, an AI-enabled editor opened alongside, or a browser assistant that completes code in the page.
Substitutes: office hours, lab partners, the class channel, the instructor.
Installing Python on your own machine
The environment is installed locally and takes about fifteen minutes. Steps 1–5 are done once; step 6 confirms the result.
| Step | Windows | macOS |
|---|---|---|
| 1. Open a terminal | PowerShell | Terminal |
| 2. Install uv | one line | one line |
3. Unzip the course folder inside PUBHLT0411 |
identical | identical |
4. Point the terminal at the python folder |
identical | identical |
| 5. Build the environment | uv sync |
uv sync |
| 6. Check that it worked | uv run jupyter lab |
uv run jupyter lab |
Python is not downloaded separately. Step 5 installs it. Anaconda is not required, and an existing Python or Anaconda installation is left untouched.
These instructions are on Canvas
SETUP.md carries every step with its failure modes. Nothing here needs to be memorized or copied down.
A terminal is a window where commands are typed rather than clicked.
| Windows | macOS |
|---|---|
Start button → type powershell → open Windows PowerShell |
Cmd + Space → type terminal → Return |
Prompt looks like PS C:\Users\yourname> |
Prompt looks like yourname@macbook ~ % |
Commands are typed at that prompt, exactly as written, each followed by Return.
| Windows — PowerShell | macOS — Terminal |
|---|---|
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
|
curl -LsSf https://astral.sh/uv/install.sh | sh
|
About 35 MB, a few seconds, no administrator rights and no password.
Then close the terminal and open a new one
The installer changes a setting that only a new window picks up. Confirm with uv --version, which should report a version number.
PUBHLT0411Download python.zip from Canvas and extract it into the PUBHLT0411 folder already built for the R half. The result:
Three failures, all common
Opening a zip is not extracting it. Windows displays the contents of a zip without unpacking them, and Python cannot work inside one. Right-click → Extract All.
python goes inside PUBHLT0411, not beside it. The two halves share one course folder.
Keep PUBHLT0411 out of OneDrive, Dropbox, and Google Drive. Syncing software interferes with the files Python creates.
python folderType cd and a space, then drag the python folder onto the terminal window and press Return. Dragging pastes the path correctly, which is faster and more reliable than typing it.
Confirm the location by listing what is there:
| Windows | macOS |
|---|---|
dir |
ls |
pyproject.toml and a data folder should appear in the list. If instead PUBHLT0411.Rproj appears, the terminal is one level too high — cd python and check again.
This installs Python itself and every package the course uses. A long list of package names scrolls past; that is expected.
The first run downloads about 200 MB — a minute or two on a good connection, longer on classroom wifi.
Everyone ends up with identical software
uv sync reads a lockfile shipped with the course, so every machine in this room installs the same version of every package. A result obtained on one laptop is obtainable on any other — which is what makes reproducibility something the course can assess.
JupyterLab opens in the browser. In the file list, open module0_lab.ipynb and choose Run → Run All Cells.
The notebook prints SETUP OK
If it does, the environment is correct and nothing further is required. If it does not, the message states which check failed.
To stop JupyterLab: return to the terminal and press Ctrl + C twice.
| Symptom | Cause and fix |
|---|---|
uv is not recognized / command not found |
The terminal was not reopened after step 2. Close all terminal windows, open a new one. Restart the computer if it persists. |
| Windows: running scripts is disabled | The command was shortened. Retype the full line from step 2, including -ExecutionPolicy ByPass. |
| macOS: operation not permitted | The terminal is denied access to that folder. Move PUBHLT0411 to Documents. |
uv sync stops partway |
The network dropped. Run it again — it resumes rather than restarting. |
Failed to spawn: jupyter / program not found |
You’re not in the python folder. cd into PUBHLT0411/python (check with dir, where you should see pyproject.toml), then uv run jupyter lab again. |
No such file or directory: 'data/...' |
The notebook isn’t saved inside the python folder. Save or move it into python/, next to module0_lab.ipynb, and reopen it from JupyterLab. |
dir/ls shows PUBHLT0411.Rproj |
The terminal is one level too high. cd python. |
None of this is assessed. Unresolved problems go to office hours or soumik@pitt.edu with a screenshot of the terminal.
Notebooks contain code cells and text cells. Shift+Enter executes a cell and advances.
The consequential property: the interpreter retains what was executed, not what is displayed.
Execution order is invocation order
Execution does not proceed top to bottom. The bracketed number beside each cell records the true sequence. This accounts for roughly half of all inexplicable notebook behavior.
Resolution: Kernel > Restart Kernel and Run All Cells, which is also the test of whether a notebook is reproducible.
python folderThe data files ship with the course, inside a data folder. Nothing is uploaded.
A file is then read by its relative path — its location starting from the python folder:
The path is relative to where the notebook is saved
FileNotFoundError on a file that plainly exists almost always means the notebook is saved somewhere other than the python folder. Move it into python/, reopen it.
“The time has come,” the Walrus said, “to talk of many things”.
– Lewis Carroll, Through the Looking-Glass.
The = operator does not assert equality. It binds: the expression on the right is evaluated, and the name on the left refers to the result.
The statement is read right to left: construct the value, then attach the name.
R analogue
county <- "Schuylkill". The semantics are equivalent. Python uses = and provides no <-.
Constraints: letters, digits, underscores; no spaces; no leading digit. Identifiers are case-sensitive — Population and population denote different names.
Convention: lower_case_with_underscores, not PopulationCount.
Readability is an assessed criterion
Identifier quality falls under presentation, which is a named grading criterion in this course.
Four types account for nearly all data handled in this course.
type() reports the type of any value and is the appropriate diagnostic when an operation behaves unexpectedly.
R analogue
character, integer, numeric, logical. Python writes True and False, not TRUE and FALSE.
Centre County, 2023: seven deaths, population 157,795.
Scenario: a file supplies "60" as text. Adding eight should yield 68.
TypeError — unsupported operand types
TypeError: can only concatenate str (not "int") to str
The + operator is defined for str + str (concatenation) and for int + int (addition), but not across the two. Rather than infer an intent, the interpreter raises.
Resolution: convert explicitly — int(overdose_deaths) + 8.
int() truncates toward zero
int(41.7) evaluates to 41. The fractional component is discarded rather than rounded. round(41.7) performs rounding and evaluates to 42.
Three operators warrant particular attention
/ returns a float even when the division is exact. // discards the remainder. Exponentiation is **; the ^ operator denotes bitwise exclusive-or and evaluates without error to an incorrect result when used for powers.
A rate per 100,000 is the count divided by the population, multiplied by 100,000. Schuylkill County, 2023.
The output carries excessive precision
41.728680121847745 is arithmetically correct and unsuitable for presentation. Formatting is addressed on the following slide.
An f prefix on a string literal causes each {expression} to be evaluated and substituted.
The :.1f format specifier fixes the output at one digit after the decimal point; :.2f gives two.
Format at presentation, retain full precision in computation
Rounding applied at the point of printing does not affect stored values. Rounding applied during computation propagates through every subsequent step.
Answering the question this session opened with
data/pa_overdose_county_year.csv — 402 observations across 67 counties, 2019–2024.
The first line is the header, naming each column.
Schuylkill and Centre, 2023. Both approximately 150,000 residents.
Figure 2. The same 59 Pennsylvania counties shown at the start of this session, with two marked. Centre County, a gold square at 4.4 deaths per 100,000, sits near the low end of the distribution; Schuylkill County, a blue diamond at 41.7, sits in the upper third. An arrow spans the two, labelled 9.5x. The state rate of 36.7 is marked by a dashed line. Every other county is greyed.
Comparable populations. A ninefold difference in rate.
What the state total concealed
4,703 describes Pennsylvania. It resolves into 41.7 in one county and 4.4 in another, and no resident experiences the state average.
1. Installing the course environment, and verifying it with module0_lab.ipynb
2. Executing cells in JupyterLab, and accounting for execution order as invocation order
3. Reading a data file by its path relative to the python folder
4. Binding values to descriptive identifiers
5. Distinguishing int, float, str, and bool via type(), and converting between them
6. Applying arithmetic operators, including //, %, and **
7. Formatting output with f-strings and the :.1f specifier
Preparation for Thursday
If module0_lab.ipynb did not print SETUP OK today, resolve it before Thursday — come to my office (A740 Crabtree Hall) and we will troubleshoot it together. Then start JupyterLab in the course folder, open a new notebook, and compute the 2023 overdose rate for one county of interest.
In 2023, how often did an overdose death occur in each county? Report days and hours.
hours = 365 * 24
# hours between deaths
schuylkill_gap = hours // 60
s_days = schuylkill_gap // 24
s_hours = schuylkill_gap % 24
centre_gap = hours // 7
c_days = centre_gap // 24
c_hours = centre_gap % 24
print(f"Schuylkill: {s_days} d {s_hours} h")
print(f"Centre: {c_days} d {c_hours} h")
Your turn!
Centre recorded 7 deaths. If the true count were 6 or 8, does “ninefold” survive?
Your turn!
If Schuylkill had Centre’s rate, how many deaths would it have had?
See you on Thursday!
soumik@pitt.edu · PUBHLT 0411 · Module 0: Notebooks and Values
Comments document rationale, not syntax
Text following
#is ignored by the interpreter and addressed to the reader.Comments are assessed
Interpretation and presentation together account for the majority of the grading criteria. Comments are where analytic reasoning becomes visible to a reader.