toxr / examples /sql_provider_example.py
vedatonuryilmaz's picture
Upload examples/sql_provider_example.py
806a0b8 verified
Raw
History Blame Contribute Delete
4.7 kB
"""SQL provider example — extract from a simulated invitroDB and tensorize.
This shows the provider-aware path: SQL source tables are preserved as
Arrow tables, keys/lineage are tracked, and pushdown filters reduce what's
pulled before canonicalization.
"""
import sqlite3
import tempfile
import os
import warnings
warnings.filterwarnings("ignore")
from toxarrow.adapters import SqlProviderAdapter, SqlTableMapping
from toxarrow.compile.arrow_store import ArrowStudyStore
from toxarrow.schemas.tensor import DenseGridSpec
from toxarrow.materialize.materializer import TensorMaterializer
# -- 1. Build a synthetic SQL database (simulating invitroDB / ToxCast schema) --
db_path = os.path.join(tempfile.mkdtemp(), "invitro.sqlite")
conn = sqlite3.connect(db_path)
conn.executescript("""
CREATE TABLE chemical (chid TEXT PRIMARY KEY, chnm TEXT);
CREATE TABLE assay (aeid TEXT PRIMARY KEY, aenm TEXT, assay_family TEXT);
CREATE TABLE mc5 (
m5id INTEGER PRIMARY KEY,
chid TEXT, aeid TEXT,
modl_acc REAL, modl_ga REAL, hitc INTEGER,
FOREIGN KEY (chid) REFERENCES chemical(chid),
FOREIGN KEY (aeid) REFERENCES assay(aeid)
);
INSERT INTO chemical VALUES
('CHEM001','Bisphenol A'),
('CHEM002','Genistein'),
('CHEM003','17b-Estradiol'),
('CHEM004','Atrazine');
INSERT INTO assay VALUES
('A01','ER_alpha_agonism','nuclear_receptor'),
('A02','AR_antagonism','nuclear_receptor'),
('A03','Mitochondrial_tox','cytotoxicity');
-- Only active hits (hitc=1) with measurable AC50
INSERT INTO mc5 VALUES
(1,'CHEM001','A01',0.45,85.0,1),
(2,'CHEM001','A03',12.0,40.0,1),
(3,'CHEM002','A01',0.08,95.0,1),
(4,'CHEM002','A02',2.4,60.0,1),
(5,'CHEM003','A01',0.002,100.0,1),
(6,'CHEM003','A02',0.15,75.0,1),
(7,'CHEM004','A03',45.0,25.0,1);
-- CHEM001 has NO hit for A02 (inactive in AR assay)
-- CHEM004 has hits only in A03
""")
conn.commit()
# -- 2. Define provider-aware mappings --
mappings = [
SqlTableMapping(
table="chemical", entity="unit", primary_key="chid",
column_map={"unit_id": "chid", "label": "chnm"},
order_by="chid",
),
SqlTableMapping(
table="assay", entity="endpoint", primary_key="aeid",
column_map={
"endpoint_id": "aeid", "endpoint_name": "aenm",
"endpoint_family": "assay_family",
},
order_by="aeid",
),
# Pushdown: only pull hitc=1 rows (active hits)
SqlTableMapping(
table="mc5", entity="observation", primary_key="m5id",
column_map={
"unit_id": "chid", "endpoint_id": "aeid",
"value": "modl_acc",
},
where="hitc = 1",
order_by="chid, aeid",
),
]
adapter = SqlProviderAdapter(
conn, mappings, study_id="invitro-example",
provider_name="invitrodb-synthetic", organism_or_system="in_vitro",
)
# -- 3. Stage (source tables preserved) + canonicalize --
tables = adapter.stage()
print(f"Staged {len(tables)} source tables:")
for name, tbl in tables.items():
print(f" {name}: {tbl.num_rows} rows, columns={tbl.column_names}")
group = next(adapter.read())
print(f"\nCanonical: {len(group.units)} chemicals, {len(group.endpoints)} assays, "
f"{len(group.observations)} active hits")
# -- 4. Compile to Arrow, build Chemical × Assay grid --
store = ArrowStudyStore.from_records(group)
import numpy as np
rows = store.observations.to_pylist()
chem_ids = sorted([u.unit_id for u in group.units])
assay_order = sorted([e.endpoint_id for e in group.endpoints])
assay_idx = {a: i for i, a in enumerate(assay_order)}
vals = np.full((len(chem_ids), len(assay_order)), np.nan, dtype=np.float32)
mask = np.ones((len(chem_ids), len(assay_order)), dtype=bool)
for r in rows:
ci = chem_ids.index(r["unit_id"])
ai = assay_idx[r["endpoint_id"]]
v = r.get("value")
if v is not None:
vals[ci, ai] = float(v)
mask[ci, ai] = False
print(f"\nChemical × Assay grid: shape={vals.shape}")
print(f"Missing cells (masked): {mask.sum()}")
for ci, cid in enumerate(chem_ids):
hits = [f"{a}:{vals[ci,i]:.2f}" if not mask[ci,i] else f"{a}:---" for i,a in enumerate(assay_order)]
print(f" {cid}: {hits}")
# Verify known missing cells
assert mask[chem_ids.index("CHEM001"), assay_idx["A02"]] == True, "CHEM001 missing AR"
assert mask[chem_ids.index("CHEM004"), assay_idx["A01"]] == True, "CHEM004 missing ER"
assert mask[chem_ids.index("CHEM004"), assay_idx["A02"]] == True, "CHEM004 missing AR"
print("\nAll checks passed: missingness masks correct, source tables preserved")
conn.close()