Chuyển đến nội dung chính

Standardized Vocabularies & Athena: the heart of OMOP CDM

Duy Tran16 min
Standardized Vocabularies & Athena: the heart of OMOP CDM

You can skip this section and still build OMOP — but your analytics will always be wrong. Vocabulary is what makes OMOP meaningful. This article is a deep dive into Concepts, hierarchies, mappings, and the Athena workflow.

1. Why vocabulary matters

A real-world example: you have three data sources:

  • Hospital A: diagnosis code "ICD-10 E11.9"
  • Hospital B: code "ICD-10 E11"
  • Hospital C: SNOMED code "44054006"

All three describe type 2 diabetes. Without standardization, "total number of type 2 diabetes patients" returns the wrong answer.

OMOP solves this by picking one Standard Concept (here concept_id = 201826, SNOMED 44054006) and mapping every source code to it.

2. Concept — the basic unit

A Concept is one row in the CONCEPT table:

concept_idconcept_namedomain_idvocabulary_idconcept_class_idstandard_conceptconcept_code
201826Type 2 diabetes mellitusConditionSNOMEDClinical FindingS44054006
192279Diabetic complicationConditionSNOMEDClinical FindingC74627003
45757466E11 (ICD-10-CM)ConditionICD10CMICD10CM code(null = Source)E11

standard_concept:

  • S (Standard): use this for analytics
  • C (Classification): for classification, not a standard concept
  • NULL (Source): the original code, used for source_value

3. Vocabulary and Domain

Vocabulary and Domain

DomainOMOP tablePrimary Standard Vocabulary
ConditionCONDITION_OCCURRENCESNOMED CT
DrugDRUG_EXPOSURERxNorm + RxNorm Extension
ProcedurePROCEDURE_OCCURRENCESNOMED CT, CPT4, ICD-10-PCS
MeasurementMEASUREMENTLOINC, SNOMED CT
ObservationOBSERVATIONSNOMED CT, LOINC
DeviceDEVICE_EXPOSURESNOMED CT
Unit(across)UCUM
Race / EthnicityPERSONOMOP Custom (needs custom mapping for Vietnam)

4. Concept Relationship

The CONCEPT_RELATIONSHIP table stores relationships between concepts:

concept_id_1concept_id_2relationship_id
45757466 (ICD10 E11)201826 (SNOMED Diabetes T2)"Maps to"
20182645757466"Mapped from"
1503297 (Metformin)1503328 (Metformin 500mg tablet)"Has form"

Maps to is critical — it is how source codes are mapped to standard concepts:

-- Find the Standard Concept that ICD-10 E11 maps to
SELECT c2.concept_id, c2.concept_name
FROM concept c1
JOIN concept_relationship cr ON c1.concept_id = cr.concept_id_1 
  AND cr.relationship_id = 'Maps to'
JOIN concept c2 ON cr.concept_id_2 = c2.concept_id
WHERE c1.vocabulary_id = 'ICD10CM' AND c1.concept_code = 'E11';

5. Concept Ancestor — the hierarchy

CONCEPT_ANCESTOR stores the descendant hierarchy:

-- Find every descendant of "Diabetes mellitus" (Type 1, Type 2, gestational, ...)
SELECT c.concept_id, c.concept_name, ca.min_levels_of_separation
FROM concept_ancestor ca
JOIN concept c ON ca.descendant_concept_id = c.concept_id
WHERE ca.ancestor_concept_id = 201820  -- Diabetes mellitus
  AND c.standard_concept = 'S';

Extremely powerful for cohort definitions: "any patient with diabetes" only needs the ancestor 201820.

6. Athena — the vocabulary portal

Athena — the vocabulary portal

Workflow:

  1. Create a free account at athena.ohdsi.org
  2. Pick the vocabularies you need (SNOMED, RxNorm, LOINC, ICD10CM, ICD10, ATC, ...)
  3. Some vocabularies require a license (SNOMED CT for Vietnam: register as a SNOMED International affiliate — free for Vietnam as a low-income member)
  4. Download the zip — it contains CONCEPT.csv, CONCEPT_RELATIONSHIP.csv, and so on
  5. Import into your CDM database

7. Importing vocabularies into Postgres

-- Create the schema and tables using the CommonDataModel DDL
\i OMOPCDM_postgresql_5.4_ddl.sql

-- Import the CSVs (example)
COPY concept FROM '/path/CONCEPT.csv' DELIMITER E'\t' CSV HEADER QUOTE E'\b';
COPY concept_relationship FROM '/path/CONCEPT_RELATIONSHIP.csv' DELIMITER E'\t' CSV HEADER QUOTE E'\b';
COPY concept_ancestor FROM '/path/CONCEPT_ANCESTOR.csv' DELIMITER E'\t' CSV HEADER QUOTE E'\b';
-- ...

