ds4th studyKnowledge Graphs & LLMs
Study Report · Chapter 03

온톨로지로 첫
지식 그래프 만들기

임상 질문에서 HPO 그래프·질의·추론까지

발표자
태영
범위
Chapter 3 · pp. 37–64
발행
2026-08-01
Section 00 · Executive Summary

0. 핵심 요약

Chapter 03 · One Sentence 쓸모 있는 지식 그래프는 데이터부터 시작하지 않는다. 사용자의 질문을 먼저 고정하고, 온톨로지로 의미를 정렬하고, 관계에 근거를 보존한 뒤, 직접 연결과 계층 추론을 구분해 검증한다.

Chapter 3은 Human Phenotype Ontology(HPO)를 이용해 희귀질환 진단을 지원하는 첫 지식 그래프를 만드는 구현 장이다. 겉으로는 Neo4j와 Cypher 실습처럼 보이지만, 실제 핵심은 문제 정의 → 데이터 의미 이해 → 표현 모델 선택 → 적재와 품질 통제 → 질문과 추론의 설계 순서다. 임상의에게 필요한 것은 “그래프가 있다”는 사실이 아니라, 관찰한 표현형 조합에서 후보 질병을 찾고 그 연결의 출처·증거·작성일을 검토할 수 있는가이다.

이를 위해 장은 성격이 다른 두 HPO 원천을 결합한다. hp.owl은 표현형의 표준 정의와 subClassOf 계층을 제공하고, phenotype.hpoa는 질병–표현형 연관과 그 근거를 제공한다. 온톨로지의 표현력은 보존하되 관계별 메타데이터를 쉽게 탐색하려고 LPG와 Neo4j를 선택하고, Neosemantics로 RDF/OWL을 가져온다. 완성된 그래프는 직접 연결 질의뿐 아니라 상위 표현형 범주 아래의 하위 개념을 따라 암시적 후보까지 확장한다.

이 리포트가 답하는 네 질문
  • 문제 — 왜 희귀질환 후보 탐색에는 문자열 통합보다 표준 의미와 계층이 필요한가?
  • 선택 — RDF 원천을 쓰면서도 왜 LPG를 애플리케이션 모델로 선택했는가?
  • 구축 — 두 원천을 어떤 식별자와 품질 게이트로 하나의 그래프로 만드는가?
  • 경계 — 후보 랭킹과 계층 추론을 어디까지 믿고, 무엇을 임상의에게 남겨야 하는가?

이 장은 개념 장이 아니라 구현 장이다. 그래서 이 리포트는 교재 Listing 3.5–3.28의 코드를 본문에 함께 싣고 한 줄씩 쉬운 말로 풀었다. §2.2는 같은 사실 하나를 RDF 세 방식과 LPG로 각각 적어 비교하고, §3은 온톨로지 적재부터 정제까지의 파이프라인을, §5–6은 추론과 근거 추적 질의를 다룬다.

아울러 이 회차의 학습 노트북 03_chapter_guide.ipynb가 모든 리스팅을 실제 Neo4j에서 끝까지 실행한 기록을 남겼다. 그 실측치를 책 값과 나란히 놓으면 재현에 관한 사실이 드러난다 — 적재 트리플은 899,558에서 926,450으로 늘었고, 진단 질의의 1위 후보는 그대로지만 2위 이하는 바뀌었으며, 책 표 3.3이 강조한 Hyperglycemia더 이상 내분비계 이상의 하위가 아니다. 코드는 그대로인데 결과가 달라지는 이 세 가지가 §3.4·4.2·5.2의 주제다.

표 1Chapter 3의 전체 논리
단계핵심 질문장의 답검증 산출물
01 · 문제누가 어떤 판단을 하는가?임상의가 관찰한 표현형으로 희귀질환 후보와 근거를 찾는다.대표 질문·실패 비용·사용자 역할
02 · 의미서로 다른 표현을 어떻게 맞추는가?HPO를 참조 어휘와 계층 스키마로 사용한다.ID 매핑·동의어·계층 방향
03 · 모델관계별 근거를 어디에 두는가?OWL 계층을 LPG로 가져오고 주석 메타데이터를 관계에 둔다.RDF/LPG 선택 근거·그래프 계약
04 · 사용무엇을 질의하고 추론하는가?직접 연관을 세고, 계층을 따라 암시적 포함을 확장한다.재현 질의·근거 경로·결과 스냅샷
05 · 통제어디까지 믿을 수 있는가?버전·적재·추론 범위와 임상 판단의 경계를 분리한다.품질 게이트·사람의 최종 검토
Section 01 · Problem & Domain

1. 그래프보다 먼저 임상 질문을 고정한다

섹션 요약
  • 의미 이질성 — 같은 개념의 여러 이름, 같은 약어의 여러 뜻, 서로 다른 세분성이 통합을 어렵게 한다.
  • 사용자 요구 — 임상의는 표현형 조합에서 희귀질환 후보와 그 연결 근거를 보고 싶다.
  • 온톨로지 역할 — 원천을 대신하는 데이터베이스가 아니라 로컬 표현을 공통 개념에 매핑하는 참조 계층이다.

1.1 형식보다 어려운 것은 ‘같은 뜻인가’이다

XML, CSV, JSON 같은 형식 차이는 파서로 처리할 수 있다. 더 어려운 문제는 의미다. 제2형 당뇨병과 ketosis-resistant diabetes처럼 같은 개념이 다른 이름으로 나타나고, PE처럼 physical examination과 pulmonary embolism이라는 서로 다른 뜻을 가질 수 있다. necrosis와 lobular necrosis처럼 표현의 세분성도 다르다. 문자열을 정규화하는 것만으로는 이 차이를 안전하게 합칠 수 없다.

온톨로지는 표준 이름·식별자·동의어·속성·범주·관계를 정의한다. 각 원천의 로컬 필드를 온톨로지 개념에 매핑하면 서로 다른 원천이 같은 의미 좌표를 공유한다. 중요한 경계는 매핑도 하나의 주장이라는 점이다. 어떤 로컬 값이 어느 표준 개념과 같은지, 누가 언제 어떤 근거로 판단했는지를 추적해야 한다.

그림 1의미 이질성을 공통 질의 공간으로 바꾸는 온톨로지
동의어, 중의적 약어, 서로 다른 세분성이 매핑을 거쳐 HPO의 표준 식별자와 계층에 정렬되고 통합 지식 그래프 질의로 이어지는 구조 교재 §3.1의 의미 통합 설명을 발표자가 문제–매핑–통합 질의의 흐름으로 재구성.

1.2 목표는 자동 진단이 아니라 검토 가능한 후보 탐색이다

이 장의 대상 사용자는 질병을 진단하고 치료하는 임상의다. 특히 희귀질환은 한두 증상으로 식별하기 어렵고, 여러 표현형 특징의 조합과 문헌 근거를 함께 살펴야 한다. 따라서 지식 기반에는 두 가지가 필요하다. 첫째, 같은 기관·계통에 속하는 표현형 이상을 연결하는 맥락적 계층. 둘째, 질병과 표현형의 연관뿐 아니라 출처·증거·빈도·작성자를 추적할 수 있는 주석이다.

도메인에는 경계 사례도 있다. 제1형 당뇨병은 하나의 질병으로 식별될 수 있고, 다른 희귀 증후군에서 관찰되는 표현형 특징으로도 식별될 수 있다. 한 문자열에 역할을 억지로 합치지 않고 OMIM:222100HP:0100651처럼 서로 다른 표준 ID를 사용하면, 질문의 문맥에 따라 질병 노드와 표현형 노드를 구분할 수 있다.

표 2희귀질환 지원 KG의 도메인 계약
항목이 장의 정의그래프 표현오해하면 생기는 문제
표현형 특징개인에게 나타나는 관찰 가능한 물리·생화학적 특성HpoPhenotype 노드와 계층질병명과 섞이면 역할별 질의가 흐려짐
질병원인·시간 경과·표현형 묶음·치료 반응으로 특징지어지는 실체HpoDisease 노드표현형 하나를 곧바로 질병으로 확정
주석특정 질병과 표현형이 연결된다는 근거 있는 주장HAS_PHENOTYPIC_FEATURE 관계출처가 노드 속성에 섞여 어느 연결의 근거인지 잃음
임상 결과관찰 특징과 연결된 검토 대상 질병 목록경로·겹침 수·근거 속성후보 목록을 진단 확률이나 확정 진단으로 해석
설계 질문

그래프 모델을 그리기 전에 “임상의가 결과 화면에서 어떤 연결과 근거를 확인해야 하는가?”를 묻는다. 이 질문이 노드·관계·속성과 기술 선택을 결정한다.

Section 02 · Data & Technology

2. 두 데이터 원천이 모델 선택을 결정한다

