Ontology engineering · taxonomy at scale

The taxonomy trap.

Pull the “organization” hierarchy out of Wikidata and you get 107,042 types. Nearly all are technically correct — and useless. Seven pitfalls turn a community knowledge graph into a Library of Babel, and a systematic pipeline turns it back into a tool.

Written for Ontologists Data engineers cleaning a large, inherited hierarchy

107,042 kinds of organization

Extract the subclass closure of “organization” (Q43229) from Wikidata — the largest open knowledge graph in existence — and it returns a tree of 107,042 distinct types. Not organizations. Types of organization. It runs seven levels deep in most branches, twelve in others, and it contains “bank,” yes — but also “Panzer division of the Wehrmacht,” “former Mayor of a German municipality,” and several thousand variants of “X in [Country]” that nobody asked for.

Every entry is technically correct. Each exists for a reason: someone looked at real entities and decided they deserved a category. The information is not wrong. It is merely useless in this form — or actively misleading. This is the taxonomy trap, and it is the natural state of any large, community-built knowledge structure. Individuals optimise for their own local purposes; local optimisations accumulate into global incoherence.

The one question to ask of every node

Is this a genuine kind of thing — or a thing in a particular state, a thing in a particular place, a thing playing a particular role, or simply a particular thing? Almost all taxonomy pollution is one of those four mistakes wearing the mask of a class.

A real ontological class is a reusable concept that (1) can have many real instances, (2) carries logical implications for all its members, and (3) inherits those implications from its parents. When we say University ⊑ EducationalOrganization, we claim that every university, without exception, is an educational organization. That is stronger than it sounds — and it rules out most of what people actually put in hierarchies. A node named “University of Oxford” fails test 1 (one instance). “Former university” fails by conflating a changeable status with an enduring type.

The seven pitfalls below name the ways a hierarchy fails those tests. Applied as a pipeline, they take the organization tree from 107,042 nodes to roughly 2,100 defensible types — and flag the 200–300 genuinely hard cases for a human.

107,042
raw P279 closure of Q43229
28,000
after removing offices & roles (Pitfall 1)
2,345
after dimension folding (Pitfall 2)
~2,100
defensible structural types
FAMILY A — THE WRONG KIND OF NODE

Five ways something that isn’t a type poses as one

The first five pitfalls are semantic: a node sits in the class hierarchy but is not, on inspection, a kind of the thing above it. Each has a label signature you can learn to see, and each has a clean repair that loses no information — it just moves the fact to where it belongs.

PIT-1 role vs type

The Role Trap — an office is not a kind of organization

Of the 107,042 nodes, 64% were not organizations at all. They were offices, hanging off the “governing body” node (Q895526): “Mayor of Berlin,” “Director-General of UNESCO,” “Commander of the Third Army Corps.” A governing body technically is an organizational construct, so the modeling is valid — but a role is a position an entity occupies, not an intrinsic kind. A city government does not become a different type of organization when its mayor changes.

The tell: labels with “of”/“for” + a proper noun (“Chancellor of Germany”), authority titles (Governor, Commander, Commissioner, Inspector), and whole subtrees rooted at Position, Office, Post or Appointment.

:MayorOfBerlin  rdfs:subClassOf  :Organization .   # an office is not an org type

# The fix: the organization CREATES a role; individuals OCCUPY it.
:Municipality   core:createsRole  :Mayor .
:Mayor          core:withinOrganization  :Municipality ;
                core:occupiedBy  ex:AngelaX .     # P31 to a person, not P279

Chubb already does this right. core.ttl’s role triangle — a PartyRole individual, a role-filler class defined by hasRole, and the property it points at via rolePropertyis the position ontology this pitfall asks for. See AP-4, role as a rigid subclass.

PIT-2 dimension vs type

The Dimension Trap — why there aren’t 19,000 kinds of hospital

Remove the offices and 28,000 nodes remain — still too many, because of a subtler pathology. “Hospital” should have perhaps twenty structural subtypes. Instead it has German hospital, French hospital, Catholic hospital, public hospital, former hospital, hospital in France… Each base concept spawns a combinatorial product of country × scope × denomination × ownership × historical-state. Fifty countries, five scopes, ten denominations, two states = 5,000 variants of one concept.

