Lesson 2 of 8
A working environment in Python and R
Install Python and R so they still work on a bad connection, lay out a project directory that survives a handover, and run the same first script in both languages.
Why this lesson is not optional
The most common reason an M&E analysis stops being reproducible is not a statistical error. It is that the person who wrote it installed a package one afternoon, never recorded which one, and left. Six months later the script raises an import error on a laptop in a field office with a satellite link, and the quarterly report goes back to being made by hand in a spreadsheet.
Ten minutes of setup now is what prevents that.
Python: use uv, not the system interpreter
Your operating system ships a Python. Do not analyse data with it — it is there to run the operating system, and changing it can break things you did not know depended on it.
uv installs an isolated Python and resolves packages fast enough to be usable
on a slow connection. It also writes a lock file, which is what makes the
environment reproducible on someone else’s machine.
# Install uv (macOS and Linux)
curl -LsSf https://astral.sh/uv/install.sh | sh
# Create a project and pin an interpreter
uv init muac-analysis
cd muac-analysis
uv python install 3.13
# Add what this course uses
uv add pandas pyarrow matplotlib
uv add writes both pyproject.toml (what you asked for) and uv.lock
(exactly what was installed, down to the hash). Commit both. A colleague then
reproduces your environment with one command:
uv sync
If you are behind a proxy or working offline, uv can install from a local
wheel directory. Downloading the wheels once, onto a USB stick, and installing
from it is a normal workflow in this sector and is worth setting up before you
need it.
R: use Projects and renv
R’s equivalent problem is the global library — packages installed into one shared location, invisibly shared across every analysis, and silently upgraded under you.
# Once per machine
install.packages("renv")
# Once per analysis, from inside an RStudio Project
renv::init()
# Add what this course uses
install.packages(c("readr", "dplyr", "tidyr", "ggplot2", "janitor"))
# Record exactly what is installed
renv::snapshot()
renv.lock is the equivalent of uv.lock. Commit it. A colleague runs
renv::restore() and gets your library, not theirs.
Two habits matter as much as the tooling:
- Work inside a Project. The working directory is then the project root, for
everyone, always. Paths like
data/raw/muac.csvwork on any machine. - Never write
setwd(). It encodes your username into the analysis and guarantees it fails for the next person.
A project layout that survives a handover
The same structure works in both languages. It is worth adopting exactly, because the value is in it being predictable rather than in it being clever.
muac-analysis/
data/
raw/ # exactly as it arrived. Read-only. Never edited.
interim/ # intermediate output. Disposable.
processed/ # analysis-ready. Regenerable from raw + code.
scripts/
01-read.py
02-clean.py
03-indicators.py
output/
figures/
tables/
README.md
One rule carries most of the weight: data/raw/ is read-only. If you open
the export in Excel, fix three cells and save it, you have destroyed the only
record of what actually arrived, and nobody — including you — can ever again
tell what was original and what was a correction.
Everything downstream of data/raw/ should be reproducible by deleting it and
rerunning the scripts. If it is not, something is being done by hand, and that
something is the part that will break.
A useful test: delete
data/processed/andoutput/entirely, rerun your scripts, and see whether you get the same results. If you cannot bring yourself to try it, you already know the answer.
The same first script, in both languages
Read the register, look at its shape, and print the first few rows. Nothing more — the point is to confirm the environment works before you depend on it.
import pandas as pd
muac = pd.read_csv("data/raw/muac-screening-artibonite-2024.v1.csv")
print(muac.shape)
print(muac.head())
print(muac.dtypes)
library(readr)
library(dplyr)
muac <- read_csv("data/raw/muac-screening-artibonite-2024.v1.csv")
dim(muac)
head(muac)
glimpse(muac)
Both should report 4,218 rows and 8 columns. If they do not, stop here — a row count that disagrees with the source is the cheapest error you will ever catch, and it gets much more expensive three scripts later.
What the two languages disagree about
You will use both, so it helps to know where they differ in ways that matter.
| Concern | pandas | dplyr / readr |
|---|---|---|
| Missing value | NaN, and None for objects |
NA, typed per column |
| Reading a CSV | pd.read_csv guesses types |
read_csv guesses and reports |
| Chaining steps | Method chaining or reassignment | The pipe, |> |
| Grouped summary | groupby().agg() |
group_by() |> summarise() |
| Categories | category dtype, opt-in |
Factors, and they bite |
Neither is better. The one that matters is the one your team already runs, and the reason this course teaches both is that you will change teams.
What comes next
The next lesson reads the export properly — which is harder than
read_csv(path), because the defaults will quietly damage identifiers, dates and
the missing-value code before you have looked at anything.