섹션 요약
  • hp.owl은 표현형의 정의·동의어·외부 참조·계층을 제공한다.
  • phenotype.hpoa는 질병–표현형 연관과 출처·증거·빈도·큐레이션 정보를 제공한다.
  • 이 사례의 LPG 선택은 일반 우열이 아니라 관계별 메타데이터를 직접 탐색하려는 목표에서 나온다.

2.1 HPO는 ‘어휘’와 ‘주석’을 따로 준다

hp.owl은 RDF/XML로 직렬화된 OWL 온톨로지다. 각 표현형을 owl:Class로 정의하고 URI, 자연어 정의, 작성자·작성일, 외부 데이터베이스 상호참조, 동의어, rdfs:subClassOf를 제공한다. 이 가운데 계층은 나중에 “내분비계 이상” 같은 넓은 질문을 더 구체적인 표현형까지 확장하는 추론의 재료가 된다.

phenotype.hpoa는 TSV 형식의 주석 파일이다. 각 행은 질병 ID와 표현형 ID의 연관을 주장하며, PubMed나 OMIM 출처, IEA·PCS·TAS 증거 코드, 발현 시점, 빈도, 성별, 수정자, 큐레이터와 날짜를 함께 담는다. 이 정보는 질병이나 표현형 자체의 속성이 아니라 둘 사이 특정 연결의 속성이다.

그림 2정의·계층과 연관·근거를 한 그래프에 결합하기
hp.owl의 표현형 정의와 계층, phenotype.hpoa의 질병 표현형 연관과 근거가 표준 ID로 결합되어 질병 노드, 표현형 노드, 속성 있는 관계를 만드는 구조 교재 §3.1.2, 그림 3.5–3.6과 Listing 3.1–3.4의 의미를 새로 배치. 정의·계층은 표현형 노드에, 한 건의 연관을 뒷받침하는 근거는 질병–표현형 관계에 둔다. 원본 도형을 복제하지 않음.
표 3HPO 두 원천의 입력 계약
관점hp.owlphenotype.hpoa결합 시 확인
형식RDF/XML · OWLTSV · HPOA파서와 헤더/메타데이터 행 수
정본 정보표현형 ID·정의·동의어·계층질병 ID와 표현형 ID의 주석hpo_id가 온톨로지 노드와 일치하는가
근거개념 자체의 작성·상호참조연관의 reference·evidence·frequency근거가 해당 관계에 보존되는가
변경 영향계층·용어의 추가·폐기주석·증거·질병 목록의 갱신같은 스냅샷 조합과 적재 로그를 재현하는가

2.2 RDF와 LPG는 요구를 기준으로 비교한다

RDF는 주어–술어–목적어 트리플을 기본 단위로 하며, W3C 표준과 웹 식별자, 상호운용, 풍부한 온톨로지 표현에 강하다. LPG는 노드와 개별 관계에 키–값 속성을 둘 수 있어 경로 순회와 관계 메타데이터 접근이 직관적이다. RDF가 “관계에 속성을 붙일 수 없다”는 단순 비교는 정확하지 않다. n항 관계, 명명 그래프, RDF-star처럼 문장에 대한 메타데이터를 표현하는 방법이 있으며, 대신 모델·질의·엔진 지원의 복잡성이 달라진다.

이 장의 사용자 질문은 특정 질병–표현형 연관의 출처·작성자·날짜를 비교하는 데 집중한다. 그래서 저자는 관계 속성을 직접 다루는 LPG와 Cypher를 선택하고, Neosemantics로 RDF/OWL 어휘와 일부 추론 기능을 Neo4j에 가져온다. 이는 “LPG가 RDF보다 낫다”가 아니라 표준 온톨로지를 입력으로 사용하면서 애플리케이션 질의는 관계 중심으로 최적화한 하이브리드 결정이다.

그림 3같은 주석 메타데이터를 표현하는 네 가지 패턴
RDF n항 관계, RDF 명명 그래프, RDF-star, LPG 관계 속성이 질병 표현형 연결의 출처 작성자 날짜를 각기 다른 구조로 표현하는 비교 교재 §3.2.2, Listing 3.5–3.12의 구조를 관계 메타데이터라는 동일 질문으로 재구성. n항 관계는 Annotation 자원에서 질병·표현형으로 나가는 관계를, 명명 그래프는 문장 묶음의 맥락을, RDF-star는 인용된 트리플의 메타데이터를, LPG는 개별 관계의 키–값을 보여준다.

같은 한 문장을 네 가지로 적어 보기

네 방식 모두 똑같은 사실 하나를 적는다. “질병 OMIM:222100과 표현형 HP:0410050이 연결되며, 그 근거는 논문 PMID:9357814이고 Nicole Vasilevsky가 2018-02-23에 기록했다.” 차이는 근거를 어디에 붙이느냐 하나뿐이다. 아래 네 블록은 각각 적는 법꺼내는 법을 한 쌍으로 묶었다. 마지막 LPG만 유일하게 우회 없이 끝난다.

① N항 관계 — 근거를 담을 노드를 하나 새로 만든다 (책 Listing 3.5 · 3.6)

# 적는 법 — 이름 없는 노드(_:Annotation)를 만들어 질병·표현형·근거를 모두 매단다
_:Annotation rdf:type :PhenotypicAnnotation ;
    :forDisease        OMIM:222100 ;
    :phenotypicFeature HP:0410050 ;
    :source            PMID:9357814 ;
    :createdBy         "Nicole Vasilevsky" ;
    :creationDate      "2018-02-23"^^xsd:date .

# 꺼내는 법 (SPARQL) — 질병·표현형으로 그 노드를 먼저 찾고 나서 근거를 읽는다
SELECT ?source ?author ?date WHERE {
  ?annotation a :PhenotypicAnnotation ;
      :forDisease        OMIM:222100 ;
      :phenotypicFeature HP:0410050 ;
      :source ?source ; :createdBy ?author ; :creationDate ?date .
}

쉽게 말하면 — “A와 B가 연결됨”이라는 한 줄을 적을 수 없어서, 연결 그 자체를 대신할 노드를 하나 발명한다. 질병과 표현형은 이제 직접 이어지지 않고 이 중간 노드를 거친다. 모델이 한 단계 깊어지고, 질의도 “중간 노드부터 찾기”로 길어진다. _:Annotation_:공백 노드(blank node), 즉 이름을 붙일 필요가 없는 익명 자원이라는 표시다.

② 명명 그래프 — 문장을 묶어 이름표를 붙인다 (책 Listing 3.7 · 3.8)

# 적는 법 (TriG) — 문장을 :Graph1 이라는 상자에 넣고, 상자에 근거를 붙인다
:Graph1 {
    OMIM:222100 :hasPhenotypicFeature HP:0410050
}
:Graph1
    :source       PMID:9357814 ;
    :createdBy    "Nicole Vasilevsky" ;
    :creationDate "2018-02-23"^^xsd:date .

# 꺼내는 법 (SPARQL) — 상자 안을 확인하고, 상자 바깥의 근거를 읽는다
SELECT ?source ?author ?date WHERE {
  GRAPH :Graph1 { OMIM:222100 :hasPhenotypicFeature HP:0410050 . }
  :Graph1 :source ?source ; :createdBy ?author ; :creationDate ?date .
}

쉽게 말하면 — 문장은 그대로 두고, 문장을 담은 봉투에 근거를 적는다. 트리플(3요소)에 “어느 봉투 소속인가”라는 네 번째 요소가 붙는 셈이다. 맥락을 담기엔 강력하지만, 주석이 28만 건이면 봉투도 28만 개가 필요해 저장·교환이 무거워지고 봉투 안 문장 하나만 고치기도 까다롭다.

③ RDF-star — 문장 자체를 주어로 삼는다 (책 Listing 3.9 · 3.10)

# 적는 법 — << >> 로 문장 하나를 통째로 감싸 그것을 다시 주어로 쓴다
<<OMIM:222100 :hasPhenotypicFeature HP:0410050>>
    :source       PMID:9357814 ;
    :createdBy    "Nicole Vasilevsky" ;
    :creationDate "2018-02-23"^^xsd:date .

# 꺼내는 법 (SPARQL-star) — 같은 << >> 표기로 바로 읽는다
SELECT ?source ?author ?date {
  <<OMIM:222100 :hasPhenotypicFeature HP:0410050>>
      :source ?source ; :createdBy ?author ; :creationDate ?date .
}

쉽게 말하면 — 우회를 없애고 “이 문장에 대해 말하기”를 문법으로 직접 지원한다. 셋 중 가장 읽기 쉽다. 다만 표준 확장이라 엔진이 지원해야만 쓸 수 있고, 질의 성능도 아직 개선 중이다. 교재는 Orlandi 등을 인용해 “새 구문 확장을 쓰려면 RDF 엔진의 특정 구현이 필요하고, 그래서 채택이 제한된다”고 짚는다.

