MedRecords
MedRecords is an opinionated wrapper around GraphRecords for medical data. It puts a graph record to work in a medical context and makes it the input medical analyses and products run on.
Installation
pip install medrecords
Rendering a report to PDF needs the pdf extra:
pip install medrecords[pdf]
Building a Record
Every call returns a new record:
import medrecords as mr
medrecord = (
mr.MedRecord()
.add_nodes_in_group(
[
("p1", {"gender": "F", "age": 71}),
("p2", {"gender": "M", "age": 64}),
("p3", {"gender": "F", "age": 38}),
],
"Patient",
)
.add_nodes_in_group(
[("I10", {"label": "Essential hypertension"})],
"Diagnosis",
)
.add_edges_in_group(
[("p1", "I10", {"year": 2019}), ("p2", "I10", {"year": 2021})],
"Patient_Diagnosis",
)
)
add_nodes and add_edges do the same without a group. Polars DataFrames work as a source, naming the index columns: add_nodes((frame, "patient_id")), add_edges((frame, "patient_id", "diagnosis_id")).
Reading a Record
medrecord.node_count() # 4
medrecord.group_indices() # ['Patient', 'Diagnosis', 'Patient_Diagnosis']
medrecord.group("Patient").nodes() # ['p1', 'p2', 'p3']
patient = medrecord.node("p1")
patient.attributes() # {'gender': 'F', 'age': 71}
patient.groups() # ['Patient']
Querying
mr.nodes() and mr.edges() start an expression tied to no record. medrecord.nodes() binds one to a record, which makes it a series. A series runs when you call evaluate().
older = medrecord.nodes().filter(
mr.nodes().in_group("Patient") & (mr.nodes().attribute("age") > 60)
)
list(older.evaluate()) # ['p1', 'p2']
diagnosed = (
medrecord.edges().filter(mr.edges().in_group("Patient_Diagnosis")).source_node()
)
list(diagnosed.evaluate()) # ['p1', 'p2']
medrecord.nodes().filter(mr.nodes().in_group("Patient")).attribute("age").mean().evaluate()
# 57.666666666666664
medrecord.query(expression) binds a free expression the same way.
Evaluations
An analytic is a class with one compute that returns one value. Analytics go into groups, groups into an evaluation:
from typing import Union
from medrecords.evaluation import Analytic, Evaluation, EvaluationGroup, read
from medrecords.evaluation.catalogue import AttributeCounts, AttributeMean, ElementCount
PATIENTS = mr.nodes().filter(mr.nodes().in_group("Patient"))
class OldestPatient(Analytic[mr.MedRecord]):
"""The age of the oldest patient."""
def compute(self, medrecord: mr.MedRecord) -> Union[mr.Value, mr.QueryError]:
return read(medrecord.query(PATIENTS).attribute("age").max().evaluate(), int)
evaluation = (
Evaluation[mr.MedRecord]("Practice snapshot")
.add_group(
"Demographics",
EvaluationGroup[mr.MedRecord]()
.add_analytic("Patients", ElementCount(PATIENTS))
.add_analytic("Mean age", AttributeMean("age", PATIENTS))
.add_analytic("Sex", AttributeCounts("gender", PATIENTS)),
)
.add_analytic("Oldest", OldestPatient())
)
The report is read by name:
report = evaluation.report(medrecord)
report.group("Demographics").analytic("Patients").result # 3
report.group("Demographics").analytic("Mean age").result # 57.666666666666664
report.analytic("Oldest").result # 71
A result is a number or a string, or one of Table, Plot, Measurement, Assessment and Distribution. report.group("Demographics").analytic("Sex").result.to_polars() gives
┌───────┬───────┬──────────┐
│ value ┆ count ┆ share │
╞═══════╪═══════╪══════════╡
│ F ┆ 2 ┆ 0.666667 │
│ M ┆ 1 ┆ 0.333333 │
└───────┴───────┴──────────┘
medrecords.evaluation.catalogue holds analytics for counts, attribute statistics, completeness, ranges and uniqueness. add_group_over and add_group_over_each run a group against something derived from the record, a cohort or one part per attribute value.
JSON and PDF are exports:
from medrecords.evaluation import Document, Report
report.to_json("report.json")
Report.from_json("report.json")
Document(report).to_pdf("report.pdf") # needs the pdf extra
Documentation
Release files for medrecords 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| medrecords-0.1.0.tar.gz | 1.0 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| medrecords-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 2.0 MB
Release files / medrecords-0.1.0.tar.gz
| Download URL | medrecords-0.1.0.tar.gz |
|---|---|
| Size | 1.0 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9c9d4c0841576c3db03b04a1ba146a88449e862b6c4eca20883ed272b4f3234d
|
|
BLAKE2b-256 checksum How to use checksums |
f60864465595d053b496939518bfea0ed769c19175f588557b3dbbf620c740a8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / medrecords-0.1.0-py3-none-any.whl
| Download URL | medrecords-0.1.0-py3-none-any.whl |
|---|---|
| Size | 1.0 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f65d923c43434e5ae8c72badb744aac54e147acbe73f78b002092073c8038b7c
|
|
BLAKE2b-256 checksum How to use checksums |
c94a69cf5438d58e7a578eb6261171de6046c8f7e71e198e2adbbae4f6dbcb18
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|