NotebookUndergraduate data analysis. Some pandas and matplotlib assumed.

Count the orbits: a census you can defend

Count every orbit regime in seven requests instead of a hundred and sixty-five, assert the classes actually add up to the catalog total, chart it, and then see why taking the first page of a class is not a sample of that class.

orbitscensussampling-biaschartsvalidation
Runtime
30 min
API requests
13

Download

Jupyter notebook, 20 cells, 14 KBDownload the notebook

.ipynb file. Nothing executes until you run it.

Anonymous callers share 50 requests per day per network. A free account raises that to 1,000 per day. Each notebook states what one full run costs.

How to run it

  1. 1Install the packages listed above, for example: pip install requests pandas matplotlib
  2. 2Open Jupyter (jupyter lab) with the downloaded .ipynb, or upload it to any hosted notebook service.
  3. 3Run the cells top to bottom. No API key is needed; add one in the first cell if you want the higher limit.

What you will learn

  • Use a filtered page total to count a category without downloading its rows.
  • Write a reconciliation assertion that fails loudly when the categories stop partitioning the data.
  • Choose a log axis honestly when one category is two orders of magnitude larger than another.
  • Recognise ordering bias in a paginated API, and say what your sample actually is.

Before you start

  • pandas Series and groupby, and a first matplotlib figure.
  • The first-API-request notebook, or equivalent.

Python packages

pip install requests pandas matplotlib

Which data

The live catalog. On 2026-08-04 the seven classes summed to exactly the /stats total of 16,476 objects; the assertion in the notebook is what tells a future reader whether that still holds.

Endpoints it calls

Every request the notebook makes, and why. Nothing here is illustrative: these are the calls it runs.

  • GET /api/v1/satellitesOne request per orbit class, reading meta.total; then one page per class for the sampling section.
  • GET /api/v1/statsAn independently computed catalogue total, to reconcile the census against.
  • GET /api/v1/operatorsShows how much of the largest orbit class is a single operator.
/api/v1/openapi.json →

From the notebook

Excerpts copied verbatim from the file. A test fails if the notebook changes and these do not.

code
total_objects = get("/stats")["data"]["total_objects"]
summed = int(counts.sum())

print("sum of orbit classes:", summed)
print("total from /stats:   ", total_objects)
print("difference:          ", summed - total_objects)
markdown
That assertion is the point of the cell. If OrbitalWiki adds an eighth orbit class next year, this notebook fails loudly instead of quietly under-counting by however many objects landed in the class you did not ask for. Write the check that breaks when your assumption breaks.

What you end up with

  • A census table of all seven orbit classes, with a passing reconciliation assertion.
  • A labelled log-scale bar chart of the catalog by orbit class.
  • An apogee/perigee scatter whose title states, on the chart itself, that it is not a random sample.
  • A statement of what the catalog excludes, so the number is quotable without being misleading.

How to cite

Quote the filter with the number, never the number alone: "OrbitalWiki, /api/v1/satellites?orbit_class=LEO, 11,709 tracked objects, retrieved 2026-08-04". A satellite count without its filter is not reproducible.

Sources