④ LPG — 관계 안에 그냥 적는다 (책 Listing 3.11 · 3.12)

// 적는 법 — 화살표 안 중괄호에 키–값을 그대로 넣는다
(d {id: "OMIM:222100"})
  -[:HAS_PHENOTYPIC_FEATURE {
      source:       "PMID:9357814",
      createdBy:    "Nicole Vasilevsky",
      creationDate: "2018-02-23"
  }]->
(p {id: "HP:0410050"})

// 꺼내는 법 (Cypher) — 관계에 변수 r 을 붙이고 r 의 속성을 읽는다
MATCH (d)-[r:HAS_PHENOTYPIC_FEATURE]->(p)
WHERE d.id = "OMIM:222100" AND p.id = "HP:0410050"
RETURN r.source, r.createdBy, r.creationDate

쉽게 말하면 — 새 노드도, 봉투도, 확장 문법도 필요 없다. 관계에 이름(r)을 붙이고 속성을 읽으면 끝이다. 앞의 세 방식이 “근거를 어디 둘까”를 먼저 설계해야 했다면, LPG는 그 질문 자체가 생기지 않는다. 이 3줄짜리 질의가 이 장이 LPG를 고른 이유의 전부다.

교재 Listing 3.5–3.12를 그대로 옮기되, 원서 조판 과정에서 깨진 닫는 괄호·구두점(예: Listing 3.11의 미종료 문자열, Listing 3.10의 ? date 띄어쓰기)은 실행 가능한 형태로 바로잡았다. 주석 줄과 들여쓰기는 읽기 편하도록 발표자가 붙였다.
표 4RDF와 LPG의 선택 기준
관점RDF 계열LPG이 사례의 판단
기본 표현전역 식별 가능한 트리플레이블 노드·고유 관계·속성HPO 원천은 RDF/OWL, 앱 모델은 LPG
관계 메타데이터n항 관계·명명 그래프·RDF-star 등개별 관계의 키–값출처·증거·날짜 접근을 단순화
주요 강점표준 교환·연합·의미 표현·추론경로 탐색·관계 속성·운영 질의Neosemantics로 두 강점을 일부 결합
선택 위험주석 모델과 엔진 지원이 복잡할 수 있음OWL 의미를 전부 자동 보존하지 않음필요한 계층·규칙만 명시적으로 검증
Section 03 · Build & Operate

3. 적재는 파일 복사가 아니라 의미 계약의 구현이다

섹션 요약
  • 제약과 인덱스를 먼저 만들고, Neosemantics 설정 뒤 HPO 온톨로지를 적재한다.
  • 표현형 ID를 정렬한 다음 질병 노드와 관계를 만들고, 주석 필드를 관계 속성으로 풍부화한다.
  • 적재 성공 로그만으로 끝내지 않고 원천 행·노드·관계·근거 속성의 보존성을 확인한다.
  • 노트북 재실행 실측으로 책 값과의 차이(트리플 926,450 대 899,558, 3.22 단계 약 8분)를 확인한다.

3.1 CRISP-DM을 일곱 개의 산출물로 바꾼다

이 장은 Chapter 2의 CRISP-DM 변형을 구체적인 KG 구축 절차로 바꾼다. 비즈니스 이해에서는 대표 임상 질문을, 데이터 이해에서는 두 HPO 원천의 의미와 식별자를, 설계에서는 RDF/LPG 선택 근거를 남긴다. 구현은 온톨로지 적재와 주석 적재로 나뉘며, 정제 뒤 직접 질의와 계층 추론으로 사용 가치를 확인한다.

그림 4임상 질문에서 검증된 그래프 질의까지
임상 질문, HPO 두 원천 이해, RDF LPG 선택, 온톨로지 적재, 주석 적재, 정제와 점검, 직접 질의와 계층 추론이 검증 게이트를 거쳐 이어지는 파이프라인 교재 그림 3.1–3.2, 3.7, 3.9–3.10의 전체 흐름을 산출물과 검증 지점 중심으로 통합 재구성. 각 단계가 남겨야 할 산출물은 대표 질문, 원천 계약, 모델 선택 근거, 적재 로그, 품질 점검과 재현 가능한 질의다.
표 5설계 이후 여섯 구축 단계의 입력·출력·확인 질문
단계주요 작업출력품질 확인
DB 준비DB 생성, URI/ID 유일성 제약, ID 인덱스중복을 거부하는 저장 공간제약 충돌과 기존 데이터 상태
n10s 설정그래프 설정, URI 처리와 Neo4j 명명 규칙 지정RDF→LPG 변환 규칙namespace를 무시해도 충돌하지 않는가
온톨로지 적재n10s.rdf.import.fetch, phenotype label·ID 부여표현형 정의와 계층교재 기준 899,558 statements와 URI·ID·SUBCLASSOF 건수 비교*
주석 적재질병 노드와 HAS_PHENOTYPIC_FEATURE 생성두 원천의 연결매칭 실패·중복·누락 건수
풍부화evidence·frequency·biocuration 파싱과 URL 생성검토 가능한 관계 속성결측·복수값·정규식 실패
정제사용하지 않는 Resource 노드 제거질의 중심 그래프추론·감사에 필요한 정보까지 지우지 않았는가

* 899,558은 Neo4j/APOC/n10s 5.20.0과 HPO 2025-02를 사용한 교재 실행값이다. 현재 릴리스의 정답 건수가 아니라 스냅샷 재현 기준선으로 사용한다.

전반부 코드 — 온톨로지가 들어오기까지 네 줄

표 5의 앞 세 단계는 코드로 보면 놀랄 만큼 짧다. 무거운 일은 전부 Neosemantics가 대신하고, 우리가 하는 일은 규칙을 미리 정해 두는 것뿐이다.

// ① 그릇 만들기 (책 3.13) — Enterprise 전용 명령이다
CREATE DATABASE hpo IF NOT EXISTS

// ② 중복을 막는 장치 (책 3.14) — 단순 무결성 장치가 아니다.
//    uri 유일성이 있어야 n10s 가 온톨로지를 MERGE 로 '몇 번 돌려도 같게' 적재한다
CREATE CONSTRAINT n10s_unique_uri IF NOT EXISTS FOR (r:Resource) REQUIRE r.uri IS UNIQUE;
CREATE CONSTRAINT IF NOT EXISTS FOR (n:Resource) REQUIRE (n.id) IS UNIQUE;
CREATE INDEX disease_id   IF NOT EXISTS FOR (n:HpoDisease)   ON (n.id);
CREATE INDEX phenotype_id IF NOT EXISTS FOR (n:HpoPhenotype) ON (n.id);

// ③ 변환 규칙 정하기 (책 3.15) — 이 두 줄이 이후 모든 Cypher 의 생김새를 결정한다
CALL n10s.graphconfig.init();
CALL n10s.graphconfig.set({ handleVocabUris: "IGNORE" });   // 네임스페이스를 버린다
CALL n10s.graphconfig.set({ applyNeo4jNaming: True });      // 관계 타입을 대문자로

// ④ 적재 (책 3.16) — 딱 한 줄이다
CALL n10s.rdf.import.fetch("http://purl.obolibrary.org/obo/hp.owl", "RDF/XML");

③이 왜 중요한가handleVocabUris: "IGNORE"는 임포트할 때 네임스페이스를 떼어 낸다. 그래서 속성 이름이 obo:IAO_0000115가 아니라 iAO_0000115가 된다. applyNeo4jNaming: True는 관계 타입을 대문자로 바꾼다. 그래서 OWL의 rdfs:subClassOf가 LPG 관용 표기인 :SUBCLASSOF로 들어온다. §5의 추론이 SUBCLASSOF라는 이름으로 계층을 훑을 수 있는 건 이 한 줄 덕분이다.

// ⑤ 접합면 만들기 (책 3.17) — 이 장에서 가장 중요한 다섯 줄
MATCH (n:Resource)
WHERE n.uri STARTS WITH "http://purl.obolibrary.org/obo/HP"
SET n:HpoPhenotype,
    n.id = coalesce(n.id,
      replace(apoc.text.replace(n.uri, '(.*)obo/', ''), '_', ':'))

쉽게 말하면 — 온톨로지에서 온 노드의 주소는 http://purl.obolibrary.org/obo/HP_0410050이고, TSV 주석 파일이 쓰는 표기는 HP:0410050이다. 생김새가 달라서 그냥 두면 두 파일은 영영 만나지 못한다. 이 질의는 URI 앞부분을 잘라내고 _:로 바꿔 두 파일이 같은 언어를 쓰게 만든다. coalesce는 “이미 id가 있으면 건드리지 말라”는 뜻이라 여러 번 실행해도 안전하다. 그림 2가 말한 “표준 ID로 결합”이 실제로 일어나는 곳이 바로 이 다섯 줄이다.

