Dataset Dependency Graphs for Leakage-Aware Evaluation
Overview
Represent biomedical dataset structure as typed dependency graphs so that sample provenance, repeated-measure structure, study design, batch effects, and temporal relationships are explicit and inspectable. Validates dataset structure, detects sample-level overlap, derives deterministic split constraints, and produces a tool-agnostic split specification for leakage-aware evaluation workflows.
Fixes made during CRAN submission
export_graph(format = "gml")now passes explicit node ids to igraph's GML writer. Leaving igraph'sidargument at itsNULLdefault made newer igraph versions hand a zero-length vector to the C layer, which rejected it with "Size of id vector must match vertex count"; the failure appeared on CRAN's R-devel machines while passing locally on igraph 2.2.1.- The README's links to
CONTRIBUTING.mdandCODE_OF_CONDUCT.mdare now absolute GitHub URLs. Both files are build-ignored, so the relative links dangled inside the installed package.
A deliberate **breaking release** (see "Breaking changes" below); everything
after it is intended to be additive. The theme is maturity: every step of the
pipeline is now linear in the size of the dataset, the split_spec contract is
Show all changes in 0.4.0Hide the rest of the changes in 0.4.0
self-contained and schema-checked, the 0.1-era API debt is gone, and what each
downstream consumer actually accepts is documented and pinned by tests.
Performance
Every step of the pipeline is now linear in the number of nodes and edges, and
the derivation results, validation issues, and written JSON are byte-identical
to 0.3.0 on regression cohorts.
- **Composite and pairwise grouping no longer enumerate sample pairs.**
derive_split_constraints(mode = "composite"),mode = "relatedness",mode = "spatial", anddetect_dependency_components()compute connected components on the bipartite sample-target graph. With the defaultvia(subject, batch, study, time) composite derivation of 500 samples took about 70 s and grew quadratically (about 3.5 min at 750 samples); it now takes under 0.1 s at 5 000 samples and about 0.2 s at 20 000. - **Direct assignments, sample maps, and rule-based composites are vectorised** (one
data.frame()per table instead of one per sample).as_split_spec(constraint, graph = g)dropped from 27 s to about 0.3 s at 5 000 samples. - **Validation builds its issue table once** instead of
rbind-ing one-row frames, checks edge signatures with a single join against the schema, and replacesstats::aggregate()withsplit()-based counting: 7 s to under 1 s at 5 000 samples. - **JSON writers hand jsonlite data frames** instead of nested per-row lists, and
spatial_edges_from_coords()scans the distance matrix withwhich()instead of a double loop. create_nodes()detects conflicting duplicate definitions in one pass over the distinct rows rather than one table scan per duplicated identifier.- New
inst/bench/pipeline.Rreproduces the timings, andtests/testthat/test-performance.R(skipped on CRAN) fails if any step exceeds a generous wall-clock budget or composite derivation stops scaling linearly.
New features
**split_spec contract (schema 0.3.0)**
- **Stratum annotation.**
as_split_spec(constraint, graph = g)fills a newstratumcolumn with the key of the singleOutcomenode attached to each sample (sample_has_outcome, falling back to the subject's single outcome viasubject_has_outcome) and declares it throughstratum_var. It is an annotation of the outcome level, exposed so consumers such as scikit-learn'sStratifiedGroupKFoldcan stratify; splitGraph still never balances folds.validate_split_spec()checks the declared column (invalid_stratum_var,empty_stratum_var,partial_stratum). The Python reader gainsstratum_var, a default forstrata(), andstratified_group_kfold(); the conformance test compares strata as well as grouping and ordering. - **Pairwise sources inside composite derivations.**
viaformode = "composite"now accepts"relatedness"and"spatial"(or their relation names) alongside the direct sources. In the strict strategy their thresholded edges join the same connected-component search; in the rule-based strategy they contribute the component label, and a singleton component counts as no assignment so the sample falls through to the next mode.metadata$vialists node types for direct sources and mode names for pairwise ones. - **Provenance.**
relatedness_edges_from_kinship()andspatial_edges_from_coords()record their threshold and metric on the edge set'ssource;build_dependency_graph()carries every edge set's source intometadata$edge_sources(also serialised); pairwise constraints reportmetadata$threshold/threshold_metric; andas_split_spec()addsvia,priority,threshold,threshold_metric, andigraph_versionto the spec metadata. - **Schema 0.3.0, versioned.**
schema_versionmoves to"0.3.0"(same major:0.1.0and0.2.0files still load silently) and the shipped schemas now live underinst/schema/<version>/, so a file's$schemaURL stays valid after later bumps.migrate_split_spec_json()fillsstratumwithNA. - **Validating readers.**
read_dependency_graph(path, validate = TRUE)andread_split_spec(path, validate = TRUE)check the file against the shipped schema before parsing and runvalidate_graph()/validate_split_spec()on the result, failing with a classed error. The R-side JSON validators themselves now check thatattrsis an object, thatsample_datacolumns have the declared types, and that vector-valued metadata fields are arrays. - **New leakage rule
subject_cross_site_overlap** (warning): a subject with samples collected at several sites, mirroringsubject_cross_study_overlap;summarize_leakage_risks()reports it as severed by subject or site grouping.
**Graph editing and export**
subset_graph(g, samples),combine_graphs(g1, g2, ...), andadd_edges(g, edge_set)derive new, validated graphs from existing ones without rebuilding from node/edge sets. Subsetting has the same semantics asderive_split_constraints(samples = ); combining regenerates edge ids and rejects conflicting definitions; adding edges preserves existing ids.export_graph(g, file, format)writes GraphML, GML, or node/edge CSV tables for Cytoscape, Gephi, networkx, andigraph::read_graph(), with theattrslist-column flattened toattr_<name>scalar columns.plot(g, focus = "sample_projection")draws the sample graph implied by the chosenviarelations, andplot(g, focus = "ego", node = )draws one node's neighbourhood. The plot method is now documented (?plot.dependency_graph).
**Input and consumers**
graph_from_metadata()is now an S3 generic with aSummarizedExperimentmethod (Bioconductor's \pkg{SummarizedExperiment} in Suggests):colData()is used as the metadata table and the assay column names becomesample_idwhen no such column exists (sample_id_col =overrides).- The adapter-cookbook vignette's
rsampleadapters are now executed whenrsampleis installed (it is in Suggests); the base-R adapter was already executed. ?as_split_specand the README gain a "What downstream consumers read" table, verified by running against bioLeak 0.3.8. The contract test now pins every row: bioLeak accepts exactly the subject / batch / study / time modes; it errors on site, region, platform, assay, relatedness and spatial (absent from its mode map) and also on composite (mapped to amake_split_plan()mode whose required arguments the adapter never supplies); and the documented workaround, joininggroup_idonto the observation frame and callingmake_split_plan()directly, works. A bioLeak release that fixes either limitation will surface as a deliberate test failure to flip.- The stale comment naming fastml as a consumer was removed; fastml has no splitGraph adapter.
inst/python/pyproject.tomlpackages the Python reader assplitspec(stdlib-only, optionalpandas/sklearnextras) for a PyPI release.
**Quality infrastructure and documentation**
- CI sets
NOT_CRAN=true, installs Python, and so runs the Python conformance test in both the check and coverage workflows for the first time; a new Linux-onlyperf-budgetworkflow runs the performance tests. dev/coverage.Rruns covr locally with a 90 % threshold;dev/release.mdis the release checklist (schema bump rules, line-ending check for Windows builds, reverse-dependency check against bioLeak);.lintrand_pkgdown.yml(grouped reference index) are committed.- New vignettes: **quick-start** (metadata frame to JSON
split_specwith a mode decision table), **faq-design-notes** (why notmake_split_plan()directly, composite over-merging, thresholds and transitive closure, the stratum annotation, schema versioning, conditions), and **case-study-gse60424** (a real public cohort: GEO series GSE60424, cached asinst/extdata/GSE60424_samples.csv).
**Conditions**
- **Classed conditions.** Every error raised by splitGraph now inherits from
splitgraph_error— including every invalid enumerated argument (mode,strategy,format,focus,layout,direction,outcome_scope), which previously produced a barematch.arg()error; partial matching is unchanged. Each condition carries acodefield,NAfor plain argument checks; the documented subclasses aresplitgraph_schema_error,splitgraph_reference_error,splitgraph_ambiguity_error,splitgraph_validation_error, andsplitgraph_io_error. Package warnings carrysplitgraph_warning. See?splitgraph_conditions. Messages are unchanged, so code matching on text keeps working. relatedness_edges_from_kinship()accepts a square kinship / GRM **matrix** with subject ids as row names (e.g. PLINK--make-rel squareoutput) in addition to the long pair table.
Bug fixes
- **Factor / numeric
site_id,region_id,platform_idcolumns.**graph_from_metadata()failed with annzchar()error when any of the three identifier columns introduced in 0.3.0 was a factor, becauseingest_metadata()did not coerce them to character alongside the older identifier columns. All identifier columns are now coerced, andcreate_nodes()coerces itsid_coldefensively so factor identifiers work on the manual constructor path too. - **
as_split_spec(constraint, graph = ...)no longer aborts on ambiguous annotations.** Graph enrichment fills the blocking / ordering columns (batch_group,study_group,site_group, ...,order_rank) on a best-effort basis. A source that cannot be resolved unambiguously (for example a sample linked to two batches on a graph built withvalidate = FALSE) is now left asNAand reported inmetadata$enrichment_warnings(also appended tometadata$warnings) instead of raising an error unrelated to the requested constraint mode. - **Consistent
severedcolumn.**summarize_leakage_risks()and theleakage_risk_summary()constructor now include theseveredcolumn in the empty diagnostics table, matching the populated case. - **Vector metadata fields are always JSON arrays.**
write_split_spec()usedauto_unbox, so a single-elementrelations_used,warnings, orenrichment_warningswas written as a bare string although the shipped schema declaresrelations_usedan array. These fields are now written as arrays regardless of length;read_split_spec()already coerced them back to character vectors, so existing files still load. - **Empty JSON objects are written as
{}, not[].** An emptymetadata$validation_overrides(every graph built with default arguments), an emptyedge_sources, and the metadata of a hand-builtsplit_spec()were serialised as empty *arrays*, which the shipped schemas reject since they declare objects. The R-side validators now distinguish the two cases — an empty JSON object and an empty array both parse to a zero-length list, but only the object carries names — and checkattrs,metadata,validation_overridesandedge_sources, soread_*(validate = TRUE)andvalidate_graph_json()catch the malformed shape. Nothing the package can write now fails its own validator, including edgeless graphs and specs with no metadata. - **
add_edges()accepts an edge set with no edges.** The thresholded helpers return an emptygraph_edge_setwhen no pair passes, which previously crashedadd_edges()with an unclassed error. The graph is now returned unchanged, with the threshold still recorded inmetadata$edge_sources. - **
build_dependency_graph()rejects an unnamedvalidation_overrideslist.** Overrides are looked up by name, so an unnamed list silently did nothing (and serialised as a JSON array). - **Pairwise
threshold/threshold_metricround-trip.** They are written asnullfor non-pairwise specs and now read back as typedNArather thanNULL, so a spec's metadata survives a write/read cycle unchanged. - **An ambiguous sample-level outcome no longer borrows the subject's.** A sample linked to several
Outcomenodes has no unique stratum and staysNA, instead of falling through to thesubject_has_outcomelabel.
Breaking changes
- **Removed the 0.1-era aliases deprecated in 0.2.0.** Migration: | Removed | Use instead | |---|---| |
new_depgraph_nodes()|graph_node_set()| |new_depgraph_edges()|graph_edge_set()| |new_depgraph()|dependency_graph()| |build_depgraph()|build_dependency_graph()| |validate_depgraph()|validate_graph()| |validate_graph(checks = ...)|validate_graph(levels = ..., severities = ...)| Calling a removed alias is now a "could not find function" error; passingchecks=is an "unused argument" error. - **Composite and pairwise constraints no longer carry
metadata$projection_edges.** The explicit sample-pair table was the quadratic part of the old derivation and is not needed for grouping.metadata$n_dependency_edges(the number of sample-target edges the components were computed from) replaces it; the pair table itself is still available fromdetect_dependency_components()$metadata$projection_edgesanddetect_shared_dependencies()when it is wanted. - **Removed the unused
dependency_constraint()constructor** and itsprint/summary/as.data.framemethods. Like theleakage_constraint()removed in 0.3.0, the class was exported but never produced or consumed anywhere in the package;split_constraint()(produced byderive_split_constraints()) is the supported constraint type.
Documentation
?read_dependency_graphno longer claims to return a *validated* graph: the reader checks table /igraphconsistency but does not re-runvalidate_graph(), so a file written withvalidate = FALSEloads without error. Callvalidate_graph()on files from untrusted or older sources.?graph_from_metadatanow listssite_id,region_id, andplatform_idamong the auto-detected columns and states that identifier columns may be character, factor, or numeric.?derive_split_constraintsdocuments how compositeviacombines direct and pairwise sources (the restriction to direct sources noted early in this cycle was lifted; see "Pairwise sources inside composite derivations" above).
Infrastructure
- The Python conformance test falls back to a
pythonexecutable whenpython3is absent (the usual situation on Windows), provided it reports itself as Python 3. - The stale
MD5file was removed from the source tree. It is a build artefact thatR CMD build --md5(the flag CRAN applies when it builds a submission) writes inside the tarball; a plainR CMD builddoes not create it, and it never belongs in the source tree.
From NEWS.md in the release
Install
Health
- OK13 OK2026-08-04
- NOTE12 OK, 1 NOTE2026-08-01
- OK8 OK2026-04-22
Documentation
- Help pages
- 19 (1 internal)includes a package overview page
- Examples that run
- 100%18 run in a plain check (4 of them under a condition)
- Exported functions with examples
- 100%
- Documented parameters
- 83%
- Return-value docs
- 100%
- Help pages citing references
- 0%
- Adapter cookbook: from split_spec to native resamples
- Case study: a real multi-donor, multi-cell-type cohort (GEO GSE60424)
- Cross-language handoff: R to JSON to Python to scikit-learn
- FAQ and design notes
- Modeling site, platform, relatedness, and spatial structure
- Quick start: from a metadata table to a split_spec in ten minutes
- splitGraph: From Metadata to Leakage-Aware Split Design
Downloads
Repository
- PRs merged (all time)
- 50%
- Median days to close, 50 most recently updated closed issues
- not measured
- Median days to close, 50 most recently updated closed PRs
- 2
Commits over time
11 development-tooling and community-health practices detected across 8 families in the upstream repository
Checks run against github.com/selcukorkmaz/splitgraph on 2026-10-18.
The R CMD check workflow runs on Linux, macOS and Windows, and on R-devel
Show all practicesHide 4 practices
This repository shows signs of AI coding tools.
- Claude Codefirst seen 2026-04-25Seen in the repository: .claudeNamed in .Rbuildignore: .claudeNamed in .gitignore: .claudePull request it wrote: opened by a project memberCommits: at least 22 crediting it in the message
- Claude Opus 522 commits · Sep 2026
Dependencies
Installing splitGraph adds 9 packages (6 with compiled code, 8.0 MB of source tarballs). Heaviest: igraph 4.8 MB, vctrs 1.0 MB, rlang 766 KB.
Code & Tests
Datasets
People & History
Pick two releases to compare their code metrics.
- 0.4.0Latest2026-09-17 · current release · diff ↗ ·
removed 6 exports
build_depgraph, dependency_constraint, new_depgraph, new_depgraph_edges, new_depgraph_nodes, validate_depgraphadded 4 exports
add_edges, combine_graphs, export_graph, subset_graph - 0.3.02026-07-03 · diff ↗ ·
removed 1 export
leakage_constraintadded 6 exports
migrate_dependency_graph_json, migrate_split_spec_json, relatedness_edges_from_kinship, spatial_edges_from_coords, validate_graph_json, validate_split_spec_json - 0.2.02026-04-29 · diff ↗ ·
added 4 exports
read_dependency_graph, read_split_spec, write_dependency_graph, write_split_spec - RR 4.6.0 released · 2026-04-24
- 0.1.02026-04-21
- RR 4.5.0 released · 2025-04-11
Package metadata
- First published
- 2026-04-21
- Total releases
- 4 / 1 yrs
- License
- MIT + file LICENSE OSI
- Minimum R
- ≥ 4.1.0
- Download size
- 490 KB
Cite
Cite this package
Run in R for the authors' preferred citation:
citation("splitGraph")The package provides a citation file that builds its entry when R runs it. Run citation() to see it.
Cite the R Observatory
For a number measured here: a download total, a coverage figure, an archival date.
From data release v2026-10-22, which the citation names so these numbers can be found later. More on citing and the projects behind them.