A dimension is any property whose value varies independently of the fundamental type. The test: move a hospital from Germany to France — does its type change? No. Change its specialisation from pediatrics to oncology — does its type change? Yes. The first is a dimension (a property); the second approaches a real subclass determinant.

:GermanHospital  rdfs:subClassOf  :Hospital .
:CatholicHospital  rdfs:subClassOf  :Hospital .
:FormerHospital  rdfs:subClassOf  :Hospital .

# The fix: one class, the dimensions as properties on the instance.
ex:StMary  a :Hospital ;
    core:hasLocation       country:DE ;
    core:hasDenomination   denom:Catholic ;
    core:operatingStatus   status:Closed ; core:closureDate "1998" .
geography · scope

Where it operates

“Japanese bank,” “hospital in France,” “national museum,” “local party.” Location and scale are properties.

denomination · mission

Whose values it serves

“Catholic school,” “public hospital,” “non-profit museum.” Affiliation and ownership are properties.

historical state

What state it is in

“Former,” “defunct,” “dissolved.” A former hospital is still a hospital — just operatingStatus: closed.

The folding rule — the intellectual crux. Not every qualifier is incidental; some are definitional. Before removing a variant, strip the qualifier and check whether a direct parent in the cleaned hierarchy already carries the stripped label. If yes, fold it up. If no, the qualifier may be definitional — keep the node for human review.

Fold up — redundant qualifier

  • “Japanese high school” → base “high school”
  • parents include high school — the base is a parent
  • Remove; retag instances to high school.

Keep — qualifier is definitional

  • “American football team” → base “football team”
  • parents are football club, sport team — base is not a parent
  • “American” names a different sport. Keep it.

Related: the same instinct that mints a subclass per country mints a subclass per code value — see AP-3, value modeled as a class, and put the values in a SKOS scheme you can govern.

PIT-3 instance vs type

The Instance Trap — when a specific bank becomes a “type” of branch

Under bank branch sat 75 subclasses: “HDFC Bank branch,” “ICICI Bank branch,” “State Bank of India branch…” — each with hundreds of instances, because every physical HDFC branch had been tagged instance of: HDFC Bank branch. A named specific entity had been modeled as a type, usually a data-entry shortcut: it is easier to mint a class than to look up the parent bank for an operatedBy link.

Three tells: a proper-noun label (“HDFC Bank branch”), a leaf with 500+ instances and zero children, and the “[Named Entity] [generic type]” construction where the generic type already exists in the hierarchy.

:HDFCBankBranch  rdfs:subClassOf  :BankBranch .    # a proper noun as a class
ex:BranchA  a  :HDFCBankBranch .

# The fix: the branch is an instance of the generic type; the bank is a property.
ex:BranchA  a :BankBranch ; :operatedBy ex:HDFCBank .
ex:BranchB  a :BankBranch ; :operatedBy ex:HDFCBank .   # class :HDFCBankBranch deleted
PIT-4 one kind vs another

The Category Mistake — when a migration became an organization

Gilbert Ryle’s category mistake: treating something of one ontological kind as another. Entire subtrees under “organization” were events, phenomena or belief systems — “military campaign,” “human migration,” “diaspora,” “Buddhism,” “conspiracy theory.” They arrive through polyhierarchy: a “protest march” is both an event and an organized group action, and if that second parent links to “organization,” the march is dragged in — not by intent, but by the path of P279 edges.

The substitution test: read it as “a military campaign is a kind of organization.” An organization is persistent, with members and governance. A campaign is time-bounded, with participants and a command structure that exists only while it runs. It fails — not because the words feel wrong, but because the structural features that define “organization” are absent.

# At scale, automate the test with property signatures.
for C in hierarchy:
    inst = entities where P31 == C
    temporal = fraction(inst with start_date and end_date)
    spatial  = fraction(inst with coordinates)
    if temporal > 0.80:                       # classify C as EVENT_TYPE
    if spatial > 0.80 and temporal < 0.20:      # classify C as PLACE_TYPE