3.2 한 행을 한 건의 감사 가능한 주장으로 보존한다

교재의 Cypher는 TSV를 읽어 질병과 표현형을 MERGE하고, 관계가 만들어진 뒤 원시 필드와 사람이 읽을 설명을 순차적으로 추가한다. FOREACH(CASE …) 패턴은 값이 있는 필드만 설정해 null로 기존 값을 덮지 않는다. APOC 정규식은 biocuration에서 큐레이터와 날짜를 뽑고, 증거 코드 IEA·PCS·TAS를 설명으로 바꾼다.

후반부 코드 — TSV 한 행이 관계 하나가 되기까지

같은 파일을 세 번 읽는다. 첫 번째는 노드를 만들고, 두 번째는 선을 잇고, 세 번째는 그 선에 근거를 붙인다. 한 번에 못 하는 이유는 관계를 만들려면 양쪽 노드가 이미 있어야 하기 때문이다.

// ① 질병 노드 만들기 (책 3.19)
LOAD CSV FROM 'https://mng.bz/qRyr' AS row
FIELDTERMINATOR '\t'
WITH row
SKIP 5                                        // 메타데이터 4줄 + 헤더 1줄을 건너뛴다
MERGE (dis:Resource:HpoDisease {id: row[0]})  // 같은 질병이 여러 행에 나오므로 MERGE
ON CREATE SET dis.label = row[1];             // 이름은 처음 만들 때만 채운다

// ② 질병 ↔ 표현형 잇기 (책 3.20)
LOAD CSV FROM 'https://mng.bz/qRyr' AS row
FIELDTERMINATOR '\t'
WITH row
SKIP 5
MATCH (dis:HpoDisease)   WHERE dis.id = row[0]   // TSV 0번 열 = OMIM:222100
MATCH (phe:HpoPhenotype) WHERE phe.id = row[3]   // TSV 3번 열 = HP:0410050
MERGE (dis)-[:HAS_PHENOTYPIC_FEATURE]->(phe)

여기가 두 파일이 만나는 지점이다. phe.id는 ①에서 hp.owl의 URI를 가공해 만든 값이고, row[3]은 TSV가 원래 갖고 있던 값이다. 형식이 전혀 다른 두 파일이 온톨로지의 표준 식별자를 공통 언어로 삼아 하나의 그래프가 된다. §1에서 말한 “의미 통합”의 구체적 실체가 이 두 줄이다.

// ③ 관계에 근거 붙이기 (책 3.22) — 열 8개를 그대로 관계 속성으로
LOAD CSV FROM 'https://mng.bz/qRyr' AS row
FIELDTERMINATOR '\t'
WITH row
SKIP 5
MATCH (dis:HpoDisease)-[rel:HAS_PHENOTYPIC_FEATURE]->(phe:HpoPhenotype)
WHERE phe.id = row[3] AND dis.id = row[0]
FOREACH(_ IN CASE WHEN row[4]  IS NOT NULL THEN [1] ELSE [] END | SET rel.source      = row[4])
FOREACH(_ IN CASE WHEN row[5]  IS NOT NULL THEN [1] ELSE [] END | SET rel.evidence    = row[5])
FOREACH(_ IN CASE WHEN row[6]  IS NOT NULL THEN [1] ELSE [] END | SET rel.onset       = row[6])
FOREACH(_ IN CASE WHEN row[7]  IS NOT NULL THEN [1] ELSE [] END | SET rel.frequency   = row[7])
FOREACH(_ IN CASE WHEN row[8]  IS NOT NULL THEN [1] ELSE [] END | SET rel.sex         = row[8])
FOREACH(_ IN CASE WHEN row[9]  IS NOT NULL THEN [1] ELSE [] END | SET rel.modifier    = row[9])
FOREACH(_ IN CASE WHEN row[10] IS NOT NULL THEN [1] ELSE [] END | SET rel.aspect      = row[10])
FOREACH(_ IN CASE WHEN row[11] IS NOT NULL THEN [1] ELSE [] END | SET rel.biocuration = row[11])

FOREACH(CASE …)가 왜 이렇게 생겼나 — Cypher에는 “조건이 맞을 때만 SET”이라는 문법이 없다. 그래서 원소가 1개인 리스트를 도는 반복문빈 리스트를 도는 반복문(=아무것도 안 함)으로 if를 흉내 낸다. 목적은 단순하다. TSV에서 빈 칸인 열이 기존 값을 null로 덮어쓰지 못하게 막는 것이다. 여덟 번 반복되는 건 열이 여덟 개이기 때문이지 다른 뜻은 없다.

// ④ 사람이 읽을 수 있게 바꾸기 (책 3.23) — 약어를 문장으로, 출처를 링크로
CALL apoc.periodic.iterate(
  "MATCH (dis:HpoDisease)-[rel:HAS_PHENOTYPIC_FEATURE]->(phe:HpoPhenotype) RETURN rel",
  "SET rel.createdBy    = apoc.text.regexGroups(rel.biocuration, 'HPO:(\\w+)\\[')[0][1],
       rel.creationDate = apoc.text.regexGroups(rel.biocuration, '\\[(\\d{4}-\\d{2}-\\d{2})\\]')[0][1],
       rel.aspectName = CASE
         WHEN rel.aspect = 'P' THEN 'Phenotypic abnormality'
         WHEN rel.aspect = 'I' THEN 'Inheritance' END,
       rel.evidenceName = CASE
         WHEN rel.evidence = 'IEA' THEN 'Inferred from electronic annotation'
         WHEN rel.evidence = 'PCS' THEN 'Published clinical study'
         WHEN rel.evidence = 'TAS' THEN 'Traceable author statement' END,
       rel.url = CASE
         WHEN rel.source STARTS WITH 'PMID:' THEN
           'https://pubmed.ncbi.nlm.nih.gov/' + apoc.text.replace(rel.source, '(.*)PMID:', '')
         WHEN rel.source STARTS WITH 'OMIM:' THEN
           'https://omim.org/entry/' + apoc.text.replace(rel.source, '(.*)OMIM:', '') END",
  {batchSize: 1000})

// ⑤ 안 쓰는 것 치우기 (책 3.24)
CALL apoc.periodic.iterate(
  "MATCH (n:Resource) RETURN id(n) AS id",
  "MATCH (n) WHERE id(n) = id
     AND NOT 'HpoPhenotype' IN labels(n)
     AND NOT 'HpoDisease'   IN labels(n)
   DETACH DELETE n",
  {batchSize: 10000})
YIELD batches, total RETURN batches, total

④는 “친절함”을 데이터에 새겨 넣는 단계다. biocuration 칸에 HPO:NicoleVasilevsky[2018-02-23]이라고만 적혀 있으면 사람이 읽기 불편하다. 정규식으로 이름과 날짜를 뽑아 별도 속성으로 만들고, PCS 같은 세 글자 약어를 Published clinical study라는 문장으로 풀고, PMID:9357814를 클릭 가능한 PubMed 주소로 바꾼다. §6이 요구하는 “임상의가 근거를 열어볼 수 있어야 한다”가 여기서 준비된다. apoc.periodic.iterate는 28만 건을 1,000건씩 나눠 처리해 메모리를 아끼는 장치다.

⑤는 되돌릴 수 없는 단계다. 표현형도 질병도 아닌 Resource 노드를 전부 지운다. 온톨로지의 동의어 타입·주석 출처 같은 메타데이터가 여기서 사라지고, 그래서 책 3.18의 중간 점검 질의는 이 단계 이후 빈 결과를 낸다(§3.3 참조).

하지만 이 구현에는 중요한 보존성 경계가 있다. 관계를 MERGE (dis)-[:HAS_PHENOTYPIC_FEATURE]->(phe)처럼 질병–표현형 쌍만으로 합치면, 같은 쌍에 서로 다른 출처·작성일의 주석이 여러 건 있어도 하나의 관계가 된다. 이후 모든 일치 관계에 속성을 설정하면 마지막 처리 행의 값이 앞선 근거를 덮을 수 있다. 장의 개념 목표인 “주석별 provenance”를 구현하려면 주석 ID를 별도 노드로 승격하거나, 관계의 고유 키를 모델에 포함하고 행 수 보존 테스트를 두어야 한다.

그림 5HPOA 한 행이 그래프 주장이 되는 생애주기
HPOA 행을 파싱하고 표준 ID로 노드를 매칭해 관계를 만든 뒤 증거 설명과 URL로 풍부화하고 건수와 근거 보존을 확인하는 단계 교재 Listing 3.19–3.24와 저장소 import_hpo.py 감사를 바탕으로 재구성. 같은 질병–표현형 쌍의 주석이 여러 건이면 관계 식별자 없이 MERGE한 한 관계에 마지막 값이 덮일 수 있다는 구현 위험은 발표자의 코드 감사 결과다.

