export_graph(format = "gml") now passes explicit node
ids to igraph’s GML writer. Leaving igraph’s id argument at
its NULL default 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.CONTRIBUTING.md and
CODE_OF_CONDUCT.md are 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 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.
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.
derive_split_constraints(mode = "composite"),
mode = "relatedness", mode = "spatial", and
detect_dependency_components() compute connected components
on the bipartite sample-target graph. With the default via
(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.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.rbind-ing one-row frames, checks edge signatures with a
single join against the schema, and replaces
stats::aggregate() with split()-based
counting: 7 s to under 1 s at 5 000 samples.spatial_edges_from_coords() scans
the distance matrix with which() 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.inst/bench/pipeline.R reproduces the timings, and
tests/testthat/test-performance.R (skipped on CRAN) fails
if any step exceeds a generous wall-clock budget or composite derivation
stops scaling linearly.split_spec contract (schema 0.3.0)
as_split_spec(constraint, graph = g) fills a new
stratum column with the key of the single
Outcome node attached to each sample
(sample_has_outcome, falling back to the subject’s single
outcome via subject_has_outcome) and declares it through
stratum_var. It is an annotation of the outcome level,
exposed so consumers such as scikit-learn’s
StratifiedGroupKFold can stratify; splitGraph still never
balances folds. validate_split_spec() checks the declared
column (invalid_stratum_var,
empty_stratum_var, partial_stratum). The
Python reader gains stratum_var, a default for
strata(), and stratified_group_kfold(); the
conformance test compares strata as well as grouping and ordering.via for mode = "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$via lists node types for direct sources
and mode names for pairwise ones.relatedness_edges_from_kinship() and
spatial_edges_from_coords() record their threshold and
metric on the edge set’s source;
build_dependency_graph() carries every edge set’s source
into metadata$edge_sources (also serialised); pairwise
constraints report metadata$threshold /
threshold_metric; and as_split_spec() adds
via, priority, threshold,
threshold_metric, and igraph_version to the
spec metadata.schema_version moves to "0.3.0" (same major:
0.1.0 and 0.2.0 files still load silently) and
the shipped schemas now live under
inst/schema/<version>/, so a file’s
$schema URL stays valid after later bumps.
migrate_split_spec_json() fills stratum with
NA.read_dependency_graph(path, validate = TRUE) and
read_split_spec(path, validate = TRUE) check the file
against the shipped schema before parsing and run
validate_graph() / validate_split_spec() on
the result, failing with a classed error. The R-side JSON validators
themselves now check that attrs is an object, that
sample_data columns have the declared types, and that
vector-valued metadata fields are arrays.subject_cross_site_overlap (warning): a subject
with samples collected at several sites, mirroring
subject_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, ...), and
add_edges(g, edge_set) derive new, validated graphs from
existing ones without rebuilding from node/edge sets. Subsetting has the
same semantics as derive_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, and
igraph::read_graph(), with the attrs
list-column flattened to attr_<name> scalar
columns.plot(g, focus = "sample_projection") draws the sample
graph implied by the chosen via relations, and
plot(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 a
SummarizedExperiment method (Bioconductor’s in Suggests):
colData() is used as the metadata table and the assay
column names become sample_id when no such column exists
(sample_id_col = overrides).rsample adapters are
now executed when rsample is installed (it is in Suggests);
the base-R adapter was already executed.?as_split_spec and 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 a make_split_plan() mode whose required
arguments the adapter never supplies); and the documented workaround,
joining group_id onto the observation frame and calling
make_split_plan() directly, works. A bioLeak release that
fixes either limitation will surface as a deliberate test failure to
flip.inst/python/pyproject.toml packages the Python reader
as splitspec (stdlib-only, optional pandas /
sklearn extras) for a PyPI release.Quality infrastructure and documentation
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-only perf-budget workflow runs the
performance tests.dev/coverage.R runs covr locally with a 90 % threshold;
dev/release.md is the release checklist (schema bump rules,
line-ending check for Windows builds, reverse-dependency check against
bioLeak); .lintr and _pkgdown.yml (grouped
reference index) are committed.split_spec with a mode decision table),
faq-design-notes (why not
make_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 as
inst/extdata/GSE60424_samples.csv).Conditions
splitgraph_error — including
every invalid enumerated argument (mode,
strategy, format, focus,
layout, direction,
outcome_scope), which previously produced a bare
match.arg() error; partial matching is unchanged. Each
condition carries a code field, NA for plain
argument checks; the documented subclasses are
splitgraph_schema_error,
splitgraph_reference_error,
splitgraph_ambiguity_error,
splitgraph_validation_error, and
splitgraph_io_error. Package warnings carry
splitgraph_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 square output) in addition to the
long pair table.site_id,
region_id, platform_id columns.
graph_from_metadata() failed with an nzchar()
error when any of the three identifier columns introduced in 0.3.0 was a
factor, because ingest_metadata() did not coerce them to
character alongside the older identifier columns. All identifier columns
are now coerced, and create_nodes() coerces its
id_col defensively 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 with validate = FALSE) is now left as
NA and reported in
metadata$enrichment_warnings (also appended to
metadata$warnings) instead of raising an error unrelated to
the requested constraint mode.severed column.
summarize_leakage_risks() and the
leakage_risk_summary() constructor now include the
severed column in the empty diagnostics table, matching the
populated case.write_split_spec() used auto_unbox, so a
single-element relations_used, warnings, or
enrichment_warnings was written as a bare string although
the shipped schema declares relations_used an 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.{}, not
[]. An empty
metadata$validation_overrides (every graph built with
default arguments), an empty edge_sources, and the metadata
of a hand-built split_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 check attrs,
metadata, validation_overrides and
edge_sources, so read_*(validate = TRUE) and
validate_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 empty
graph_edge_set when no pair passes, which previously
crashed add_edges() with an unclassed error. The graph is
now returned unchanged, with the threshold still recorded in
metadata$edge_sources.build_dependency_graph() rejects an unnamed
validation_overrides list. Overrides are looked up
by name, so an unnamed list silently did nothing (and serialised as a
JSON array).threshold /
threshold_metric round-trip. They are written as
null for non-pairwise specs and now read back as typed
NA rather than NULL, so a spec’s metadata
survives a write/read cycle unchanged.Outcome
nodes has no unique stratum and stays NA, instead of
falling through to the subject_has_outcome label.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;
passing checks= 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 from
detect_dependency_components()$metadata$projection_edges
and detect_shared_dependencies() when it is
wanted.
Removed the unused dependency_constraint()
constructor and its print / summary /
as.data.frame methods. Like the
leakage_constraint() removed in 0.3.0, the class was
exported but never produced or consumed anywhere in the package;
split_constraint() (produced by
derive_split_constraints()) is the supported constraint
type.
?read_dependency_graph no longer claims to return a
validated graph: the reader checks table / igraph
consistency but does not re-run validate_graph(), so a file
written with validate = FALSE loads without error. Call
validate_graph() on files from untrusted or older
sources.?graph_from_metadata now lists site_id,
region_id, and platform_id among the
auto-detected columns and states that identifier columns may be
character, factor, or numeric.?derive_split_constraints documents how composite
via combines direct and pairwise sources (the restriction
to direct sources noted early in this cycle was lifted; see “Pairwise
sources inside composite derivations” above).python
executable when python3 is absent (the usual situation on
Windows), provided it reports itself as Python 3.MD5 file was removed from the source tree. It
is a build artefact that R CMD build --md5 (the flag CRAN
applies when it builds a submission) writes inside the tarball; a plain
R CMD build does not create it, and it never belongs in the
source tree.This release broadens the vocabulary of leakage relations splitGraph
can model, hardens the split_spec interchange format into a
formally specified contract, and demonstrates that contract across a
language boundary. splitGraph continues to stop at the constraint /
split_spec boundary: generating folds, fitting models,
applying purge/embargo, and producing statistical leakage evidence
remain the responsibility of downstream consumers such as
bioLeak.
New leakage relations
Site node type and
sample_collected_at_site edge. Multi-site /
multi-center structure is now a first-class typed relation.
graph_from_metadata() auto-detects a site_id
column; validate_graph() flags samples assigned to multiple
sites (sample_multiple_site_assignments);
derive_split_constraints(mode = "site") groups samples by
collection site (and rejects ambiguous multi-site assignments); and
as_split_spec() carries site_group as a
blocking annotation in sample_data. Site also
participates in mode = "composite" derivations and the
typed plot() layout.Region node type and
sample_located_in_region edge. Categorical tissue
/ anatomical region structure is now a first-class typed relation,
mirroring Site: region_id auto-detection,
multi-region validation
(sample_multiple_region_assignments),
derive_split_constraints(mode = "region"), a
region_group blocking annotation in
split_spec, and composite / plot support.Platform node type and
sample_run_on_platform edge, plus platform and
assay constraint modes. Sequencing /
measurement-platform structure is now a first-class typed relation:
graph_from_metadata() auto-detects a
platform_id column, validate_graph() flags
samples run on multiple platforms
(sample_multiple_platform_assignments), and
derive_split_constraints(mode = "platform") groups samples
by platform. mode = "assay" reuses the existing
sample_measured_by_assay relation to group by assay /
modality. Both carry a blocking annotation in split_spec
(platform_group, assay_group), participate in
mode = "composite"
(via = c("Subject", "Platform")), and are covered by the
typed plot() layout. The assay_uses_platform
edge (Assay -> Platform) is also part of
the schema for manually encoding which platform an assay runs on.relatedness and spatial modes. Some
leakage sources are pairwise and continuous rather than clean groups.
Two new undirected, thresholded edge types model them —
subject_related_to (genetic relatedness between subjects)
and sample_adjacent_to (spatial proximity between samples)
— together with the edge-building helpers
relatedness_edges_from_kinship(pairs, threshold) and
spatial_edges_from_coords(coords, radius).
derive_split_constraints(mode = "relatedness") and
mode = "spatial" form groups as connected components
(transitive closure) over the thresholded edges, so a chain of
individually near neighbours still lands in one group — a grouping that
column-based approaches structurally cannot express. Both modes honor
the samples= subset (components are recomputed within the
subset, so an excluded bridge sample cannot leak structure across the
split).Interchange-format hardening
dependency_graph and split_spec on-disk
formats now have formal JSON Schemas (Draft 2020-12) shipped in
inst/schema/, and every written file references its schema
via a $schema key.validate_graph_json()
and validate_split_spec_json() check a handoff file against
the contract — required fields, value types, node/edge-type
enumerations, and referential integrity of edge endpoints — with no
dependency beyond jsonlite.schema_version is now "0.2.0", and the
compatibility rule is explicit and enforced: the major version is the
compatibility boundary, so files sharing the installed major (including
all "0.1.0" files) load silently, while a differing major
loads with a warning. migrate_dependency_graph_json() and
migrate_split_spec_json() upgrade an older file in place,
filling fields introduced since it was written and stamping the current
version and $schema.split_spec metadata now
records derivation provenance (splitgraph_version,
derived_at) alongside the existing source_mode
/ source_strategy / relations_used.Cross-language interoperability
inst/python/splitspec/) parses the split_spec
JSON and exposes the grouping, ordering, and stratum annotations needed
to drive scikit-learn GroupKFold /
StratifiedGroupKFold / TimeSeriesSplit (the
reader itself needs only the standard library). A conformance script
(inst/python/conformance.py) and an accompanying R test
assert that the Python reader recovers exactly the grouping
(grouping_vector()) and order_rank that R
emitted; the test is skipped when python3 is absent and
never runs on CRAN.derive_split_constraints(mode = "composite", strategy = "strict", samples = ...)
now recomputes connected components within the requested
subset. Previously two in-subset samples connected only through an
out-of-subset sample could inherit a shared group, silently leaking
excluded structure into the split.summarize_leakage_risks() now reports a
severed column indicating whether the chosen constraint
mode structurally eliminates each leakage path
(TRUE/FALSE), or NA when no
constraint is supplied. Previously the same risk report was returned
regardless of the mode.query_paths() truncation is now
visible. When the search hits the finite
max_length cap, the result metadata carries a
truncated = TRUE flag so suppressed longer paths are no
longer silent.created_at now round-trips as NA (POSIXct)
instead of falling back to the current time.split_spec JSON round-trip fidelity.
The new site_group / region_group /
platform_group / assay_group columns are
written and read back correctly; earlier development builds dropped them
on serialization.leakage_constraint()
constructor and its print / summary /
as.data.frame methods. The object was exported but never
produced or consumed anywhere in the package;
leakage_risk_summary() (produced by
summarize_leakage_risks()) is the supported
leakage-reporting type and is unaffected.schema_version moved from
"0.1.0" to "0.2.0". This is backward
compatible — existing "0.1.0" files load without warning —
but the stamped version in newly written files changes.cross-language-handoff:
the full R -> JSON -> Python -> scikit-learn path, showing
split_spec as an interchange format rather than downstream
plumbing.modeling-structure:
modeling site, platform, assay, relatedness, and spatial structure end
to end.?splitGraph package doc
now states scope and non-goals explicitly.Suggests: bioLeak,
skipped if absent) pins the seam to the reference consumer, asserting
that a splitGraph split_spec satisfies
bioLeak::as_leaksplits().R-CMD-check, test-coverage) and a JOSS
paper.md framed on the representation and
interchange-format contribution.stats and utils are now declared in
Imports (both were already used).write_dependency_graph() /
read_dependency_graph() and write_split_spec()
/ read_split_spec(). The on-disk format is documented under
?write_dependency_graph and ?write_split_spec,
carries a schema_version field, and round-trips
NA values faithfully. This makes splitGraph
objects portable across R sessions and across language boundaries
(Python, Julia, CLI tools — anything that can read JSON). Requires
jsonlite (added to Suggests); a helpful error
is raised if it is not installed.adapter-cookbook, walks through three
small adapter patterns: a base-R leave-one-group-out adapter (executed),
a grouped-CV adapter built on rsample::group_vfold_cv()
(illustrative), and an ordered-evaluation adapter built on
rsample::rolling_origin() (illustrative). It also shows the
JSON cross-language handoff path.validate_graph() and validate_depgraph()
now accept a documented validation_overrides argument.
Currently supported override: allow_multi_subject_samples
(default FALSE); when TRUE, samples linked to
multiple subjects are not flagged by the semantic validator and
derive_split_constraints(mode = "subject") keeps the first
listed assignment instead of erroring. The override mechanism is now
first-class and merges into any graph-level override for the duration of
the call only.derive_split_constraints(mode = "subject") honors
allow_multi_subject_samples consistently with
validate_graph(). When the override is active and ambiguity
exists, the choice is recorded in metadata$warnings so it
is not invisible.query_paths() now applies a finite default safety cap
on max_length (8 edges) so that
igraph::all_simple_paths() cannot blow up on dense graphs.
Pass max_length = Inf to opt out and search exhaustively,
or any non-negative integer for an explicit cap. Negative values and
non-numeric inputs are rejected with a clear error.build_dependency_graph() returns a more useful error
when edge endpoints don’t match any node ID and the mismatch looks like
a prefix problem (e.g. edges built with from_prefix = TRUE
but nodes built with prefix = FALSE). The error now points
at the likely cause.graph_from_metadata() warns when it constructs
Outcome nodes from a numeric outcome_value
column (which produces outcome:0 / outcome:1
nodes that are usually not what the user wants). To silence the warning,
pass outcome_id (character class label) instead, or coerce
outcome_value to character first.schema_version is now explicitly
decoupled from the package version. Bumping the package will not bump
the schema; only an explicit, documented schema change should.These remain functional in 0.2.0 but emit .Deprecated()
warnings and are scheduled for removal in a future release. Use the
canonical names instead:
validate_graph(checks = …) → use
levels = … and severities = ….build_depgraph() → use
build_dependency_graph().validate_depgraph() → use
validate_graph().new_depgraph_nodes() → use
graph_node_set().new_depgraph_edges() → use
graph_edge_set().new_depgraph() → use
dependency_graph().outcome_id
(character) in their quick-start example instead of numeric
outcome_value, matching the new warning behavior.validation_overrides parameter and the
allow_multi_subject_samples key are now documented on
?build_dependency_graph /
?validate_graph.Initial public release. Highlights:
graph_from_metadata(), or from explicit node and edge
tables via create_nodes() / create_edges() /
build_dependency_graph().validate_graph()), typed query helpers, and projected
sample-dependency detection.derive_split_constraints()) for subject,
batch, study, time, and
composite modes, translated into a stable, tool-agnostic
split_spec via as_split_spec() with preflight
validation and leakage-risk summaries.plot() method for dependency_graph with a
typed, layered layout, per-type node colors, and an auto-generated
node-type legend.splitGraph emits split_spec objects
without runtime dependencies on downstream tooling. Adapters are
expected to live in consumer packages (e.g. packages built on top of
rsample) so that splitGraph itself stays
neutral.See the README for a full feature overview and a runnable Quick Start.