~85% accurate on the cases it adjudicates, and it flags the big subtrees (campaigns, migrations, legislative sessions) that human review then confirms. It is the scalable cousin of AP-1, making an entity a subclass of the wrong kind.

PIT-5 what it is vs what it does

The Industry Trap — Apple is not (only) a technology company

“Technology company,” “oil company,” “pharmaceutical company” are real, useful categories — but they classify organizations by business domain, not by structure. Apple is a technology company, a retail company, a media company and a financial-services company at once. Subclassing forces it into multiple, conflicting inheritance chains. That multiplicity is the sign the dimension is wrong.

  • It can join or leave without changing form. A firm can exit cement and enter software — same legal entity, same governance.
  • It is defined by what the org does, not what it is. “Corporation” is structural; “pharmaceutical company” is an activity.
  • It overlaps with its siblings. Media and technology at once — a domain tag, not an exclusive category.
# Structure stays in OWL — logical commitments, inheritance.
biz:Corporation  a owl:Class ; rdfs:subClassOf biz:Company .

# Industry becomes a SKOS concept scheme — flexible domain tagging.
ind:Technology   a skos:Concept ; skos:inScheme ind:IndustryScheme .

# The instance is one structural type, tagged with many industries.
ex:Apple  a biz:Corporation ;
    biz:hasIndustry  ind:Technology , ind:Retail , ind:Media .

Full brief: A vocabulary you can govern — why industries, code lists and taxonomies belong in SKOS, kept out of the OWL class hierarchy, so you keep both logical rigor and flexible retrieval.

FAMILY B — THE WRONG KIND OF TRAVERSAL

Two traps in the machinery, not the meaning

The last two pitfalls are not about what concepts mean but about how algorithms walk the graph and where you draw the cut-off. They are the most technically treacherous, because they produce silently wrong results — a hierarchy that looks complete but is not.

PIT-6 graph, not tree

The Diamond Trap — traverse fully, then subtract

Wikidata has multiple inheritance: “administrative territorial entity of Canada” is a child of both “administrative territorial entity” (which you want to exclude) and “Canadian organization” (which you want to keep). That diamond breaks the obvious algorithm. If you block descent whenever you enter an excluded subtree, the node still reaches your results through the other, non-excluded path — and you cannot tell during traversal whether you arrived “through” or “around” an excluded parent.

# WRONG — block during traversal
def bfs_with_blocking(root, excluded):
    while queue:
        n = queue.pop()
        if n in excluded: continue   # the other path still delivers n
        visited.add(n); queue.extend(children[n])

# RIGHT — traverse everything, then subtract the closures
def bfs_full_then_subtract(root, excluded_roots):
    all_nodes = full_bfs(root)                       # ignore exclusions
    excluded  = union(expand_subtree(r) for r in excluded_roots)
    return all_nodes - excluded                       # identity, not path

The lesson: inclusion in an excluded set is a property of the node, not of the traversal path. We found this empirically — Q3750285 survived the naive blocker because a non-geographic parent still reached it.

PIT-7 keeping everything

The Threshold Illusion — the danger of keeping everything

Even at 28,000 nodes, the tree is too large to review or maintain. A simple proxy sorts genuine types from noise: count how many real entities claim to be of that type. Leaf nodes with fewer than ~50 instances are, in practice, almost never genuine structural types — they are named entities misfiled as classes, or concepts too narrow to add information (“Briquette Factory” tells you little more than “manufacturing facility”).

Instance countSample leaf nodesVerdict
< 10“pizza delivery service,” “Karzer,” “Briquette Factory,” “concrete plant”hyper-specific / misfiled
10 – 19“palaestra,” “studiolo,” “Harem”too narrow to classify
20 – 49“computer manufacturer,” “congregation”borderline
50 – 99“college of the University of Oxford,” “community radio”keep
100+“hospital,” “school,” “bank,” “museum”clearly a type