3.3 책의 결과와 현재 실행 결과는 분리한다

교재 결과는 Neo4j 5.20.0, APOC 5.20.0, Neosemantics 5.20.0과 2025년 2월 HPO를 기준으로 한다. 저장소의 학습 노트북은 2026-06-23 HPOA 메타데이터를 확인했으며, “latest” URL을 쓰면 데이터가 계속 바뀐다. 따라서 노드·관계 수와 상위 질병 목록이 책과 다른 것은 곧바로 오류가 아니라 데이터 드리프트일 수 있다. 재현하려면 온톨로지·주석의 릴리스 URL과 체크섬을 함께 고정해야 한다.

또한 교재 Listing 3.18의 중간 상태 질의는 최종 정제 후 실행하면 실패한다. 이는 동일 코드가 어떤 시점에 실행됐는지가 결과의 일부라는 뜻이다. 실습 저장소의 파일 번호는 최종 교재보다 +3 오프셋이 있고, 일부 업스트림 파일은 중복 또는 0바이트라 study.toml[mapping.listings.ch03]가 정본이다.

표 6원자료·코드 감사에서 확인한 재현 경계
경계관찰영향권장 통제
데이터 버전책은 2025-02, 노트북 확인본은 2026-06-23건수·후보 순위·주석 내용 변경릴리스 URL·체크섬·적재일 기록
주석 다중성질병–표현형 쌍만으로 관계 MERGE여러 provenance가 한 관계에서 덮일 수 있음주석 식별자 또는 Annotation 노드
파싱 가정큐레이터·날짜 정규식이 첫 패턴을 기대결측·복수값에서 오류 또는 정보 손실파싱 실패율과 원문 보존
단계 의존중간 조회는 최종 정제 뒤 실패실행 순서에 따라 데모 결과가 달라짐단계별 스냅샷 또는 독립 검증 질의
조회 부작용Listing 3.21이 조회 목적에 미바인딩 MERGE 사용노드·관계가 새로 생겨 그래프 오염MATCH로 교정하고 실행 전 스냅샷 보존
추론 결과 고정교재 3.28은 5개 질병 화이트리스트, 저장소 3.31은 SKIP 100 LIMIT 5같은 추론도 표시되는 부분집합이 다름필터·정렬·페이지 기준을 결과와 함께 기록
리스팅 번호업스트림 ch03 파일은 교재 번호와 +3잘못된 코드 참조 위험study.toml 전수 대응표 사용

3.4 직접 돌려 보면 숫자가 이만큼 달라진다

이 회차의 학습 노트북 src/ch03/03_chapter_guide.ipynb는 책의 리스팅을 실제 Neo4j에 처음부터 끝까지 실행한 기록이다. 앞 절의 “드리프트가 있을 수 있다”는 말을 추상으로 두지 않고, 실측치를 책 값과 나란히 두면 무엇이 얼마나 움직이는지 눈으로 보인다.

따라 하기 전에 알아야 할 것

노트북은 Docker로 띄운 Neo4j 5.26.28 Community에서 돌렸다(플러그인 n10s·apoc·gds). 그런데 책의 첫 명령 CREATE DATABASE hpo가 Community 에디션에서는 실패한다 — 멀티 데이터베이스는 Enterprise 전용 기능이라 UnsupportedAdministrationCommand 오류가 난다. 노트북은 기본 neo4j 데이터베이스를 그대로 쓰는 것으로 우회했고, 학습 내용에는 영향이 없다. 교재가 Enterprise 5.20 기준이라는 점을 모르면 첫 줄에서 막힌다.

표 7책 실행값과 노트북 재실행 실측값
항목교재 (HPO 2025-02)노트북 (HPOA 2026-06-23)읽는 법
적재된 트리플899,558926,45016개월 사이 약 2.7만 건 증가. 숫자가 다른 것이 정상이다
HpoPhenotype 노드20,413표현형 어휘 규모
HpoDisease 노드12,956HPOA 헤더의 8574 OMIM · 47 DECIPHER · 4337 ORPHANET과 대조 가능
HAS_PHENOTYPIC_FEATURE 관계284,994주석 행 수와 이 값을 비교하는 것이 §3.2가 지적한 보존성 테스트다
정제 단계 삭제 노드207,754적재한 것의 상당수가 최종 그래프에 남지 않는다
SUBCLASSOF 관계24,378§5 추론이 훑는 계층의 크기

소요 시간도 실습 준비에 직결된다. 대부분의 단계는 1초 안팎이고 온톨로지 적재(3.16)가 9.5초, 질병 노드·관계 생성(3.19·3.20)이 각 3초 남짓이다. 그런데 관계에 속성을 붙이는 3.22 한 단계만 약 8분(496초) 걸린다. 28만 5천 개 관계에 FOREACH 여덟 개를 배치 처리 없이 돌리기 때문이다. 전체를 처음부터 다시 만들면 9분 남짓 걸리므로, 시연 중에 이 셀을 실행하면 안 된다.

노트북이 추가로 바로잡은 것
  • 업스트림 파일 2건이 망가져 있다 — 책 3.21에 해당하는 저장소 파일은 다음 파일과 내용이 완전히 중복돼 엉뚱한 질의가 들어 있고, 책 3.25에 해당하는 파일은 0바이트 빈 파일이다. 두 건 모두 해설판 본문으로 대체했고, study.toml이 각각 upstream-wrong·upstream-missing으로 선언한다. 정정 내역은 errata.toml에 기록돼 있다.
  • 해설판 본문의 3.21도 그대로 쓰면 안 된다MERGE (dis:HpoDisease)-[…]->(phe:HpoPhenotype)로 시작하는데, 바인딩되지 않은 변수에 MERGE를 쓰면 조회가 아니라 노드와 관계를 새로 만들어 그래프를 오염시킨다. 의도가 조회이므로 MATCH로 고쳐 실행했다.
  • 교재의 HPO 용어 링크가 지금은 404다hpo.jax.org/app/browse/term/… 체계가 바뀌었다. 노트북은 같은 용어를 EBI OLS4에서 여는 링크로 대체했다.
Section 04 · Query & Case

4. 그래프는 표현형 조합을 후보 경로로 바꾼다

섹션 요약
  • 직접 질의는 특정 질병과 연결된 표현형을 먼저 펼쳐 기준 패턴을 확인한다.
  • 새로 관찰한 표현형들을 공유하는 질병을 찾고 겹치는 특징 수로 후보를 정렬한다.
  • 결과는 후보 생성과 근거 탐색을 지원할 뿐, 진단 확정이나 확률 추정이 아니다.
  • 같은 질의를 최신 스냅샷에서 다시 돌리면 1위는 유지되지만 2위 이하는 바뀐다.

4.1 직접 연결은 답보다 검토 경로를 준다

첫 질의는 OMIM:222100 제1형 당뇨병 노드에 직접 연결된 표현형을 가져온다. 질문은 “이 질병의 증상은 무엇인가”다.

// 책 3.25 — 한 질병에서 뻗어 나가는 모든 표현형
MATCH path=(dis:HpoDisease)-[:HAS_PHENOTYPIC_FEATURE]->(phe:HpoPhenotype)
WHERE dis.id = "OMIM:222100"
RETURN path

노트북 재실행 결과 OMIM:222100에는 표현형 8개가 달려 있다 — Autoimmunity, Decreased circulating 1,5 anhydroglucitol concentration, Diabetes mellitus, Hyperglycemia, Ketoacidosis, Polydipsia, Polyphagia, Polyuria. 다음(그림 6)에서 중심 노드를 둘러싼 별 모양이 이것이다.

이어 임상의가 환자에게서 성장 지연(HP:0001510), 큰 무릎(HP:0030866), 감각신경성 청각 장애(HP:0000407), 소양증(HP:0000989)을 추가로 관찰했다고 가정한다. 위 목록에 없는 증상들이다. 이제 질문이 뒤집힌다 — “이 질병의 증상은?”이 아니라 “이 증상 조합을 가진 질병은?”이다. 그래프에서는 화살표를 반대로 따라가기만 하면 되는 같은 질의다. 두 번째 질의는 다섯 표현형과 연결된 질병을 모으고, 겹치는 특징 수를 내림차순으로 정렬한다.

// 책 3.26 — 증상 조합에서 질병 후보로
MATCH (phe:HpoPhenotype)
WHERE phe.label IN [
  "Growth delay", "Large knee",
  "Sensorineural hearing impairment", "Pruritus",
  "Type I diabetes mellitus"
]
WITH phe
MATCH path=(dis:HpoDisease)-[:HAS_PHENOTYPIC_FEATURE]->(phe)
UNWIND dis AS nodes
RETURN dis.id AS disease_id,
       dis.label AS disease_name,
       collect(phe.label) AS features,
       count(nodes) AS num_of_features
ORDER BY num_of_features DESC, disease_name
LIMIT 5

