Exploratory Data Analysis
spacering-net/codeg
Perform comprehensive exploratory data analysis on scientific data files across 200+ file formats.
Audit every surface that renders a dataset's indicators — charts, map tabs, MDim views, explorer views, narrative charts, and article references — for views whose pinned entity selection has no data…
$ npx skills add owid/etl --skill check-empty-entities -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install owid/etl check-empty-entities --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/owid/etl.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.claude/skills/check-empty-entities .claude/skills/check-empty-entities && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "check-empty-entities" agent skill from https://github.com/owid/etl/tree/master/.claude/skills/check-empty-entities into .claude/skills/check-empty-entities/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "check-empty-entities", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/owid/etl/tree/master/.claude/skills/check-empty-entitiesType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add owid/etl --skill check-empty-entities -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install owid/etl check-empty-entities --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/owid/etl.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.claude/skills/check-empty-entities .agents/skills/check-empty-entities && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "check-empty-entities" agent skill from https://github.com/owid/etl/tree/master/.claude/skills/check-empty-entities into .agents/skills/check-empty-entities/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "check-empty-entities", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add owid/etl --skill check-empty-entities -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install owid/etl check-empty-entities --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/owid/etl.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.claude/skills/check-empty-entities .cursor/skills/check-empty-entities && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "check-empty-entities" agent skill from https://github.com/owid/etl/tree/master/.claude/skills/check-empty-entities into .cursor/skills/check-empty-entities/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "check-empty-entities", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/owid/etl.git --path .claude/skills/check-empty-entities--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add owid/etl --skill check-empty-entities -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install owid/etl check-empty-entities --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/owid/etl.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.claude/skills/check-empty-entities .gemini/skills/check-empty-entities && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "check-empty-entities" agent skill from https://github.com/owid/etl/tree/master/.claude/skills/check-empty-entities into .gemini/skills/check-empty-entities/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "check-empty-entities", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install owid/etl check-empty-entitiesInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add owid/etl --skill check-empty-entities -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/owid/etl.git skills-src && mkdir -p .github/skills && cp -r skills-src/.claude/skills/check-empty-entities .github/skills/check-empty-entities && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "check-empty-entities" agent skill from https://github.com/owid/etl/tree/master/.claude/skills/check-empty-entities into .github/skills/check-empty-entities/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "check-empty-entities", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add owid/etl --skill check-empty-entities -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install owid/etl check-empty-entities --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/owid/etl.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.claude/skills/check-empty-entities .opencode/skills/check-empty-entities && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "check-empty-entities" agent skill from https://github.com/owid/etl/tree/master/.claude/skills/check-empty-entities into .opencode/skills/check-empty-entities/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "check-empty-entities", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
check-empty-entitiesAudit every surface that renders a dataset's indicators — charts, map tabs, MDim views, explorer views, narrative charts, and article references — for views whose pinned entity selection has no data…
Check Empty Entities is an agent skill from owid/etl. Audit every surface that renders a dataset's indicators — charts, map tabs, MDim views, explorer views, narrative charts, and article references — for views whose pinned entity selection has no data in the new indicators (they render as empty charts with no error anywhere). Grades findings against production to separate update regressions from pre-existing gaps. Use when the user asks to "check for empty entities/views/charts", or as the optional audit step offered by /update-dataset (step 7) and /review-data-pr…
Its SKILL.md is about 4.8k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Data & Analytics. The repository describes itself as: A compute graph for loading and transforming OWID's data. The licence is MIT.
5 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit bf5dc8e. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
No scripts in the folder and no shell commands in SKILL.md (its code samples are python).
From the folder's file list and the shell code blocks in SKILL.md.
Hosts in commands or code, which the agent is likely to contact:
api.ourworldindata.orgFrom URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Check Empty Entities loads about 4.8k tokens when it runs. Until then it costs about 163 tokens; SKILL.md has 2,236 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from owid/etl at commit bf5dc8e, republished under its MIT licence (© owid). 2,236 words, ~4,799 tokens.
.claude/skills/check-empty-entities/SKILL.md (or your agent's skills folder).After an indicator upgrade, a view can end up pinned to entities that have no data in the new indicators. Nothing fails: the pipeline is green, chart-diff renders, and the chart silently opens empty. This skill audits every surface that stores an entity selection against the entities that actually have data, and grades each finding against production so regressions and pre-existing gaps aren't conflated.
Called from:
/update-datasetstep 7 (author side — fix regressions before merge) and/review-data-pr§8d (reviewer side — verify the outcome). Keep those pointers in sync with this file.
Marigold/wdi-update) — determines the DB (OWIDEnv.from_staging(branch)) and the indicators API prefix.<ns>/<version>/<short_name> or its datasets.id on staging.A variable's entities-with-data come from the indicators API metadata.json — not MySQL (indicator data lives outside the DB):
OWIDEnv.from_staging(branch).indicators_url + /<id>.metadata.json → dimensions.entities.values[].name. Don't hand-build the staging-site-<branch> prefix — branch names with /, ., _ or over 28 characters get normalized/truncated (etl.config.get_container_name()), and a wrong prefix silently serves another environment instead of 404ing.https://api.ourworldindata.org/v1/indicators/<id>.metadata.jsonCache per variable id and fetch in parallel (a large dataset means hundreds of variables). A failed fetch is unknown availability, not an empty set — track those variable ids separately and report them as coverage caveats; never grade a view on them.
Overwhelmingly from entity-rename cycles of a dataset's own aggregate entities — a past update changed the suffix convention (Multilaterals (OECD) → Multilateral organizations, Low-income countries (WB) → hyphenated unsuffixed forms) and every surface that pinned the old names kept them. Real-country selections almost never die; dataset-defined aggregates (donor groups, income groups, provider regions) are the population to watch. Two consequences for the audit: the moment one finding surfaces a renamed-suffix pattern, grep every surface for that pattern directly (chart selectedEntityNames and focusedSeriesNames, narrative-chart patches, article country= URLs) instead of relying only on per-view availability checks — the same rename hits them all; and note that dead names sit in both directions (an old suffixed form can die while its unsuffixed twin lives, or vice versa — check availability, don't pattern-guess the fix). (ODA 2026-07: one rename cycle left dead names on 5 charts, 6 narrative charts, and 4 published articles simultaneously.)
String similarity picks the fix, never validates it — it proposed Nigeria → Niger (different countries) in a real sweep. Before applying any rename, read the chart's whole selection, and gate every edit on three checks: the replacement must have data in that chart's own y-variables, must not already be selected, and the result must contain no duplicates. Drops need the mirror guard — refuse to remove any entity that does have data.
Reading the whole selection is what catches the two failure modes similarity can't:
South Asia, North America). Applying them silently mixes two incompatible region definitions in one chart. The fix is a decision about which scheme the chart should use, not a rename — put it to the dataset owner.Sub-Saharan Africa and Sub-Saharan Africa (WB), or both the old and new MENA names. (Watch for genuinely renamed aggregates too: the World Bank's Middle East and North Africa became Middle East, North Africa, Afghanistan and Pakistan (WB) — a definitional change, not cosmetic.) Same for aggregates we never carry: Middle-income countries isn't one of the four WB income groups, and charts pinning it usually already have Upper-middle-income countries and Lower-middle-income countries.Keep selectedEntityColors in step with every edit: on a rename, move the entry from the old name to the replacement (deleting it discards a deliberately assigned color — a visual regression); on a drop, delete the entry. Either way, don't leave a dangling color key. And check the excluded-countries file before proposing anything: an entity in <short_name>.excluded_countries.json (IDA only, Arab World, Heavily-indebted poor countries) can never have data, so a drop is the only option.
Getting the surface list. find-chart-references sweeps every surface in §1–§4
from a dataset or indicator subject and returns each reference with its
query_string (where country= pins live) and an embed/render/link
classification — use it (--dataset-id, plus --transitive for the article hop)
rather than re-deriving the joins. It also expands chart subjects through
chart_slug_redirects, which §4 requires. What stays here is the part it can't do:
reading each surface's entity selection, checking it against entities-with-data, and
grading the result against production.
For every chart on the new dataset (chart_dimensions → variables.datasetId), parse chart_configs.config:
selectedEntityNames must intersect the union of the chart's y-variables' entities-with-data. Zero overlap on a non-empty selection = the chart renders empty.selectedEntityNames (they plot all entities). Detect them by shape, not by the type field: a chart with an x dimension renders as a scatter even when type is absent (reporting as the LineChart default). An empty selection means every entity renders, not none — so a bad value in such a chart is maximally visible, not hidden. (share-of-rural-population-with-electricity-access-vs-…, 0 selected + minTime: latest, is where a reader spotted Chad plotted at 100% rural electricity access.) Phrasing a report line as "corrected entity not in selection" for these charts is actively misleading.chartTypes: [] with hasMapTab: true has no chart tab, so selectedEntityNames never renders and a dead or partly dead selection is harmless. Note that [] is not the absent-field case: an absent chartTypes defaults to LineChart and must still be checked. The map itself is still covered by §2.For every chart with hasMapTab, validate map.columnSlug only when it is set — an absent columnSlug is valid (grapher defaults the map to the first y variable), so flagging None produces false blockers. When present it must be one of the chart's dimension variable ids; it's stored as a string — str-cast before comparing (the int-vs-string mismatch is exactly how the upgrader left map tabs pinned to old variables for years; see #6457). A set columnSlug that resolves to a variable outside the chart's dimensions, or to a dangling id, is a finding.
Same selection-vs-availability check on their configs:
config_id, covering all MDims (not just the dataset's own — another MDim can carry these variables in its y-dimensions) and finding multi-indicator views that mx.variableId alone misses, since that column records only the first y indicator.selectedEntityNames too, so an upgraded explorer view can be empty while everything else passes. The sweep returns them one row per view (surface = "explorer view"), each with its own config_id, so they go through the same config loop as charts and MDim views. A surface = "explorer" row with no config_id means the indicator is registered on that explorer but no view config names it — report it as unchecked rather than passing it. Legacy CSV-backed explorers (data://explorers/... wide tables — e.g. the poverty explorer) appear in no DB table at all: their data and selections live in the explorer TSV, outside grapher configs, so report them as a coverage caveat instead of silently passing.narrative_charts.patchConfigId → chart_configs.config lacks every inherited field, so a narrative chart inheriting selectedEntityNames or dimensions from its parent can falsely pass — use AdminAPI(OWIDEnv.from_staging("<branch>")).get_narrative_chart(id)["configFull"] (this is the stored materialized chart_configs.config; it lags a parent edit until the child is re-saved — see /update-dataset step 7's narrative-chart notes). Pass the staging env explicitly — the global OWID_ENV points at your local/default environment unless the process was launched with STAGING=<branch>, and reading narrative configs from the wrong DB silently hides staging-only regressions.Article rows come back from the sweep with their query_string; the ones carrying country= pin entities in the URL, and the upgrader never rewrites these. (The sweep covers linkType='url' rows pointing at live grapher pages too, and drops archive.ourworldindata.org snapshots — frozen by design.) Filter to published rows for reader-facing findings. Parsing rules learned the hard way:
~-separated; legacy URLs use +, which parse_qs decodes to spaces — a chunk with spaces may itself be one entity name ("South Asia"), so try a full-chunk match first, then greedy multi-word matching against the entities table.$entityCode / $entityName template placeholders (country-page dynamic embeds).entities table (code → name).posts_gdocs_links.target through chart_slug_redirects too — embeds often use old slugs.country= references are exactly that — URLs on words, sometimes split across several adjacent anchors by formatting runs. The handoff must say to click each link → edit → paste the corrected URL (only {.chart} url: lines are plain text and F&R-able). When building the corrected URL, edit at token level and anchor deletions on the leading ~ — entity names prefix-overlap (DAC+countries+%28OECD%29 is a substring of Non-DAC+countries+%28OECD%29), so a naive substring replace corrupts both.find_chart_citations_in_content from apps/wizard/app_pages/chart_diff/citations.py. Caveats: its embedded-chart pass only scans top-level body blocks (recurse yourself for charts nested in layout containers), most country= references turn out to be hyperlinks (its second pass, which does recurse), and data insights may store the chart reference where neither pass looks — fall back to the data-insight page URL. Wrap fragment URLs in <angle brackets> in markdown (they can contain parentheses).posts_gdocs_links lags and can show the stale queryString well after the edit is live — fetch the published article URL and grep its grapher URLs / country= params instead (e.g. a fixed data-insight grapher-url showed on ourworldindata.org minutes before the mirror caught up).For every finding, fetch the same chart's production y-variables (chart ids are shared; get prod chart_dimensions via public Datasette) and their entity lists from the production API:
One pass over staging, then a grading pass against production:
import json
from concurrent.futures import ThreadPoolExecutor
from etl.config import OWIDEnv
from etl.http import session as http_session
env = OWIDEnv.from_staging("<branch>")
PREFIX = env.indicators_url # normalized container name — never hand-build staging-site-<branch>
cache = {}
def entities(var_id, prefix=PREFIX):
key = (prefix, var_id)
if key not in cache:
r = http_session.get(f"{prefix}/{var_id}.metadata.json", timeout=60)
cache[key] = {e["name"] for e in r.json()["dimensions"]["entities"]["values"]} if r.ok else None
return cache[key]
# Surfaces come from find-chart-references (--dataset-id ... --json refs.json);
# every config-bearing row carries a config_id, so one query fetches them all.
refs = [r for r in json.load(open("refs.json")) if r["config_id"]]
ids = tuple({r["config_id"] for r in refs})
if not ids: # `WHERE id IN ()` is a MySQL syntax error, not an empty result
raise SystemExit("No config-bearing references — report the unchecked surfaces instead.")
cfgs = env.read_sql(
"SELECT id, slug, config FROM chart_configs WHERE id IN %(i)s",
params={"i": ids},
)
for _, row in cfgs.iterrows():
cfg = json.loads(row["config"])
types = cfg.get("chartTypes", ["LineChart"])
sel = cfg.get("selectedEntityNames") or []
y_ids = [d["variableId"] for d in cfg.get("dimensions", []) if d.get("property") == "y"]
if cfg.get("hasMapTab"):
slug = (cfg.get("map") or {}).get("columnSlug")
if slug is not None: # absent = grapher defaults to the first y variable (valid)
assert str(slug) in {str(d["variableId"]) for d in cfg["dimensions"]}
ents = [entities(v) for v in y_ids]
if any(e is None for e in ents):
unknown.append(row["slug"]) # fetch failed = unknown availability — coverage caveat, never a finding
continue
dead_vars = [v for v, e in zip(y_ids, ents) if not e]
if dead_vars:
... # broken indicator (zero entities) — a finding on EVERY chart type, so check before the scatter skip
if types and types[0] in ("ScatterPlot", "Marimekko"):
continue # no pinned selection to check
if types == [] and cfg.get("hasMapTab"):
continue # map-only: the selection never renders
avail = set().union(*ents) if ents else set()
if sel and not (set(sel) & avail):
... # finding -> grade against productionThe same loop covers MDim and explorer views — they are chart_configs rows the sweep already returned a config_id for. Two surfaces need their own handling: narrative charts (merged parent+patch via AdminAPI.get_narrative_chart(id)["configFull"], never the bare config_id row) and article references (parse country= out of each row's query_string). Query gotcha: pymysql %-formats break on quoted literals and LIKE patterns — parameterize everything (params={...}), and use CHAR_LENGTH(x) = 0 instead of x = ''.
OWIDEnv.from_staging(branch).chart_site(slug) — same normalized-host rule as the API prefix; never hand-build staging-site-<branch>), and dead entities — the common pattern is a rename-cycle mismatch between the URL and the live entities (unsuffixed names in old URLs while data lives under suffixed entities, or stale (OECD)/(WB)-suffixed names after the data moved to unsuffixed forms).End every run by saying what's still open — a line or two in chat, written out in the PR body when there is one. .claude/docs/open-items.md lists what tends to get dropped. The "nobody checked it" category matters most here: fetch failures, unparsed legacy explorer TSVs, and surfaces skipped for cost read as "clean" when unmentioned, and a pre-existing gap left unlisted gets re-discovered from scratch next cycle.
© owid, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in .claude/skills/check-empty-entities of owid/etl.
Open the folder on GitHubat commit bf5dc8e
Check Empty Entities next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Check Empty Entities this skillowid/etl | 158 | — | ~4.8k | Automated safety check: Pass | MIT | |
| Exploratory Data Analysisspacering-net/codeg | 3.8k | 15 repos | ~3.6k | Automated safety check: Pass | MIT | |
| MatplotlibzLanqing/codex-claude-academic-skills | 4.6k | 18 repos | ~2.9k | Automated safety check: Pass | MIT | |
| Scikit LearnzLanqing/codex-claude-academic-skills | 4.6k | 17 repos | ~3.9k | Automated safety check: Pass | BSD-3-Clause | |
| Chart Visualizationbytedance/deer-flow | 83k | 2 repos | ~840 | Automated safety check: Pass | MIT | |
| TimesFM Forecastinggoogle-research/timesfm | 34k | — | ~4.7k | Automated safety check: Pass | Apache-2.0 |
spacering-net/codeg
Perform comprehensive exploratory data analysis on scientific data files across 200+ file formats.
zLanqing/codex-claude-academic-skills
Low-level plotting library for full customization. An agent skill from zLanqing/codex-claude-academic-skills.
zLanqing/codex-claude-academic-skills
Machine learning in Python with scikit-learn. An agent skill from zLanqing/codex-claude-academic-skills.
bytedance/deer-flow
Picks a suitable chart type from 26 options for your data, maps the data to that chart's parameters and generates a chart image through a JavaScript script.
google-research/timesfm
Forecasts any univariate time series zero-shot with Google's TimesFM model, returning point forecasts and calibrated prediction intervals without training.
vercel/next.js
Benchmark React or Next.js changes on Vercel Sandbox VMs with paired A/B statistics: react PR/commit vs base, or Next.js PR/commit vs base, measured end-to-end through the bench/render-pipeline app…
owid/etl
Find every OWID surface that references a chart, indicator, MDIM, or explorer — articles (links vs embeds), explorers, narrative charts, data insights, static viz, key-chart slots, MDIM views.
owid/etl
Add a scatter view (with GDP per capita on x) to existing OWID charts via the admin API, mirroring the admin UI's "Add scatter type" defaults, then retire the old standalone "X vs.
owid/etl
Add new survey question codes (e.g. An agent skill from owid/etl.
owid/etl
Build or refresh an OWID static visualization end to end — resolve what data it needs from an old static viz image, an indicator, or a grapher chart; check both the ETL catalog and the producer's…
owid/etl
Propose redirects from (soon-to-sunset) grapher charts to the matching views of published MDIMs.
owid/etl
Take (soon-to-sunset) OWID explorers to redirected MDIMs, end to end.
Categories
Audit every surface that renders a dataset's indicators — charts, map tabs, MDim views, explorer views, narrative charts, and article references — for views whose pinned entity selection has no data…. Check Empty Entities is an agent skill from owid/etl. Audit every surface that renders a dataset's indicators — charts, map tabs, MDim views, explorer views, narrative charts, and article references — for views whose pinned entity selection has no data in the new indicators (they render as empty charts with no error anywhere).
Check Empty Entities fits situations like: the user asks to check for empty entities/views/charts; as the optional audit step offered by /update-dataset (step; /review-data-pr (§8d) — offered rather than automatic because the full sweep can consume many tokens on widely-charted datasets.
Run `npx skills add owid/etl --skill check-empty-entities -a claude-code`. Or copy the skill folder (.claude/skills/check-empty-entities in owid/etl) into .claude/skills/check-empty-entities in your project. Claude Code loads it when a task matches its description.
Run `npx skills add owid/etl --skill check-empty-entities -a codex`. Or copy the skill folder (.claude/skills/check-empty-entities in owid/etl) into .agents/skills/check-empty-entities in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add owid/etl --skill check-empty-entities -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/check-empty-entities, .gemini/skills/check-empty-entities, .github/skills/check-empty-entities and .opencode/skills/check-empty-entities in your project.
SKILL.md names no scripts, command-line tools or credentials: Check Empty Entities is instructions for the agent only.
SKILL.md names 1 domain. In commands or code: api.ourworldindata.org; the agent is likely to contact it when it follows the instructions. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Check Empty Entities is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 4.8k tokens (SKILL.md is roughly 19k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Check Empty Entities: Exploratory Data Analysis (spacering-net/codeg, 3.8k stars), Matplotlib (zLanqing/codex-claude-academic-skills, 4.6k stars), Scikit Learn (zLanqing/codex-claude-academic-skills, 4.6k stars) and Chart Visualization (bytedance/deer-flow, 83k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
owid (a GitHub organization) maintains it in owid/etl, which has 158 GitHub stars. The repository holds 35 skills in this directory. The repository was last updated on October 8, 2026.
Source: owid/etl on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.