Back to the lesson·Lesson 1 of 8·How the database is shaped
What a value in DHIS2 actually is
The same deck as the downloads, rendered as a page. Start the slideshow to present it full screen — arrow keys or a click advance one slide, Escape leaves.
What this lesson covers
- A value has five coordinates
- Data element: the thing that is counted
- Category combinations: the breakdown built into the element
- Data element versus indicator
- Datasets: the form, and what "expected" means
- Ask for the metadata, not just the data
- What comes next
Speaker notes
Five coordinates, not one number. Data element, category option combination, org unit, period and dataset — and the difference between a data element and an indicator, which is where most confusion about routine figures starts.A value has five coordinates
Coordinate In the extract What it answers Data element antigenplus the measureWhat was counted Category option combination not present Which breakdown of it Org unit facility_idWhere Period periodWhen Dataset not present Which form it was collected on Speaker notes
The vaccination extract looks like a spreadsheet: facility, period, antigen, doses. In the system it came from, a single stored value is addressed by five things, and knowing all five is what makes a figure defensible. Two of the five are missing from the export, and that is the normal case. Most of this lesson is about what those two carry and why their absence causes arguments.Data element: the thing that is counted — In Python
import pandas as pd vax = pd.read_csv("vaccination-coverage-2024.v1.csv", parse_dates=["period"]) print(sorted(vax["antigen"].unique())) print(vax.groupby("antigen")["doses_administered"].sum())Speaker notes
A data element is the raw measure a form collects — "Penta3 doses administered", "BCG doses administered". It is a count of something, entered by a person, and it is stored exactly as entered.Data element: the thing that is counted — In R
library(dplyr) library(readr) vax <- read_csv("vaccination-coverage-2024.v1.csv") vax |> summarise(doses = sum(doses_administered), .by = antigen)Data element: the thing that is counted
- Ask which it is — A denominator stored as an annual data element can be revised retrospectively; one stored as an…
Speaker notes
Six antigens, six data elements. Thetarget_populationcolumn is a seventh value of a different kind, and in a real instance it would usually not be a data element at all — it is a denominator, stored either as a data element captured once a year or as an org unit attribute. Ask which it is. A denominator stored as an annual data element can be revised retrospectively; one stored as an attribute changes for every period at once. That difference is why a coverage figure computed in January and again in June can differ with no new doses.Category combinations: the breakdown built into the element — Example
Data element: Penta3 doses administered Category combo: Age (<1, 1+) x Sex (F, M) Stored values: 4 per org unit per periodSpeaker notes
A category combination splits a data element into the cells the form actually collects. Penta3 might be broken down by age (under 1 / 1 and over) and by sex, giving four category option combinations per data element.Category combinations: the breakdown built into the element
- The data element has no disaggregation, so there is one value and nothing was lost.
- The export aggregated across category option combinations, in which case the disaggregation exists in the system…
Speaker notes
This is where the most common misreading of a DHIS2 export lives. If your extract has one row per antigen per facility per month, one of two things is true, and they are not equivalent:Category combinations: the breakdown built into the element — In Python
expected = (vax["facility_id"].nunique() * vax["period"].nunique() * vax["antigen"].nunique()) print(f"{expected} expected rows, {len(vax)} in the file")Category combinations: the breakdown built into the element — In R
c(expected = n_distinct(vax$facility_id) * n_distinct(vax$period) * n_distinct(vax$antigen), actual = nrow(vax))Speaker notes
38 × 12 × 6 = 2,736, and the file has 2,736 rows. The grid is exactly full, which tells you the export is at data element level with no category disaggregation surviving. If a report then asks for coverage by sex, the answer is not in this file, and it may or may not be in the system.Data element versus indicator
- A data element is stored. Somebody typed it.
- An indicator is calculated. It has a numerator, a denominator and a factor, and DHIS2 evaluates it at whatever…
Speaker notes
The distinction that causes the most trouble, and it is worth being precise about.Data element versus indicator — Example
Indicator: Penta3 coverage Numerator: #{Penta3 doses administered} Denominator: #{Surviving infants} Factor: 100Data element versus indicator
- An indicator is not stored, so it cannot be wrong in the database — If a coverage figure is wrong, either a data…
- The indicator is evaluated at the level you request — and the arithmetic is sum-then-divide, not divide-then-average
Speaker notes
Three consequences that come up constantly. An indicator is not stored, so it cannot be wrong in the database. If a coverage figure is wrong, either a data element is wrong or the indicator definition is. Those have different owners and different fixes. The indicator is evaluated at the level you request, and the arithmetic is sum-then-divide, not divide-then-average. Requesting district-level coverage sums numerators and denominators across facilities; it does not average facility coverages. That is the right behaviour and it is the opposite of what a spreadsheet user usually does.Data element versus indicator — In Python
district = vax[vax["report_submitted"] == True] correct = (district["doses_administered"].sum() / district["target_population"].sum()) wrong = (district["doses_administered"] / district["target_population"]).mean() print(f"ratio of sums {correct:.3f}, mean of ratios {wrong:.3f}")Data element versus indicator — In R
vax |> filter(report_submitted) |> summarise(ratio_of_sums = sum(doses_administered) / sum(target_population), mean_of_ratios = mean(doses_administered / target_population))Data element versus indicator
- An indicator can be requested for a period the data does not support — Ask for annual coverage and DHIS2 will sum…
Speaker notes
An indicator can be requested for a period the data does not support. Ask for annual coverage and DHIS2 will sum twelve monthly numerators over a denominator that may be an annual figure or a summed monthly one, depending on how the denominator element is configured. Getting a plausible number out is not evidence that the aggregation was right.Datasets: the form, and what "expected" means — In Python
reported = vax["report_submitted"] == True print(f"{reported.sum()} of {len(vax)} facility-month-antigen values reported") print(f"reporting rate {reported.mean():.1%}")Speaker notes
A dataset is the form a group of data elements is collected on, assigned to a set of org units, with a period type. It is the thing that decides which units are expected to report, which is the foundation of every completeness figure the system produces.Datasets: the form, and what "expected" means — In Python
partial = ( vax[reported].groupby(["facility_id", "period"])["antigen"].nunique() ) print(f"{(partial < 6).sum()} facility-months with only some antigens reported")Speaker notes
76.5%. That number only means anything because a dataset assignment says all 38 facilities were expected to submit every month. Without the dataset, there is no denominator and no completeness. Note also what the extract does not carry: reporting here is all-or-nothing per facility-month across all six antigens, which is what a single dataset containing all six looks like. A system with the antigens split across two datasets would show partial months, and that structure would be visible in the data.Datasets: the form, and what "expected" means — In R
vax |> filter(report_submitted) |> summarise(antigens = n_distinct(antigen), .by = c(facility_id, period)) |> filter(antigens < 6) |> nrow()Speaker notes
Zero. One dataset, six elements, submitted together.Ask for the metadata, not just the data
- The data element definitions, including how each is aggregated over periods (summed, averaged, or last value — this…
- The category combination for each element, so you know what was collapsed.
- The dataset assignment, so you know how many units were expected.
- The indicator definitions for anything calculated, with numerator and denominator expressions.
Speaker notes
The practical conclusion of this lesson. When you request an extract, request the metadata with it: That is four short files and they turn an export into something a reference sheet can be written from. The Indicator Design course asked for a source down to the field; this is what that looks like when the source is DHIS2.What comes next
- You know what a value is and where it sits.
Speaker notes
You know what a value is and where it sits. The next lesson moves it — up an org unit hierarchy that is not fixed, where a facility reassigned between districts in March means a district total that cannot simply be summed.