교재의 2025년 2월 스냅샷에서는 다섯 특징을 모두 공유하는 Odontochondrodysplasia 2 with hearing loss and diabetes(OMIM:619269)가 첫 후보로 나타난다. 그다음 후보는 세 개 또는 두 개의 특징을 공유한다. 그래프의 가치는 질병 이름만 반환하는 데 있지 않다. 어떤 표현형 경로가 점수를 만들었는지, 각 관계에 어떤 출처·증거·빈도가 있는지 다시 열어볼 수 있다.

그림 6관찰한 다섯 표현형으로 질병 후보를 순위화하기
제1형 당뇨병과 성장 지연, 큰 무릎, 감각신경성 청각 장애, 소양증이 연결된 질병 후보들을 공유 특징 수 5개, 3개, 2개 순으로 정렬하는 구조 교재 §3.4, Listing 3.25–3.26과 표 3.2를 경로와 겹침 수 중심으로 재구성. 선은 후보 랭킹에서 공유 주석의 존재만 나타내며 관계 방향은 그림 2의 질병→표현형을 따른다. 질병명과 수치는 HPO 2025-02 교재 스냅샷이다. 표기 주의 — 교재와 현재 HPOA 데이터는 모두 Ondontochondrodysplasia로 적혀 있고, 이 리포트 본문은 OMIM:619269의 정식 표기 Odontochondrodysplasia를 쓴다. 노트북을 직접 돌리면 데이터 원문 표기가 그대로 출력된다.
표 8교재 스냅샷의 상위 후보와 해석 경계
후보공유 특징이 결과가 말하는 것말하지 않는 것
Odontochondrodysplasia 2 with hearing loss and diabetes5입력 다섯 표현형 모두와 주석 경로가 있음환자가 이 질병일 확률 또는 확정 진단
Holoprosencephaly 12 with or without pancreatic agenesis (OMIM:618500)3세 표현형을 공유하는 비교 후보세 특징의 특이도·중요도가 같다는 뜻
3-methylglutaconic aciduria VIII (OMIM:614700), Alobar holoprosencephaly (OMIM:616192), Alpha-thalassemia/mental retardation syndrome, X-linked (OMIM:602782)각 2교재 스냅샷의 동률 3건빈도·부정 표현형·환자 맥락을 반영한 랭킹

4.2 같은 질의를 지금 돌리면 2위 이하가 바뀐다

§3.4의 노트북으로 똑같은 책 3.26 질의를 2026-06-23 HPOA에서 다시 돌린 결과다. 결론(1위)은 그대로지만 그 아래는 상당히 달라졌다.

표 9같은 질의, 다른 스냅샷 — 후보 순위 대조
순위교재 (HPO 2025-02)노트북 재실행 (HPOA 2026-06-23)
1OMIM:619269 Ondontochondrodysplasia 2 … (5)OMIM:619269 동일 (5) — 변화 없음
2OMIM:618500 Holoprosencephaly 12 … (3)OMIM:618500 동일 (3) — 변화 없음
3OMIM:614700 3-methylglutaconic aciduria VIII (2)OMIM:621585 Mendez-Johnson immunoneurologic syndrome (3) — 신규
4OMIM:616192 Alobar holoprosencephaly (2)OMIM:256500 Netherton syndrome (3) — 신규
5OMIM:602782 Alpha-thalassemia/mental retardation, X-linked (2)OMIM:617248 3-methylglutaconic aciduria, type VIII (2) — 같은 병, 다른 OMIM 번호

세 가지를 읽어야 한다. 첫째, 진단 결론은 견고하다 — 다섯 특징을 모두 공유하는 1위는 16개월이 지나도 그대로다. 둘째, 3개 공유 후보가 두 건 새로 생겼다. 교재에서는 3개 공유가 1건뿐이라 2위가 도드라져 보였지만, 지금은 3개 공유가 3건이라 “2위와 3위의 격차”라는 인상 자체가 사라진다. 셋째, 3-methylglutaconic aciduria VIII의 OMIM 번호가 614700에서 617248로 바뀌었다 — 질병 식별자조차 고정된 상수가 아니다.

이것이 §3.3이 말한 드리프트의 실물이다. 슬라이드에 표 8만 띄우고 “이 목록이 나옵니다”라고 말하면, 청중이 노트북을 돌렸을 때 다른 화면을 보고 코드가 틀렸다고 생각하게 된다. 결과를 보여줄 때는 반드시 어느 릴리스에서 나온 값인지 함께 적어야 한다.

임상 경계

단순 겹침 수 count(nodes)는 표현형의 희귀도, 부정 소견, 발현 시점, 증거 질, 환자 인구집단을 가중하지 않는다. 결과 화면은 후보·경로·근거를 함께 보여주고 임상의의 다음 검토를 지원해야 한다. 위의 순위 변동이 보여주듯 “2위”라는 자리 자체가 데이터 갱신에 흔들린다.

Section 05 · Ontology Reasoning

5. 계층은 저장되지 않은 포함 관계를 질의한다

섹션 요약
  • 직접 연결만 찾으면 상위 범주 아래의 더 구체적인 표현형 주석을 놓친다.
  • SUBCLASSOF 경로 또는 Neosemantics 추론은 하위 표현형을 상위 범주 질의에 포함한다.
  • 추론은 새 임상 사실을 발명하지 않으며, 계층·깊이·방향·주석 품질의 영향을 그대로 받는다.
  • 깊이 *1..3은 실제 계층의 절반만 보고, 온톨로지 재분류는 코드 없이도 결과를 바꾼다.

5.1 직접 사실과 암시적 포함을 분리한다

“내분비계 이상(Abnormality of the endocrine system, HP:0000818)과 관련된 질병은?”이라는 질문을 생각해 보자. 어떤 질병은 이 상위 범주에 직접 주석되어 있지만, 더 많은 질병은 갑상선 생리 이상(HP:0002926), 갑상선기능저하증(HP:0000821), 갑상선종(HP:0000853)처럼 검증된 하위 표현형에 연결되어 있다. 직접 관계만 조회하면 후자를 놓친다.

교재는 먼저 자식에서 부모로 향하는 [:SUBCLASSOF*1..3] 경로를 역방향으로 탐색해 상위 범주 아래의 하위 표현형을 찾고, 이어 n10s.inference.nodesInCategory로 이 계층을 따라 질병을 포함한다. 반환된 질병–표현형 주석은 원래 그래프에 명시적으로 존재하지만, 상위 범주와 질병의 포함 관계는 계층 규칙에서 도출된다. 교재 표 3.3은 재현성을 위해 5개 질병 화이트리스트로 제한한 부분집합이고, 저장소 Listing 3.31은 화이트리스트 대신 SKIP 100 LIMIT 5를 사용하므로 표시 결과가 같지 않다.

코드로 보면 두 줄 차이다

// 책 3.27 — 계층을 손으로 훑는다
MATCH (p:HpoPhenotype)<-[:SUBCLASSOF*1..3]-(n:HpoPhenotype)
WHERE p.id = "HP:0000818"
RETURN p, n

// 책 3.28 — 같은 일을 Neosemantics 프로시저에 맡기고, 그 결과로 질병까지 끌어온다
MATCH (cat:HpoPhenotype {label: "Abnormality of the endocrine system"})
CALL n10s.inference.nodesInCategory(cat, {
    inCatRel: "HAS_PHENOTYPIC_FEATURE",   // 질병을 표현형에 잇는 관계
    subCatRel: "SUBCLASSOF"})             // 하위 개념을 잇는 관계
YIELD node AS dis
MATCH (dis)-[:HAS_PHENOTYPIC_FEATURE]->(phe:HpoPhenotype)
RETURN dis.label AS disease, collect(DISTINCT phe.label) AS features
ORDER BY size(features) ASC, disease

쉽게 말하면*1..3은 “하위 클래스 관계를 1단계에서 3단계까지 따라가라”는 뜻이다. 화살표 방향(<-)에 주의해야 한다. HPO는 자식 → 부모 방향으로 SUBCLASSOF를 저장하므로, 부모에서 자식을 찾으려면 화살표를 거꾸로 읽어야 한다. 3.28의 nodesInCategory는 이 순회를 대신 해 주면서 깊이 제한 없이 훑고, 찾은 표현형에 주석된 질병까지 한 번에 돌려준다.

그림 7하위 표현형 주석을 상위 범주 질의에 포함하기
갑상선 생리 이상, 갑상선기능저하증, 갑상선종에서 내분비계 이상 상위 범주로 향하는 SUBCLASSOF 관계가 있고, 하위 표현형에 직접 주석된 질병이 상위 범주 질의에 추론으로 포함되는 구조 교재 §3.5, Listing 3.27–3.28과 표 3.3의 의미를 직접 사실과 암시적 포함으로 구분해 재구성. 화살표는 HPO 저장 방향과 같은 하위→상위 SUBCLASSOF이며, 세 예시는 HPO Ontology Service 2026-06-23 기준 HP:0000818의 2–3단계 하위임을 확인했다. 추론은 새 임상 사실을 만들지 않고 기존 주석의 질의 범위를 넓힌다.

