Overview
Before extracting data, you need to understand what’s available in each CDM. dsOMOP v2 provides schema metadata and aggregate exploration functions without returning clinical rows to the client.
Each reviewed aggregate has its own disclosure contract.
Person-bearing summaries gate on distinct persons; counts/cells may then
be banded, omitted or blocked (see the Security vignette). This
is not a blanket guarantee for unrestricted adaptive queries or for
methods exported by other server packages. Client wrappers return a
dsomop_result object with a $per_site list
(one entry per server), an optional $pooled slot (when
scope = "pooled"), and a $meta slot that
records the generated call and any server warnings.
Prerequisites
library(dsOMOPClient)
# Assume connections are established and the OMOP resource is attached:
# ds.omop.connect(resource = "project.omop_cdm", conns = conns, symbol = "omop")Listing Tables and Columns
tables <- ds.omop.tables(symbol = "omop", conns = conns)
# Per-site character vectors of discovered table names
cols <- ds.omop.columns("condition_occurrence", symbol = "omop", conns = conns)
# Per-site column metadata: name, type, nullabilitydsOMOP combines runtime schema introspection with an authorized OHDSI
CDM specification. Standard tables/columns outside the recognized
version fail closed; custom tables and columns appear only when the
controller explicitly lists them in
dsomop.allowed_cdm_extensions.
Relationships
joins <- ds.omop.joins(symbol = "omop", conns = conns)
# Inferred join edges (person_id, visit_occurrence_id, *_concept_id, ...)Table and Column Statistics
ts <- ds.omop.table.stats("measurement", symbol = "omop", conns = conns)
# row_count and person_count (COUNT(DISTINCT person_id)); suppressed if small
cs <- ds.omop.column.stats("measurement", "value_as_number",
concept_id = 3004410, symbol = "omop", conns = conns)
cs$per_site$server1ds.omop.column.stats() returns banded
support/completeness fields and, for a reviewed numeric measure, a mean
and sample sd over one within-person mean per contributor.
The mean and standard deviation have different minimum-support floors.
Raw minima/maxima and quantiles are deliberately absent; request the
separately gated quantile or histogram endpoints when their estimand is
appropriate.
Concept Prevalence
Concepts in a table can be ranked by distinct persons (default) or
records. The server suppresses small cells first and orders by the
released count band, then by the public concept key; top_n
never exposes exact within-band rank:
prev <- ds.omop.concept.prevalence("condition_occurrence",
metric = "persons", top_n = 50, symbol = "omop", conns = conns)
head(prev$per_site$server1)Person-column prevalence (demographics)
Pass an explicit concept_col to rank the categorical
concepts on the person table. This is how you obtain
gender, race, and ethnicity breakdowns:
ds.omop.concept.prevalence("person", concept_col = "gender_concept_id",
symbol = "omop", conns = conns)
ds.omop.concept.prevalence("person", concept_col = "race_concept_id",
symbol = "omop", conns = conns)
ds.omop.concept.prevalence("person", concept_col = "ethnicity_concept_id",
symbol = "omop", conns = conns)Death table
The death table is explored the same way, naming its
cause concept column:
ds.omop.concept.prevalence("death", concept_col = "cause_concept_id",
symbol = "omop", conns = conns)All-concepts / global prevalence with pagination
global = TRUE ranks concepts across all
person-bearing CDM tables in one merged, re-gated ranking instead of a
single table. Use offset together with top_n
to page through the banded, public-key-stabilized ordering (the page is
[offset+1 .. offset+top_n]):
# First page of the 25 most prevalent concepts across the whole CDM
page1 <- ds.omop.concept.prevalence(global = TRUE, top_n = 25,
symbol = "omop", conns = conns)
# Next page
page2 <- ds.omop.concept.prevalence(global = TRUE, top_n = 25, offset = 25,
symbol = "omop", conns = conns)Small cells are suppressed over the merged set before the page is taken, so pagination never reveals a blocked count or the exact ordering inside a count band.
Value Frequencies and Histograms
ds.omop.value.counts("person", "gender_concept_id",
symbol = "omop", conns = conns)
ds.omop.value.histogram("measurement", "value_as_number",
concept_id = 3004410, symbol = "omop", conns = conns)
ds.omop.value.quantiles("measurement", "value_as_number",
symbol = "omop", conns = conns)Value counts enforce small-cell suppression, band-based ordering and
a maximum number of distinct levels (see Security). Profiling a
clinical/results table also requires a reviewed direct route to
person_id; dsOMOP does not silently treat repeated event
rows as independent people or ignore an inapplicable cohort scope.
Other Profiling
ds.omop.domain.coverage(scope = "pooled", symbol = "omop", conns = conns)
ds.omop.missingness("measurement",
columns = c("value_as_number", "value_as_concept_id"),
symbol = "omop", conns = conns)
ds.omop.crosstab("person", "gender_concept_id", "race_concept_id",
symbol = "omop", conns = conns)Scoping Exploration to a Cohort
Most exploration functions accept a unified cohort =
argument so you can run the same profiling within a subpopulation. The
argument accepts a cohort handle (from
ds.omop.cohort.create() /
ds.omop.cohort.combine() /
ds.omop.cohort.from_table()), a numeric cohort definition
id, or a server-side cohort table name:
dm <- ds.omop.cohort.create(
spec = list(type = "condition", concept_set = c(201826)),
symbol = "omop", conns = conns)
# Prevalence, statistics and missingness restricted to the diabetes cohort
ds.omop.concept.prevalence("drug_exposure", cohort = dm,
symbol = "omop", conns = conns)
ds.omop.column.stats("measurement", "value_as_number",
concept_id = 3004410, cohort = dm, symbol = "omop", conns = conns)
ds.omop.missingness("measurement", cohort = dm,
symbol = "omop", conns = conns)See the Cohort Management vignette for building cohorts,
including turning a workspace omop.table into a reusable
cohort with ds.omop.cohort.from_table().
Disclosure-Gated Manipulation of Extracted Tables
Once a plan or recipe has produced a server-side
omop.table (see Extraction), you can reshape it
without pulling any row-level data using four
disclosure-aware verbs. Each verb takes the name(s) of
server-side omop.table symbols, computes the new frame on
every server, and stores it under newobj. Only
omop.table inputs are accepted; the result is re-gated on
the number of distinct person tokens and re-tagged as
an omop.table. Merge and row-bind can increase row counts
(and a person-key join can multiply longitudinal rows), so analysts must
still choose the intended grain. The distinct-person gate is reapplied
and the verbs fail closed when support is insufficient.
# Join two token-keyed frames on the person key (only the person key is allowed)
joined <- ds.omop.merge("baseline", "labs", by = "person_id", type = "left",
symbol = "omop", conns = conns)
# Keep broad categories; numeric/date ranges belong in the recipe filter DSL
women <- ds.omop.filter("baseline", var = "sex", op = "==", value = "F",
symbol = "omop", conns = conns)
# Project to a subset of columns (the person token is always retained)
slim <- ds.omop.select(joined, cols = c("age", "gender_concept_id"),
symbol = "omop", conns = conns)
# Row-bind two schema-identical frames; the gate counts DISTINCT persons,
# so binding a frame to itself cannot inflate the count past the threshold
stacked <- ds.omop.bind_rows("wave1", "wave2", symbol = "omop", conns = conns)Each call returns (invisibly) the name of the new server-side symbol,
which you can feed into further reviewed verbs, into
ds.omop.cohort.from_table(), or into an allowlisted
analysis method that explicitly supports the object’s grain and
protected columns. Generic dsBaseClient compatibility is
not implied.
Multi-Server Schema Comparison
When working with several servers, compare their schemas before planning:
comparison <- ds.omop.compare(symbol = "omop", conns = conns)
comparison$common_tables # tables present on every server
comparison$server_only # tables unique to particular servers
comparison$column_diffs # column differences across serversThis comparison feeds plan harmonization (see the Multi-Server vignette).
Schema Snapshot
snapshot <- ds.omop.snapshot(symbol = "omop", conns = conns)
# Tables, columns and relationships per server, for documentation/visualisation