-- Create indexes
\i OMOPCDM_postgresql_5.4_indices.sql

-- Create primary keys
\i OMOPCDM_postgresql_5.4_primary_keys.sql

After import: ~6 million concepts (full set), ~12 GB. You can trim it down by only loading the vocabularies you need.

8. USAGI — the code mapping tool

USAGI helps you map source codes (Vietnamese ICD-10, Ministry of Health drug catalogue) to Standard Concepts:

USAGI — the code mapping tool

Workflow:

  1. Provide an input CSV with source_code, source_name columns
  2. USAGI searches with Lucene
  3. The user reviews the top matches and accepts or edits them
  4. Export → into the SOURCE_TO_CONCEPT_MAP table

9. Vocabularies for Vietnam

Vocabularies for Vietnam

9.1 Custom vocabularies for Vietnam

When no standard equivalent exists (e.g. Vietnam's 54 ethnic groups), create a Custom Vocabulary with:

  • vocabulary_id = 'VN_DANTOC'
  • concept_id starting at 2 billion (the OHDSI range reserved for custom concepts)
  • Register it in the VOCABULARY table
INSERT INTO vocabulary VALUES
  ('VN_DANTOC', 'Vietnamese ethnic groups (54)', 'http://...', '1.0', 2000000001);

INSERT INTO concept VALUES
  (2000001001, 'Kinh', 'Race', 'VN_DANTOC', 'Race', 'S', '01', NULL, NULL);
-- ...

9.2 SNOMED CT in Vietnam

Vietnam is a SNOMED International member (registered in 2024 via the Ministry of Health). The affiliate license is free for in-country individuals and organizations. Register at snomed.org.

10. Vocabulary upgrades

Athena releases vocabularies monthly. Upgrade workflow:

Vocabulary upgrade

Note: concept_id is stable across versions, but Maps to relationships can change → you must re-run ETL to update the *_concept_id columns.

11. Common SQL patterns

11.1 Look up the standard concept for a source code

SELECT c2.concept_id, c2.concept_name, c2.vocabulary_id
FROM concept c1
JOIN concept_relationship cr ON c1.concept_id = cr.concept_id_1 
  AND cr.relationship_id = 'Maps to'
JOIN concept c2 ON cr.concept_id_2 = c2.concept_id
WHERE c1.vocabulary_id = 'ICD10CM' 
  AND c1.concept_code = 'E11.9';

11.2 Find every descendant of an ancestor

SELECT c.concept_id, c.concept_name
FROM concept_ancestor ca
JOIN concept c ON ca.descendant_concept_id = c.concept_id
WHERE ca.ancestor_concept_id = 201820  -- Diabetes mellitus
  AND c.standard_concept = 'S';

11.3 RxNorm: find every product containing an ingredient

SELECT product.concept_id, product.concept_name
FROM concept ing
JOIN concept_ancestor ca ON ing.concept_id = ca.ancestor_concept_id
JOIN concept product ON ca.descendant_concept_id = product.concept_id
WHERE ing.concept_name = 'Metformin'
  AND ing.concept_class_id = 'Ingredient'
  AND product.concept_class_id IN ('Branded Drug', 'Clinical Drug');

12. Common pitfalls

  • ❌ Forgetting to upgrade vocabularies → analytics use obsolete concepts
  • ❌ Mapping source → standard incorrectly → cohorts off by thousands of patients
  • ❌ Using Classification (C) concepts instead of Standard (S) for analytics
  • ❌ Forgetting hierarchies → cohorts miss disease variants (e.g. "Diabetes" without Type 1.5)
  • ❌ Custom concept_id colliding with the standard range (must use the 2-billion+ range)
  • ❌ Failing to back up the vocabulary before an upgrade

13. Recommended workflow for a new project

  1. Read the OHDSI Themis conventions
  2. Download a vocabulary subset (SNOMED + ICD10CM + RxNorm + LOINC + ATC + UCUM) — covers 90% of Vietnamese needs
  3. Register as a SNOMED International affiliate (free for Vietnam)
  4. Import into Postgres + index
  5. Set up USAGI for the mapping team
  6. Map the largest source codes first (top 100 ICD-10 + top 200 drugs covers ~80% of volume)
  7. Save the mapping in SOURCE_TO_CONCEPT_MAP and commit it to Git
  8. Schedule quarterly vocabulary upgrades

Conclusion

Vocabulary is 50% of OMOP's value. Invest correctly — and you can join and analyze any dataset. Invest poorly — and your analytics will always be in doubt. Start small, review carefully, upgrade regularly.

Next article: OMOP Core Clinical Tables — Person, Visit, Condition, Drug, Measurement, Observation.