5.2 더 넓은 결과가 더 정확한 결과는 아니다

계층 추론은 검색 누락을 줄이지만 품질을 자동 보장하지 않는다. 1..3 깊이는 구현자가 정한 범위이고, 더 깊은 하위 개념을 놓칠 수 있다. 반대로 범위를 무제한으로 넓히면 너무 일반적인 상위 개념을 통해 관련성이 약한 후보가 늘 수 있다. 또한 HPO와 주석은 불완전하게 갱신되므로 “연결 없음”을 “관련 없음”으로 해석해서는 안 된다.

노트북이 이 두 문장을 숫자로 확인했다. HP:0000818의 하위 표현형은 깊이 1\~3으로 세면 256개, 깊이 제한을 풀면 474개다. 즉 책 3.27의 *1..3실제 계층의 절반가량만 본다. 무엇을 놓치는지도 구체적이다. 책 표 3.3에 나오는 Transient neonatal diabetes mellitusHP:0000818에서 4단계 아래*1..3으로는 잡히지 않는다. 3.28이 프로시저를 쓰는 이유가 바로 이것이다.

표 10추론 범위와 온톨로지 변경의 실측 영향
관찰실측무엇을 뜻하는가
깊이 *1..3 하위 표현형256개책 3.27이 실제로 보는 범위
깊이 무제한 하위 표현형474개3.28의 nodesInCategory가 보는 범위. 임의로 정한 깊이는 절반을 가린다
Transient neonatal diabetes mellitus깊이 4책 표 3.3에 실제로 등장하는 항목인데 *1..3으로는 못 찾는다
Hyperglycemia의 소속더 이상 내분비계 이상의 하위가 아님책 표 3.3은 굵게 표시했지만 현재 HPO는 대사 이상 쪽으로 재분류했다
추론은 온톨로지가 바뀌면 함께 바뀐다

마지막 행이 이 절에서 가장 중요한 관찰이다. 책 표 3.3은 Diabetes mellitus, transient neonatal, 1의 특징 가운데 Hyperglycemia를 “내분비계 이상의 하위”로 굵게 강조한다. 그런데 지금 HPO에서 이 용어는 내분비계가 아니라 대사 이상 쪽으로 옮겨졌다. 코드는 한 글자도 바뀌지 않았고 데이터도 그대로인데, 온톨로지의 분류가 바뀌었다는 이유만으로 추론 결과가 달라진 것이다.

§4.2의 순위 변동이 “데이터가 늘면 결과가 바뀐다”였다면, 이것은 “의미 구조가 바뀌면 결과가 바뀐다”다. 추론은 그래프에 저장된 사실뿐 아니라 계층 자체를 재료로 쓰기 때문에, 온톨로지 버전이 곧 결과의 일부다. 재현 가능한 운영이라면 주석 릴리스와 함께 온톨로지 릴리스도 고정해야 한다.

따라서 결과에는 직접 연결인지 계층으로 포함됐는지, 어떤 SUBCLASSOF 경로를 따라왔는지, 어느 HPO 릴리스를 썼는지를 함께 표시해야 한다. 임상의가 추론 경로를 검토할 수 없다면 추론의 설명 가능성은 저장소 내부에만 머문다.

표 11직접 질의와 계층 추론의 역할·위험 비교
관점직접 연결 질의계층 추론화면에 남길 근거
범위명시된 질병–표현형 주석하위 표현형을 상위 범주에 포함direct / inferred 상태
가치정확한 주석과 provenance 검토계층 때문에 숨은 후보 회수원 주석과 전체 계층 경로
주요 위험계층적 의미를 놓쳐 누락범위가 넓어져 관련성 약화깊이·규칙·온톨로지 버전
부재 해석주석이 없을 수 있음하위 개념이 불완전할 수 있음“근거 없음”과 “거짓”을 구분
Section 06 · Evidence & Limits

6. 신뢰도는 가장 짧은 미검증 구간에서 무너진다

섹션 요약
  • 버전이 다른 두 HPO 원천을 섞으면 재현성과 의미 정합성이 흔들린다.
  • 식별자 매핑, 주석 다중성, 파싱, 정제, 추론 범위를 단계별로 검증해야 한다.
  • 기술적으로 재현된 후보도 임상적 타당성을 자동 증명하지 않는다.
  • 근거 화면은 개념이 아니라 이미 만들 수 있는 질의다 — 관계 속성 하나를 더 반환하면 된다.

6.1 네 게이트가 후보 결과의 감사 경로를 만든다

첫째, 원천 게이트에서 릴리스·컬럼·식별자·체크섬을 고정한다. 둘째, 의미 게이트에서 질병과 표현형 역할, HPO ID, 동의어와 계층 방향을 표본 감사한다. 셋째, 적재 게이트에서 원천 행 수가 노드·관계·주석으로 어떻게 변했는지, 중복과 결측이 어떻게 처리됐는지 비교한다. 넷째, 사용 게이트에서 대표 질의를 재현하고 직접·추론 결과와 임상 판단의 경계를 확인한다.

그림 8온톨로지 기반 임상 지원 KG의 네 검증 게이트
원천, 의미, 적재, 사용의 네 검증 게이트를 차례로 통과하고 실패하면 이전 단계로 돌아가며 최종 결과는 사람 검토로 이어지는 구조 교재의 데이터 품질 강조와 Chapter 3 구현 감사를 운영 통제 관점으로 재구성. 게이트 통과는 의료적 타당성의 자동 증명이 아니라 임상의가 검토할 후보와 근거를 만들었다는 뜻이다.
표 12주장–조건–실패 신호 추적표
핵심 주장근거적용 조건실패 신호
온톨로지가 의미 통합을 돕는다표준 ID·동의어·계층으로 원천을 정렬매핑 규칙과 온톨로지 범위가 맞음동일 개념의 복수 ID·잘못된 계층
LPG가 이 사례의 주석 탐색을 단순화한다관계 속성으로 출처·증거에 직접 접근개별 주석 다중성을 모델이 보존관계 MERGE로 근거가 덮임
표현형 조합이 후보를 좁힌다공유 특징 수 기준의 재현 가능한 질의입력 코딩·주석·버전이 적절결과가 드리프트하거나 근거가 빈약
계층 추론이 숨은 연관을 찾는다SUBCLASSOF 경로의 암시적 포함계층 방향·깊이·규칙이 타당너무 넓은 후보·설명할 수 없는 경로

6.2 근거 화면은 이미 만들 수 있다

지금까지 “임상의가 근거를 열어볼 수 있어야 한다”를 반복했다. 그것이 실제로 어떤 모습인지 보여주는 것이 교재 §3.4의 연습문제다 — “Listing 3.26의 질의를 확장해 evidence_name, evidence_description, source, url을 포함한 관계 속성까지 조회하라.” 노트북이 이 문제를 풀어 두었다.

// 책 3.26 + 관계 속성 — 후보와 '왜 그렇게 판단했는지'를 함께 반환한다
MATCH (phe:HpoPhenotype) WHERE phe.label IN $features
MATCH (dis:HpoDisease)-[rel:HAS_PHENOTYPIC_FEATURE]->(phe)
WITH dis, count(DISTINCT phe) AS matched,
     collect({feature: phe.label, evidence: rel.evidenceName,
              description: rel.evidenceDescription, source: rel.source,
              url: rel.url, curator: rel.createdBy, date: rel.creationDate}) AS grounds
RETURN dis.id AS disease_id, dis.label AS disease, matched, grounds
ORDER BY matched DESC, disease

달라진 것은 한 군데뿐이다. 관계에 rel이라는 이름을 붙이고, rel.source·rel.url·rel.createdBy를 함께 담았다. 실행 결과는 이렇게 나온다.

OMIM:619269  Ondontochondrodysplasia 2 with hearing loss and diabetes — 특징 5개 일치
  · Pruritus
      증거   : Published clinical study
      출처   : PMID:32101163  ->  https://pubmed.ncbi.nlm.nih.gov/32101163
      큐레이션: probinson (2021-06-20)
  · Growth delay                     (이하 3건 동일 근거)
      ...

OMIM:618500  Holoprosencephaly 12 with or without pancreatic agenesis — 특징 3개 일치
  · Growth delay
      증거   : Published clinical study
      출처   : PMID:31006513  ->  https://pubmed.ncbi.nlm.nih.gov/31006513
      큐레이션: probinson (2023-03-16)
  · Sensorineural hearing impairment
      증거   : Inferred from electronic annotation
      출처   : OMIM:618500   ->  https://omim.org/entry/618500
      큐레이션: skoehler (2019-09-07)