Fifty is not magic — it is a proxy for “has the world provided enough evidence, in typed entities, that this concept deserves a named class?” And the pruning must run iteratively to a fixed point: removing a leaf can orphan its parent, whose count may now be below threshold too. In this cleanup, nine iterations were needed, and ~40% of all removals happened in rounds 2 through 9. A single pass would have left thousands of empty intermediate nodes organizing nothing.

Not a guarantee. Some legitimate types fall below 50 because the graph hasn’t cataloged them yet; some dubious ones exceed it through rapid historical entry. As a probabilistic filter at scale, though, it is strikingly effective — and it is the last step, applied only to leaves.

WHERE THE PIPELINE STOPS

The cases no algorithm settles

The pitfalls overlap in uncomfortable ways — a node can be both a dimension variant and a specific instance — and any ontology of scale contains dozens of genuinely hard cases. Honesty demands the admission: no automated pipeline handles these correctly.

madrasa

Dimension or distinct type?

“School + Islamic denomination,” or a genuinely distinct educational form with its own pedagogy and governance across centuries and dozens of countries?

Silicon Valley startup

Industry tag or real form?

An industry-as-class violation on its face — yet it captures a real funding-and-growth model with structural implications for how the org operates and exits.

Soviet kolkhoz

Variant or structure?

Country-specific and historical — but a distinct structure (state ownership, collective labor, quotas) that reduces cleanly to no Western equivalent.

These need a person — ideally a domain expert — who can say what the taxonomy is for, what inferences it must support, and how much cultural specificity its purpose warrants. The pipeline earns its value not by deciding these, but by reducing 107,042 nodes to the 200–300 that genuinely require human judgment, and flagging them clearly enough to work through in days rather than months.

QUICK REFERENCE

Seven rules for the practicing ontologist

The seven pitfalls, the tell that gives each one away, and the move that fixes it. Keep it beside a taxonomy review.

#PitfallThe tellThe rule
PIT-1Role Trap“X of/for [Proper Noun]”; titles of authorityRoles belong in a position ontology, linked via createsRole
PIT-2Dimension Trapcountry / scope / denomination / status variantsEncode as properties; fold only when a parent carries the base · folding rule
PIT-3Instance Trapproper-noun class, 500+ instances, no childrenDemote to an instance; connect via a property
PIT-4Category Mistakeevents / places / beliefs under “organization”Substitution test; automate with property signatures
PIT-5Industry Trapclass named after a business domain, overlapping siblingsIndustries go in a SKOS scheme, not OWL subclasses · brief
PIT-6Diamond Trapexcluded node survives via a second parentTraverse fully, then subtract — identity, not path
PIT-7Threshold Illusionthin leaves; empty intermediate nodesPrune leaves below ~50 instances, iteratively to a fixed point

The parent test, one more time

Before you keep any node, ask what it really is. A kind stays. A state, a place, a role becomes a property. A particular becomes an instance. Everything else is a category mistake — and the reasoner will faithfully propagate it if you let it stand.

CLOSING

The Library and its purpose

The universe (which others call the Library) is composed of an indefinite, perhaps infinite number of hexagonal galleries… Jorge Luis Borges, The Library of Babel

The horror of Borges’ Library is not its size — it is its uselessness. Every book exists; no book can be found. A knowledge graph with 107,042 types of organization is a Library of Babel: every type is there, and none of them helps you find what you are looking for.

The real work of ontology engineering — the unglamorous work of removing, folding, reclassifying and pruning — is the work of turning a library into a tool. It requires being willing to throw away true things in order to make useful things, and to impose a purpose on a structure that was built without one. The engineer who knows the pitfalls can take 107,042 nodes down to about 2,100 defensible, semantically rigorous types in a few weeks. It is less glamorous than building new things. It is arguably more valuable.

✓ How to use this in a review

Walk any inherited hierarchy against the four masks. Is each node a genuine kind — or a role (PIT-1), a dimension (PIT-2, PIT-5), an instance (PIT-3), or a different kind entirely (PIT-4)? Then check the machinery: did your exclusions subtract after a full traversal (PIT-6), and did you prune leaves to a fixed point (PIT-7)? Send the survivors that resist every rule to a human, and stop there.