여기서 두 후보의 성격이 갈린다. 1위 후보는 다섯 특징이 전부 같은 논문 하나(PMID:32101163)에서 왔다. 즉 이 증후군을 기술한 단일 임상 연구가 근거다. 2위 후보는 근거가 섞여 있다 — 하나는 Published clinical study(PMID:31006513 — 사람이 논문을 읽고 기록), 둘은 Inferred from electronic annotation(OMIM:618500 텍스트를 기계가 파싱). 같은 “3개 일치”라도 근거의 무게가 다르다.

겹침 수만 보면 이 차이가 보이지 않는다. §4.2의 “단순 겹침 수는 증거 질을 가중하지 않는다”는 경고가 추상이 아닌 이유가 이것이고, §2.2에서 LPG를 고른 결정이 최종적으로 값을 치르는 지점이기도 하다. RDF였다면 이 화면을 만들기 위해 n항 관계·명명 그래프·RDF-star 중 하나로 우회해야 했다.

6.3 이 장은 임상 의사결정 지원의 개념 증명이다

교재 사례는 HPO 데이터 위에서 질의와 추론이 어떻게 작동하는지 보여주는 교육용 예시다. 환자 EHR의 실제 수집·동의·권한·비식별화, 부정 표현형, 발현 시점, 환자군별 빈도, 임상 검증, 규제와 책임 배분은 다루지 않는다. 따라서 결과를 “진단”이라고 표현하기보다 검토 가능한 후보 생성으로 제한해야 한다.

Safety Boundary그래프가 반환한 질병명은 정답이 아니라 조사 순서다. 원 주석·추론 경로·데이터 버전을 함께 보여주고, 최종 판단은 임상의와 검증 절차에 남긴다.
Section 07 · Decision & Takeaways

7. 온톨로지·LPG·추론은 질문에 필요한 만큼만 쓴다

섹션 요약
  • 여러 원천의 의미 정렬과 계층 질의가 핵심이면 온톨로지 우선 접근의 가치가 크다.
  • 관계별 provenance와 경로 탐색이 중심이면 LPG가 단순할 수 있지만 RDF 대안도 요구별로 비교한다.
  • 작은 대표 질문으로 데이터·모델·추론·검토 비용을 함께 시험한다.

7.1 기술 이름이 아니라 지식 질문으로 선택한다

표 13온톨로지 기반 KG 도입 판단표
질문도입 가치가 커지는 신호다른 접근이 나은 신호작은 실험
의미 정렬이 핵심인가?동의어·중의어·세분성 때문에 통합 오류가 큼단일 시스템의 고정 코드만 사용상위 100개 값의 표준 ID 매핑 감사
관계 경로가 답인가?여러 개체의 연결과 provenance가 판단 근거단일 테이블 필터·집계로 충분대표 질문 3개를 RDB와 그래프로 비교
계층 추론이 필요한가?상·하위 개념을 따라 누락을 줄여야 함정확 일치만 요구하고 분류체계가 불안정직접/추론 결과의 정밀 검토 표본
갱신을 책임질 수 있는가?릴리스·매핑·품질 소유자와 롤백이 있음latest 적재만 하고 결과 드리프트를 추적하지 않음두 릴리스 diff와 회귀 질의 실행
결과를 검토할 수 있는가?경로·출처·증거를 사용자에게 보여줄 수 있음질병명만 반환하고 검증 책임자가 없음임상의 리뷰용 설명 화면 프로토타입

7.2 최소 파일럿은 질문–스냅샷–그래프–근거 화면이다

첫 파일럿은 거대한 의료 온톨로지 플랫폼이 아니다. 대표 임상 질문 3개와 기대 경로를 정의하고, 같은 날짜의 hp.owlphenotype.hpoa를 고정한다. 필요한 HPO 하위 범위만 적재하고, 주석 다중성을 보존하는 모델을 선택한다. 직접 질의와 계층 추론의 결과를 분리해 표시하고, 원 출처와 경로를 한 번에 열 수 있는 검토 화면을 만든다.

중단 기준도 함께 둔다. 표준 ID 매핑 오류가 허용 범위를 넘거나, 주석 건수 보존이 설명되지 않거나, 추론 후보가 임상의 검토 시간을 줄이지 못하거나, 릴리스 갱신 때 회귀 결과를 설명할 수 없다면 범위를 줄이거나 모델을 바꾼다.

Three Takeaways질문이 모델을 고르고, 온톨로지가 의미를 고정하며, 근거 경로가 결과의 신뢰 범위를 정한다. 그래프의 완성은 적재 성공이 아니라 사용자가 후보와 이유를 함께 검토할 수 있을 때다.

7.3 토론 질문

표 14책의 개념을 우리 설계로 옮기는 질문
질문드러나는 설계 결정좋은 답의 증거
우리 데이터의 “같은 개념”은 누가 승인하는가?매핑 소유권·변경 이력·충돌 정책표본 감사와 되돌릴 수 있는 매핑 버전
관계 하나에 여러 근거가 있을 때 어떻게 보존할까?관계 속성 vs Annotation 노드원천 주석 수와 그래프 assertion 수의 대응
추론으로 포함된 후보를 사용자에게 어떻게 설명할까?경로 표시·깊이·규칙·불확실성 UI원 주석과 전체 계층 경로를 한 번에 검토
Appendix A · Glossary

핵심 용어

표 15Chapter 3 용어 사전
용어이 리포트에서의 의미
온톨로지도메인의 표준 개념·식별자·속성·범주·관계를 명시한 참조 어휘와 스키마.
HPO인간 표현형 이상을 표준화하고 계층화하며 질병 연관 주석을 제공하는 Human Phenotype Ontology.
표현형개인에게 관찰되는 물리적·생화학적 특징. 이 사례에서는 HPO 식별자로 코딩한다.
주석특정 질병–표현형 연관과 그 출처·증거·빈도·큐레이션 정보를 묶은 주장.
RDF주어–술어–목적어 트리플로 지식을 표현·교환하는 W3C 표준 데이터 모델.
LPG레이블이 있는 노드와 개별 관계에 키–값 속성을 둘 수 있는 그래프 모델.
Neosemantics(n10s)Neo4j에서 RDF/OWL 어휘를 가져오고 일부 의미 질의를 지원하는 플러그인.
추론저장된 주석과 온톨로지 규칙·계층에서 명시되지 않은 포함 관계를 도출하는 과정.
provenance어떤 주장이 어느 출처·작성자·시점·처리 과정에서 왔는지를 추적하는 정보.
Appendix B · References

참고 자료와 재구성 범위

  1. [1]Alessandro Negro et al., Knowledge Graphs and LLMs in Action, Chapter 3 “Create Your First Knowledge Graph from Ontologies,” Manning, pp. 37–64. 본문의 개념·코드·예제·수치는 이 장을 자체 해설로 재구성했다. 저장소의 한국어 해설판원문 마크다운에서 대조할 수 있다.
  2. [2]Human Phenotype Ontology 공식 포털과 hp.owl 배포 경로. 교재와 로컬 실습이 참조한 온톨로지 원천.
  3. [3]HPOA release data. 이 URL은 latest이므로 재현 가능한 운영에서는 고정 릴리스와 체크섬을 기록해야 한다.
  4. [4]저장소의 Chapter 3 원문·한국어 번역·해설판, src/ch03/importer/import_hpo.py, study.toml 리스팅 대응표를 원자료 감사에 사용했다. 업스트림 리스팅 원문은 저자 저장소 alenegro81/…/chapters/ch03에 있다.
  5. [5]그림 1–8은 교재 도형을 복제하지 않고 Chapter 3의 논리 관계를 발표자가 새로 구성한 SVG다. 임상 결과 그림의 질병명과 공유 특징 수만 교재의 HPO 2025-02 결과 스냅샷을 따른다.
  6. [6]학습 노트북 03_chapter_guide.ipynb. 표 7·9·10과 §3.4·4.2·5.2·6.2의 모든 실측 수치·실행 결과·소요 시간은 이 노트북을 Neo4j 5.26.28 Community(플러그인 n10s·apoc·gds)에서 HPOA 2026-06-23 스냅샷으로 끝까지 실행한 기록에서 가져왔다. 교재 값과 다른 숫자는 모두 이 재실행 결과다.
  7. [7]본문의 Cypher·Turtle·TriG·SPARQL 코드는 교재 Listing 3.5–3.28을 옮긴 것이다. 실행 가능한 리스팅은 저장소 src/ch03/listings/의 원문을 따랐고(파일 번호는 교재 번호 +3), 원서 조판 과정에서 깨진 구두점만 바로잡았다. 주석 줄과 들여쓰기는 읽기 편하도록 발표자가 붙인 것이며 교재 원문에는 없다.