UC1 — Multi-condition causal signaling with AnnNet as the hub¶
An end-to-end biology pipeline where AnnNet is the central data structure that holds the prior-knowledge network, multi-omics state, per-patient causal inferences, and downstream-ML hand-off — without ever leaving a single graph object.
We start from the OmniPath signed-directed prior knowledge network
(PKN). CPTAC LUAD (lung adenocarcinoma) transcriptomics and
phosphoproteomics are layered on top, one Kivelä elementary layer per
patient. decoupler infers per-patient transcription-factor and kinase
activities. CORNETO/CARNIVAL solves a sparse signed-causal sub-network
per patient. The inferred topology is registered as a second AnnNet
slice alongside the prior. A consensus elementary layer aggregates
across the cohort. Inter-layer coupling edges materialize the same gene
across patient layers. A backend swap into igraph computes PageRank in
C. Provenance is captured as a snapshot diff. The graph is exported to
CX2 for Cytoscape and to a PyG HeteroData for GNN training.
What AnnNet provides that nothing else does, exercised here
- one container for PKN topology and per-patient state — no parallel dict-of-dicts (§§3–8)
- slices as alternative-topology overlays on the same graph — prior vs inferred views without rebuilding (§13)
- native Kivelä multilayer with inter-layer coupling edges, sliced per layer-tuple (§§8, 14, 20)
- multi-backend algorithm access (NetworkX, igraph, graph-tool, PyG) with the result written straight back as a vertex-layer attribute (§17)
- Polars-backed attribute tables for vectorized edge/vertex queries
- provenance snapshots + diff at every pipeline stage (§22)
- lossless export to CX2 (Cytoscape) and PyG (GNN training) from the same object (§§23, 25)
The notebook is framed as a software case study. External biological validation and method-vs-method benchmarks are out of scope; we report sanity checks (degree-bias, null-cohort) but do not claim biological discovery.
1. Configuration¶
All major assumptions are explicit here. The defaults are conservative and publication-oriented rather than optimized for speed.
from pathlib import Path
SEED = 7
TOP_MUT_QUANTILES = {
'top10': 0.90,
'top20': 0.80,
}
PRIMARY_COHORT_LABEL = 'top10'
SENSITIVITY_COHORT_LABELS = ['top10', 'top20']
INCLUDE_ALL_MATCHED_COHORT = True
KIN_TH = 1.5
TF_TH = 1.5
TOP_K_KIN = 10
TOP_TF_POS = 8
TOP_TF_NEG = 7
ULM_TMIN_CANDIDATES = [5, 3, 1]
REGULARIZATION_GRID = [0.0, 0.01, 0.1]
PRIMARY_LAMBDA = 0.01
SOLVER_CANDIDATES = ['scip', 'highs', 'cbc', 'glpk']
ACCEPTABLE_SOLVER_STATUSES = {
'optimal',
'optimal_inaccurate',
'feasible',
'feasible_inaccurate',
}
CONSENSUS_MIN_FREQ = 0.50
CORNETO_SIG_THRESHOLD = 0.50
NULL_REPEATS = 100
TOP_N_PLOT = 50
EXPORT_PYG = True
SAVE_ANNNET = True
EXPORT_HISTORY = True
PSP_URL = 'https://omnipathdb.org/enz_sub?genesymbols=1'
REPO_ROOT = Path.cwd().resolve()
if not (REPO_ROOT / 'annnet').exists():
for parent in [REPO_ROOT, *REPO_ROOT.parents]:
if (parent / 'annnet').exists() and (parent / 'pyproject.toml').exists():
REPO_ROOT = parent
break
NOTEBOOK_DIR = REPO_ROOT / 'notebooks'
OUTPUT_DIR = NOTEBOOK_DIR / 'uc1_fixed_outputs'
CACHE_DIR = OUTPUT_DIR / 'cache'
TABLE_DIR = OUTPUT_DIR / 'tables'
FIG_DIR = OUTPUT_DIR / 'figures'
for path in [OUTPUT_DIR, CACHE_DIR, TABLE_DIR, FIG_DIR]:
path.mkdir(parents=True, exist_ok=True)
ANNNET_OUT = OUTPUT_DIR / 'UC1_fixed.annnet'
PYG_OUT = OUTPUT_DIR / 'UC1_fixed_heterodata.pt'
HISTORY_OUT = OUTPUT_DIR / 'UC1_fixed_history.json'
print({'repo_root': str(REPO_ROOT), 'output_dir': str(OUTPUT_DIR)})
{'repo_root': '/mnt/c/Users/pc/desktop/annnet-remote', 'output_dir': '/mnt/c/Users/pc/desktop/annnet-remote/notebooks/uc1_fixed_outputs'}
2. Reproducibility Setup¶
This section captures versions, records optional dependencies, and fixes random seeds where relevant. If a downstream solver or optional export dependency is missing, the notebook reports that explicitly.
import importlib.metadata as im
import platform
import random
import sys
import time
from collections import Counter, defaultdict
from dataclasses import dataclass
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from IPython.display import display
from scipy.stats import fisher_exact, spearmanr
random.seed(SEED)
np.random.seed(SEED)
import annnet
from annnet import to_pyg
from annnet.io.omnipath import from_omnipath
from annnet.utils.plotting import to_matplotlib
import cptac
import corneto as cn
import decoupler as dc
from corneto.methods.signalling.carnival import multi_carnival
def package_version(name: str) -> str:
try:
return im.version(name)
except Exception:
return 'not-installed'
version_table = pd.DataFrame(
{
'package': [
'python',
'annnet',
'numpy',
'pandas',
'polars',
'matplotlib',
'scipy',
'cptac',
'decoupler',
'corneto',
'anndata',
'torch',
'torch-geometric',
],
'version': [
platform.python_version(),
getattr(annnet, '__version__', 'unknown'),
package_version('numpy'),
package_version('pandas'),
package_version('polars'),
package_version('matplotlib'),
package_version('scipy'),
package_version('cptac'),
package_version('decoupler'),
package_version('corneto'),
package_version('anndata'),
package_version('torch'),
package_version('torch-geometric'),
],
}
)
version_table.to_csv(TABLE_DIR / 'versions.csv', index=False)
runtime_manifest = {
'python_executable': sys.executable,
'platform': platform.platform(),
'seed': SEED,
'solver_candidates': SOLVER_CANDIDATES,
'regularization_grid': REGULARIZATION_GRID,
'retrieval_time_utc': pd.Timestamp.utcnow().isoformat(),
}
version_table
/home/l1boll/miniconda3/envs/myenv/lib/python3.11/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html from .autonotebook import tqdm as notebook_tqdm
| package | version | |
|---|---|---|
| 0 | python | 3.11.6 |
| 1 | annnet | 0.2.0 |
| 2 | numpy | 2.3.5 |
| 3 | pandas | 2.3.3 |
| 4 | polars | 1.35.2 |
| 5 | matplotlib | 3.10.8 |
| 6 | scipy | 1.16.3 |
| 7 | cptac | 1.5.14 |
| 8 | decoupler | 2.1.4 |
| 9 | corneto | 1.0.0b7 |
| 10 | anndata | 0.12.10 |
| 11 | torch | 2.9.1 |
| 12 | torch-geometric | 2.7.0 |
3. Load OmniPath PKN¶
We import the prior knowledge network with signed and directional metadata plus several annotation sources. This notebook uses the loaded PKN as a reusable prior rather than claiming that OmniPath itself is a tumor-specific ground truth.
G = from_omnipath(
dataset='omnipath',
query={'organism': 'human', 'genesymbols': True},
source_col='source_genesymbol',
target_col='target_genesymbol',
edge_attr_cols=[
'is_stimulation',
'is_inhibition',
'consensus_direction',
'consensus_stimulation',
'consensus_inhibition',
'n_sources',
'n_references',
'sources',
],
slice='omnipath_pkn',
vertex_annotation_sources=[
'HGNC',
'CancerGeneCensus',
'SignaLink_function',
'SignaLink_pathway',
'UniProt_location',
'IntOGen',
'kinase.com',
'Phosphatome',
],
load_vertex_annotations=True,
)
G.history.enable(True)
G.history.snapshot('after_pkn_load')
print(f'PKN loaded: |V|={G.global_count("vertices"):,} |E|={G.global_count("edges"):,}')
print(f'Active slice: {G.slices.active}')
print(f'Edge attribute columns: {list(G.edge_attributes.columns)}')
2026-06-01 19:59:40 | [INFO] Downloading data from `https://omnipathdb.org/queries/enzsub?format=json` 2026-06-01 19:59:40 | [INFO] Downloading data from `https://omnipathdb.org/queries/interactions?format=json` 2026-06-01 19:59:40 | [INFO] Downloading data from `https://omnipathdb.org/queries/complexes?format=json` 2026-06-01 19:59:40 | [INFO] Downloading data from `https://omnipathdb.org/queries/annotations?format=json` 2026-06-01 19:59:41 | [INFO] Downloading data from `https://omnipathdb.org/queries/intercell?format=json` 2026-06-01 19:59:41 | [INFO] Downloading data from `https://omnipathdb.org/about?format=text`
[timing] fetch/receive df: 4.800s
[timing] column resolution: 0.0154s
source='source_genesymbol' target='target_genesymbol' directed='is_directed'
edge_attr_cols (8): ['is_stimulation', 'is_inhibition', 'consensus_direction', 'consensus_stimulation', 'consensus_inhibition', 'n_sources', 'n_references', 'sources']
[timing] AnnNet init: 0.236s (pre-sized n=85217 e=85217)
[timing] to_rows setup: 0.000s (85217 rows, streaming=True)
[timing] bulk list build: 0.553s (85217 edges)
[timing] add_edges_bulk: 2.728s
vertices=8791 edges=85217
[vertex annotations] loading from cache (lazy scan + pushdown): /home/l1boll/.cache/annnet/omnipath_annotations.tsv.gz
[vertex annotations] streamed + aggregated in 22.5s rows=28876
[vertex annotations] using pre-aggregated frame: 28,876 (gene, attr) pairs
[vertex annotations] pivoted in 0.1s
[vertex annotations] loaded 7677 vertices in 0.2s
PKN loaded: |V|=8,791 |E|=85,217
Active slice: default
Edge attribute columns: ['edge_id', 'is_stimulation', 'is_inhibition', 'consensus_direction', 'consensus_stimulation', 'consensus_inhibition', 'n_sources', 'n_references', 'sources']
4. PKN Diagnostics and Trusted Edge Summary¶
Before any patient-specific inference, we inspect the imported network. This is not a validation step for biological truth, but it helps distinguish strongly supported prior edges from lower-confidence prior edges.
pkn_edges_df = G.views.edges().to_pandas()
pkn_edges_df = pkn_edges_df[pkn_edges_df['source'].notna() & pkn_edges_df['target'].notna()].copy()
trusted_edges_df = pkn_edges_df[
(pkn_edges_df['n_sources'].fillna(0) >= 3)
& (pkn_edges_df['consensus_direction'].fillna(False).astype(bool))
].copy()
pkn_degree = (
pd.concat(
[
pkn_edges_df['source'].value_counts(),
pkn_edges_df['target'].value_counts(),
],
axis=1,
)
.fillna(0)
.sum(axis=1)
.astype(int)
.rename('pkn_degree')
.sort_values(ascending=False)
)
pkn_summary = pd.DataFrame(
{
'metric': [
'total_edges',
'trusted_edges',
'trusted_fraction',
'unique_vertices_with_degree',
],
'value': [
len(pkn_edges_df),
len(trusted_edges_df),
len(trusted_edges_df) / max(len(pkn_edges_df), 1),
pkn_degree.index.nunique(),
],
}
)
pkn_summary.to_csv(TABLE_DIR / 'pkn_summary.csv', index=False)
pkn_degree.rename_axis('vertex_id').reset_index().to_csv(TABLE_DIR / 'pkn_degree.csv', index=False)
pkn_summary
| metric | value | |
|---|---|---|
| 0 | total_edges | 85217.000000 |
| 1 | trusted_edges | 12923.000000 |
| 2 | trusted_fraction | 0.151648 |
| 3 | unique_vertices_with_degree | 8791.000000 |
5. Load CPTAC LUAD Data¶
We load transcriptomics, phosphoproteomics, and somatic mutations. The next section checks value ranges before choosing any tumor-normal transformation.
luad = cptac.Luad()
rna_raw = luad.get_transcriptomics('bcm').copy()
pp_raw = luad.get_phosphoproteomics('umich').copy()
soms = luad.get_somatic_mutation('harmonized').copy()
print(
{'rna_raw_shape': rna_raw.shape, 'phospho_raw_shape': pp_raw.shape, 'somatic_rows': len(soms)}
)
{'rna_raw_shape': (213, 59286), 'phospho_raw_shape': (213, 74579), 'somatic_rows': 39029}
6. Tumor-normal contrasts with range diagnostics¶
CPTAC processed matrices may already be normalized, centered, or contain negative values. We:
- inspect numeric ranges and missingness before transforming anything
- construct matched tumor-normal pairs explicitly
- use paired differences unless a positive raw scale is clearly justified, and record the choice in the saved tables
def flatten_axis(values) -> pd.Index:
if isinstance(values, pd.MultiIndex):
flat = []
for item in values.to_flat_index():
parts = [str(x) for x in item if pd.notna(x) and str(x) != 'nan']
flat.append('_'.join(parts))
return pd.Index(flat, dtype='object')
return pd.Index(values.astype(str), dtype='object')
def normalize_feature_axis(values, mode: str) -> pd.Index:
if isinstance(values, pd.MultiIndex):
if mode == 'gene_symbol':
return pd.Index(values.get_level_values(0).astype(str), dtype='object')
if mode == 'flat':
flat = []
for item in values.to_flat_index():
parts = [str(x) for x in item if pd.notna(x) and str(x) != 'nan']
flat.append('_'.join(parts))
return pd.Index(flat, dtype='object')
raise ValueError(f'Unknown feature normalization mode: {mode}')
return pd.Index(values.astype(str), dtype='object')
def sanitize_sample_feature_matrix(df: pd.DataFrame, *, feature_mode: str) -> pd.DataFrame:
out = df.copy()
out.index = flatten_axis(out.index)
out.columns = normalize_feature_axis(out.columns, feature_mode)
return out
def summarize_numeric_matrix(df: pd.DataFrame, label: str) -> pd.Series:
arr = df.to_numpy(dtype=float)
finite_mask = np.isfinite(arr)
finite_vals = arr[finite_mask]
return pd.Series(
{
'label': label,
'n_rows': df.shape[0],
'n_cols': df.shape[1],
'n_values': arr.size,
'n_finite': int(finite_mask.sum()),
'finite_fraction': float(finite_mask.mean()),
'min': float(np.nanmin(arr)),
'max': float(np.nanmax(arr)),
'mean': float(np.nanmean(arr)),
'median': float(np.nanmedian(arr)),
'n_negative': int((arr < 0).sum()),
'n_zero': int((arr == 0).sum()),
'nan_fraction': float(np.isnan(arr).mean()),
'inf_fraction': float(np.isinf(arr).mean()),
'finite_mean_abs': float(np.mean(np.abs(finite_vals))) if finite_vals.size else np.nan,
}
)
def build_matched_pairs(sample_ids: pd.Index) -> pd.DataFrame:
normals = {s[:-2]: s for s in sample_ids if s.endswith('.N')}
tumors = [s for s in sample_ids if not s.endswith('.N')]
matched = [t for t in tumors if t in normals]
pairs = pd.DataFrame(
{
'tumor': matched,
'normal': [normals[t] for t in matched],
},
index=matched,
)
pairs.index.name = 'patient_id'
return pairs
def choose_contrast_method(df: pd.DataFrame, label: str) -> tuple[str, str]:
summary = summarize_numeric_matrix(df, label)
strictly_positive = bool(summary['min'] > 0 and summary['n_negative'] == 0)
if strictly_positive:
return (
'difference',
f'{label}: all observed values are positive, but the processed CPTAC scale is not explicitly confirmed as raw abundance; paired difference is used conservatively.',
)
return (
'difference',
f'{label}: detected non-positive or negative values; log2 fold-change with pseudocount would be unsafe, so paired difference is used.',
)
def compute_paired_contrast(
df: pd.DataFrame, matched_pairs: pd.DataFrame, method: str
) -> pd.DataFrame:
tumor = df.loc[matched_pairs['tumor']].copy()
normal = df.loc[matched_pairs['normal']].copy()
tumor.index = matched_pairs.index
normal.index = matched_pairs.index
if method == 'log2fc':
contrast = np.log2(tumor + 1.0) - np.log2(normal + 1.0)
elif method == 'difference':
contrast = tumor - normal
else:
raise ValueError(f'Unknown contrast method: {method}')
return contrast.T
rna_sf = sanitize_sample_feature_matrix(rna_raw, feature_mode='gene_symbol')
# Keep the column MultiIndex; the contrast collapses to `{gene}_{site}`
# keys that match the PSP enzyme-substrate format.
pp_sf = pp_raw.copy()
pp_sf.index = flatten_axis(pp_sf.index)
if isinstance(pp_sf.columns, pd.MultiIndex):
print(
{
'pp_column_levels': list(pp_sf.columns.names),
'sample_columns': pp_sf.columns[:3].tolist(),
}
)
else:
print(
{
'pp_columns_not_multiindex': True,
'sample_columns': pp_sf.columns[:3].tolist(),
}
)
matched_pairs = build_matched_pairs(rna_sf.index)
assert len(matched_pairs) > 0, 'No matched tumor-normal RNA pairs were found.'
rna_method, rna_method_reason = choose_contrast_method(rna_sf, 'transcriptomics')
pp_method, pp_method_reason = choose_contrast_method(pp_sf, 'phosphoproteomics')
rna_contrast = compute_paired_contrast(rna_sf, matched_pairs, rna_method)
rna_contrast = rna_contrast.groupby(level=0).mean()
rna_contrast.columns = rna_contrast.columns.astype(str)
pp_contrast = compute_paired_contrast(pp_sf, matched_pairs, pp_method)
if isinstance(pp_contrast.index, pd.MultiIndex):
pp_contrast = pp_contrast.groupby(level=[0, 1]).mean()
pp_contrast.index = pd.Index(
[
'_'.join([str(x) for x in idx if pd.notna(x) and str(x) != 'nan'])
for idx in pp_contrast.index.to_flat_index()
]
)
else:
pp_contrast = pp_contrast.groupby(level=0).mean()
pp_contrast.columns = pp_contrast.columns.astype(str)
contrast_diagnostics = pd.DataFrame(
[
summarize_numeric_matrix(rna_sf, 'rna_raw_samples_by_features'),
summarize_numeric_matrix(pp_sf, 'phospho_raw_samples_by_features'),
summarize_numeric_matrix(rna_contrast, 'rna_contrast_features_by_patient'),
summarize_numeric_matrix(pp_contrast, 'phospho_contrast_features_by_patient'),
]
)
contrast_diagnostics.to_csv(TABLE_DIR / 'contrast_diagnostics.csv', index=False)
contrast_method_table = pd.DataFrame(
{
'data_type': ['transcriptomics', 'phosphoproteomics'],
'method': [rna_method, pp_method],
'justification': [rna_method_reason, pp_method_reason],
}
)
contrast_method_table.to_csv(TABLE_DIR / 'contrast_methods.csv', index=False)
assert not np.isinf(rna_contrast.to_numpy(dtype=float)).any(), (
'Infinite values detected in RNA contrast.'
)
assert not np.isinf(pp_contrast.to_numpy(dtype=float)).any(), (
'Infinite values detected in phosphoproteomics contrast.'
)
print(rna_method_reason)
print(pp_method_reason)
contrast_diagnostics
{'pp_column_levels': ['Name', 'Site', 'Peptide', 'Database_ID'], 'sample_columns': [('ARF5', 'S137', 'QDMPNAMPVsELTDK', 'ENSP00000000233.5'), ('M6PR', 'S267', 'GVGDDQLGEEsEERDDHLLPM', 'ENSP00000000412.3'), ('ESRRA', 'S19S22', 'AEPAsPDsPK', 'ENSP00000000442.6')]}
transcriptomics: detected non-positive or negative values; log2 fold-change with pseudocount would be unsafe, so paired difference is used.
phosphoproteomics: detected non-positive or negative values; log2 fold-change with pseudocount would be unsafe, so paired difference is used.
| label | n_rows | n_cols | n_values | n_finite | finite_fraction | min | max | mean | median | n_negative | n_zero | nan_fraction | inf_fraction | finite_mean_abs | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | rna_raw_samples_by_features | 213 | 59286 | 12627918 | 12627918 | 1.000000 | 0.000000 | 22.910000 | 3.936233 | 2.600000 | 0 | 4544395 | 0.000000 | 0.0 | 3.936233 |
| 1 | phospho_raw_samples_by_features | 213 | 74579 | 15885327 | 5402034 | 0.340064 | -7.522626 | 8.640576 | -0.018713 | -0.003251 | 2714385 | 52 | 0.659936 | 0.0 | 0.490923 |
| 2 | rna_contrast_features_by_patient | 59286 | 102 | 6047172 | 6047172 | 1.000000 | -15.370000 | 16.330000 | -0.005661 | 0.000000 | 2120525 | 1833012 | 0.000000 | 0.0 | 0.758337 |
| 3 | phospho_contrast_features_by_patient | 69186 | 102 | 7056972 | 2448441 | 0.346953 | -7.588287 | 9.259073 | 0.017126 | 0.031855 | 1183027 | 0 | 0.653047 | 0.0 | 0.706737 |
6.1. Missing-data handling¶
Missing values are not dropped: they flow into the diagnostic tables and
are imputed with 0.0 only at the decoupler ULM step, encoding no
observed differential signal for that feature in that patient.
activity_input_diagnostics = pd.DataFrame(
{
'matrix': ['rna_contrast', 'phospho_contrast'],
'nan_fraction_before_fill': [
float(np.isnan(rna_contrast.to_numpy(dtype=float)).mean()),
float(np.isnan(pp_contrast.to_numpy(dtype=float)).mean()),
],
}
)
activity_input_diagnostics.to_csv(TABLE_DIR / 'activity_input_missingness.csv', index=False)
activity_input_diagnostics
| matrix | nan_fraction_before_fill | |
|---|---|---|
| 0 | rna_contrast | 0.000000 |
| 1 | phospho_contrast | 0.653047 |
7. TF and Kinase Activity Inference¶
TF activities are inferred from the RNA contrast using CollecTRI regulons. Kinase activities are inferred from phosphosite-level contrast values using OmniPath enzyme-substrate links. This remains an inference layer, not a direct measurement layer.
collectri = dc.op.collectri()
collectri_targets = set(collectri['target'].astype(str))
rna_targets = set(rna_contrast.index.astype(str))
shared_tf_targets = sorted(collectri_targets & rna_targets)
print(
{
'collectri_targets': len(collectri_targets),
'rna_contrast_features': len(rna_targets),
'shared_targets_for_tf_activity': len(shared_tf_targets),
}
)
assert shared_tf_targets, (
'No shared gene symbols were found between rna_contrast and CollecTRI. '
'Inspect transcriptomics feature normalization.'
)
tf_es, _ = dc.mt.ulm(rna_contrast.fillna(0.0).T, net=collectri)
psp_cache = CACHE_DIR / 'omnipath_enz_sub.tsv'
if psp_cache.exists():
psp = pd.read_csv(psp_cache, sep=' ')
else:
psp = pd.read_csv(PSP_URL, sep=' ')
psp.to_csv(psp_cache, sep=' ', index=False)
psp_kin = psp[psp['modification'].eq('phosphorylation')][
['enzyme_genesymbol', 'substrate_genesymbol', 'residue_type', 'residue_offset']
].copy()
psp_kin['target'] = (
psp_kin['substrate_genesymbol'].astype(str)
+ '_'
+ psp_kin['residue_type'].astype(str)
+ psp_kin['residue_offset'].astype(str)
)
psp_net = (
psp_kin[['enzyme_genesymbol', 'target']]
.rename(columns={'enzyme_genesymbol': 'source'})
.drop_duplicates()
.assign(weight=1.0)
)
pp_targets = set(pp_contrast.index.astype(str))
psp_targets = set(psp_net['target'].astype(str))
shared_phospho_targets = sorted(pp_targets & psp_targets)
print(
{
'psp_targets': len(psp_targets),
'phospho_contrast_features': len(pp_targets),
'shared_targets_for_kinase_activity': len(shared_phospho_targets),
}
)
assert shared_phospho_targets, (
'No shared phosphosite identifiers were found between pp_contrast and the OmniPath enzyme-substrate network. '
'Inspect phosphoproteomics feature normalization.'
)
psp_overlap_net = psp_net[psp_net['target'].isin(shared_phospho_targets)].copy()
source_target_counts = (
psp_overlap_net.groupby('source')['target'].nunique().sort_values(ascending=False)
)
print(
{
'kinases_with_at_least_1_shared_target': int((source_target_counts >= 1).sum()),
'kinases_with_at_least_3_shared_targets': int((source_target_counts >= 3).sum()),
'kinases_with_at_least_5_shared_targets': int((source_target_counts >= 5).sum()),
}
)
kin_es = None
kin_tmin_used = None
kin_ulm_error = None
for tmin in ULM_TMIN_CANDIDATES:
try:
kin_es, _ = dc.mt.ulm(pp_contrast.fillna(0.0).T, net=psp_net, tmin=tmin)
kin_tmin_used = tmin
break
except AssertionError as exc:
kin_ulm_error = str(exc)
assert kin_es is not None, (
f'Kinase activity inference failed for all tested tmin values. Last error: {kin_ulm_error}'
)
print({'kinase_ulm_tmin_used': kin_tmin_used})
activity_diagnostics = pd.DataFrame(
[
summarize_numeric_matrix(tf_es.T, 'tf_activity_features_by_patient'),
summarize_numeric_matrix(kin_es.T, 'kinase_activity_features_by_patient'),
]
)
activity_diagnostics.to_csv(TABLE_DIR / 'activity_diagnostics.csv', index=False)
activity_overlap_diagnostics = pd.DataFrame(
[
{
'activity_layer': 'tf',
'n_network_targets': len(collectri_targets),
'n_matrix_features': len(rna_targets),
'n_shared_targets': len(shared_tf_targets),
'tmin_used': 5,
},
{
'activity_layer': 'kinase',
'n_network_targets': len(psp_targets),
'n_matrix_features': len(pp_targets),
'n_shared_targets': len(shared_phospho_targets),
'tmin_used': kin_tmin_used,
},
]
)
activity_overlap_diagnostics.to_csv(TABLE_DIR / 'activity_overlap_diagnostics.csv', index=False)
assert np.isfinite(tf_es.to_numpy(dtype=float)).all(), 'Non-finite TF activities detected.'
assert np.isfinite(kin_es.to_numpy(dtype=float)).all(), 'Non-finite kinase activities detected.'
print({'tf_activity_shape': tf_es.shape, 'kinase_activity_shape': kin_es.shape})
display(activity_diagnostics)
activity_overlap_diagnostics
{'collectri_targets': 6675, 'rna_contrast_features': 59286, 'shared_targets_for_tf_activity': 6604}
{'psp_targets': 16200, 'phospho_contrast_features': 69186, 'shared_targets_for_kinase_activity': 3958}
{'kinases_with_at_least_1_shared_target': 1064, 'kinases_with_at_least_3_shared_targets': 444, 'kinases_with_at_least_5_shared_targets': 325}
{'kinase_ulm_tmin_used': 5}
{'tf_activity_shape': (102, 772), 'kinase_activity_shape': (102, 325)}
| label | n_rows | n_cols | n_values | n_finite | finite_fraction | min | max | mean | median | n_negative | n_zero | nan_fraction | inf_fraction | finite_mean_abs | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | tf_activity_features_by_patient | 772 | 102 | 78744 | 78744 | 1.0 | -14.387785 | 16.719053 | -0.059162 | -0.065713 | 40671 | 0 | 0.0 | 0.0 | 1.400222 |
| 1 | kinase_activity_features_by_patient | 325 | 102 | 33150 | 33150 | 1.0 | -8.299891 | 17.205483 | -0.192067 | -0.261700 | 19172 | 0 | 0.0 | 0.0 | 1.405289 |
| activity_layer | n_network_targets | n_matrix_features | n_shared_targets | tmin_used | |
|---|---|---|---|---|---|
| 0 | tf | 6675 | 59286 | 6604 | 5 |
| 1 | kinase | 16200 | 69186 | 3958 | 5 |
8. Build AnnNet Patient Layers and Record Provenance¶
We use matched tumor patients as the patient aspect. Vertex-layer attributes store RNA contrast, TF activity, and kinase activity. History snapshots are taken after major structural milestones.
graph_vertices = set(map(str, G.vertices()))
matched_patients = sorted(set(rna_contrast.columns) & set(tf_es.index) & set(kin_es.index))
assert matched_patients, 'No patients are shared across contrast and activity matrices.'
G.layers.set_aspects(['patient'], {'patient': matched_patients})
G.history.snapshot('after_patient_layers_defined')
rna_vertices = [g for g in rna_contrast.index if g in graph_vertices]
tf_vertices = [tf for tf in tf_es.columns if tf in graph_vertices]
kin_vertices = [k for k in kin_es.columns if k in graph_vertices]
for patient in matched_patients:
aa = (patient,)
needed = set(rna_vertices)
needed |= set(tf_vertices)
needed |= set(kin_vertices)
G.add_vertices(sorted(needed), layer=aa)
for g in rna_vertices:
value = rna_contrast.at[g, patient]
if pd.notna(value):
G.layers.set_vertex_layer_attrs(g, aa, rna_contrast=float(value))
for tf in tf_vertices:
value = tf_es.at[patient, tf]
if pd.notna(value):
G.layers.set_vertex_layer_attrs(tf, aa, tf_activity=float(value))
for kin in kin_vertices:
value = kin_es.at[patient, kin]
if pd.notna(value):
G.layers.set_vertex_layer_attrs(kin, aa, kinase_activity=float(value))
patient_layer_summary = pd.DataFrame(
{
'metric': [
'matched_patients',
'rna_vertices_in_graph',
'tf_vertices_in_graph',
'kinase_vertices_in_graph',
],
'value': [len(matched_patients), len(rna_vertices), len(tf_vertices), len(kin_vertices)],
}
)
patient_layer_summary.to_csv(TABLE_DIR / 'patient_layer_summary.csv', index=False)
G.history.snapshot('after_omics_loaded_into_layers')
patient_layer_summary
/tmp/ipykernel_130096/1185479494.py:5: UserWarning: Declared aspects ('patient',); existing flat vertices were reassigned to placeholder layer ('_',). Set explicit layer coordinates if needed.
G.layers.set_aspects(['patient'], {'patient': matched_patients})
| metric | value | |
|---|---|---|
| 0 | matched_patients | 102 |
| 1 | rna_vertices_in_graph | 7715 |
| 2 | tf_vertices_in_graph | 607 |
| 3 | kinase_vertices_in_graph | 315 |
9. Cohort selection and sensitivity¶
CORNETO scales superlinearly with input/output cardinality and PKN density. We define a demonstration cohort (top-10% mutation burden) and a sensitivity cohort (top-20%); both are recorded and re-used in §16.
interesting_mutations = {
'Missense_Mutation',
'Nonsense_Mutation',
'Frame_Shift_Del',
'Frame_Shift_Ins',
}
mut_mat = (
soms[soms['Mutation'].isin(interesting_mutations)][['Gene', 'Tumor_Sample_Barcode']]
.drop_duplicates()
.assign(mutated=1)
.pivot(index='Gene', columns='Tumor_Sample_Barcode', values='mutated')
.fillna(0)
)
mut_mat.columns = mut_mat.columns.astype(str).str.replace('_T', '', regex=False)
mut_sum = (
mut_mat.reindex(columns=matched_patients, fill_value=0).sum(axis=0).sort_values(ascending=False)
)
cohort_definitions = {}
for label, quantile in TOP_MUT_QUANTILES.items():
threshold = float(mut_sum.quantile(quantile))
cohort_definitions[label] = mut_sum[mut_sum >= threshold].index.tolist()
if INCLUDE_ALL_MATCHED_COHORT:
cohort_definitions['all_matched'] = list(mut_sum.index)
cohort_summary_rows = []
for label, members in cohort_definitions.items():
cohort_summary_rows.append(
{
'cohort_label': label,
'n_patients': len(members),
'mean_mutation_burden': float(mut_sum.reindex(members).mean()) if members else np.nan,
'median_mutation_burden': float(mut_sum.reindex(members).median())
if members
else np.nan,
'selection_note': (
'demonstration cohort enriched for high mutation burden'
if label.startswith('top')
else 'sensitivity cohort'
),
}
)
cohort_summary = pd.DataFrame(cohort_summary_rows).sort_values('n_patients')
cohort_summary.to_csv(TABLE_DIR / 'cohort_summary.csv', index=False)
analysis_patients = sorted(
set().union(
*(
set(cohort_definitions[label])
for label in SENSITIVITY_COHORT_LABELS
if label in cohort_definitions
)
)
)
assert analysis_patients, 'No patients were selected for the analysis cohorts.'
cohort_summary
| cohort_label | n_patients | mean_mutation_burden | median_mutation_burden | selection_note | |
|---|---|---|---|---|---|
| 0 | top10 | 11 | 734.000000 | 607.0 | demonstration cohort enriched for high mutatio... |
| 1 | top20 | 21 | 604.095238 | 530.0 | demonstration cohort enriched for high mutatio... |
| 2 | all_matched | 102 | 203.294118 | 92.5 | sensitivity cohort |
10. Signed PKN restricted to the kinase → kinase∪TF subnet¶
The activity-inference layer yields kinase inputs and TF outputs, so we restrict the PKN to that subspace before CARNIVAL. This is a modeling choice for tractability — not a claim that other signaling nodes are irrelevant.
kin_set = set(map(str, kin_es.columns))
tf_set = set(map(str, tf_es.columns))
pkn_filt = pkn_edges_df[
pkn_edges_df['source'].isin(kin_set) & pkn_edges_df['target'].isin(kin_set | tf_set)
].copy()
pkn_filt['sign'] = np.where(
pkn_filt['consensus_stimulation'].fillna(False).astype(bool),
1,
np.where(pkn_filt['consensus_inhibition'].fillna(False).astype(bool), -1, 0),
)
pkn_filt = pkn_filt[pkn_filt['sign'] != 0].copy()
C_filt = cn.Graph.from_tuples(pkn_filt[['source', 'sign', 'target']].apply(tuple, axis=1).tolist())
filtered_pkn_summary = pd.DataFrame(
{
'metric': ['filtered_edges', 'filtered_nodes'],
'value': [len(pkn_filt), C_filt.num_vertices],
}
)
filtered_pkn_summary.to_csv(TABLE_DIR / 'filtered_pkn_summary.csv', index=False)
filtered_pkn_summary
| metric | value | |
|---|---|---|
| 0 | filtered_edges | 2624 |
| 1 | filtered_nodes | 601 |
11. CORNETO/CARNIVAL setup¶
For each patient:
- inputs are the highest-magnitude kinase activities above
KIN_TH - outputs are positive and negative TF activities above
TF_TH multi_carnivalsolves a signed-flow problem on the filtered PKNlambdcontrols sparsity pressure and is swept over a small grid
Solver status and empty solutions are reported explicitly. Inferred subnetworks are model-based hypotheses.
@dataclass
class PatientIO:
patient_id: str
inputs: dict
outputs: dict
n_inputs: int
n_outputs: int
def build_patient_io(patient_id: str) -> PatientIO | None:
if patient_id not in kin_es.index or patient_id not in tf_es.index:
return None
kr = kin_es.loc[patient_id]
tr = tf_es.loc[patient_id]
kin_top = kr[kr.abs() >= KIN_TH].abs().nlargest(TOP_K_KIN).index.tolist()
tf_pos = tr[tr >= TF_TH].nlargest(TOP_TF_POS).index.tolist()
tf_neg = tr[tr <= -TF_TH].nsmallest(TOP_TF_NEG).index.tolist()
inputs = {k: float(np.sign(kr[k])) for k in kin_top}
outputs = {tf: float(tr[tf]) for tf in tf_pos + tf_neg}
if not inputs or not outputs:
return None
return PatientIO(
patient_id=patient_id,
inputs=inputs,
outputs=outputs,
n_inputs=len(inputs),
n_outputs=len(outputs),
)
def normalize_solver_status(status) -> str:
return str(status).strip().lower()
def is_solver_ok(status) -> bool:
return normalize_solver_status(status) in ACCEPTABLE_SOLVER_STATUSES
def solve_multi_carnival_with_fallback(graph, patient_payload: dict, lambd: float):
P, G_p, meta = multi_carnival(graph, patient_payload, lambd=lambd)
last_error = None
for solver_name in SOLVER_CANDIDATES:
try:
sol = P.solve(solver=solver_name)
return P, G_p, meta, sol, solver_name, None
except Exception as exc:
last_error = f'{type(exc).__name__}: {exc}'
return P, G_p, meta, None, None, last_error
def extract_active_network(P, G_p, threshold: float) -> tuple[dict, dict]:
vvals = np.asarray(P.expr.vertex_value.value)
evals = np.asarray(P.expr.edge_value.value)
if vvals.ndim == 2:
vvals = vvals[:, 0]
if evals.ndim == 2:
evals = evals[:, 0]
nodes = list(G_p.V)
edges_cn = list(G_p.E)
node_signal = {}
for idx, node in enumerate(nodes):
node = str(node)
if node.startswith('_') or not G.has_vertex(node):
continue
value = float(vvals[idx])
if abs(value) >= threshold:
node_signal[node] = value
edge_signal = {}
for idx, (src_fs, tgt_fs) in enumerate(edges_cn):
value = float(evals[idx])
if abs(value) < threshold:
continue
src_list = [str(x) for x in src_fs if not str(x).startswith('_')]
tgt_list = [str(x) for x in tgt_fs if not str(x).startswith('_')]
if not src_list or not tgt_list:
continue
src, tgt = src_list[0], tgt_list[0]
if not (G.has_vertex(src) and G.has_vertex(tgt)):
continue
edge_signal[(src, tgt)] = value
return node_signal, edge_signal
patient_io_map = {
p.patient_id: p for p in [build_patient_io(pid) for pid in analysis_patients] if p is not None
}
assert patient_io_map, 'No patients had valid kinase-input and TF-output constraints for CARNIVAL.'
carnival_rows = []
active_networks_by_lambda = {}
for lambd in REGULARIZATION_GRID:
active_networks_by_lambda[lambd] = {}
for _idx, patient_id in enumerate(sorted(patient_io_map), 1):
io = patient_io_map[patient_id]
payload = {patient_id: {'input': io.inputs, 'output': io.outputs}}
t0 = time.time()
P, G_p, meta, sol, solver_name, error_text = solve_multi_carnival_with_fallback(
C_filt, payload, lambd
)
runtime_s = time.time() - t0
if sol is None:
active_networks_by_lambda[lambd][patient_id] = {
'patient_id': patient_id,
'lambd': lambd,
'solver': solver_name,
'solver_status': 'solve_failed',
'solver_ok': False,
'objective_value': np.nan,
'runtime_s': runtime_s,
'node_signal': {},
'edge_signal': {},
'active_node_count': 0,
'active_edge_count': 0,
'n_inputs': io.n_inputs,
'n_outputs': io.n_outputs,
'error': error_text,
}
else:
status = normalize_solver_status(getattr(sol, 'status', 'unknown'))
objective_value = (
float(getattr(sol, 'value', np.nan))
if getattr(sol, 'value', None) is not None
else np.nan
)
node_signal, edge_signal = extract_active_network(P, G_p, CORNETO_SIG_THRESHOLD)
active_networks_by_lambda[lambd][patient_id] = {
'patient_id': patient_id,
'lambd': lambd,
'solver': solver_name,
'solver_status': status,
'solver_ok': is_solver_ok(status),
'objective_value': objective_value,
'runtime_s': runtime_s,
'node_signal': node_signal,
'edge_signal': edge_signal,
'active_node_count': len(node_signal),
'active_edge_count': len(edge_signal),
'n_inputs': io.n_inputs,
'n_outputs': io.n_outputs,
'error': None,
}
rec = active_networks_by_lambda[lambd][patient_id]
carnival_rows.append(
{
'patient_id': patient_id,
'lambd': lambd,
'solver': rec['solver'],
'solver_status': rec['solver_status'],
'solver_ok': rec['solver_ok'],
'objective_value': rec['objective_value'],
'runtime_s': rec['runtime_s'],
'active_node_count': rec['active_node_count'],
'active_edge_count': rec['active_edge_count'],
'n_inputs': rec['n_inputs'],
'n_outputs': rec['n_outputs'],
'error': rec['error'],
}
)
carnival_summary = pd.DataFrame(carnival_rows)
carnival_summary.to_csv(TABLE_DIR / 'carnival_patient_summary.csv', index=False)
lambda_summary = (
carnival_summary.groupby('lambd', dropna=False)
.agg(
n_patients=('patient_id', 'nunique'),
solver_ok_fraction=('solver_ok', 'mean'),
mean_active_nodes=('active_node_count', 'mean'),
mean_active_edges=('active_edge_count', 'mean'),
median_runtime_s=('runtime_s', 'median'),
)
.reset_index()
)
lambda_summary.to_csv(TABLE_DIR / 'carnival_lambda_summary.csv', index=False)
assert PRIMARY_LAMBDA in active_networks_by_lambda, (
f'PRIMARY_LAMBDA={PRIMARY_LAMBDA} missing from active_networks_by_lambda.'
)
assert (carnival_summary['solver_ok']).any(), 'No successful/feasible CARNIVAL solution was found.'
lambda_summary
| lambd | n_patients | solver_ok_fraction | mean_active_nodes | mean_active_edges | median_runtime_s | |
|---|---|---|---|---|---|---|
| 0 | 0.00 | 21 | 1.0 | 43.952381 | 36.190476 | 1.748831 |
| 1 | 0.01 | 21 | 1.0 | 20.476190 | 12.666667 | 1.028711 |
| 2 | 0.10 | 21 | 1.0 | 20.476190 | 12.666667 | 1.120919 |
12. Write per-patient inferred subnetworks back into AnnNet¶
We materialize only the solutions for the analysis cohort under the primary regularization. Solver failures and empty networks are recorded in the summary table, not silently merged into the graph.
primary_patient_networks = active_networks_by_lambda[PRIMARY_LAMBDA]
written_rows = []
for patient_id, result in primary_patient_networks.items():
if not result['solver_ok']:
written_rows.append(
{'patient_id': patient_id, 'written': False, 'reason': result['solver_status']}
)
continue
if result['active_node_count'] == 0:
written_rows.append(
{'patient_id': patient_id, 'written': False, 'reason': 'empty_solution'}
)
continue
aa = (patient_id,)
endpoints = set(result['node_signal'])
for src, tgt in result['edge_signal']:
endpoints.add(src)
endpoints.add(tgt)
if endpoints:
G.add_vertices(sorted(endpoints), layer=aa)
for node_id in sorted(endpoints):
G.layers.set_vertex_layer_attrs(
node_id,
aa,
corneto_signal=float(result['node_signal'].get(node_id, 0.0)),
)
edge_specs = [
{
'source': (src, aa),
'target': (tgt, aa),
'weight': float(weight),
}
for (src, tgt), weight in sorted(result['edge_signal'].items())
]
if edge_specs:
G.add_edges(edge_specs)
written_rows.append(
{
'patient_id': patient_id,
'written': True,
'reason': 'ok',
'active_nodes': result['active_node_count'],
'active_edges': result['active_edge_count'],
}
)
written_summary = pd.DataFrame(written_rows)
written_summary.to_csv(TABLE_DIR / 'written_patient_networks.csv', index=False)
G.history.snapshot('after_patient_specific_causal_networks')
written_summary.head()
| patient_id | written | reason | active_nodes | active_edges | |
|---|---|---|---|---|---|
| 0 | C3L-00080 | True | ok | 20 | 12 |
| 1 | C3L-00095 | True | ok | 27 | 19 |
| 2 | C3L-00144 | True | ok | 19 | 12 |
| 3 | C3L-00279 | True | ok | 24 | 15 |
| 4 | C3L-01924 | True | ok | 18 | 10 |
13. Slices: prior vs inferred topology on the same graph¶
AnnNet slices carry alternative edge sets and let us switch between them
with a single write. We register every patient-specific CARNIVAL edge
in a dedicated carnival_inferred slice alongside the default
omnipath_pkn. Toggling the active slice gives instant prior-vs-inferred
views without rebuilding.
CARNIVAL_SLICE = 'carnival_inferred'
if not G.slices.exists(CARNIVAL_SLICE):
G.slices.add(CARNIVAL_SLICE)
# Map (src_supra, tgt_supra) -> eid via the multilayer src-to-edges index.
src_to_edges = G._src_to_edges
carnival_eids: list[str] = []
for patient_id, result in primary_patient_networks.items():
if not result['solver_ok']:
continue
aa = (patient_id,)
for src, tgt in result['edge_signal']:
skey = (src, aa)
tkey = (tgt, aa)
for eid in src_to_edges.get(skey, ()):
rec = G._edges.get(eid)
if rec is not None and rec.tgt == tkey:
carnival_eids.append(eid)
break
G.slices.add_edges(CARNIVAL_SLICE, carnival_eids)
print(f'CARNIVAL slice "{CARNIVAL_SLICE}": {len(carnival_eids):,} edges registered')
# Demo: same graph, two slices, two answers.
n_omni = len(G.slices.edges('omnipath_pkn'))
n_carn = len(G.slices.edges(CARNIVAL_SLICE))
print(f' omnipath_pkn : {n_omni:>7,} edges')
print(f' carnival_inferred : {n_carn:>7,} edges')
G.history.snapshot('after_carnival_slice')
CARNIVAL slice "carnival_inferred": 266 edges registered omnipath_pkn : 85,217 edges carnival_inferred : 266 edges
{'label': 'after_carnival_slice',
'version': 148,
'vertex_ids': {'IGFL1',
'PTGFRN',
'CASQ2',
'UBE2S',
'NOTCH4',
'BAG2',
'RIOK2',
'TRIM77',
'PRNP',
'CLIC5',
'KLRD1',
'PPFIA3',
'MAZ',
'GRPEL1',
'TNK2',
'ANAPC2',
'GCG',
'TRIM43B',
'TSPAN1',
'PML',
'RETN',
'TAPBP',
'ENAH',
'VLDLR',
'MNAT1',
'CSN1S1',
'ATF2_JUN',
'BANP',
'MTCP1',
'ADGRE5',
'H2BK1',
'G3BP2',
'ATRX',
'GLG1',
'TFEB',
'CDH9',
'SCARB1',
'DLX5',
'FMNL2',
'PARP1',
'RNF141',
'KPNA1',
'AP2M1',
'ITGAV_ITGB1',
'MCHR2',
'GATA6',
'CDH8',
'DEFB4B',
'NOXA1',
'PLAA',
'UGT2B7',
'RABEPK',
'PIM1',
'UBIAD1',
'PIP5K1B',
'TREML1',
'BRDT',
'FAAP20',
'CSNK1D',
'SEC62',
'TBX3',
'KAT5',
'ACVR1B_ACVR2A_CFC1',
'FLRT2',
'RPS27A',
'CLEC1B',
'NSMCE1',
'ABR',
'CD8A_CD8B',
'STRAP',
'PITRM1',
'OMA1',
'TP53TG5',
'IRS4',
'SELE',
'TRIM22',
'IFNA14',
'S100A4',
'DDX24',
'PAX8',
'FLT3',
'PPP3CB',
'RBM17',
'RPL6',
'CDCA5',
'MAP2K7',
'UBE3B',
'MYOM2',
'IKZF1',
'PHF12',
'LAMP1',
'SGK3',
'RNF123',
'BTN2A1',
'DUSP13B',
'PPP1R1A',
'CHRNA7',
'DHH',
'CXCL3',
'ADAMTS13',
'VMP1',
'S100A12',
'FLRT1',
'VEZT',
'IL12A_IL12B',
'CRK',
'ACSS2',
'TM4SF1',
'MYLK3',
'ICAM2',
'CHEBI:166824_CHEBI:53000_HLA-DMB_HLA-DPA1',
'PLD2',
'MARCKSL1',
'PHACTR1',
'PLAU',
'RPS27A_UBE2D3',
'LAMTOR1',
'UBC_UBE2V2',
'TRPM2',
'DAB1',
'PDK1',
'UBC_UBE2D2',
'RAD51D',
'RNF139',
'HLA-E',
'SMOC2',
'CEP68',
'UBA52_UBE2Q1',
'GRM7',
'PIH1D2_RUVBL1_RUVBL2_SPAG1',
'CDC37',
'LAMA2',
'MATK',
'PRC1',
'TENM4',
'ALDH2',
'KCNA5',
'APAF1',
'NHLH2',
'PCBD1',
'PLCL1',
'SLC12A5',
'RPS27L',
'LRRFIP2',
'BST1',
'PUM3',
'XRCC6',
'IGBP1',
'BCL9',
'PSMD4',
'GSTP1',
'PANX1',
'LTN1',
'NR1H4',
'SMAD3_SMAD4',
'GSTA1',
'ASTN2',
'P13288_RPS27A',
'RECQL5',
'HOMER2',
'IFI16',
'ARHGAP30',
'SCAND1',
'CGAS',
'MRE11',
'TG',
'DDX46',
'CDR2',
'NLK',
'SSTR5',
'WNT10B',
'PPP2CA_PPP2R1A_PPP2R2B',
'PPP2CA',
'IL1A',
'COL6A6',
'TBXA2R',
'KIF14',
'PABPC4',
'LCTL',
'GTF2F2',
'SKIL',
'ACTA1_ACTL6A_ALKBH3_BRD2_BRD8_DMAP1_EP400_EPC1_ING3_KAT5_MORF4L1_MRGBP_RUVBL1_RUVBL2_TRRAP_VPS72_YEATS4',
'SPHK1',
'SCP2',
'GABRA6_GABRB3_GABRG2',
'HSPB1',
'UBC_UBE2K',
'TMEM219',
'COBL',
'UBB_UBE2E3',
'EEF1B2_EEF1D_EEF1G',
'LAMA1',
'CNOT9',
'SLAMF6',
'ESPL1',
'NPS',
'CDK4',
'TERF2IP',
'PPP6R2',
'LASP1',
'CBY1',
'CDC7',
'HCK',
'CALCA',
'SRCAP',
'NEU1',
'SLIT2',
'FLOT1',
'MPG',
'JAM3',
'GREB1',
'B4GALT1',
'FYN',
'PPP1R12B',
'RAD21_SMC1A_SMC3_STAG1_STAG3',
'RSPO2',
'S100A11',
'EDAR',
'NPBWR2',
'IFIT1',
'GAR1',
'NR4A1',
'ADO',
'PRDM1',
'PIAS2',
'POLD1_POLD2_POLD3_POLD4',
'TOP2B',
'CEP43',
'DACT2',
'CDCA8_PRC1',
'PHEX',
'TDG',
'CDH1',
'ATR_ATRIP',
'SIGLEC9',
'ARHGEF11',
'TLR4',
'CCDC106',
'AIFM1',
'UBB_UBE2Q2',
'KIF2C',
'DAZAP1',
'CACNA1H',
'SLC18A2_TPH1',
'DBNDD1',
'NCOR1',
'CENPB',
'CKS1B',
'KMT5A',
'JPH2',
'SPSB2',
'IL18',
'TNC',
'GDI2',
'APPL1',
'TP53INP2',
'CSTF2',
'EDA2R',
'Q6FHA6',
'PLXNA1',
'PKIB',
'AMHR2',
'SHH',
'FRZB',
'FANCG',
'CCNA1',
'CSF2RB_IL5RA',
'TPP1',
'CCL23',
'CD47_ITGAV_ITGB3',
'RNF169',
'ILF2_ILF3',
'CYSLTR1',
'ADORA3',
'SIL1',
'EPN1',
'EIF2B5',
'SHC4',
'PPP2R2A',
'EFNB2',
'STK38L',
'ADCY3',
'MTOR',
'VPS35',
'GABRA1_GABRB2_GABRG2',
'CALM1',
'EPHA2',
'NMB',
'HLA-DRB1',
'ASB3',
'CLCN5',
'PEAR1',
'TNIP2',
'GP6',
'TPH1',
'TNPO1',
'TNFSF8',
'PITX1',
'IGF1',
'SOX2',
'RPS15',
'UBA6_UBB',
'USP16',
'GAB2',
'COPS2_COPS3_COPS4_COPS5_COPS6_COPS7B_COPS8_GPS1',
'PTPRT',
'CNP',
'ARHGAP6',
'MARCHF10',
'NCR3LG1',
'CHM',
'FAU_RPS10_RPS11_RPS12_RPS13_RPS14_RPS15_RPS15A_RPS16_RPS17_RPS18_RPS19_RPS2_RPS20_RPS21_RPS23_RPS24_RPS25_RPS26_RPS27_RPS27A_RPS27L_RPS28_RPS29_RPS3_RPS3A_RPS4X_RPS4Y1_RPS4Y2_RPS5_RPS6_RPS7_RPS8_RPS9_RPSA',
'TNF_TNFRSF1A',
'BCAN',
'GRIN1_GRIN2C',
'PRKX',
'UBA52_UBE2E1',
'TENT2',
'ABI1_BRK1_CYFIP1_NCKAP1_WASF2',
'FZD5_LRP6',
'ACTL6A_ACTL6B_ARID1A_ARID1B_ARID2_PBRM1_SMARCA2_SMARCA4_SMARCB1_SMARCC1_SMARCC2_SMARCD1_SMARCD2_SMARCD3_SMARCE1',
'RNF20',
'CTDSP2',
'UBLCP1',
'CASP2_CRADD_PIDD1',
'PDCD4',
'NCSTN',
'P03217_UBA52',
'SORBS1',
'ULBP2',
'CD52',
'MSL3',
'MEF2C',
'TRAF5',
'BBIP1_BBS1_BBS2_BBS4_BBS5_BBS7_BBS9_TTC8',
'FGF1',
'CYTH2',
'LYPLA2',
'HNRNPL',
'GALR1',
'CDK5_CDK5R1',
'FTH1',
'PCDHA3',
'MRC2',
'MMP24',
'ATG10',
'CDC14B',
'GPR34',
'ITGA2',
'TCF4',
'UBE2L6',
'CRKL',
'KLF1',
'PRDM2',
'XAF1',
'KLF11',
'LPAR5',
'GABARAPL1',
'RIF1',
'SSH3',
'C2_C4A_C4B_2',
'AGTR1',
'EIF5B',
'FBXW11',
'JAK1_STAT1_STAT3',
'NHERF1',
'COMMD1',
'RNF38',
'IRX2',
'OSBPL11',
'HELB',
'CLK2',
'PTGIR',
'RPN1',
'SIGLEC10',
'CNOT3',
'TNNT2',
'MT-ND1_MT-ND2_MT-ND3_MT-ND4_MT-ND4L_MT-ND5_MT-ND6_NDUFA1_NDUFA10_NDUFA11_NDUFA12_NDUFA13_NDUFA2_NDUFA3_NDUFA5_NDUFA6_NDUFA7_NDUFA8_NDUFA9_NDUFAB1_NDUFB1_NDUFB10_NDUFB11_NDUFB2_NDUFB3_NDUFB4_NDUFB5_NDUFB6_NDUFB7_NDUFB8_NDUFB9_NDUFC1_NDUFC2_NDUFS1_NDUFS2_NDUFS3_NDUFS4_NDUFS5_NDUFS6_NDUFS7_NDUFS8_NDUFV1_NDUFV2_NDUFV3',
'DGKZ',
'DNAH5',
'IL2RG_IL4R',
'ARHGAP18',
'PAX5',
'BMP15',
'FKBP15',
'NDP',
'ERBB2',
'GLRA2',
'SPON1',
'RABGEF1',
'WNT2',
'SGCG',
'ERBIN',
'REXO1',
'BRS3',
'DUSP26',
'MAPK9',
'NCAPD3',
'SMYD5',
'CRLF2_IL7R',
'Q80H93_RPS27A',
'NEURL1',
'SRPRA',
'DSN1',
'RMND5A',
'FXN',
'RNF216',
'BCKDHA',
'ITGA5_ITGB1',
'RASD2',
'MAST3',
'PEX1',
'RHBDL2',
'BNIP3L',
'CPEB3',
'MAP4K2',
'PTCRA',
'THBD',
'ROS1',
'MID1',
'IST1',
'FBXO44',
'DGCR8',
'NFKBIA',
'CHUK_IKBKB_IKBKG',
'COL3A1',
'ADRB3',
'POLR1G',
'DHX58',
'POLL',
'MTCH2',
'RAVER1',
'RNF121',
'CHD2',
'SLC12A4',
'CHKB',
'SLC2A4',
'FBXL12',
'METTL3',
'NXPH3',
'FMR1',
'ADH4',
'UCHL3',
'CHEBI:166824_CHEBI:53000_HLA-DMB_HLA-DRA',
'AICDA',
'GRIN1_GRIN2D',
'GRIN1_GRIN2B',
'RYBP',
'SDC1',
'P03231_UBC',
'RPL12',
'ATP5IF1',
'BMPR1B_BMPR2',
'MSL1',
'MMP8',
'NCOA2',
'CADM3',
'FBXL21P',
'P59634_UBC',
'DPP6',
'TIA1',
'TTBK1',
'TCF20',
'SPIB',
'GADD45B',
'F10_F5',
'MIPOL1',
'PCDHGB7',
'PHPT1',
'TAF1A',
'ANLN',
'MET',
'MBD3',
'CCND2',
'CRY1',
'ATAT1',
'DNA2',
'CARD18',
'FLRT3_TENM4',
'TPT1',
'COL27A1',
'GAD1_SLC6A6',
'FNBP4',
'ELOVL7',
'LRP10',
'BTF3',
'CDC42EP4',
'PLPP6',
'APLP1',
'PPM1B',
'NXF1',
'PRMT8',
'TECTA',
'GLIS2',
'KCNN1',
'BUB1',
'WASF3',
'VTN',
'BRD2',
'NR1I2',
'P03205_UBB',
'EIF1AY',
'PTRH2',
'KIF1B',
'TAF10',
'MT3',
'PPP2R1B',
'FCER2',
'DKK3',
'MAP3K4',
'ACP5',
'M6PR',
'ZNF322',
'CELSR1',
'P4HB',
'CORO1A',
'NR1D1',
'ITGA1',
'ZNF341',
'JUNB',
'WSB1',
'GTF2I',
'CDH24',
'FGF14',
'KNTC1_ZW10_ZWILCH_ZWINT',
'GPR135',
'GRID2',
'ACVR1B_ACVR2B',
'PTK7',
'FLRT3',
'TULP2',
'PDE6A',
'RABGGTB',
'IL7',
'PPP1R3B',
'ACAP2',
'RALA',
'RTKN',
'NNMT',
'SENP1',
'RFX3',
'FCN2',
'HAS1',
'CLIC4',
'KCNIP3',
'PDK3',
'CDON',
'USP13',
'CYP3A5',
'IL11RA',
'OPHN1',
'JKAMP',
'MTTP',
'BIRC6_UBB',
'SLITRK6',
'DDX3X',
'LRRC4B',
'NHERF4',
'ANP32A',
'MARCHF5',
'DNAJC3',
'PSMA3',
'CLSTN1',
'NKX6-1',
'UIMC1',
'MYL12A',
'NELFE',
'ADCY1',
'TRIP11',
'FBXO31',
'TRPC6',
'SDCBP2',
'CFLAR_FAS_RIPK1_TNFRSF10A_TNFRSF1A_TNFSF10_TRADD_TRAF2',
'TOLLIP',
'GTF3C1',
'GAD1_SLC6A1',
'PABPC1',
'ARTN',
'CREB3L2',
'GALNT8',
'PIAS1',
'MAP2K2',
'IL6R',
'CNIH1',
'H2BW2',
'TBXAS1',
'BRSK2',
'CDK13',
'FZD2_LRP5',
'SCN8A',
'DFFA',
'DKK4',
'PAN2',
'NEDD8',
'HMGCR',
'ABL2',
'CREBBP',
'ITGA1_ITGB1',
'C1QA_C1QB_C1QC',
'CX3CR1',
'LMO1',
'PTN',
'PTPRH',
'DYNC1LI2',
'CCN4',
'ARHGAP44',
'CASP14',
'CASP1',
'AP3D1',
'GRK4',
'CKS2',
'C1QA_C1QB_C1QC_C1R_C1S',
'CTSD',
'HSPA9',
'KSR1',
'AGER',
'A9UF07',
'H2BC5',
'PI4K2B',
'PKD2L1',
'TBC1D4',
'CTSG',
'IFNA1',
'TNS2',
'TTL',
'LHB',
'YWHAE',
'CD63',
'PRKAB1',
'TCTN3',
'CCNF',
'TGFB3',
'PIDD1',
'GLUD2',
'ITM2C',
'MYRIP',
'PPEF1',
'SLC39A7',
'PTH2R',
'NABP2',
'WNT3A',
'C1D',
'ADAMTS3',
'MC3R',
'OPTN',
'NKD1',
'TSPAN10',
'AMIGO2',
'UBE2R2',
'CALM2',
'FHL2',
'HMOX1',
'HAPLN1',
'RALGAPA2',
'APH1A_NCSTN_PSEN1_PSENEN',
'PYGO1',
'INCA1',
'PCDHAC2',
'OGG1',
'BAZ2A_SMARCA5',
'SPON2',
'PIK3R4',
'TNFRSF10C',
'LRRTM2',
'DHX9',
'SNAP29',
'GRIN2C',
'FCHO1',
'TFF2',
'SLC67A1',
'TRIM3',
'TARM1',
'PPP1CA_PPP1R12A_PPP1R12B',
'NCK2',
'TBX21',
'DCX',
'SND1',
'ZNF106',
'NOL4L',
'CDC25B',
'DDX23',
'AMHR2_BMPR1A',
'UBC_UBE2A',
'GSTA2',
'CCNO',
'NGEF',
'ITGA2B',
'ZNF622',
'LYN',
'NUSAP1',
'DBN1',
'KLHL15',
'FZD5_LRP5',
'TBC1D1',
'EIF2AK3',
'DUSP3',
'PTPRO',
'SRSF3',
'MEX3A',
'STXBP6',
'SBF1',
'SERBP1',
'RNF207',
'SDCBP',
'GCNT1',
'MAP4',
'AGTRAP',
'MYL6B',
'NRP1_PLXNA4',
'GSK3B',
'C4BPA',
'BTN1A1',
'RALB',
'SYNPO',
'MUC20',
'MAP9',
'XPA',
'WIPF1',
'GTF2H1',
'PRH2',
'TANC2',
'RPS27A_UBE2N',
'CAMK1',
'MICAL1',
'SIAH3',
'BMP6',
'RNF224',
'GABRA2_GABRB1_GABRG2',
'LMNA',
'MAPK8IP1',
'ISX',
'H2AC20',
'CDKN2D',
'CTPS2',
'ATP5F1B',
'PELI1',
'DGKA',
'ZFY',
'XRCC4',
'NPR3',
'SCN4A',
'PSPN',
'MSMB',
'SLC12A3',
'MMP9_TIMP1',
'ATP2C1',
'P03186_UBC',
'PIK3CA_PIK3R1',
'GNA15',
'PDE3A',
'FBXO33',
'CCL7',
'VEGFD',
'CHD3_CHD4_GATAD2A_GATAD2B_HDAC1_HDAC2_MBD2_MTA1_MTA2_MTA3_RBBP4_RBBP7',
'PRKG1',
'SERINC3',
'GAD1_SLC6A11',
'PA2G4',
'TRIM31',
'AMELY',
'LPCAT2',
'NOVA2',
'ROCK2',
'COL5A3',
'TLK2',
'ASAH2B',
'APEX1',
'JAM2',
'PCGF3',
'DLL4',
'ASB2',
'PANX2',
'PICK1',
'TNFRSF13B',
'RFPL4AL1',
'CFAP53',
'ITGA4_ITGB7',
'TAL2',
'HRC',
'GABRA5_GABRB3_GABRG2',
'CLN8',
'AHSG',
'GLS2_SLC1A6',
'SATB2',
'RBM39',
'FNIP2',
'ISL1',
'NOXO1',
'CLDN14',
'SETBP1',
'EVPL',
'FBXO15',
'NAA10',
'ZMIZ1',
'F2_THBD',
'ATG12',
'ACVR2B',
'GOLGA3',
'SMAD3',
'DVL2',
'THPO',
'NSFL1C',
'KMT2D',
'CDH7',
'CCNC',
'RPL5',
'IL12RB2_IL6ST',
'SVIL',
'PKMYT1',
'FGF7',
'UBA52_UBE2M',
'NLGN2',
'P2RY6',
'FMO1',
'CDH15',
'RIT1',
'ERGIC3',
'ALOX15',
'UBC_UBE2D3',
'GADD45GIP1',
'CHRM1',
'ARHGAP19',
'GPR75',
'CPAMD8',
'PAK6',
'EGR2',
'UBA52_UBE2Q2',
'CDKN2A',
'NFATC2',
'CCR7',
'IL21R',
'CUL2',
'TSSK4',
'EPB42',
'ITGAV_ITGB8',
'FAM3C',
'B2M_CHEBI:166824_HLA-B',
'HDAC5',
'ANGPTL1',
'BRCC3',
'PFKFB2',
'NRP1_PLXNA3',
'TMBIM6',
'PPARGC1B',
'JAK1',
'GCHFR',
'ATG16L1',
'ELMO2',
'TXN',
'CLDN1',
'RSPO4',
'MSN',
'ITGB2',
'EXTL1',
'SIAH1',
'CABYR',
'PAK3',
'TM9SF4',
'ACTA1',
'COPS6',
'C1QL2',
'FBXL6',
'SETDB2',
'CDC45',
'LEPR',
'KCNJ1',
'CALCRL_RAMP2',
'ARHGEF18',
'PNOC',
'TRIM47',
'SSTR3',
'PRG4',
'CENPS',
'CDK5',
'COL18A1',
'EGFR_FZD9_LRP5',
'GLRA1',
'WWP2',
'CD79A_CD79B_P0DOX6_P0DOX7',
'TSSC4',
'DYRK1A',
'ACTL6A_ARID1A_ARID1B_ARID2_SMARCA4_SMARCB1_SMARCC1_SMARCC2_SMARCD1_SMARCE1',
'PLAGL1',
'RHOB',
'GAB1',
'GABRA1_GABRB3_GABRG2',
'SMAP1',
'MUC2',
'DTD1',
'RASSF1',
'ADAM12',
'PDCD6IP',
'CXCL6',
'DTX4',
'PLD1',
'KLC2',
'SIRT2',
'SLBP',
'NR2F6',
'TPX2',
'GOLGA4',
'RUFY3',
'NCF4',
'PCGF5',
'CRTC1',
'PTGR1',
'MSX1',
'ADGRL3',
'ACKR2',
'IL4',
'ID3',
'CDCA3',
'SLC52A2',
'RAD51C',
'ZC3HAV1',
'TRAF6',
'DAXX',
'LRRK1',
'BTG2',
'MAP1A',
'DISP2',
'WT1',
'NBR1',
'CD79A',
'DCC',
'CPM',
'IZUMO1R',
'CHEBI:166824_CHEBI:53000_HLA-DRA_HLA-DRB3',
'YWHAZ',
'DHFR',
'ITGB7_VCAM1',
'DDX19B',
'STIL',
'PROKR2',
'PIK3C2G',
'AGR2',
'DTX3',
'ADRM1',
'NR0B2',
'WNT9B',
'NREP',
'GABRB2',
'TFIP11',
'CDH3',
'MAGI1',
'GEMIN4',
'CASP4',
'CCND3_CDK11B',
'ATP5F1C',
'ZFYVE26',
'GABBR1_GABBR2',
'FGF20',
'ENAM',
'PRPS1',
'DEFB104A',
'MYO15A',
'HNRNPK',
'PC',
'ZW10',
'CCL3L1',
'PXN',
'NSMCE2',
'GFRA4_RET',
'LIME1',
'SFRP5',
'ETV3',
'MADD',
'MAX',
'CREBBP_EP300_KAT2B',
'KIR2DL3',
'GTF2F1',
'RSPO3',
'STK40',
'LPAR1',
'PIP5K1A',
'PCDHGB5',
'RAPGEF6',
'ASH2L_DPY30_KDM6A_KMT2B_KMT2C_NCOA6_PAXIP1_RBBP5_WDR5',
'FLNB',
'RITA1',
'YY1',
'DLAT_DLD_PDHA1_PDHA2_PDHB_PDHX',
'EIF2AK2',
'TRIM64',
'TECTB',
'UBC_UBE2D1',
'ARHGAP17',
'PCDHA7',
'SEMA4D',
'GPAA1',
'BAD',
'STRN4',
'NFIX_PRKCQ',
'FCGR3A',
'PDE1B',
'ITGAV',
'KIFC1',
'PDCD5',
'CHD7_NLK_SETDB1',
'PYGL',
...},
'edge_ids': {'edge_35849',
'edge_29343',
'edge_36861',
'edge_9632',
'edge_26146',
'edge_70778',
'edge_61871',
'edge_82883',
'edge_44829',
'edge_79572',
'edge_55223',
'edge_62146',
'edge_63625',
'edge_21689',
'edge_27421',
'edge_71298',
'edge_77272',
'edge_85351',
'edge_15282',
'edge_24499',
'edge_31985',
'edge_31997',
'edge_54441',
'edge_6834',
'edge_37481',
'edge_63355',
'edge_27554',
'edge_23159',
'edge_22800',
'edge_69669',
'edge_21574',
'edge_40065',
'edge_46384',
'edge_1057',
'edge_62947',
'edge_24570',
'edge_79028',
'edge_82473',
'edge_84873',
'edge_53807',
'edge_13507',
'edge_18891',
'edge_22073',
'edge_35665',
'edge_53960',
'edge_60330',
'edge_69487',
'edge_4090',
'edge_21462',
'edge_62615',
'edge_61161',
'edge_2888',
'edge_23522',
'edge_39292',
'edge_3773',
'edge_70916',
'edge_62861',
'edge_74844',
'edge_64441',
'edge_62149',
'edge_8947',
'edge_37370',
'edge_72819',
'edge_9960',
'edge_72892',
'edge_45885',
'edge_75136',
'edge_16300',
'edge_62303',
'edge_55729',
'edge_7493',
'edge_62799',
'edge_11727',
'edge_52485',
'edge_49890',
'edge_25223',
'edge_9080',
'edge_83275',
'edge_52127',
'edge_52231',
'edge_35015',
'edge_25198',
'edge_19187',
'edge_82583',
'edge_53098',
'edge_3617',
'edge_52294',
'edge_29059',
'edge_34961',
'edge_8247',
'edge_44441',
'edge_41475',
'edge_73497',
'edge_63341',
'edge_13416',
'edge_516',
'edge_51383',
'edge_36803',
'edge_69618',
'edge_19829',
'edge_11128',
'edge_20274',
'edge_38672',
'edge_53535',
'edge_23454',
'edge_56913',
'edge_75769',
'edge_26485',
'edge_37902',
'edge_67984',
'edge_65791',
'edge_37025',
'edge_54671',
'edge_22835',
'edge_77759',
'edge_45487',
'edge_84337',
'edge_42898',
'edge_41697',
'edge_32752',
'edge_64028',
'edge_78097',
'edge_41752',
'edge_72003',
'edge_19826',
'edge_58092',
'edge_25210',
'edge_28095',
'edge_38913',
'edge_36613',
'edge_78189',
'edge_80899',
'edge_83677',
'edge_20387',
'edge_79408',
'edge_19553',
'edge_82208',
'edge_39389',
'edge_501',
'edge_12166',
'edge_20927',
'edge_61449',
'edge_9979',
'edge_51715',
'edge_46582',
'edge_62477',
'edge_37021',
'edge_2819',
'edge_55308',
'edge_32614',
'edge_47421',
'edge_64162',
'edge_51044',
'edge_15674',
'edge_48687',
'edge_23354',
'edge_48792',
'edge_16350',
'edge_82286',
'edge_72320',
'edge_59928',
'edge_25631',
'edge_17639',
'edge_31350',
'edge_2066',
'edge_25269',
'edge_44718',
'edge_64008',
'edge_74353',
'edge_48998',
'edge_41130',
'edge_25121',
'edge_79760',
'edge_34821',
'edge_1250',
'edge_45506',
'edge_28906',
'edge_36162',
'edge_48975',
'edge_35587',
'edge_50301',
'edge_68146',
'edge_67832',
'edge_30649',
'edge_30967',
'edge_71370',
'edge_37412',
'edge_79488',
'edge_17500',
'edge_24074',
'edge_698',
'edge_9119',
'edge_77596',
'edge_84730',
'edge_67862',
'edge_53523',
'edge_62514',
'edge_37827',
'edge_79135',
'edge_10302',
'edge_76221',
'edge_52562',
'edge_37785',
'edge_3214',
'edge_25690',
'edge_58167',
'edge_59551',
'edge_14598',
'edge_14332',
'edge_29444',
'edge_63316',
'edge_64320',
'edge_43923',
'edge_63781',
'edge_72057',
'edge_54310',
'edge_25349',
'edge_39787',
'edge_12250',
'edge_1541',
'edge_45658',
'edge_24462',
'edge_34495',
'edge_67271',
'edge_122',
'edge_14161',
'edge_27674',
'edge_76550',
'edge_19754',
'edge_39109',
'edge_42361',
'edge_43562',
'edge_55232',
'edge_70788',
'edge_56613',
'edge_36324',
'edge_74129',
'edge_48101',
'edge_39695',
'edge_20073',
'edge_10977',
'edge_25556',
'edge_23428',
'edge_51916',
'edge_38668',
'edge_8284',
'edge_61017',
'edge_75818',
'edge_19552',
'edge_54048',
'edge_24188',
'edge_47690',
'edge_32850',
'edge_77843',
'edge_49827',
'edge_71914',
'edge_22781',
'edge_26118',
'edge_43297',
'edge_77428',
'edge_76656',
'edge_30721',
'edge_22242',
'edge_66670',
'edge_6207',
'edge_58880',
'edge_44336',
'edge_58750',
'edge_66336',
'edge_31909',
'edge_36958',
'edge_62296',
'edge_35261',
'edge_10283',
'edge_46937',
'edge_44766',
'edge_51213',
'edge_50834',
'edge_45484',
'edge_54677',
'edge_48860',
'edge_50003',
'edge_57552',
'edge_60051',
'edge_67970',
'edge_13405',
'edge_46093',
'edge_75815',
'edge_28577',
'edge_62977',
'edge_23497',
'edge_40007',
'edge_33360',
'edge_75460',
'edge_81489',
'edge_14889',
'edge_35489',
'edge_5187',
'edge_76681',
'edge_24926',
'edge_82456',
'edge_79710',
'edge_75292',
'edge_38335',
'edge_72878',
'edge_73865',
'edge_38908',
'edge_61601',
'edge_1593',
'edge_16678',
'edge_5412',
'edge_47466',
'edge_15126',
'edge_59302',
'edge_15161',
'edge_40526',
'edge_21839',
'edge_42223',
'edge_10122',
'edge_54975',
'edge_29460',
'edge_81813',
'edge_32379',
'edge_26956',
'edge_4969',
'edge_58360',
'edge_15421',
'edge_20782',
'edge_40174',
'edge_43084',
'edge_35129',
'edge_57680',
'edge_69641',
'edge_73659',
'edge_37019',
'edge_67188',
'edge_42287',
'edge_38101',
'edge_34976',
'edge_60883',
'edge_46232',
'edge_51221',
'edge_38805',
'edge_32061',
'edge_5878',
'edge_59895',
'edge_33836',
'edge_35236',
'edge_7802',
'edge_36488',
'edge_49222',
'edge_24625',
'edge_5971',
'edge_41265',
'edge_7113',
'edge_57700',
'edge_24293',
'edge_14945',
'edge_62084',
'edge_65132',
'edge_66391',
'edge_71527',
'edge_23288',
'edge_62707',
'edge_84961',
'edge_77281',
'edge_14515',
'edge_61497',
'edge_7776',
'edge_73714',
'edge_45582',
'edge_42281',
'edge_75635',
'edge_60513',
'edge_61895',
'edge_75802',
'edge_28792',
'edge_77345',
'edge_11865',
'edge_8213',
'edge_76968',
'edge_27538',
'edge_18650',
'edge_83922',
'edge_7873',
'edge_4498',
'edge_74629',
'edge_82617',
'edge_69729',
'edge_47854',
'edge_75039',
'edge_25531',
'edge_68148',
'edge_35805',
'edge_62563',
'edge_2586',
'edge_73663',
'edge_58547',
'edge_46776',
'edge_54609',
'edge_14655',
'edge_10538',
'edge_26772',
'edge_77194',
'edge_394',
'edge_39819',
'edge_56250',
'edge_3665',
'edge_61876',
'edge_71385',
'edge_57967',
'edge_71056',
'edge_7971',
'edge_24521',
'edge_37389',
'edge_11288',
'edge_73437',
'edge_44918',
'edge_43665',
'edge_50951',
'edge_63862',
'edge_51658',
'edge_52868',
'edge_46024',
'edge_41132',
'edge_76637',
'edge_34291',
'edge_59614',
'edge_20427',
'edge_58878',
'edge_48650',
'edge_65495',
'edge_48716',
'edge_59725',
'edge_81693',
'edge_62583',
'edge_21724',
'edge_81190',
'edge_14937',
'edge_35067',
'edge_71642',
'edge_54560',
'edge_55798',
'edge_85142',
'edge_61780',
'edge_6263',
'edge_11938',
'edge_73543',
'edge_20075',
'edge_3157',
'edge_6661',
'edge_57788',
'edge_33953',
'edge_76620',
'edge_46352',
'edge_74346',
'edge_82718',
'edge_49439',
'edge_17234',
'edge_30848',
'edge_48370',
'edge_11391',
'edge_41652',
'edge_37948',
'edge_30875',
'edge_78421',
'edge_11293',
'edge_28888',
'edge_3296',
'edge_18864',
'edge_25216',
'edge_34028',
'edge_14507',
'edge_83146',
'edge_33229',
'edge_82392',
'edge_39134',
'edge_8540',
'edge_18342',
'edge_28800',
'edge_35859',
'edge_39910',
'edge_27103',
'edge_84056',
'edge_11379',
'edge_47479',
'edge_51402',
'edge_13581',
'edge_18841',
'edge_42221',
'edge_4278',
'edge_43385',
'edge_56189',
'edge_45446',
'edge_5234',
'edge_68992',
'edge_71478',
'edge_25945',
'edge_7507',
'edge_41508',
'edge_59916',
'edge_31604',
'edge_24632',
'edge_42569',
'edge_15698',
'edge_34228',
'edge_38709',
'edge_32323',
'edge_27581',
'edge_47899',
'edge_81344',
'edge_2444',
'edge_56282',
'edge_66314',
'edge_78809',
'edge_52072',
'edge_16944',
'edge_48412',
'edge_75906',
'edge_5988',
'edge_72712',
'edge_63347',
'edge_70485',
'edge_59784',
'edge_67195',
'edge_78200',
'edge_53822',
'edge_21109',
'edge_43885',
'edge_43367',
'edge_74796',
'edge_38875',
'edge_52079',
'edge_73647',
'edge_18715',
'edge_23123',
'edge_51852',
'edge_50035',
'edge_16238',
'edge_27673',
'edge_61558',
'edge_32131',
'edge_53190',
'edge_1703',
'edge_36358',
'edge_63961',
'edge_36412',
'edge_65897',
'edge_16473',
'edge_46862',
'edge_17123',
'edge_50194',
'edge_80295',
'edge_32545',
'edge_40864',
'edge_85217',
'edge_37249',
'edge_82897',
'edge_78909',
'edge_5173',
'edge_65859',
'edge_11923',
'edge_50198',
'edge_33351',
'edge_1370',
'edge_50218',
'edge_71560',
'edge_65145',
'edge_72705',
'edge_3600',
'edge_60517',
'edge_50722',
'edge_10642',
'edge_30990',
'edge_51767',
'edge_73482',
'edge_61743',
'edge_46751',
'edge_73212',
'edge_83988',
'edge_84839',
'edge_23551',
'edge_33863',
'edge_80794',
'edge_15963',
'edge_37604',
'edge_69628',
'edge_67051',
'edge_14466',
'edge_31068',
'edge_70981',
'edge_72491',
'edge_17069',
'edge_47985',
'edge_40010',
'edge_68644',
'edge_61267',
'edge_13136',
'edge_67733',
'edge_9683',
'edge_19496',
'edge_32820',
'edge_44902',
'edge_77823',
'edge_52772',
'edge_79922',
'edge_31102',
'edge_76339',
'edge_18503',
'edge_29328',
'edge_37966',
'edge_67667',
'edge_75850',
'edge_14657',
'edge_51959',
'edge_61860',
'edge_30089',
'edge_24936',
'edge_40728',
'edge_83466',
'edge_80672',
'edge_2651',
'edge_6822',
'edge_57521',
'edge_11694',
'edge_84453',
'edge_54898',
'edge_43275',
'edge_79427',
'edge_68073',
'edge_39319',
'edge_4870',
'edge_6210',
'edge_25206',
'edge_6620',
'edge_14309',
'edge_67824',
'edge_42739',
'edge_74920',
'edge_80010',
'edge_7476',
'edge_43320',
'edge_33189',
'edge_62949',
'edge_49635',
'edge_46475',
'edge_42235',
'edge_53070',
'edge_60027',
'edge_66872',
'edge_27462',
'edge_7439',
'edge_24607',
'edge_39411',
'edge_43026',
'edge_4023',
'edge_22267',
'edge_39558',
'edge_37652',
'edge_23113',
'edge_61515',
'edge_43403',
'edge_18630',
'edge_84101',
'edge_62891',
'edge_21924',
'edge_51114',
'edge_42154',
'edge_47383',
'edge_75357',
'edge_82666',
'edge_61013',
'edge_4078',
'edge_10307',
'edge_53925',
'edge_71717',
'edge_45095',
'edge_24843',
'edge_27469',
'edge_34225',
'edge_83798',
'edge_29599',
'edge_58365',
'edge_26882',
'edge_33064',
'edge_14894',
'edge_17785',
'edge_20117',
'edge_38241',
'edge_53438',
'edge_17808',
'edge_48011',
'edge_8393',
'edge_61275',
'edge_80536',
'edge_20797',
'edge_67724',
'edge_11411',
'edge_19531',
'edge_70902',
'edge_14403',
'edge_71330',
'edge_85326',
'edge_59169',
'edge_34912',
'edge_38907',
'edge_70294',
'edge_2151',
'edge_79729',
'edge_84480',
'edge_11801',
'edge_21440',
'edge_10074',
'edge_72013',
'edge_16684',
'edge_65687',
'edge_31658',
'edge_33046',
'edge_80644',
'edge_36602',
'edge_75495',
'edge_57231',
'edge_52482',
'edge_72684',
'edge_74515',
'edge_78282',
'edge_13930',
'edge_21170',
'edge_27130',
'edge_45081',
'edge_3224',
'edge_56190',
'edge_33367',
'edge_81415',
'edge_83288',
'edge_83868',
'edge_47682',
'edge_66072',
'edge_4019',
'edge_20509',
'edge_67160',
'edge_7745',
'edge_82977',
'edge_3013',
'edge_62829',
'edge_43036',
'edge_44232',
'edge_76749',
'edge_59948',
'edge_75889',
'edge_85388',
'edge_54514',
'edge_8466',
'edge_58988',
'edge_85424',
'edge_19484',
'edge_58188',
'edge_74677',
'edge_38276',
'edge_82858',
'edge_27530',
'edge_60926',
'edge_76469',
'edge_47202',
'edge_55466',
'edge_46784',
'edge_2099',
'edge_68169',
'edge_60549',
'edge_41399',
'edge_12845',
'edge_73169',
'edge_51308',
'edge_35371',
'edge_18208',
'edge_24522',
'edge_2103',
'edge_70311',
'edge_72845',
'edge_25728',
'edge_57129',
'edge_60864',
'edge_20567',
'edge_27613',
'edge_11515',
'edge_7167',
'edge_45778',
'edge_67782',
'edge_29765',
'edge_30224',
'edge_55016',
'edge_29580',
'edge_9611',
'edge_83950',
'edge_78311',
'edge_59266',
'edge_35066',
'edge_84678',
'edge_56096',
'edge_51112',
'edge_77835',
'edge_44819',
'edge_50371',
'edge_29728',
'edge_2800',
'edge_68308',
'edge_40701',
'edge_62165',
'edge_6279',
'edge_84835',
'edge_49818',
'edge_8312',
'edge_73313',
'edge_50064',
'edge_42571',
'edge_9329',
'edge_1828',
'edge_22738',
'edge_60530',
'edge_34860',
'edge_49288',
'edge_60207',
'edge_85020',
'edge_26632',
'edge_31982',
'edge_44780',
'edge_32889',
'edge_61359',
'edge_54103',
'edge_59860',
'edge_74949',
'edge_29671',
'edge_46462',
'edge_52426',
'edge_26359',
'edge_30299',
'edge_41280',
'edge_3340',
'edge_43770',
'edge_29655',
'edge_14528',
'edge_72660',
'edge_4096',
'edge_18686',
'edge_52410',
'edge_69094',
'edge_68901',
'edge_61114',
'edge_74322',
'edge_33835',
'edge_15767',
'edge_21895',
'edge_31507',
'edge_67557',
'edge_23254',
'edge_66187',
'edge_38745',
'edge_32094',
'edge_51679',
'edge_85457',
'edge_63207',
'edge_77379',
'edge_5418',
'edge_78949',
'edge_82845',
'edge_16945',
'edge_3834',
'edge_17504',
'edge_62572',
'edge_72370',
'edge_54578',
'edge_54003',
'edge_13404',
'edge_82721',
'edge_1275',
'edge_24318',
'edge_30014',
'edge_79153',
'edge_9675',
'edge_60495',
'edge_54420',
'edge_9111',
'edge_60559',
'edge_82190',
'edge_17571',
'edge_64988',
'edge_85051',
'edge_40812',
'edge_17114',
'edge_30207',
'edge_21929',
'edge_60776',
'edge_44713',
'edge_57571',
'edge_78123',
'edge_15184',
'edge_9748',
'edge_14428',
'edge_53132',
'edge_21803',
'edge_10898',
'edge_30839',
'edge_53016',
'edge_68603',
'edge_56603',
'edge_35369',
'edge_39843',
'edge_73855',
'edge_65197',
'edge_22924',
'edge_44781',
'edge_37037',
'edge_10426',
'edge_39022',
'edge_44300',
'edge_65337',
'edge_35357',
'edge_28241',
'edge_1386',
'edge_18964',
'edge_80738',
'edge_42527',
'edge_53663',
'edge_31659',
'edge_56660',
'edge_78146',
'edge_265',
'edge_43303',
'edge_44452',
'edge_74108',
'edge_15692',
'edge_28617',
'edge_13815',
'edge_56734',
'edge_84748',
'edge_75260',
'edge_68145',
'edge_5778',
'edge_42842',
'edge_4325',
'edge_47616',
'edge_36285',
'edge_60862',
'edge_54205',
'edge_17100',
'edge_43657',
'edge_54497',
'edge_75486',
'edge_27222',
'edge_53351',
'edge_49952',
'edge_61679',
'edge_32970',
'edge_70937',
'edge_75683',
'edge_62702',
'edge_10339',
'edge_66305',
'edge_70766',
'edge_25737',
'edge_74800',
'edge_80948',
'edge_72530',
'edge_69866',
'edge_4894',
'edge_19801',
'edge_10259',
'edge_46186',
'edge_18124',
'edge_20697',
'edge_61791',
'edge_43775',
'edge_68144',
'edge_59486',
'edge_6802',
'edge_25843',
'edge_40562',
'edge_10239',
'edge_79702',
'edge_83153',
'edge_83303',
'edge_33147',
'edge_47677',
'edge_80878',
'edge_74576',
'edge_70612',
'edge_61340',
'edge_83636',
'edge_7046',
'edge_56071',
'edge_68421',
'edge_51132',
'edge_54791',
'edge_33651',
'edge_69521',
'edge_71255',
'edge_71821',
'edge_3299',
'edge_19411',
'edge_53042',
...},
'slice_ids': {'carnival_inferred', 'default', 'omnipath_pkn'}}
14. Consensus construction¶
The consensus layer aggregates per-patient inferred subnetworks. We
aggregate from per-patient active-node and active-edge dictionaries,
so for n patients every count is in [0, n] and every frequency is in
[0, 1] by construction.
def unique_vertex_ids(values) -> list[str]:
seen = set()
out = []
for value in values:
if isinstance(value, tuple):
value = value[0]
value = str(value)
if value not in seen:
seen.add(value)
out.append(value)
return out
def get_patient_active_nodes(patient_result: dict) -> set[str]:
return set(unique_vertex_ids(patient_result.get('node_signal', {}).keys()))
def get_patient_active_edges(patient_result: dict) -> set[tuple[str, str]]:
edges = set()
for src, tgt in patient_result.get('edge_signal', {}):
edges.add((str(src), str(tgt)))
return edges
def build_consensus_layer(
patient_results: dict, min_freq: float
) -> tuple[pd.DataFrame, pd.DataFrame, dict]:
if not patient_results:
raise ValueError('patient_results is empty')
n_patients = len(patient_results)
node_counter = Counter()
edge_counter = Counter()
node_signal_acc = defaultdict(list)
edge_signal_acc = defaultdict(list)
union_nodes = set()
union_edges = set()
for _patient_id, result in patient_results.items():
nodes = get_patient_active_nodes(result)
edges = get_patient_active_edges(result)
union_nodes |= nodes
union_edges |= edges
for node_id in nodes:
node_counter[node_id] += 1
node_signal_acc[node_id].append(float(result['node_signal'][node_id]))
for edge_key in edges:
edge_counter[edge_key] += 1
edge_signal_acc[edge_key].append(float(result['edge_signal'][edge_key]))
node_rows = []
for node_id, count in sorted(node_counter.items(), key=lambda kv: (-kv[1], kv[0])):
freq = count / n_patients
assert 0 <= count <= n_patients, f'Invalid node count for {node_id}: {count}'
assert 0.0 <= freq <= 1.0, f'Invalid node frequency for {node_id}: {freq}'
node_rows.append(
{
'vertex_id': node_id,
'active_count': count,
'patient_frequency': freq,
'mean_signal': float(np.mean(node_signal_acc[node_id])),
'median_signal': float(np.median(node_signal_acc[node_id])),
'selected_for_consensus': bool(freq >= min_freq),
}
)
edge_rows = []
for (src, tgt), count in sorted(edge_counter.items(), key=lambda kv: (-kv[1], kv[0])):
freq = count / n_patients
assert 0 <= count <= n_patients, f'Invalid edge count for {(src, tgt)}: {count}'
assert 0.0 <= freq <= 1.0, f'Invalid edge frequency for {(src, tgt)}: {freq}'
edge_rows.append(
{
'source': src,
'target': tgt,
'active_count': count,
'patient_frequency': freq,
'mean_signal': float(np.mean(edge_signal_acc[(src, tgt)])),
'median_signal': float(np.median(edge_signal_acc[(src, tgt)])),
'selected_for_consensus': bool(freq >= min_freq),
}
)
node_df = pd.DataFrame(node_rows)
edge_df = pd.DataFrame(edge_rows)
selected_nodes = node_df[node_df['selected_for_consensus']] if not node_df.empty else node_df
selected_edges = edge_df[edge_df['selected_for_consensus']] if not edge_df.empty else edge_df
assert selected_nodes.shape[0] <= len(union_nodes), (
'Consensus node count exceeded union of unique active patient nodes.'
)
if not selected_edges.empty:
assert (selected_edges['patient_frequency'] <= 1.0).all(), (
'Consensus edge frequency exceeded 1.0.'
)
summary = {
'n_patients': n_patients,
'n_union_nodes': len(union_nodes),
'n_union_edges': len(union_edges),
'n_consensus_nodes': int(selected_nodes.shape[0]),
'n_consensus_edges': int(selected_edges.shape[0]),
}
return node_df, edge_df, summary
def add_consensus_layer_to_graph(
graph, layer_label: str, node_df: pd.DataFrame, edge_df: pd.DataFrame
):
if layer_label not in graph.layers.elem_layers['patient']:
graph.layers.add_elementary_layer('patient', layer_label)
aa = (layer_label,)
selected_nodes = node_df[node_df['selected_for_consensus']].copy()
selected_edges = edge_df[edge_df['selected_for_consensus']].copy()
if not selected_nodes.empty:
graph.add_vertices(selected_nodes['vertex_id'].tolist(), layer=aa)
for row in selected_nodes.itertuples(index=False):
graph.layers.set_vertex_layer_attrs(
row.vertex_id,
aa,
corneto_signal=float(row.mean_signal),
patient_frequency=float(row.patient_frequency),
active_count=int(row.active_count),
)
if not selected_edges.empty:
edge_specs = [
{
'source': (row.source, aa),
'target': (row.target, aa),
'weight': float(row.mean_signal),
}
for row in selected_edges.itertuples(index=False)
if row.source in set(selected_nodes['vertex_id'])
and row.target in set(selected_nodes['vertex_id'])
]
if edge_specs:
graph.add_edges(edge_specs)
return aa
consensus_outputs = {}
for cohort_label in SENSITIVITY_COHORT_LABELS:
cohort_members = [
p
for p in cohort_definitions[cohort_label]
if p in primary_patient_networks and primary_patient_networks[p]['solver_ok']
]
cohort_patient_results = {p: primary_patient_networks[p] for p in cohort_members}
node_df, edge_df, summary = build_consensus_layer(cohort_patient_results, CONSENSUS_MIN_FREQ)
summary['cohort_label'] = cohort_label
node_df.to_csv(TABLE_DIR / f'consensus_nodes_{cohort_label}.csv', index=False)
edge_df.to_csv(TABLE_DIR / f'consensus_edges_{cohort_label}.csv', index=False)
consensus_outputs[cohort_label] = {
'node_df': node_df,
'edge_df': edge_df,
'summary': summary,
'layer_aa': add_consensus_layer_to_graph(G, f'consensus_{cohort_label}', node_df, edge_df),
}
consensus_summary = pd.DataFrame([x['summary'] for x in consensus_outputs.values()])
consensus_summary.to_csv(TABLE_DIR / 'consensus_summary.csv', index=False)
G.history.snapshot('after_consensus_layers')
primary_consensus_nodes = consensus_outputs[PRIMARY_COHORT_LABEL]['node_df']
primary_consensus_edges = consensus_outputs[PRIMARY_COHORT_LABEL]['edge_df']
primary_consensus_aa = consensus_outputs[PRIMARY_COHORT_LABEL]['layer_aa']
assert (
primary_consensus_nodes['active_count'].max()
<= consensus_outputs[PRIMARY_COHORT_LABEL]['summary']['n_patients']
)
assert primary_consensus_nodes['patient_frequency'].max() <= 1.0
if not primary_consensus_edges.empty:
assert (
primary_consensus_edges['active_count'].max()
<= consensus_outputs[PRIMARY_COHORT_LABEL]['summary']['n_patients']
)
assert primary_consensus_edges['patient_frequency'].max() <= 1.0
primary_consensus_nodes.sort_values(
['patient_frequency', 'active_count', 'vertex_id'], ascending=[False, False, True]
).head(20)
# Convenience handle reused by downstream sections (inter-layer
# coupling, sanity checks, backend swap).
primary_consensus_selected = primary_consensus_nodes[
primary_consensus_nodes['selected_for_consensus']
].copy()
15. Consensus diagnostics¶
print(consensus_summary)
print('\nTop consensus nodes (primary cohort):')
display(
primary_consensus_nodes.sort_values(
['patient_frequency', 'active_count', 'mean_signal'], ascending=[False, False, False]
).head(20)
)
print('\nTop consensus edges (primary cohort):')
display(
primary_consensus_edges.sort_values(
['patient_frequency', 'active_count', 'mean_signal'], ascending=[False, False, False]
).head(20)
)
n_patients n_union_nodes n_union_edges n_consensus_nodes \ 0 11 93 87 10 1 21 115 127 11 n_consensus_edges cohort_label 0 3 top10 1 3 top20 Top consensus nodes (primary cohort):
| vertex_id | active_count | patient_frequency | mean_signal | median_signal | selected_for_consensus | |
|---|---|---|---|---|---|---|
| 0 | GSK3B | 11 | 1.000000 | 0.636364 | 1.0 | True |
| 1 | CDK1 | 10 | 0.909091 | 1.000000 | 1.0 | True |
| 2 | CDK2 | 10 | 0.909091 | 1.000000 | 1.0 | True |
| 4 | MAPK3 | 10 | 0.909091 | 0.600000 | 1.0 | True |
| 3 | CEBPA | 10 | 0.909091 | -1.000000 | -1.0 | True |
| 5 | MAPK1 | 8 | 0.727273 | -1.000000 | -1.0 | True |
| 6 | E2F1 | 7 | 0.636364 | 1.000000 | 1.0 | True |
| 7 | MAPK14 | 7 | 0.636364 | 1.000000 | 1.0 | True |
| 8 | SPI1 | 7 | 0.636364 | -1.000000 | -1.0 | True |
| 9 | CSNK2A1 | 6 | 0.545455 | 1.000000 | 1.0 | True |
| 10 | CSNK2A2 | 5 | 0.454545 | 1.000000 | 1.0 | False |
| 12 | MAPK8 | 5 | 0.454545 | 1.000000 | 1.0 | False |
| 13 | MYC | 5 | 0.454545 | 1.000000 | 1.0 | False |
| 11 | EGFR | 5 | 0.454545 | 0.600000 | 1.0 | False |
| 14 | PPARA | 5 | 0.454545 | -1.000000 | -1.0 | False |
| 15 | FOSL1 | 4 | 0.363636 | 1.000000 | 1.0 | False |
| 16 | LCK | 4 | 0.363636 | 0.500000 | 1.0 | False |
| 17 | PRKACA | 4 | 0.363636 | -1.000000 | -1.0 | False |
| 18 | RUNX1 | 4 | 0.363636 | -1.000000 | -1.0 | False |
| 19 | BUB1 | 3 | 0.272727 | 1.000000 | 1.0 | False |
Top consensus edges (primary cohort):
| source | target | active_count | patient_frequency | mean_signal | median_signal | selected_for_consensus | |
|---|---|---|---|---|---|---|---|
| 0 | CDK1 | MAPK3 | 8 | 0.727273 | 1.0 | 1.0 | True |
| 1 | MAPK3 | CEBPA | 8 | 0.727273 | -1.0 | -1.0 | True |
| 2 | CDK1 | E2F1 | 7 | 0.636364 | 1.0 | 1.0 | True |
| 3 | CDK2 | MYC | 5 | 0.454545 | 1.0 | 1.0 | False |
| 4 | GSK3B | SPI1 | 5 | 0.454545 | -1.0 | -1.0 | False |
| 5 | MAPK1 | PPARA | 4 | 0.363636 | -1.0 | -1.0 | False |
| 6 | MAPK1 | RUNX1 | 4 | 0.363636 | -1.0 | -1.0 | False |
| 7 | CHEK1 | E2F3 | 3 | 0.272727 | 1.0 | 1.0 | False |
| 8 | EGFR | IKBKE | 3 | 0.272727 | 1.0 | 1.0 | False |
| 9 | IKBKE | IRF1 | 3 | 0.272727 | -1.0 | -1.0 | False |
| 10 | MAPK14 | MAPK1 | 3 | 0.272727 | -1.0 | -1.0 | False |
| 11 | CDK1 | CHEK1 | 2 | 0.181818 | 1.0 | 1.0 | False |
| 13 | GSK3B | FOSL1 | 2 | 0.181818 | 1.0 | 1.0 | False |
| 15 | LCK | PRKCQ | 2 | 0.181818 | 1.0 | 1.0 | False |
| 16 | MAPK1 | EGFR | 2 | 0.181818 | 1.0 | 1.0 | False |
| 17 | MAPK1 | GSK3B | 2 | 0.181818 | 1.0 | 1.0 | False |
| 19 | MAPK14 | DLX5 | 2 | 0.181818 | 1.0 | 1.0 | False |
| 21 | MAPK3 | LCK | 2 | 0.181818 | 1.0 | 1.0 | False |
| 23 | PRKCQ | FOSL1 | 2 | 0.181818 | 1.0 | 1.0 | False |
| 12 | GSK3B | CEBPA | 2 | 0.181818 | -1.0 | -1.0 | False |
16. Inter-layer (coupling) edges across patient layers¶
A real Kivelä multilayer needs coupling edges between layers, not just
intra-layer edges. For every consensus gene v we add a coupling edge
(v, p_i) ↔ (v, p_j) for every pair of patient layers where v was
active. After this, each patient subgraph is genuinely multilayer.
sig_threshold = CORNETO_SIG_THRESHOLD
inter_layer_specs: list[dict] = []
for v_row in primary_consensus_selected.itertuples():
vid = v_row.vertex_id
activating = [
p
for p, rec in primary_patient_networks.items()
if rec.get('solver_ok') and abs(rec.get('node_signal', {}).get(vid, 0.0)) >= sig_threshold
]
for i in range(len(activating)):
for j in range(i + 1, len(activating)):
inter_layer_specs.append(
{
'source': (vid, (activating[i],)),
'target': (vid, (activating[j],)),
'edge_directed': False,
'weight': 1.0,
}
)
added_eids = G._add_edges_bulk(inter_layer_specs) if inter_layer_specs else []
print(f'Added {len(added_eids):,} inter-layer coupling edges across consensus genes')
# Re-extract one patient's subgraph WITH coupling included — now multilayer
sample_patient = sorted(primary_patient_networks.keys())[0]
sg_intra = G.layers.subgraph_from_layer_tuple((sample_patient,))
sg_full = G.layers.subgraph_from_layer_tuple((sample_patient,), include_inter=True)
print(
f'Patient {sample_patient}: intra-only |E|={sg_intra.num_edges} +inter |E|={sg_full.num_edges}'
)
G.history.snapshot('after_interlayer_coupling')
Added 1,134 inter-layer coupling edges across consensus genes Patient C3L-00080: intra-only |E|=12 +inter |E|=12
{'label': 'after_interlayer_coupling',
'version': 154,
'vertex_ids': {'IGFL1',
'PTGFRN',
'CASQ2',
'UBE2S',
'NOTCH4',
'BAG2',
'RIOK2',
'TRIM77',
'PRNP',
'CLIC5',
'KLRD1',
'PPFIA3',
'MAZ',
'GRPEL1',
'TNK2',
'ANAPC2',
'GCG',
'TRIM43B',
'TSPAN1',
'PML',
'RETN',
'TAPBP',
'ENAH',
'VLDLR',
'MNAT1',
'CSN1S1',
'ATF2_JUN',
'BANP',
'MTCP1',
'ADGRE5',
'H2BK1',
'G3BP2',
'ATRX',
'GLG1',
'TFEB',
'CDH9',
'SCARB1',
'DLX5',
'FMNL2',
'PARP1',
'RNF141',
'KPNA1',
'AP2M1',
'ITGAV_ITGB1',
'MCHR2',
'GATA6',
'CDH8',
'DEFB4B',
'NOXA1',
'PLAA',
'UGT2B7',
'RABEPK',
'PIM1',
'UBIAD1',
'PIP5K1B',
'TREML1',
'BRDT',
'FAAP20',
'CSNK1D',
'SEC62',
'TBX3',
'KAT5',
'ACVR1B_ACVR2A_CFC1',
'FLRT2',
'RPS27A',
'CLEC1B',
'NSMCE1',
'ABR',
'CD8A_CD8B',
'STRAP',
'PITRM1',
'OMA1',
'TP53TG5',
'IRS4',
'SELE',
'TRIM22',
'IFNA14',
'S100A4',
'DDX24',
'PAX8',
'FLT3',
'PPP3CB',
'RBM17',
'RPL6',
'CDCA5',
'MAP2K7',
'UBE3B',
'MYOM2',
'IKZF1',
'PHF12',
'LAMP1',
'SGK3',
'RNF123',
'BTN2A1',
'DUSP13B',
'PPP1R1A',
'CHRNA7',
'DHH',
'CXCL3',
'ADAMTS13',
'VMP1',
'S100A12',
'FLRT1',
'VEZT',
'IL12A_IL12B',
'CRK',
'ACSS2',
'TM4SF1',
'MYLK3',
'ICAM2',
'CHEBI:166824_CHEBI:53000_HLA-DMB_HLA-DPA1',
'PLD2',
'MARCKSL1',
'PHACTR1',
'PLAU',
'RPS27A_UBE2D3',
'LAMTOR1',
'UBC_UBE2V2',
'TRPM2',
'DAB1',
'PDK1',
'UBC_UBE2D2',
'RAD51D',
'RNF139',
'HLA-E',
'SMOC2',
'CEP68',
'UBA52_UBE2Q1',
'GRM7',
'PIH1D2_RUVBL1_RUVBL2_SPAG1',
'CDC37',
'LAMA2',
'MATK',
'PRC1',
'TENM4',
'ALDH2',
'KCNA5',
'APAF1',
'NHLH2',
'PCBD1',
'PLCL1',
'SLC12A5',
'RPS27L',
'LRRFIP2',
'BST1',
'PUM3',
'XRCC6',
'IGBP1',
'BCL9',
'PSMD4',
'GSTP1',
'PANX1',
'LTN1',
'NR1H4',
'SMAD3_SMAD4',
'GSTA1',
'ASTN2',
'P13288_RPS27A',
'RECQL5',
'HOMER2',
'IFI16',
'ARHGAP30',
'SCAND1',
'CGAS',
'MRE11',
'TG',
'DDX46',
'CDR2',
'NLK',
'SSTR5',
'WNT10B',
'PPP2CA_PPP2R1A_PPP2R2B',
'PPP2CA',
'IL1A',
'COL6A6',
'TBXA2R',
'KIF14',
'PABPC4',
'LCTL',
'GTF2F2',
'SKIL',
'ACTA1_ACTL6A_ALKBH3_BRD2_BRD8_DMAP1_EP400_EPC1_ING3_KAT5_MORF4L1_MRGBP_RUVBL1_RUVBL2_TRRAP_VPS72_YEATS4',
'SPHK1',
'SCP2',
'GABRA6_GABRB3_GABRG2',
'HSPB1',
'UBC_UBE2K',
'TMEM219',
'COBL',
'UBB_UBE2E3',
'EEF1B2_EEF1D_EEF1G',
'LAMA1',
'CNOT9',
'SLAMF6',
'ESPL1',
'NPS',
'CDK4',
'TERF2IP',
'PPP6R2',
'LASP1',
'CBY1',
'CDC7',
'HCK',
'CALCA',
'SRCAP',
'NEU1',
'SLIT2',
'FLOT1',
'MPG',
'JAM3',
'GREB1',
'B4GALT1',
'FYN',
'PPP1R12B',
'RAD21_SMC1A_SMC3_STAG1_STAG3',
'RSPO2',
'S100A11',
'EDAR',
'NPBWR2',
'IFIT1',
'GAR1',
'NR4A1',
'ADO',
'PRDM1',
'PIAS2',
'POLD1_POLD2_POLD3_POLD4',
'TOP2B',
'CEP43',
'DACT2',
'CDCA8_PRC1',
'PHEX',
'TDG',
'CDH1',
'ATR_ATRIP',
'SIGLEC9',
'ARHGEF11',
'TLR4',
'CCDC106',
'AIFM1',
'UBB_UBE2Q2',
'KIF2C',
'DAZAP1',
'CACNA1H',
'SLC18A2_TPH1',
'DBNDD1',
'NCOR1',
'CENPB',
'CKS1B',
'KMT5A',
'JPH2',
'SPSB2',
'IL18',
'TNC',
'GDI2',
'APPL1',
'TP53INP2',
'CSTF2',
'EDA2R',
'Q6FHA6',
'PLXNA1',
'PKIB',
'AMHR2',
'SHH',
'FRZB',
'FANCG',
'CCNA1',
'CSF2RB_IL5RA',
'TPP1',
'CCL23',
'CD47_ITGAV_ITGB3',
'RNF169',
'ILF2_ILF3',
'CYSLTR1',
'ADORA3',
'SIL1',
'EPN1',
'EIF2B5',
'SHC4',
'PPP2R2A',
'EFNB2',
'STK38L',
'ADCY3',
'MTOR',
'VPS35',
'GABRA1_GABRB2_GABRG2',
'CALM1',
'EPHA2',
'NMB',
'HLA-DRB1',
'ASB3',
'CLCN5',
'PEAR1',
'TNIP2',
'GP6',
'TPH1',
'TNPO1',
'TNFSF8',
'PITX1',
'IGF1',
'SOX2',
'RPS15',
'UBA6_UBB',
'USP16',
'GAB2',
'COPS2_COPS3_COPS4_COPS5_COPS6_COPS7B_COPS8_GPS1',
'PTPRT',
'CNP',
'ARHGAP6',
'MARCHF10',
'NCR3LG1',
'CHM',
'FAU_RPS10_RPS11_RPS12_RPS13_RPS14_RPS15_RPS15A_RPS16_RPS17_RPS18_RPS19_RPS2_RPS20_RPS21_RPS23_RPS24_RPS25_RPS26_RPS27_RPS27A_RPS27L_RPS28_RPS29_RPS3_RPS3A_RPS4X_RPS4Y1_RPS4Y2_RPS5_RPS6_RPS7_RPS8_RPS9_RPSA',
'TNF_TNFRSF1A',
'BCAN',
'GRIN1_GRIN2C',
'PRKX',
'UBA52_UBE2E1',
'TENT2',
'ABI1_BRK1_CYFIP1_NCKAP1_WASF2',
'FZD5_LRP6',
'ACTL6A_ACTL6B_ARID1A_ARID1B_ARID2_PBRM1_SMARCA2_SMARCA4_SMARCB1_SMARCC1_SMARCC2_SMARCD1_SMARCD2_SMARCD3_SMARCE1',
'RNF20',
'CTDSP2',
'UBLCP1',
'CASP2_CRADD_PIDD1',
'PDCD4',
'NCSTN',
'P03217_UBA52',
'SORBS1',
'ULBP2',
'CD52',
'MSL3',
'MEF2C',
'TRAF5',
'BBIP1_BBS1_BBS2_BBS4_BBS5_BBS7_BBS9_TTC8',
'FGF1',
'CYTH2',
'LYPLA2',
'HNRNPL',
'GALR1',
'CDK5_CDK5R1',
'FTH1',
'PCDHA3',
'MRC2',
'MMP24',
'ATG10',
'CDC14B',
'GPR34',
'ITGA2',
'TCF4',
'UBE2L6',
'CRKL',
'KLF1',
'PRDM2',
'XAF1',
'KLF11',
'LPAR5',
'GABARAPL1',
'RIF1',
'SSH3',
'C2_C4A_C4B_2',
'AGTR1',
'EIF5B',
'FBXW11',
'JAK1_STAT1_STAT3',
'NHERF1',
'COMMD1',
'RNF38',
'IRX2',
'OSBPL11',
'HELB',
'CLK2',
'PTGIR',
'RPN1',
'SIGLEC10',
'CNOT3',
'TNNT2',
'MT-ND1_MT-ND2_MT-ND3_MT-ND4_MT-ND4L_MT-ND5_MT-ND6_NDUFA1_NDUFA10_NDUFA11_NDUFA12_NDUFA13_NDUFA2_NDUFA3_NDUFA5_NDUFA6_NDUFA7_NDUFA8_NDUFA9_NDUFAB1_NDUFB1_NDUFB10_NDUFB11_NDUFB2_NDUFB3_NDUFB4_NDUFB5_NDUFB6_NDUFB7_NDUFB8_NDUFB9_NDUFC1_NDUFC2_NDUFS1_NDUFS2_NDUFS3_NDUFS4_NDUFS5_NDUFS6_NDUFS7_NDUFS8_NDUFV1_NDUFV2_NDUFV3',
'DGKZ',
'DNAH5',
'IL2RG_IL4R',
'ARHGAP18',
'PAX5',
'BMP15',
'FKBP15',
'NDP',
'ERBB2',
'GLRA2',
'SPON1',
'RABGEF1',
'WNT2',
'SGCG',
'ERBIN',
'REXO1',
'BRS3',
'DUSP26',
'MAPK9',
'NCAPD3',
'SMYD5',
'CRLF2_IL7R',
'Q80H93_RPS27A',
'NEURL1',
'SRPRA',
'DSN1',
'RMND5A',
'FXN',
'RNF216',
'BCKDHA',
'ITGA5_ITGB1',
'RASD2',
'MAST3',
'PEX1',
'RHBDL2',
'BNIP3L',
'CPEB3',
'MAP4K2',
'PTCRA',
'THBD',
'ROS1',
'MID1',
'IST1',
'FBXO44',
'DGCR8',
'NFKBIA',
'CHUK_IKBKB_IKBKG',
'COL3A1',
'ADRB3',
'POLR1G',
'DHX58',
'POLL',
'MTCH2',
'RAVER1',
'RNF121',
'CHD2',
'SLC12A4',
'CHKB',
'SLC2A4',
'FBXL12',
'METTL3',
'NXPH3',
'FMR1',
'ADH4',
'UCHL3',
'CHEBI:166824_CHEBI:53000_HLA-DMB_HLA-DRA',
'AICDA',
'GRIN1_GRIN2D',
'GRIN1_GRIN2B',
'RYBP',
'SDC1',
'P03231_UBC',
'RPL12',
'ATP5IF1',
'BMPR1B_BMPR2',
'MSL1',
'MMP8',
'NCOA2',
'CADM3',
'FBXL21P',
'P59634_UBC',
'DPP6',
'TIA1',
'TTBK1',
'TCF20',
'SPIB',
'GADD45B',
'F10_F5',
'MIPOL1',
'PCDHGB7',
'PHPT1',
'TAF1A',
'ANLN',
'MET',
'MBD3',
'CCND2',
'CRY1',
'ATAT1',
'DNA2',
'CARD18',
'FLRT3_TENM4',
'TPT1',
'COL27A1',
'GAD1_SLC6A6',
'FNBP4',
'ELOVL7',
'LRP10',
'BTF3',
'CDC42EP4',
'PLPP6',
'APLP1',
'PPM1B',
'NXF1',
'PRMT8',
'TECTA',
'GLIS2',
'KCNN1',
'BUB1',
'WASF3',
'VTN',
'BRD2',
'NR1I2',
'P03205_UBB',
'EIF1AY',
'PTRH2',
'KIF1B',
'TAF10',
'MT3',
'PPP2R1B',
'FCER2',
'DKK3',
'MAP3K4',
'ACP5',
'M6PR',
'ZNF322',
'CELSR1',
'P4HB',
'CORO1A',
'NR1D1',
'ITGA1',
'ZNF341',
'JUNB',
'WSB1',
'GTF2I',
'CDH24',
'FGF14',
'KNTC1_ZW10_ZWILCH_ZWINT',
'GPR135',
'GRID2',
'ACVR1B_ACVR2B',
'PTK7',
'FLRT3',
'TULP2',
'PDE6A',
'RABGGTB',
'IL7',
'PPP1R3B',
'ACAP2',
'RALA',
'RTKN',
'NNMT',
'SENP1',
'RFX3',
'FCN2',
'HAS1',
'CLIC4',
'KCNIP3',
'PDK3',
'CDON',
'USP13',
'CYP3A5',
'IL11RA',
'OPHN1',
'JKAMP',
'MTTP',
'BIRC6_UBB',
'SLITRK6',
'DDX3X',
'LRRC4B',
'NHERF4',
'ANP32A',
'MARCHF5',
'DNAJC3',
'PSMA3',
'CLSTN1',
'NKX6-1',
'UIMC1',
'MYL12A',
'NELFE',
'ADCY1',
'TRIP11',
'FBXO31',
'TRPC6',
'SDCBP2',
'CFLAR_FAS_RIPK1_TNFRSF10A_TNFRSF1A_TNFSF10_TRADD_TRAF2',
'TOLLIP',
'GTF3C1',
'GAD1_SLC6A1',
'PABPC1',
'ARTN',
'CREB3L2',
'GALNT8',
'PIAS1',
'MAP2K2',
'IL6R',
'CNIH1',
'H2BW2',
'TBXAS1',
'BRSK2',
'CDK13',
'FZD2_LRP5',
'SCN8A',
'DFFA',
'DKK4',
'PAN2',
'NEDD8',
'HMGCR',
'ABL2',
'CREBBP',
'ITGA1_ITGB1',
'C1QA_C1QB_C1QC',
'CX3CR1',
'LMO1',
'PTN',
'PTPRH',
'DYNC1LI2',
'CCN4',
'ARHGAP44',
'CASP14',
'CASP1',
'AP3D1',
'GRK4',
'CKS2',
'C1QA_C1QB_C1QC_C1R_C1S',
'CTSD',
'HSPA9',
'KSR1',
'AGER',
'A9UF07',
'H2BC5',
'PI4K2B',
'PKD2L1',
'TBC1D4',
'CTSG',
'IFNA1',
'TNS2',
'TTL',
'LHB',
'YWHAE',
'CD63',
'PRKAB1',
'TCTN3',
'CCNF',
'TGFB3',
'PIDD1',
'GLUD2',
'ITM2C',
'MYRIP',
'PPEF1',
'SLC39A7',
'PTH2R',
'NABP2',
'WNT3A',
'C1D',
'ADAMTS3',
'MC3R',
'OPTN',
'NKD1',
'TSPAN10',
'AMIGO2',
'UBE2R2',
'CALM2',
'FHL2',
'HMOX1',
'HAPLN1',
'RALGAPA2',
'APH1A_NCSTN_PSEN1_PSENEN',
'PYGO1',
'INCA1',
'PCDHAC2',
'OGG1',
'BAZ2A_SMARCA5',
'SPON2',
'PIK3R4',
'TNFRSF10C',
'LRRTM2',
'DHX9',
'SNAP29',
'GRIN2C',
'FCHO1',
'TFF2',
'SLC67A1',
'TRIM3',
'TARM1',
'PPP1CA_PPP1R12A_PPP1R12B',
'NCK2',
'TBX21',
'DCX',
'SND1',
'ZNF106',
'NOL4L',
'CDC25B',
'DDX23',
'AMHR2_BMPR1A',
'UBC_UBE2A',
'GSTA2',
'CCNO',
'NGEF',
'ITGA2B',
'ZNF622',
'LYN',
'NUSAP1',
'DBN1',
'KLHL15',
'FZD5_LRP5',
'TBC1D1',
'EIF2AK3',
'DUSP3',
'PTPRO',
'SRSF3',
'MEX3A',
'STXBP6',
'SBF1',
'SERBP1',
'RNF207',
'SDCBP',
'GCNT1',
'MAP4',
'AGTRAP',
'MYL6B',
'NRP1_PLXNA4',
'GSK3B',
'C4BPA',
'BTN1A1',
'RALB',
'SYNPO',
'MUC20',
'MAP9',
'XPA',
'WIPF1',
'GTF2H1',
'PRH2',
'TANC2',
'RPS27A_UBE2N',
'CAMK1',
'MICAL1',
'SIAH3',
'BMP6',
'RNF224',
'GABRA2_GABRB1_GABRG2',
'LMNA',
'MAPK8IP1',
'ISX',
'H2AC20',
'CDKN2D',
'CTPS2',
'ATP5F1B',
'PELI1',
'DGKA',
'ZFY',
'XRCC4',
'NPR3',
'SCN4A',
'PSPN',
'MSMB',
'SLC12A3',
'MMP9_TIMP1',
'ATP2C1',
'P03186_UBC',
'PIK3CA_PIK3R1',
'GNA15',
'PDE3A',
'FBXO33',
'CCL7',
'VEGFD',
'CHD3_CHD4_GATAD2A_GATAD2B_HDAC1_HDAC2_MBD2_MTA1_MTA2_MTA3_RBBP4_RBBP7',
'PRKG1',
'SERINC3',
'GAD1_SLC6A11',
'PA2G4',
'TRIM31',
'AMELY',
'LPCAT2',
'NOVA2',
'ROCK2',
'COL5A3',
'TLK2',
'ASAH2B',
'APEX1',
'JAM2',
'PCGF3',
'DLL4',
'ASB2',
'PANX2',
'PICK1',
'TNFRSF13B',
'RFPL4AL1',
'CFAP53',
'ITGA4_ITGB7',
'TAL2',
'HRC',
'GABRA5_GABRB3_GABRG2',
'CLN8',
'AHSG',
'GLS2_SLC1A6',
'SATB2',
'RBM39',
'FNIP2',
'ISL1',
'NOXO1',
'CLDN14',
'SETBP1',
'EVPL',
'FBXO15',
'NAA10',
'ZMIZ1',
'F2_THBD',
'ATG12',
'ACVR2B',
'GOLGA3',
'SMAD3',
'DVL2',
'THPO',
'NSFL1C',
'KMT2D',
'CDH7',
'CCNC',
'RPL5',
'IL12RB2_IL6ST',
'SVIL',
'PKMYT1',
'FGF7',
'UBA52_UBE2M',
'NLGN2',
'P2RY6',
'FMO1',
'CDH15',
'RIT1',
'ERGIC3',
'ALOX15',
'UBC_UBE2D3',
'GADD45GIP1',
'CHRM1',
'ARHGAP19',
'GPR75',
'CPAMD8',
'PAK6',
'EGR2',
'UBA52_UBE2Q2',
'CDKN2A',
'NFATC2',
'CCR7',
'IL21R',
'CUL2',
'TSSK4',
'EPB42',
'ITGAV_ITGB8',
'FAM3C',
'B2M_CHEBI:166824_HLA-B',
'HDAC5',
'ANGPTL1',
'BRCC3',
'PFKFB2',
'NRP1_PLXNA3',
'TMBIM6',
'PPARGC1B',
'JAK1',
'GCHFR',
'ATG16L1',
'ELMO2',
'TXN',
'CLDN1',
'RSPO4',
'MSN',
'ITGB2',
'EXTL1',
'SIAH1',
'CABYR',
'PAK3',
'TM9SF4',
'ACTA1',
'COPS6',
'C1QL2',
'FBXL6',
'SETDB2',
'CDC45',
'LEPR',
'KCNJ1',
'CALCRL_RAMP2',
'ARHGEF18',
'PNOC',
'TRIM47',
'SSTR3',
'PRG4',
'CENPS',
'CDK5',
'COL18A1',
'EGFR_FZD9_LRP5',
'GLRA1',
'WWP2',
'CD79A_CD79B_P0DOX6_P0DOX7',
'TSSC4',
'DYRK1A',
'ACTL6A_ARID1A_ARID1B_ARID2_SMARCA4_SMARCB1_SMARCC1_SMARCC2_SMARCD1_SMARCE1',
'PLAGL1',
'RHOB',
'GAB1',
'GABRA1_GABRB3_GABRG2',
'SMAP1',
'MUC2',
'DTD1',
'RASSF1',
'ADAM12',
'PDCD6IP',
'CXCL6',
'DTX4',
'PLD1',
'KLC2',
'SIRT2',
'SLBP',
'NR2F6',
'TPX2',
'GOLGA4',
'RUFY3',
'NCF4',
'PCGF5',
'CRTC1',
'PTGR1',
'MSX1',
'ADGRL3',
'ACKR2',
'IL4',
'ID3',
'CDCA3',
'SLC52A2',
'RAD51C',
'ZC3HAV1',
'TRAF6',
'DAXX',
'LRRK1',
'BTG2',
'MAP1A',
'DISP2',
'WT1',
'NBR1',
'CD79A',
'DCC',
'CPM',
'IZUMO1R',
'CHEBI:166824_CHEBI:53000_HLA-DRA_HLA-DRB3',
'YWHAZ',
'DHFR',
'ITGB7_VCAM1',
'DDX19B',
'STIL',
'PROKR2',
'PIK3C2G',
'AGR2',
'DTX3',
'ADRM1',
'NR0B2',
'WNT9B',
'NREP',
'GABRB2',
'TFIP11',
'CDH3',
'MAGI1',
'GEMIN4',
'CASP4',
'CCND3_CDK11B',
'ATP5F1C',
'ZFYVE26',
'GABBR1_GABBR2',
'FGF20',
'ENAM',
'PRPS1',
'DEFB104A',
'MYO15A',
'HNRNPK',
'PC',
'ZW10',
'CCL3L1',
'PXN',
'NSMCE2',
'GFRA4_RET',
'LIME1',
'SFRP5',
'ETV3',
'MADD',
'MAX',
'CREBBP_EP300_KAT2B',
'KIR2DL3',
'GTF2F1',
'RSPO3',
'STK40',
'LPAR1',
'PIP5K1A',
'PCDHGB5',
'RAPGEF6',
'ASH2L_DPY30_KDM6A_KMT2B_KMT2C_NCOA6_PAXIP1_RBBP5_WDR5',
'FLNB',
'RITA1',
'YY1',
'DLAT_DLD_PDHA1_PDHA2_PDHB_PDHX',
'EIF2AK2',
'TRIM64',
'TECTB',
'UBC_UBE2D1',
'ARHGAP17',
'PCDHA7',
'SEMA4D',
'GPAA1',
'BAD',
'STRN4',
'NFIX_PRKCQ',
'FCGR3A',
'PDE1B',
'ITGAV',
'KIFC1',
'PDCD5',
'CHD7_NLK_SETDB1',
'PYGL',
...},
'edge_ids': {'edge_86118',
'edge_35849',
'edge_29343',
'edge_36861',
'edge_9632',
'edge_26146',
'edge_70778',
'edge_61871',
'edge_82883',
'edge_44829',
'edge_79572',
'edge_55223',
'edge_62146',
'edge_63625',
'edge_21689',
'edge_27421',
'edge_71298',
'edge_77272',
'edge_85351',
'edge_15282',
'edge_24499',
'edge_31985',
'edge_31997',
'edge_54441',
'edge_6834',
'edge_37481',
'edge_63355',
'edge_27554',
'edge_23159',
'edge_22800',
'edge_69669',
'edge_21574',
'edge_40065',
'edge_46384',
'edge_1057',
'edge_62947',
'edge_24570',
'edge_79028',
'edge_82473',
'edge_84873',
'edge_53807',
'edge_13507',
'edge_18891',
'edge_22073',
'edge_35665',
'edge_53960',
'edge_60330',
'edge_69487',
'edge_4090',
'edge_21462',
'edge_62615',
'edge_61161',
'edge_2888',
'edge_23522',
'edge_39292',
'edge_3773',
'edge_70916',
'edge_62861',
'edge_74844',
'edge_64441',
'edge_62149',
'edge_8947',
'edge_37370',
'edge_72819',
'edge_9960',
'edge_72892',
'edge_45885',
'edge_75136',
'edge_16300',
'edge_62303',
'edge_55729',
'edge_7493',
'edge_62799',
'edge_11727',
'edge_52485',
'edge_49890',
'edge_25223',
'edge_9080',
'edge_83275',
'edge_52127',
'edge_52231',
'edge_35015',
'edge_25198',
'edge_19187',
'edge_82583',
'edge_53098',
'edge_3617',
'edge_52294',
'edge_29059',
'edge_34961',
'edge_8247',
'edge_44441',
'edge_41475',
'edge_73497',
'edge_63341',
'edge_13416',
'edge_516',
'edge_51383',
'edge_36803',
'edge_69618',
'edge_19829',
'edge_11128',
'edge_20274',
'edge_38672',
'edge_53535',
'edge_23454',
'edge_56913',
'edge_75769',
'edge_26485',
'edge_37902',
'edge_67984',
'edge_65791',
'edge_37025',
'edge_54671',
'edge_22835',
'edge_77759',
'edge_45487',
'edge_84337',
'edge_42898',
'edge_41697',
'edge_32752',
'edge_64028',
'edge_78097',
'edge_41752',
'edge_72003',
'edge_19826',
'edge_58092',
'edge_25210',
'edge_28095',
'edge_38913',
'edge_36613',
'edge_78189',
'edge_80899',
'edge_83677',
'edge_20387',
'edge_79408',
'edge_19553',
'edge_82208',
'edge_39389',
'edge_501',
'edge_12166',
'edge_20927',
'edge_61449',
'edge_9979',
'edge_51715',
'edge_46582',
'edge_62477',
'edge_37021',
'edge_2819',
'edge_55308',
'edge_32614',
'edge_47421',
'edge_64162',
'edge_51044',
'edge_15674',
'edge_48687',
'edge_23354',
'edge_48792',
'edge_16350',
'edge_82286',
'edge_72320',
'edge_59928',
'edge_25631',
'edge_17639',
'edge_31350',
'edge_2066',
'edge_25269',
'edge_44718',
'edge_64008',
'edge_74353',
'edge_48998',
'edge_41130',
'edge_25121',
'edge_79760',
'edge_34821',
'edge_1250',
'edge_45506',
'edge_28906',
'edge_36162',
'edge_48975',
'edge_35587',
'edge_50301',
'edge_68146',
'edge_67832',
'edge_30649',
'edge_30967',
'edge_71370',
'edge_37412',
'edge_79488',
'edge_17500',
'edge_24074',
'edge_698',
'edge_9119',
'edge_77596',
'edge_84730',
'edge_67862',
'edge_53523',
'edge_62514',
'edge_37827',
'edge_79135',
'edge_10302',
'edge_76221',
'edge_52562',
'edge_37785',
'edge_3214',
'edge_25690',
'edge_58167',
'edge_86096',
'edge_59551',
'edge_14598',
'edge_14332',
'edge_29444',
'edge_63316',
'edge_64320',
'edge_43923',
'edge_63781',
'edge_72057',
'edge_54310',
'edge_25349',
'edge_39787',
'edge_12250',
'edge_1541',
'edge_45658',
'edge_24462',
'edge_34495',
'edge_67271',
'edge_122',
'edge_14161',
'edge_27674',
'edge_76550',
'edge_19754',
'edge_39109',
'edge_42361',
'edge_43562',
'edge_55232',
'edge_70788',
'edge_56613',
'edge_36324',
'edge_74129',
'edge_48101',
'edge_39695',
'edge_20073',
'edge_10977',
'edge_25556',
'edge_23428',
'edge_51916',
'edge_38668',
'edge_8284',
'edge_61017',
'edge_75818',
'edge_19552',
'edge_54048',
'edge_24188',
'edge_47690',
'edge_32850',
'edge_77843',
'edge_49827',
'edge_71914',
'edge_22781',
'edge_26118',
'edge_43297',
'edge_77428',
'edge_76656',
'edge_30721',
'edge_22242',
'edge_66670',
'edge_6207',
'edge_58880',
'edge_86359',
'edge_44336',
'edge_58750',
'edge_66336',
'edge_31909',
'edge_36958',
'edge_62296',
'edge_35261',
'edge_10283',
'edge_46937',
'edge_44766',
'edge_51213',
'edge_50834',
'edge_45484',
'edge_54677',
'edge_48860',
'edge_50003',
'edge_57552',
'edge_60051',
'edge_67970',
'edge_13405',
'edge_46093',
'edge_75815',
'edge_28577',
'edge_62977',
'edge_23497',
'edge_40007',
'edge_33360',
'edge_75460',
'edge_81489',
'edge_14889',
'edge_35489',
'edge_5187',
'edge_76681',
'edge_24926',
'edge_82456',
'edge_85664',
'edge_79710',
'edge_75292',
'edge_38335',
'edge_72878',
'edge_73865',
'edge_38908',
'edge_61601',
'edge_1593',
'edge_16678',
'edge_5412',
'edge_47466',
'edge_15126',
'edge_59302',
'edge_15161',
'edge_40526',
'edge_21839',
'edge_42223',
'edge_10122',
'edge_54975',
'edge_29460',
'edge_81813',
'edge_32379',
'edge_26956',
'edge_4969',
'edge_58360',
'edge_15421',
'edge_20782',
'edge_40174',
'edge_43084',
'edge_35129',
'edge_57680',
'edge_69641',
'edge_73659',
'edge_37019',
'edge_67188',
'edge_42287',
'edge_38101',
'edge_34976',
'edge_60883',
'edge_46232',
'edge_51221',
'edge_38805',
'edge_32061',
'edge_5878',
'edge_59895',
'edge_33836',
'edge_35236',
'edge_7802',
'edge_86336',
'edge_36488',
'edge_49222',
'edge_24625',
'edge_5971',
'edge_41265',
'edge_7113',
'edge_57700',
'edge_24293',
'edge_14945',
'edge_62084',
'edge_65132',
'edge_66391',
'edge_71527',
'edge_86414',
'edge_23288',
'edge_62707',
'edge_84961',
'edge_77281',
'edge_14515',
'edge_61497',
'edge_7776',
'edge_73714',
'edge_45582',
'edge_42281',
'edge_75635',
'edge_60513',
'edge_61895',
'edge_75802',
'edge_28792',
'edge_77345',
'edge_11865',
'edge_8213',
'edge_76968',
'edge_27538',
'edge_18650',
'edge_83922',
'edge_7873',
'edge_4498',
'edge_74629',
'edge_82617',
'edge_69729',
'edge_47854',
'edge_75039',
'edge_25531',
'edge_68148',
'edge_85928',
'edge_35805',
'edge_62563',
'edge_2586',
'edge_73663',
'edge_58547',
'edge_46776',
'edge_54609',
'edge_14655',
'edge_10538',
'edge_26772',
'edge_77194',
'edge_394',
'edge_39819',
'edge_56250',
'edge_3665',
'edge_61876',
'edge_71385',
'edge_57967',
'edge_71056',
'edge_7971',
'edge_24521',
'edge_37389',
'edge_11288',
'edge_73437',
'edge_44918',
'edge_43665',
'edge_50951',
'edge_63862',
'edge_51658',
'edge_52868',
'edge_46024',
'edge_41132',
'edge_76637',
'edge_34291',
'edge_59614',
'edge_20427',
'edge_58878',
'edge_48650',
'edge_65495',
'edge_48716',
'edge_59725',
'edge_81693',
'edge_62583',
'edge_21724',
'edge_81190',
'edge_14937',
'edge_35067',
'edge_71642',
'edge_54560',
'edge_55798',
'edge_85142',
'edge_61780',
'edge_6263',
'edge_11938',
'edge_73543',
'edge_20075',
'edge_3157',
'edge_6661',
'edge_57788',
'edge_33953',
'edge_76620',
'edge_46352',
'edge_74346',
'edge_82718',
'edge_49439',
'edge_17234',
'edge_30848',
'edge_48370',
'edge_11391',
'edge_41652',
'edge_37948',
'edge_30875',
'edge_78421',
'edge_11293',
'edge_28888',
'edge_3296',
'edge_18864',
'edge_25216',
'edge_34028',
'edge_14507',
'edge_83146',
'edge_33229',
'edge_82392',
'edge_39134',
'edge_86348',
'edge_8540',
'edge_18342',
'edge_28800',
'edge_35859',
'edge_39910',
'edge_27103',
'edge_84056',
'edge_11379',
'edge_47479',
'edge_51402',
'edge_13581',
'edge_18841',
'edge_42221',
'edge_4278',
'edge_43385',
'edge_56189',
'edge_45446',
'edge_5234',
'edge_68992',
'edge_71478',
'edge_25945',
'edge_7507',
'edge_41508',
'edge_59916',
'edge_31604',
'edge_24632',
'edge_42569',
'edge_15698',
'edge_34228',
'edge_38709',
'edge_32323',
'edge_27581',
'edge_47899',
'edge_81344',
'edge_86480',
'edge_2444',
'edge_56282',
'edge_66314',
'edge_78809',
'edge_52072',
'edge_16944',
'edge_48412',
'edge_75906',
'edge_5988',
'edge_72712',
'edge_63347',
'edge_70485',
'edge_59784',
'edge_67195',
'edge_78200',
'edge_53822',
'edge_21109',
'edge_43885',
'edge_43367',
'edge_74796',
'edge_38875',
'edge_52079',
'edge_73647',
'edge_18715',
'edge_23123',
'edge_51852',
'edge_50035',
'edge_16238',
'edge_27673',
'edge_61558',
'edge_32131',
'edge_53190',
'edge_1703',
'edge_36358',
'edge_63961',
'edge_36412',
'edge_65897',
'edge_16473',
'edge_46862',
'edge_17123',
'edge_50194',
'edge_80295',
'edge_32545',
'edge_40864',
'edge_85217',
'edge_37249',
'edge_82897',
'edge_78909',
'edge_5173',
'edge_65859',
'edge_11923',
'edge_50198',
'edge_33351',
'edge_1370',
'edge_50218',
'edge_71560',
'edge_65145',
'edge_72705',
'edge_3600',
'edge_60517',
'edge_50722',
'edge_10642',
'edge_30990',
'edge_51767',
'edge_73482',
'edge_61743',
'edge_46751',
'edge_73212',
'edge_83988',
'edge_84839',
'edge_23551',
'edge_33863',
'edge_80794',
'edge_15963',
'edge_37604',
'edge_69628',
'edge_67051',
'edge_14466',
'edge_31068',
'edge_70981',
'edge_72491',
'edge_17069',
'edge_47985',
'edge_40010',
'edge_68644',
'edge_61267',
'edge_13136',
'edge_67733',
'edge_9683',
'edge_19496',
'edge_32820',
'edge_44902',
'edge_77823',
'edge_52772',
'edge_79922',
'edge_31102',
'edge_76339',
'edge_18503',
'edge_29328',
'edge_37966',
'edge_67667',
'edge_75850',
'edge_14657',
'edge_51959',
'edge_61860',
'edge_30089',
'edge_24936',
'edge_40728',
'edge_83466',
'edge_80672',
'edge_2651',
'edge_6822',
'edge_57521',
'edge_11694',
'edge_84453',
'edge_54898',
'edge_43275',
'edge_79427',
'edge_68073',
'edge_39319',
'edge_4870',
'edge_6210',
'edge_25206',
'edge_6620',
'edge_14309',
'edge_67824',
'edge_42739',
'edge_74920',
'edge_80010',
'edge_7476',
'edge_43320',
'edge_33189',
'edge_62949',
'edge_49635',
'edge_46475',
'edge_42235',
'edge_53070',
'edge_86262',
'edge_60027',
'edge_66872',
'edge_27462',
'edge_7439',
'edge_24607',
'edge_39411',
'edge_43026',
'edge_4023',
'edge_22267',
'edge_39558',
'edge_37652',
'edge_23113',
'edge_61515',
'edge_43403',
'edge_18630',
'edge_84101',
'edge_62891',
'edge_21924',
'edge_51114',
'edge_42154',
'edge_47383',
'edge_75357',
'edge_82666',
'edge_61013',
'edge_4078',
'edge_10307',
'edge_53925',
'edge_71717',
'edge_45095',
'edge_24843',
'edge_27469',
'edge_34225',
'edge_83798',
'edge_29599',
'edge_58365',
'edge_26882',
'edge_33064',
'edge_14894',
'edge_17785',
'edge_20117',
'edge_38241',
'edge_53438',
'edge_17808',
'edge_48011',
'edge_8393',
'edge_61275',
'edge_80536',
'edge_20797',
'edge_67724',
'edge_11411',
'edge_19531',
'edge_70902',
'edge_14403',
'edge_71330',
'edge_85326',
'edge_59169',
'edge_34912',
'edge_38907',
'edge_70294',
'edge_2151',
'edge_79729',
'edge_84480',
'edge_11801',
'edge_86326',
'edge_21440',
'edge_10074',
'edge_72013',
'edge_16684',
'edge_65687',
'edge_31658',
'edge_33046',
'edge_80644',
'edge_36602',
'edge_75495',
'edge_57231',
'edge_52482',
'edge_72684',
'edge_74515',
'edge_78282',
'edge_13930',
'edge_21170',
'edge_27130',
'edge_45081',
'edge_3224',
'edge_56190',
'edge_33367',
'edge_81415',
'edge_83288',
'edge_83868',
'edge_47682',
'edge_66072',
'edge_4019',
'edge_20509',
'edge_67160',
'edge_7745',
'edge_82977',
'edge_3013',
'edge_62829',
'edge_43036',
'edge_44232',
'edge_76749',
'edge_59948',
'edge_75889',
'edge_85388',
'edge_54514',
'edge_8466',
'edge_58988',
'edge_85424',
'edge_19484',
'edge_58188',
'edge_74677',
'edge_38276',
'edge_82858',
'edge_27530',
'edge_60926',
'edge_76469',
'edge_47202',
'edge_55466',
'edge_46784',
'edge_2099',
'edge_68169',
'edge_60549',
'edge_41399',
'edge_12845',
'edge_73169',
'edge_51308',
'edge_35371',
'edge_18208',
'edge_24522',
'edge_2103',
'edge_70311',
'edge_72845',
'edge_25728',
'edge_57129',
'edge_60864',
'edge_20567',
'edge_27613',
'edge_11515',
'edge_7167',
'edge_45778',
'edge_67782',
'edge_29765',
'edge_30224',
'edge_55016',
'edge_29580',
'edge_9611',
'edge_83950',
'edge_78311',
'edge_59266',
'edge_35066',
'edge_84678',
'edge_56096',
'edge_51112',
'edge_77835',
'edge_44819',
'edge_50371',
'edge_29728',
'edge_2800',
'edge_68308',
'edge_40701',
'edge_62165',
'edge_6279',
'edge_84835',
'edge_49818',
'edge_8312',
'edge_73313',
'edge_50064',
'edge_42571',
'edge_9329',
'edge_1828',
'edge_22738',
'edge_60530',
'edge_34860',
'edge_49288',
'edge_60207',
'edge_85020',
'edge_26632',
'edge_31982',
'edge_44780',
'edge_32889',
'edge_61359',
'edge_54103',
'edge_59860',
'edge_74949',
'edge_29671',
'edge_46462',
'edge_52426',
'edge_26359',
'edge_30299',
'edge_41280',
'edge_3340',
'edge_43770',
'edge_29655',
'edge_14528',
'edge_72660',
'edge_4096',
'edge_18686',
'edge_52410',
'edge_69094',
'edge_68901',
'edge_61114',
'edge_74322',
'edge_33835',
'edge_15767',
'edge_21895',
'edge_31507',
'edge_67557',
'edge_23254',
'edge_66187',
'edge_38745',
'edge_32094',
'edge_51679',
'edge_85457',
'edge_63207',
'edge_77379',
'edge_5418',
'edge_78949',
'edge_82845',
'edge_16945',
'edge_3834',
'edge_17504',
'edge_62572',
'edge_72370',
'edge_54578',
'edge_54003',
'edge_13404',
'edge_82721',
'edge_1275',
'edge_24318',
'edge_30014',
'edge_79153',
'edge_9675',
'edge_60495',
'edge_54420',
'edge_9111',
'edge_60559',
'edge_82190',
'edge_17571',
'edge_64988',
'edge_85051',
'edge_40812',
'edge_17114',
'edge_30207',
'edge_21929',
'edge_60776',
'edge_85640',
'edge_86050',
'edge_44713',
'edge_57571',
'edge_78123',
'edge_15184',
'edge_9748',
'edge_14428',
'edge_53132',
'edge_21803',
'edge_10898',
'edge_30839',
'edge_53016',
'edge_68603',
'edge_56603',
'edge_35369',
'edge_39843',
'edge_73855',
'edge_65197',
'edge_22924',
'edge_44781',
'edge_37037',
'edge_10426',
'edge_39022',
'edge_44300',
'edge_65337',
'edge_35357',
'edge_28241',
'edge_1386',
'edge_18964',
'edge_80738',
'edge_42527',
'edge_53663',
'edge_31659',
'edge_56660',
'edge_78146',
'edge_265',
'edge_43303',
'edge_44452',
'edge_74108',
'edge_15692',
'edge_28617',
'edge_13815',
'edge_56734',
'edge_84748',
'edge_75260',
'edge_68145',
'edge_5778',
'edge_42842',
'edge_4325',
'edge_47616',
'edge_36285',
'edge_60862',
'edge_54205',
'edge_17100',
'edge_43657',
'edge_54497',
'edge_75486',
'edge_27222',
'edge_53351',
'edge_49952',
'edge_61679',
'edge_32970',
'edge_70937',
'edge_75683',
'edge_62702',
'edge_10339',
'edge_66305',
'edge_70766',
'edge_25737',
'edge_74800',
'edge_80948',
'edge_72530',
'edge_69866',
'edge_4894',
'edge_19801',
'edge_10259',
'edge_46186',
'edge_18124',
'edge_20697',
'edge_61791',
'edge_43775',
'edge_68144',
'edge_59486',
'edge_6802',
'edge_25843',
'edge_40562',
'edge_10239',
'edge_79702',
'edge_83153',
'edge_83303',
'edge_33147',
'edge_47677',
'edge_80878',
'edge_74576',
'edge_70612',
'edge_61340',
...},
'slice_ids': {'carnival_inferred', 'default', 'omnipath_pkn'}}
17. Sanity checks¶
Consensus hubs can arise because a node is repeatedly inferred across patients, or because the prior network gives that node many opportunities to appear. We test the second possibility by comparing consensus frequency to raw PKN degree on (i) the primary cohort alone and (ii) the union of all sensitivity cohorts (more nodes, better power), plus a random patient-set null control.
At cohort sizes typical of UC1 the per-cohort Spearman test is
power-limited; a non-significant p is consistent with — but does not
prove — the absence of a degree confound. The pooled and null-model
rows below carry more weight.
consensus_with_degree = primary_consensus_selected.merge(
pkn_degree.rename_axis('vertex_id').reset_index(),
on='vertex_id',
how='left',
).fillna({'pkn_degree': 0})
if len(consensus_with_degree) >= 2:
degree_rho, degree_p = spearmanr(
consensus_with_degree['patient_frequency'],
consensus_with_degree['pkn_degree'],
)
else:
degree_rho, degree_p = np.nan, np.nan
# Pooled across all sensitivity cohorts — deduplicates consensus nodes, takes
# the max patient_frequency per vertex. Gives a slightly larger N for Spearman.
pooled_rows = []
for cohort_label in SENSITIVITY_COHORT_LABELS:
node_df = consensus_outputs[cohort_label]['node_df']
sel = node_df[node_df['selected_for_consensus']]
if not sel.empty:
pooled_rows.append(sel[['vertex_id', 'patient_frequency']])
if pooled_rows:
pooled = (
pd.concat(pooled_rows, ignore_index=True)
.groupby('vertex_id', as_index=False)['patient_frequency']
.max()
.merge(
pkn_degree.rename_axis('vertex_id').reset_index(),
on='vertex_id',
how='left',
)
.fillna({'pkn_degree': 0})
)
if len(pooled) >= 2:
pooled_rho, pooled_p = spearmanr(pooled['patient_frequency'], pooled['pkn_degree'])
else:
pooled_rho, pooled_p = np.nan, np.nan
pooled_n = int(len(pooled))
else:
pooled_rho, pooled_p, pooled_n = np.nan, np.nan, 0
bias_summary = pd.DataFrame(
{
'metric': [
'primary_n',
'primary_spearman_rho',
'primary_spearman_pvalue',
'pooled_n',
'pooled_spearman_rho',
'pooled_spearman_pvalue',
],
'value': [
int(len(consensus_with_degree)),
degree_rho,
degree_p,
pooled_n,
pooled_rho,
pooled_p,
],
}
)
bias_summary.to_csv(TABLE_DIR / 'degree_bias_summary.csv', index=False)
consensus_with_degree.to_csv(TABLE_DIR / 'consensus_nodes_with_degree.csv', index=False)
solved_primary_patients = [
p
for p in cohort_definitions[PRIMARY_COHORT_LABEL]
if p in primary_patient_networks and primary_patient_networks[p]['solver_ok']
]
solved_pool = [p for p, rec in primary_patient_networks.items() if rec['solver_ok']]
null_rows = []
rng = np.random.default_rng(SEED)
if solved_primary_patients and len(solved_pool) >= len(solved_primary_patients):
observed_size = int(primary_consensus_selected.shape[0])
observed_mean_degree = (
float(consensus_with_degree['pkn_degree'].mean())
if not consensus_with_degree.empty
else np.nan
)
for repeat in range(NULL_REPEATS):
sampled = rng.choice(solved_pool, size=len(solved_primary_patients), replace=False)
sample_results = {p: primary_patient_networks[p] for p in sampled}
sample_nodes, _, sample_summary = build_consensus_layer(sample_results, CONSENSUS_MIN_FREQ)
sample_selected = sample_nodes[sample_nodes['selected_for_consensus']].copy()
sample_deg = sample_selected.merge(
pkn_degree.rename_axis('vertex_id').reset_index(),
on='vertex_id',
how='left',
).fillna({'pkn_degree': 0})
null_rows.append(
{
'repeat': repeat,
'consensus_size': int(sample_selected.shape[0]),
'mean_pkn_degree': float(sample_deg['pkn_degree'].mean())
if not sample_deg.empty
else np.nan,
}
)
null_df = pd.DataFrame(null_rows)
null_df.to_csv(TABLE_DIR / 'null_patient_set_summary.csv', index=False)
null_summary = pd.DataFrame(
{
'metric': [
'observed_consensus_size',
'null_mean_consensus_size',
'observed_mean_degree',
'null_mean_degree',
],
'value': [
observed_size,
float(null_df['consensus_size'].mean()),
observed_mean_degree,
float(null_df['mean_pkn_degree'].mean()),
],
}
)
else:
null_df = pd.DataFrame()
null_summary = pd.DataFrame(
{
'metric': ['null_model_note'],
'value': ['insufficient solved patients for random patient-set null model'],
}
)
null_summary.to_csv(TABLE_DIR / 'null_summary.csv', index=False)
print('Degree-bias check (primary vs pooled cohorts):')
display(bias_summary)
print('\nRandom patient-set null model:')
null_summary
Degree-bias check (primary vs pooled cohorts):
| metric | value | |
|---|---|---|
| 0 | primary_n | 10.000000 |
| 1 | primary_spearman_rho | 0.424467 |
| 2 | primary_spearman_pvalue | 0.221463 |
| 3 | pooled_n | 12.000000 |
| 4 | pooled_spearman_rho | 0.426824 |
| 5 | pooled_spearman_pvalue | 0.166424 |
Random patient-set null model:
| metric | value | |
|---|---|---|
| 0 | observed_consensus_size | 10.000000 |
| 1 | null_mean_consensus_size | 10.810000 |
| 2 | observed_mean_degree | 344.800000 |
| 3 | null_mean_degree | 314.103517 |
17.1. Optional annotation-based pathway screen¶
When the imported vertex annotations include a pathway-like column we compute a Fisher's-exact over-representation screen. Reported as a lightweight sanity check — not a formal pathway-discovery pipeline.
vertex_attr_df = G.vertex_attributes.to_pandas()
pathway_cols = [col for col in vertex_attr_df.columns if 'pathway' in col.lower()]
annotation_enrichment = pd.DataFrame()
def split_annotation_terms(value) -> list[str]:
if value is None or (isinstance(value, float) and np.isnan(value)):
return []
text = str(value)
for sep in ['|', ';', ',']:
text = text.replace(sep, ';')
return [
term.strip() for term in text.split(';') if term.strip() and term.strip().lower() != 'nan'
]
if pathway_cols and not primary_consensus_selected.empty:
col = pathway_cols[0]
background = vertex_attr_df[['vertex_id', col]].dropna().copy()
selected_ids = set(primary_consensus_selected['vertex_id'])
term_to_background = Counter()
term_to_selected = Counter()
background_ids_with_term = defaultdict(set)
selected_ids_with_term = defaultdict(set)
# itertuples(name=None) -> plain tuples, so column names with ':'
# don't get mangled by namedtuple attribute mapping.
for vid, raw_val in background.itertuples(index=False, name=None):
vid = str(vid)
terms = split_annotation_terms(raw_val)
for term in set(terms):
background_ids_with_term[term].add(vid)
term_to_background[term] += 1
if vid in selected_ids:
selected_ids_with_term[term].add(vid)
term_to_selected[term] += 1
rows = []
universe = set(background['vertex_id'].astype(str))
selected_universe = universe & selected_ids
for term, bg_count in term_to_background.items():
sel_count = len(selected_ids_with_term[term])
if sel_count == 0:
continue
a = sel_count
b = len(selected_universe) - a
c = len(background_ids_with_term[term] - selected_universe)
d = len(universe - selected_universe) - c
_, pvalue = fisher_exact([[a, b], [c, d]], alternative='greater')
rows.append(
{
'annotation_column': col,
'term': term,
'selected_count': a,
'background_count': bg_count,
'pvalue': pvalue,
}
)
annotation_enrichment = pd.DataFrame(rows).sort_values(['pvalue', 'selected_count']).head(20)
annotation_enrichment.to_csv(TABLE_DIR / 'annotation_pathway_screen.csv', index=False)
else:
annotation_enrichment = pd.DataFrame(
{
'note': [
'No pathway-like vertex annotation column was available for a lightweight screen.'
]
}
)
annotation_enrichment.head(20)
| annotation_column | term | selected_count | background_count | pvalue | |
|---|---|---|---|---|---|
| 3 | SignaLink_pathway:pathway | T-cell receptor | 4 | 122 | 0.001936 |
| 7 | SignaLink_pathway:pathway | TGF | 4 | 123 | 0.001999 |
| 6 | SignaLink_pathway:pathway | Nuclear hormone receptor | 2 | 61 | 0.045494 |
| 4 | SignaLink_pathway:pathway | WNT | 2 | 105 | 0.121480 |
| 0 | SignaLink_pathway:pathway | Receptor tyrosine kinase | 4 | 378 | 0.133214 |
| 8 | SignaLink_pathway:pathway | Toll-like receptor | 1 | 25 | 0.141320 |
| 2 | SignaLink_pathway:pathway | Innate immune pathways | 1 | 36 | 0.198200 |
| 5 | SignaLink_pathway:pathway | Hedgehog | 1 | 41 | 0.223037 |
| 1 | SignaLink_pathway:pathway | JAK/STAT | 2 | 152 | 0.226073 |
18. Cohort sensitivity¶
Comparison of consensus summaries between the top-10% and top-20% mutation-burden cohorts.
cohort_sensitivity = []
for cohort_label, payload in consensus_outputs.items():
node_df = payload['node_df']
selected = node_df[node_df['selected_for_consensus']].copy()
cohort_sensitivity.append(
{
'cohort_label': cohort_label,
'n_patients': payload['summary']['n_patients'],
'n_consensus_nodes': payload['summary']['n_consensus_nodes'],
'median_node_frequency': float(selected['patient_frequency'].median())
if not selected.empty
else np.nan,
'mean_abs_signal': float(selected['mean_signal'].abs().mean())
if not selected.empty
else np.nan,
}
)
cohort_sensitivity_df = pd.DataFrame(cohort_sensitivity)
cohort_sensitivity_df.to_csv(TABLE_DIR / 'cohort_sensitivity.csv', index=False)
cohort_sensitivity_df
| cohort_label | n_patients | n_consensus_nodes | median_node_frequency | mean_abs_signal | |
|---|---|---|---|---|---|
| 0 | top10 | 11 | 10 | 0.818182 | 0.923636 |
| 1 | top20 | 21 | 11 | 0.761905 | 0.823180 |
19. Multilayer slicing and consensus visualization¶
G.layers.subgraph_from_layer_tuple returns a real AnnNet subgraph for
any (aspect, layer) coordinate. Here we slice out the consensus layer
and render the top-N nodes.
primary_layer_label = f'consensus_{PRIMARY_COHORT_LABEL}'
G_consensus = G.layers.subgraph_from_layer_tuple((primary_layer_label,))
print(
{
'consensus_layer': primary_layer_label,
'vertices': G_consensus.global_count('vertices'),
'edges': G_consensus.global_count('edges'),
}
)
selected_nodes = primary_consensus_selected.sort_values(
['patient_frequency', 'active_count'], ascending=[False, False]
)
plot_nodes = selected_nodes['vertex_id'].head(TOP_N_PLOT).tolist()
# Extract from G_consensus (a flat single-layer subgraph) — keeps the
# extracted plot subgraph free of multilayer placeholder coordinates.
G_plot = G_consensus.ops.extract_subgraph(vertices=set(plot_nodes))
fig, ax = plt.subplots(figsize=(12, 12))
try:
to_matplotlib(
G_plot,
ax=ax,
show_vertex_labels=True,
node_size=600,
)
ax.set_title(
f'Corrected consensus subgraph ({PRIMARY_COHORT_LABEL}, top {len(plot_nodes)} nodes)'
)
plt.tight_layout()
fig.savefig(
FIG_DIR / f'consensus_plot_{PRIMARY_COHORT_LABEL}.png', dpi=200, bbox_inches='tight'
)
plt.show()
except Exception as exc:
plt.close(fig)
print(f'Plot backend issue: {type(exc).__name__}: {exc}')
{'consensus_layer': 'consensus_top10', 'vertices': 10, 'edges': 3}
20. Backend swap — igraph PageRank, written back as a layer attribute¶
The G.ig, G.gt, G.nx accessors materialize the graph as the
foreign library's native object on demand, cached across calls. For
algorithms with a fast C implementation, this is the path to take.
Results land back in AnnNet as vertex-layer attributes via
set_vertex_layer_attrs.
import time as _time
t0 = _time.perf_counter()
ig_sub = G_consensus.ig.backend()
t_export = _time.perf_counter() - t0
t0 = _time.perf_counter()
pageranks = ig_sub.pagerank()
t_pr = _time.perf_counter() - t0
def _bare(n):
"""Unwrap (vid, layer_coord) supra-node names back to bare vid."""
if isinstance(n, tuple) and len(n) == 2 and isinstance(n[1], tuple):
return n[0]
return n
names = ig_sub.vs['name']
pr_map = {_bare(names[i]): float(pageranks[i]) for i in range(len(names))}
consensus_aa = (f'consensus_{PRIMARY_COHORT_LABEL}',)
for vid, p in pr_map.items():
G.layers.set_vertex_layer_attrs(vid, consensus_aa, pagerank=p)
print(f'igraph backend export: {t_export * 1000:6.1f} ms')
print(f'igraph pagerank : {t_pr * 1000:6.1f} ms')
print('Top consensus nodes by PageRank:')
for vid, p in sorted(pr_map.items(), key=lambda kv: -kv[1])[:8]:
print(f' {vid:>10} {p:.4f}')
G.history.snapshot('after_backend_swap')
igraph backend export: 65.9 ms
igraph pagerank : 1.3 ms
Top consensus nodes by PageRank:
CEBPA 0.1377
MAPK3 0.0887
E2F1 0.0887
GSK3B 0.0623
MAPK1 0.0623
CDK1 0.0623
CDK2 0.0623
SPI1 0.0623
/mnt/c/Users/pc/desktop/annnet-remote/annnet/core/backend_accessors/_base.py:156: RuntimeWarning: AnnNet-igraph conversion is lossy: multiple slices flattened into single igraph graph. entry = dict(build())
{'label': 'after_backend_swap',
'version': 155,
'vertex_ids': {'IGFL1',
'PTGFRN',
'CASQ2',
'UBE2S',
'NOTCH4',
'BAG2',
'RIOK2',
'TRIM77',
'PRNP',
'CLIC5',
'KLRD1',
'PPFIA3',
'MAZ',
'GRPEL1',
'TNK2',
'ANAPC2',
'GCG',
'TRIM43B',
'TSPAN1',
'PML',
'RETN',
'TAPBP',
'ENAH',
'VLDLR',
'MNAT1',
'CSN1S1',
'ATF2_JUN',
'BANP',
'MTCP1',
'ADGRE5',
'H2BK1',
'G3BP2',
'ATRX',
'GLG1',
'TFEB',
'CDH9',
'SCARB1',
'DLX5',
'FMNL2',
'PARP1',
'RNF141',
'KPNA1',
'AP2M1',
'ITGAV_ITGB1',
'MCHR2',
'GATA6',
'CDH8',
'DEFB4B',
'NOXA1',
'PLAA',
'UGT2B7',
'RABEPK',
'PIM1',
'UBIAD1',
'PIP5K1B',
'TREML1',
'BRDT',
'FAAP20',
'CSNK1D',
'SEC62',
'TBX3',
'KAT5',
'ACVR1B_ACVR2A_CFC1',
'FLRT2',
'RPS27A',
'CLEC1B',
'NSMCE1',
'ABR',
'CD8A_CD8B',
'STRAP',
'PITRM1',
'OMA1',
'TP53TG5',
'IRS4',
'SELE',
'TRIM22',
'IFNA14',
'S100A4',
'DDX24',
'PAX8',
'FLT3',
'PPP3CB',
'RBM17',
'RPL6',
'CDCA5',
'MAP2K7',
'UBE3B',
'MYOM2',
'IKZF1',
'PHF12',
'LAMP1',
'SGK3',
'RNF123',
'BTN2A1',
'DUSP13B',
'PPP1R1A',
'CHRNA7',
'DHH',
'CXCL3',
'ADAMTS13',
'VMP1',
'S100A12',
'FLRT1',
'VEZT',
'IL12A_IL12B',
'CRK',
'ACSS2',
'TM4SF1',
'MYLK3',
'ICAM2',
'CHEBI:166824_CHEBI:53000_HLA-DMB_HLA-DPA1',
'PLD2',
'MARCKSL1',
'PHACTR1',
'PLAU',
'RPS27A_UBE2D3',
'LAMTOR1',
'UBC_UBE2V2',
'TRPM2',
'DAB1',
'PDK1',
'UBC_UBE2D2',
'RAD51D',
'RNF139',
'HLA-E',
'SMOC2',
'CEP68',
'UBA52_UBE2Q1',
'GRM7',
'PIH1D2_RUVBL1_RUVBL2_SPAG1',
'CDC37',
'LAMA2',
'MATK',
'PRC1',
'TENM4',
'ALDH2',
'KCNA5',
'APAF1',
'NHLH2',
'PCBD1',
'PLCL1',
'SLC12A5',
'RPS27L',
'LRRFIP2',
'BST1',
'PUM3',
'XRCC6',
'IGBP1',
'BCL9',
'PSMD4',
'GSTP1',
'PANX1',
'LTN1',
'NR1H4',
'SMAD3_SMAD4',
'GSTA1',
'ASTN2',
'P13288_RPS27A',
'RECQL5',
'HOMER2',
'IFI16',
'ARHGAP30',
'SCAND1',
'CGAS',
'MRE11',
'TG',
'DDX46',
'CDR2',
'NLK',
'SSTR5',
'WNT10B',
'PPP2CA_PPP2R1A_PPP2R2B',
'PPP2CA',
'IL1A',
'COL6A6',
'TBXA2R',
'KIF14',
'PABPC4',
'LCTL',
'GTF2F2',
'SKIL',
'ACTA1_ACTL6A_ALKBH3_BRD2_BRD8_DMAP1_EP400_EPC1_ING3_KAT5_MORF4L1_MRGBP_RUVBL1_RUVBL2_TRRAP_VPS72_YEATS4',
'SPHK1',
'SCP2',
'GABRA6_GABRB3_GABRG2',
'HSPB1',
'UBC_UBE2K',
'TMEM219',
'COBL',
'UBB_UBE2E3',
'EEF1B2_EEF1D_EEF1G',
'LAMA1',
'CNOT9',
'SLAMF6',
'ESPL1',
'NPS',
'CDK4',
'TERF2IP',
'PPP6R2',
'LASP1',
'CBY1',
'CDC7',
'HCK',
'CALCA',
'SRCAP',
'NEU1',
'SLIT2',
'FLOT1',
'MPG',
'JAM3',
'GREB1',
'B4GALT1',
'FYN',
'PPP1R12B',
'RAD21_SMC1A_SMC3_STAG1_STAG3',
'RSPO2',
'S100A11',
'EDAR',
'NPBWR2',
'IFIT1',
'GAR1',
'NR4A1',
'ADO',
'PRDM1',
'PIAS2',
'POLD1_POLD2_POLD3_POLD4',
'TOP2B',
'CEP43',
'DACT2',
'CDCA8_PRC1',
'PHEX',
'TDG',
'CDH1',
'ATR_ATRIP',
'SIGLEC9',
'ARHGEF11',
'TLR4',
'CCDC106',
'AIFM1',
'UBB_UBE2Q2',
'KIF2C',
'DAZAP1',
'CACNA1H',
'SLC18A2_TPH1',
'DBNDD1',
'NCOR1',
'CENPB',
'CKS1B',
'KMT5A',
'JPH2',
'SPSB2',
'IL18',
'TNC',
'GDI2',
'APPL1',
'TP53INP2',
'CSTF2',
'EDA2R',
'Q6FHA6',
'PLXNA1',
'PKIB',
'AMHR2',
'SHH',
'FRZB',
'FANCG',
'CCNA1',
'CSF2RB_IL5RA',
'TPP1',
'CCL23',
'CD47_ITGAV_ITGB3',
'RNF169',
'ILF2_ILF3',
'CYSLTR1',
'ADORA3',
'SIL1',
'EPN1',
'EIF2B5',
'SHC4',
'PPP2R2A',
'EFNB2',
'STK38L',
'ADCY3',
'MTOR',
'VPS35',
'GABRA1_GABRB2_GABRG2',
'CALM1',
'EPHA2',
'NMB',
'HLA-DRB1',
'ASB3',
'CLCN5',
'PEAR1',
'TNIP2',
'GP6',
'TPH1',
'TNPO1',
'TNFSF8',
'PITX1',
'IGF1',
'SOX2',
'RPS15',
'UBA6_UBB',
'USP16',
'GAB2',
'COPS2_COPS3_COPS4_COPS5_COPS6_COPS7B_COPS8_GPS1',
'PTPRT',
'CNP',
'ARHGAP6',
'MARCHF10',
'NCR3LG1',
'CHM',
'FAU_RPS10_RPS11_RPS12_RPS13_RPS14_RPS15_RPS15A_RPS16_RPS17_RPS18_RPS19_RPS2_RPS20_RPS21_RPS23_RPS24_RPS25_RPS26_RPS27_RPS27A_RPS27L_RPS28_RPS29_RPS3_RPS3A_RPS4X_RPS4Y1_RPS4Y2_RPS5_RPS6_RPS7_RPS8_RPS9_RPSA',
'TNF_TNFRSF1A',
'BCAN',
'GRIN1_GRIN2C',
'PRKX',
'UBA52_UBE2E1',
'TENT2',
'ABI1_BRK1_CYFIP1_NCKAP1_WASF2',
'FZD5_LRP6',
'ACTL6A_ACTL6B_ARID1A_ARID1B_ARID2_PBRM1_SMARCA2_SMARCA4_SMARCB1_SMARCC1_SMARCC2_SMARCD1_SMARCD2_SMARCD3_SMARCE1',
'RNF20',
'CTDSP2',
'UBLCP1',
'CASP2_CRADD_PIDD1',
'PDCD4',
'NCSTN',
'P03217_UBA52',
'SORBS1',
'ULBP2',
'CD52',
'MSL3',
'MEF2C',
'TRAF5',
'BBIP1_BBS1_BBS2_BBS4_BBS5_BBS7_BBS9_TTC8',
'FGF1',
'CYTH2',
'LYPLA2',
'HNRNPL',
'GALR1',
'CDK5_CDK5R1',
'FTH1',
'PCDHA3',
'MRC2',
'MMP24',
'ATG10',
'CDC14B',
'GPR34',
'ITGA2',
'TCF4',
'UBE2L6',
'CRKL',
'KLF1',
'PRDM2',
'XAF1',
'KLF11',
'LPAR5',
'GABARAPL1',
'RIF1',
'SSH3',
'C2_C4A_C4B_2',
'AGTR1',
'EIF5B',
'FBXW11',
'JAK1_STAT1_STAT3',
'NHERF1',
'COMMD1',
'RNF38',
'IRX2',
'OSBPL11',
'HELB',
'CLK2',
'PTGIR',
'RPN1',
'SIGLEC10',
'CNOT3',
'TNNT2',
'MT-ND1_MT-ND2_MT-ND3_MT-ND4_MT-ND4L_MT-ND5_MT-ND6_NDUFA1_NDUFA10_NDUFA11_NDUFA12_NDUFA13_NDUFA2_NDUFA3_NDUFA5_NDUFA6_NDUFA7_NDUFA8_NDUFA9_NDUFAB1_NDUFB1_NDUFB10_NDUFB11_NDUFB2_NDUFB3_NDUFB4_NDUFB5_NDUFB6_NDUFB7_NDUFB8_NDUFB9_NDUFC1_NDUFC2_NDUFS1_NDUFS2_NDUFS3_NDUFS4_NDUFS5_NDUFS6_NDUFS7_NDUFS8_NDUFV1_NDUFV2_NDUFV3',
'DGKZ',
'DNAH5',
'IL2RG_IL4R',
'ARHGAP18',
'PAX5',
'BMP15',
'FKBP15',
'NDP',
'ERBB2',
'GLRA2',
'SPON1',
'RABGEF1',
'WNT2',
'SGCG',
'ERBIN',
'REXO1',
'BRS3',
'DUSP26',
'MAPK9',
'NCAPD3',
'SMYD5',
'CRLF2_IL7R',
'Q80H93_RPS27A',
'NEURL1',
'SRPRA',
'DSN1',
'RMND5A',
'FXN',
'RNF216',
'BCKDHA',
'ITGA5_ITGB1',
'RASD2',
'MAST3',
'PEX1',
'RHBDL2',
'BNIP3L',
'CPEB3',
'MAP4K2',
'PTCRA',
'THBD',
'ROS1',
'MID1',
'IST1',
'FBXO44',
'DGCR8',
'NFKBIA',
'CHUK_IKBKB_IKBKG',
'COL3A1',
'ADRB3',
'POLR1G',
'DHX58',
'POLL',
'MTCH2',
'RAVER1',
'RNF121',
'CHD2',
'SLC12A4',
'CHKB',
'SLC2A4',
'FBXL12',
'METTL3',
'NXPH3',
'FMR1',
'ADH4',
'UCHL3',
'CHEBI:166824_CHEBI:53000_HLA-DMB_HLA-DRA',
'AICDA',
'GRIN1_GRIN2D',
'GRIN1_GRIN2B',
'RYBP',
'SDC1',
'P03231_UBC',
'RPL12',
'ATP5IF1',
'BMPR1B_BMPR2',
'MSL1',
'MMP8',
'NCOA2',
'CADM3',
'FBXL21P',
'P59634_UBC',
'DPP6',
'TIA1',
'TTBK1',
'TCF20',
'SPIB',
'GADD45B',
'F10_F5',
'MIPOL1',
'PCDHGB7',
'PHPT1',
'TAF1A',
'ANLN',
'MET',
'MBD3',
'CCND2',
'CRY1',
'ATAT1',
'DNA2',
'CARD18',
'FLRT3_TENM4',
'TPT1',
'COL27A1',
'GAD1_SLC6A6',
'FNBP4',
'ELOVL7',
'LRP10',
'BTF3',
'CDC42EP4',
'PLPP6',
'APLP1',
'PPM1B',
'NXF1',
'PRMT8',
'TECTA',
'GLIS2',
'KCNN1',
'BUB1',
'WASF3',
'VTN',
'BRD2',
'NR1I2',
'P03205_UBB',
'EIF1AY',
'PTRH2',
'KIF1B',
'TAF10',
'MT3',
'PPP2R1B',
'FCER2',
'DKK3',
'MAP3K4',
'ACP5',
'M6PR',
'ZNF322',
'CELSR1',
'P4HB',
'CORO1A',
'NR1D1',
'ITGA1',
'ZNF341',
'JUNB',
'WSB1',
'GTF2I',
'CDH24',
'FGF14',
'KNTC1_ZW10_ZWILCH_ZWINT',
'GPR135',
'GRID2',
'ACVR1B_ACVR2B',
'PTK7',
'FLRT3',
'TULP2',
'PDE6A',
'RABGGTB',
'IL7',
'PPP1R3B',
'ACAP2',
'RALA',
'RTKN',
'NNMT',
'SENP1',
'RFX3',
'FCN2',
'HAS1',
'CLIC4',
'KCNIP3',
'PDK3',
'CDON',
'USP13',
'CYP3A5',
'IL11RA',
'OPHN1',
'JKAMP',
'MTTP',
'BIRC6_UBB',
'SLITRK6',
'DDX3X',
'LRRC4B',
'NHERF4',
'ANP32A',
'MARCHF5',
'DNAJC3',
'PSMA3',
'CLSTN1',
'NKX6-1',
'UIMC1',
'MYL12A',
'NELFE',
'ADCY1',
'TRIP11',
'FBXO31',
'TRPC6',
'SDCBP2',
'CFLAR_FAS_RIPK1_TNFRSF10A_TNFRSF1A_TNFSF10_TRADD_TRAF2',
'TOLLIP',
'GTF3C1',
'GAD1_SLC6A1',
'PABPC1',
'ARTN',
'CREB3L2',
'GALNT8',
'PIAS1',
'MAP2K2',
'IL6R',
'CNIH1',
'H2BW2',
'TBXAS1',
'BRSK2',
'CDK13',
'FZD2_LRP5',
'SCN8A',
'DFFA',
'DKK4',
'PAN2',
'NEDD8',
'HMGCR',
'ABL2',
'CREBBP',
'ITGA1_ITGB1',
'C1QA_C1QB_C1QC',
'CX3CR1',
'LMO1',
'PTN',
'PTPRH',
'DYNC1LI2',
'CCN4',
'ARHGAP44',
'CASP14',
'CASP1',
'AP3D1',
'GRK4',
'CKS2',
'C1QA_C1QB_C1QC_C1R_C1S',
'CTSD',
'HSPA9',
'KSR1',
'AGER',
'A9UF07',
'H2BC5',
'PI4K2B',
'PKD2L1',
'TBC1D4',
'CTSG',
'IFNA1',
'TNS2',
'TTL',
'LHB',
'YWHAE',
'CD63',
'PRKAB1',
'TCTN3',
'CCNF',
'TGFB3',
'PIDD1',
'GLUD2',
'ITM2C',
'MYRIP',
'PPEF1',
'SLC39A7',
'PTH2R',
'NABP2',
'WNT3A',
'C1D',
'ADAMTS3',
'MC3R',
'OPTN',
'NKD1',
'TSPAN10',
'AMIGO2',
'UBE2R2',
'CALM2',
'FHL2',
'HMOX1',
'HAPLN1',
'RALGAPA2',
'APH1A_NCSTN_PSEN1_PSENEN',
'PYGO1',
'INCA1',
'PCDHAC2',
'OGG1',
'BAZ2A_SMARCA5',
'SPON2',
'PIK3R4',
'TNFRSF10C',
'LRRTM2',
'DHX9',
'SNAP29',
'GRIN2C',
'FCHO1',
'TFF2',
'SLC67A1',
'TRIM3',
'TARM1',
'PPP1CA_PPP1R12A_PPP1R12B',
'NCK2',
'TBX21',
'DCX',
'SND1',
'ZNF106',
'NOL4L',
'CDC25B',
'DDX23',
'AMHR2_BMPR1A',
'UBC_UBE2A',
'GSTA2',
'CCNO',
'NGEF',
'ITGA2B',
'ZNF622',
'LYN',
'NUSAP1',
'DBN1',
'KLHL15',
'FZD5_LRP5',
'TBC1D1',
'EIF2AK3',
'DUSP3',
'PTPRO',
'SRSF3',
'MEX3A',
'STXBP6',
'SBF1',
'SERBP1',
'RNF207',
'SDCBP',
'GCNT1',
'MAP4',
'AGTRAP',
'MYL6B',
'NRP1_PLXNA4',
'GSK3B',
'C4BPA',
'BTN1A1',
'RALB',
'SYNPO',
'MUC20',
'MAP9',
'XPA',
'WIPF1',
'GTF2H1',
'PRH2',
'TANC2',
'RPS27A_UBE2N',
'CAMK1',
'MICAL1',
'SIAH3',
'BMP6',
'RNF224',
'GABRA2_GABRB1_GABRG2',
'LMNA',
'MAPK8IP1',
'ISX',
'H2AC20',
'CDKN2D',
'CTPS2',
'ATP5F1B',
'PELI1',
'DGKA',
'ZFY',
'XRCC4',
'NPR3',
'SCN4A',
'PSPN',
'MSMB',
'SLC12A3',
'MMP9_TIMP1',
'ATP2C1',
'P03186_UBC',
'PIK3CA_PIK3R1',
'GNA15',
'PDE3A',
'FBXO33',
'CCL7',
'VEGFD',
'CHD3_CHD4_GATAD2A_GATAD2B_HDAC1_HDAC2_MBD2_MTA1_MTA2_MTA3_RBBP4_RBBP7',
'PRKG1',
'SERINC3',
'GAD1_SLC6A11',
'PA2G4',
'TRIM31',
'AMELY',
'LPCAT2',
'NOVA2',
'ROCK2',
'COL5A3',
'TLK2',
'ASAH2B',
'APEX1',
'JAM2',
'PCGF3',
'DLL4',
'ASB2',
'PANX2',
'PICK1',
'TNFRSF13B',
'RFPL4AL1',
'CFAP53',
'ITGA4_ITGB7',
'TAL2',
'HRC',
'GABRA5_GABRB3_GABRG2',
'CLN8',
'AHSG',
'GLS2_SLC1A6',
'SATB2',
'RBM39',
'FNIP2',
'ISL1',
'NOXO1',
'CLDN14',
'SETBP1',
'EVPL',
'FBXO15',
'NAA10',
'ZMIZ1',
'F2_THBD',
'ATG12',
'ACVR2B',
'GOLGA3',
'SMAD3',
'DVL2',
'THPO',
'NSFL1C',
'KMT2D',
'CDH7',
'CCNC',
'RPL5',
'IL12RB2_IL6ST',
'SVIL',
'PKMYT1',
'FGF7',
'UBA52_UBE2M',
'NLGN2',
'P2RY6',
'FMO1',
'CDH15',
'RIT1',
'ERGIC3',
'ALOX15',
'UBC_UBE2D3',
'GADD45GIP1',
'CHRM1',
'ARHGAP19',
'GPR75',
'CPAMD8',
'PAK6',
'EGR2',
'UBA52_UBE2Q2',
'CDKN2A',
'NFATC2',
'CCR7',
'IL21R',
'CUL2',
'TSSK4',
'EPB42',
'ITGAV_ITGB8',
'FAM3C',
'B2M_CHEBI:166824_HLA-B',
'HDAC5',
'ANGPTL1',
'BRCC3',
'PFKFB2',
'NRP1_PLXNA3',
'TMBIM6',
'PPARGC1B',
'JAK1',
'GCHFR',
'ATG16L1',
'ELMO2',
'TXN',
'CLDN1',
'RSPO4',
'MSN',
'ITGB2',
'EXTL1',
'SIAH1',
'CABYR',
'PAK3',
'TM9SF4',
'ACTA1',
'COPS6',
'C1QL2',
'FBXL6',
'SETDB2',
'CDC45',
'LEPR',
'KCNJ1',
'CALCRL_RAMP2',
'ARHGEF18',
'PNOC',
'TRIM47',
'SSTR3',
'PRG4',
'CENPS',
'CDK5',
'COL18A1',
'EGFR_FZD9_LRP5',
'GLRA1',
'WWP2',
'CD79A_CD79B_P0DOX6_P0DOX7',
'TSSC4',
'DYRK1A',
'ACTL6A_ARID1A_ARID1B_ARID2_SMARCA4_SMARCB1_SMARCC1_SMARCC2_SMARCD1_SMARCE1',
'PLAGL1',
'RHOB',
'GAB1',
'GABRA1_GABRB3_GABRG2',
'SMAP1',
'MUC2',
'DTD1',
'RASSF1',
'ADAM12',
'PDCD6IP',
'CXCL6',
'DTX4',
'PLD1',
'KLC2',
'SIRT2',
'SLBP',
'NR2F6',
'TPX2',
'GOLGA4',
'RUFY3',
'NCF4',
'PCGF5',
'CRTC1',
'PTGR1',
'MSX1',
'ADGRL3',
'ACKR2',
'IL4',
'ID3',
'CDCA3',
'SLC52A2',
'RAD51C',
'ZC3HAV1',
'TRAF6',
'DAXX',
'LRRK1',
'BTG2',
'MAP1A',
'DISP2',
'WT1',
'NBR1',
'CD79A',
'DCC',
'CPM',
'IZUMO1R',
'CHEBI:166824_CHEBI:53000_HLA-DRA_HLA-DRB3',
'YWHAZ',
'DHFR',
'ITGB7_VCAM1',
'DDX19B',
'STIL',
'PROKR2',
'PIK3C2G',
'AGR2',
'DTX3',
'ADRM1',
'NR0B2',
'WNT9B',
'NREP',
'GABRB2',
'TFIP11',
'CDH3',
'MAGI1',
'GEMIN4',
'CASP4',
'CCND3_CDK11B',
'ATP5F1C',
'ZFYVE26',
'GABBR1_GABBR2',
'FGF20',
'ENAM',
'PRPS1',
'DEFB104A',
'MYO15A',
'HNRNPK',
'PC',
'ZW10',
'CCL3L1',
'PXN',
'NSMCE2',
'GFRA4_RET',
'LIME1',
'SFRP5',
'ETV3',
'MADD',
'MAX',
'CREBBP_EP300_KAT2B',
'KIR2DL3',
'GTF2F1',
'RSPO3',
'STK40',
'LPAR1',
'PIP5K1A',
'PCDHGB5',
'RAPGEF6',
'ASH2L_DPY30_KDM6A_KMT2B_KMT2C_NCOA6_PAXIP1_RBBP5_WDR5',
'FLNB',
'RITA1',
'YY1',
'DLAT_DLD_PDHA1_PDHA2_PDHB_PDHX',
'EIF2AK2',
'TRIM64',
'TECTB',
'UBC_UBE2D1',
'ARHGAP17',
'PCDHA7',
'SEMA4D',
'GPAA1',
'BAD',
'STRN4',
'NFIX_PRKCQ',
'FCGR3A',
'PDE1B',
'ITGAV',
'KIFC1',
'PDCD5',
'CHD7_NLK_SETDB1',
'PYGL',
...},
'edge_ids': {'edge_86118',
'edge_35849',
'edge_29343',
'edge_36861',
'edge_9632',
'edge_26146',
'edge_70778',
'edge_61871',
'edge_82883',
'edge_44829',
'edge_79572',
'edge_55223',
'edge_62146',
'edge_63625',
'edge_21689',
'edge_27421',
'edge_71298',
'edge_77272',
'edge_85351',
'edge_15282',
'edge_24499',
'edge_31985',
'edge_31997',
'edge_54441',
'edge_6834',
'edge_37481',
'edge_63355',
'edge_27554',
'edge_23159',
'edge_22800',
'edge_69669',
'edge_21574',
'edge_40065',
'edge_46384',
'edge_1057',
'edge_62947',
'edge_24570',
'edge_79028',
'edge_82473',
'edge_84873',
'edge_53807',
'edge_13507',
'edge_18891',
'edge_22073',
'edge_35665',
'edge_53960',
'edge_60330',
'edge_69487',
'edge_4090',
'edge_21462',
'edge_62615',
'edge_61161',
'edge_2888',
'edge_23522',
'edge_39292',
'edge_3773',
'edge_70916',
'edge_62861',
'edge_74844',
'edge_64441',
'edge_62149',
'edge_8947',
'edge_37370',
'edge_72819',
'edge_9960',
'edge_72892',
'edge_45885',
'edge_75136',
'edge_16300',
'edge_62303',
'edge_55729',
'edge_7493',
'edge_62799',
'edge_11727',
'edge_52485',
'edge_49890',
'edge_25223',
'edge_9080',
'edge_83275',
'edge_52127',
'edge_52231',
'edge_35015',
'edge_25198',
'edge_19187',
'edge_82583',
'edge_53098',
'edge_3617',
'edge_52294',
'edge_29059',
'edge_34961',
'edge_8247',
'edge_44441',
'edge_41475',
'edge_73497',
'edge_63341',
'edge_13416',
'edge_516',
'edge_51383',
'edge_36803',
'edge_69618',
'edge_19829',
'edge_11128',
'edge_20274',
'edge_38672',
'edge_53535',
'edge_23454',
'edge_56913',
'edge_75769',
'edge_26485',
'edge_37902',
'edge_67984',
'edge_65791',
'edge_37025',
'edge_54671',
'edge_22835',
'edge_77759',
'edge_45487',
'edge_84337',
'edge_42898',
'edge_41697',
'edge_32752',
'edge_64028',
'edge_78097',
'edge_41752',
'edge_72003',
'edge_19826',
'edge_58092',
'edge_25210',
'edge_28095',
'edge_38913',
'edge_36613',
'edge_78189',
'edge_80899',
'edge_83677',
'edge_20387',
'edge_79408',
'edge_19553',
'edge_82208',
'edge_39389',
'edge_501',
'edge_12166',
'edge_20927',
'edge_61449',
'edge_9979',
'edge_51715',
'edge_46582',
'edge_62477',
'edge_37021',
'edge_2819',
'edge_55308',
'edge_32614',
'edge_47421',
'edge_64162',
'edge_51044',
'edge_15674',
'edge_48687',
'edge_23354',
'edge_48792',
'edge_16350',
'edge_82286',
'edge_72320',
'edge_59928',
'edge_25631',
'edge_17639',
'edge_31350',
'edge_2066',
'edge_25269',
'edge_44718',
'edge_64008',
'edge_74353',
'edge_48998',
'edge_41130',
'edge_25121',
'edge_79760',
'edge_34821',
'edge_1250',
'edge_45506',
'edge_28906',
'edge_36162',
'edge_48975',
'edge_35587',
'edge_50301',
'edge_68146',
'edge_67832',
'edge_30649',
'edge_30967',
'edge_71370',
'edge_37412',
'edge_79488',
'edge_17500',
'edge_24074',
'edge_698',
'edge_9119',
'edge_77596',
'edge_84730',
'edge_67862',
'edge_53523',
'edge_62514',
'edge_37827',
'edge_79135',
'edge_10302',
'edge_76221',
'edge_52562',
'edge_37785',
'edge_3214',
'edge_25690',
'edge_58167',
'edge_86096',
'edge_59551',
'edge_14598',
'edge_14332',
'edge_29444',
'edge_63316',
'edge_64320',
'edge_43923',
'edge_63781',
'edge_72057',
'edge_54310',
'edge_25349',
'edge_39787',
'edge_12250',
'edge_1541',
'edge_45658',
'edge_24462',
'edge_34495',
'edge_67271',
'edge_122',
'edge_14161',
'edge_27674',
'edge_76550',
'edge_19754',
'edge_39109',
'edge_42361',
'edge_43562',
'edge_55232',
'edge_70788',
'edge_56613',
'edge_36324',
'edge_74129',
'edge_48101',
'edge_39695',
'edge_20073',
'edge_10977',
'edge_25556',
'edge_23428',
'edge_51916',
'edge_38668',
'edge_8284',
'edge_61017',
'edge_75818',
'edge_19552',
'edge_54048',
'edge_24188',
'edge_47690',
'edge_32850',
'edge_77843',
'edge_49827',
'edge_71914',
'edge_22781',
'edge_26118',
'edge_43297',
'edge_77428',
'edge_76656',
'edge_30721',
'edge_22242',
'edge_66670',
'edge_6207',
'edge_58880',
'edge_86359',
'edge_44336',
'edge_58750',
'edge_66336',
'edge_31909',
'edge_36958',
'edge_62296',
'edge_35261',
'edge_10283',
'edge_46937',
'edge_44766',
'edge_51213',
'edge_50834',
'edge_45484',
'edge_54677',
'edge_48860',
'edge_50003',
'edge_57552',
'edge_60051',
'edge_67970',
'edge_13405',
'edge_46093',
'edge_75815',
'edge_28577',
'edge_62977',
'edge_23497',
'edge_40007',
'edge_33360',
'edge_75460',
'edge_81489',
'edge_14889',
'edge_35489',
'edge_5187',
'edge_76681',
'edge_24926',
'edge_82456',
'edge_85664',
'edge_79710',
'edge_75292',
'edge_38335',
'edge_72878',
'edge_73865',
'edge_38908',
'edge_61601',
'edge_1593',
'edge_16678',
'edge_5412',
'edge_47466',
'edge_15126',
'edge_59302',
'edge_15161',
'edge_40526',
'edge_21839',
'edge_42223',
'edge_10122',
'edge_54975',
'edge_29460',
'edge_81813',
'edge_32379',
'edge_26956',
'edge_4969',
'edge_58360',
'edge_15421',
'edge_20782',
'edge_40174',
'edge_43084',
'edge_35129',
'edge_57680',
'edge_69641',
'edge_73659',
'edge_37019',
'edge_67188',
'edge_42287',
'edge_38101',
'edge_34976',
'edge_60883',
'edge_46232',
'edge_51221',
'edge_38805',
'edge_32061',
'edge_5878',
'edge_59895',
'edge_33836',
'edge_35236',
'edge_7802',
'edge_86336',
'edge_36488',
'edge_49222',
'edge_24625',
'edge_5971',
'edge_41265',
'edge_7113',
'edge_57700',
'edge_24293',
'edge_14945',
'edge_62084',
'edge_65132',
'edge_66391',
'edge_71527',
'edge_86414',
'edge_23288',
'edge_62707',
'edge_84961',
'edge_77281',
'edge_14515',
'edge_61497',
'edge_7776',
'edge_73714',
'edge_45582',
'edge_42281',
'edge_75635',
'edge_60513',
'edge_61895',
'edge_75802',
'edge_28792',
'edge_77345',
'edge_11865',
'edge_8213',
'edge_76968',
'edge_27538',
'edge_18650',
'edge_83922',
'edge_7873',
'edge_4498',
'edge_74629',
'edge_82617',
'edge_69729',
'edge_47854',
'edge_75039',
'edge_25531',
'edge_68148',
'edge_85928',
'edge_35805',
'edge_62563',
'edge_2586',
'edge_73663',
'edge_58547',
'edge_46776',
'edge_54609',
'edge_14655',
'edge_10538',
'edge_26772',
'edge_77194',
'edge_394',
'edge_39819',
'edge_56250',
'edge_3665',
'edge_61876',
'edge_71385',
'edge_57967',
'edge_71056',
'edge_7971',
'edge_24521',
'edge_37389',
'edge_11288',
'edge_73437',
'edge_44918',
'edge_43665',
'edge_50951',
'edge_63862',
'edge_51658',
'edge_52868',
'edge_46024',
'edge_41132',
'edge_76637',
'edge_34291',
'edge_59614',
'edge_20427',
'edge_58878',
'edge_48650',
'edge_65495',
'edge_48716',
'edge_59725',
'edge_81693',
'edge_62583',
'edge_21724',
'edge_81190',
'edge_14937',
'edge_35067',
'edge_71642',
'edge_54560',
'edge_55798',
'edge_85142',
'edge_61780',
'edge_6263',
'edge_11938',
'edge_73543',
'edge_20075',
'edge_3157',
'edge_6661',
'edge_57788',
'edge_33953',
'edge_76620',
'edge_46352',
'edge_74346',
'edge_82718',
'edge_49439',
'edge_17234',
'edge_30848',
'edge_48370',
'edge_11391',
'edge_41652',
'edge_37948',
'edge_30875',
'edge_78421',
'edge_11293',
'edge_28888',
'edge_3296',
'edge_18864',
'edge_25216',
'edge_34028',
'edge_14507',
'edge_83146',
'edge_33229',
'edge_82392',
'edge_39134',
'edge_86348',
'edge_8540',
'edge_18342',
'edge_28800',
'edge_35859',
'edge_39910',
'edge_27103',
'edge_84056',
'edge_11379',
'edge_47479',
'edge_51402',
'edge_13581',
'edge_18841',
'edge_42221',
'edge_4278',
'edge_43385',
'edge_56189',
'edge_45446',
'edge_5234',
'edge_68992',
'edge_71478',
'edge_25945',
'edge_7507',
'edge_41508',
'edge_59916',
'edge_31604',
'edge_24632',
'edge_42569',
'edge_15698',
'edge_34228',
'edge_38709',
'edge_32323',
'edge_27581',
'edge_47899',
'edge_81344',
'edge_86480',
'edge_2444',
'edge_56282',
'edge_66314',
'edge_78809',
'edge_52072',
'edge_16944',
'edge_48412',
'edge_75906',
'edge_5988',
'edge_72712',
'edge_63347',
'edge_70485',
'edge_59784',
'edge_67195',
'edge_78200',
'edge_53822',
'edge_21109',
'edge_43885',
'edge_43367',
'edge_74796',
'edge_38875',
'edge_52079',
'edge_73647',
'edge_18715',
'edge_23123',
'edge_51852',
'edge_50035',
'edge_16238',
'edge_27673',
'edge_61558',
'edge_32131',
'edge_53190',
'edge_1703',
'edge_36358',
'edge_63961',
'edge_36412',
'edge_65897',
'edge_16473',
'edge_46862',
'edge_17123',
'edge_50194',
'edge_80295',
'edge_32545',
'edge_40864',
'edge_85217',
'edge_37249',
'edge_82897',
'edge_78909',
'edge_5173',
'edge_65859',
'edge_11923',
'edge_50198',
'edge_33351',
'edge_1370',
'edge_50218',
'edge_71560',
'edge_65145',
'edge_72705',
'edge_3600',
'edge_60517',
'edge_50722',
'edge_10642',
'edge_30990',
'edge_51767',
'edge_73482',
'edge_61743',
'edge_46751',
'edge_73212',
'edge_83988',
'edge_84839',
'edge_23551',
'edge_33863',
'edge_80794',
'edge_15963',
'edge_37604',
'edge_69628',
'edge_67051',
'edge_14466',
'edge_31068',
'edge_70981',
'edge_72491',
'edge_17069',
'edge_47985',
'edge_40010',
'edge_68644',
'edge_61267',
'edge_13136',
'edge_67733',
'edge_9683',
'edge_19496',
'edge_32820',
'edge_44902',
'edge_77823',
'edge_52772',
'edge_79922',
'edge_31102',
'edge_76339',
'edge_18503',
'edge_29328',
'edge_37966',
'edge_67667',
'edge_75850',
'edge_14657',
'edge_51959',
'edge_61860',
'edge_30089',
'edge_24936',
'edge_40728',
'edge_83466',
'edge_80672',
'edge_2651',
'edge_6822',
'edge_57521',
'edge_11694',
'edge_84453',
'edge_54898',
'edge_43275',
'edge_79427',
'edge_68073',
'edge_39319',
'edge_4870',
'edge_6210',
'edge_25206',
'edge_6620',
'edge_14309',
'edge_67824',
'edge_42739',
'edge_74920',
'edge_80010',
'edge_7476',
'edge_43320',
'edge_33189',
'edge_62949',
'edge_49635',
'edge_46475',
'edge_42235',
'edge_53070',
'edge_86262',
'edge_60027',
'edge_66872',
'edge_27462',
'edge_7439',
'edge_24607',
'edge_39411',
'edge_43026',
'edge_4023',
'edge_22267',
'edge_39558',
'edge_37652',
'edge_23113',
'edge_61515',
'edge_43403',
'edge_18630',
'edge_84101',
'edge_62891',
'edge_21924',
'edge_51114',
'edge_42154',
'edge_47383',
'edge_75357',
'edge_82666',
'edge_61013',
'edge_4078',
'edge_10307',
'edge_53925',
'edge_71717',
'edge_45095',
'edge_24843',
'edge_27469',
'edge_34225',
'edge_83798',
'edge_29599',
'edge_58365',
'edge_26882',
'edge_33064',
'edge_14894',
'edge_17785',
'edge_20117',
'edge_38241',
'edge_53438',
'edge_17808',
'edge_48011',
'edge_8393',
'edge_61275',
'edge_80536',
'edge_20797',
'edge_67724',
'edge_11411',
'edge_19531',
'edge_70902',
'edge_14403',
'edge_71330',
'edge_85326',
'edge_59169',
'edge_34912',
'edge_38907',
'edge_70294',
'edge_2151',
'edge_79729',
'edge_84480',
'edge_11801',
'edge_86326',
'edge_21440',
'edge_10074',
'edge_72013',
'edge_16684',
'edge_65687',
'edge_31658',
'edge_33046',
'edge_80644',
'edge_36602',
'edge_75495',
'edge_57231',
'edge_52482',
'edge_72684',
'edge_74515',
'edge_78282',
'edge_13930',
'edge_21170',
'edge_27130',
'edge_45081',
'edge_3224',
'edge_56190',
'edge_33367',
'edge_81415',
'edge_83288',
'edge_83868',
'edge_47682',
'edge_66072',
'edge_4019',
'edge_20509',
'edge_67160',
'edge_7745',
'edge_82977',
'edge_3013',
'edge_62829',
'edge_43036',
'edge_44232',
'edge_76749',
'edge_59948',
'edge_75889',
'edge_85388',
'edge_54514',
'edge_8466',
'edge_58988',
'edge_85424',
'edge_19484',
'edge_58188',
'edge_74677',
'edge_38276',
'edge_82858',
'edge_27530',
'edge_60926',
'edge_76469',
'edge_47202',
'edge_55466',
'edge_46784',
'edge_2099',
'edge_68169',
'edge_60549',
'edge_41399',
'edge_12845',
'edge_73169',
'edge_51308',
'edge_35371',
'edge_18208',
'edge_24522',
'edge_2103',
'edge_70311',
'edge_72845',
'edge_25728',
'edge_57129',
'edge_60864',
'edge_20567',
'edge_27613',
'edge_11515',
'edge_7167',
'edge_45778',
'edge_67782',
'edge_29765',
'edge_30224',
'edge_55016',
'edge_29580',
'edge_9611',
'edge_83950',
'edge_78311',
'edge_59266',
'edge_35066',
'edge_84678',
'edge_56096',
'edge_51112',
'edge_77835',
'edge_44819',
'edge_50371',
'edge_29728',
'edge_2800',
'edge_68308',
'edge_40701',
'edge_62165',
'edge_6279',
'edge_84835',
'edge_49818',
'edge_8312',
'edge_73313',
'edge_50064',
'edge_42571',
'edge_9329',
'edge_1828',
'edge_22738',
'edge_60530',
'edge_34860',
'edge_49288',
'edge_60207',
'edge_85020',
'edge_26632',
'edge_31982',
'edge_44780',
'edge_32889',
'edge_61359',
'edge_54103',
'edge_59860',
'edge_74949',
'edge_29671',
'edge_46462',
'edge_52426',
'edge_26359',
'edge_30299',
'edge_41280',
'edge_3340',
'edge_43770',
'edge_29655',
'edge_14528',
'edge_72660',
'edge_4096',
'edge_18686',
'edge_52410',
'edge_69094',
'edge_68901',
'edge_61114',
'edge_74322',
'edge_33835',
'edge_15767',
'edge_21895',
'edge_31507',
'edge_67557',
'edge_23254',
'edge_66187',
'edge_38745',
'edge_32094',
'edge_51679',
'edge_85457',
'edge_63207',
'edge_77379',
'edge_5418',
'edge_78949',
'edge_82845',
'edge_16945',
'edge_3834',
'edge_17504',
'edge_62572',
'edge_72370',
'edge_54578',
'edge_54003',
'edge_13404',
'edge_82721',
'edge_1275',
'edge_24318',
'edge_30014',
'edge_79153',
'edge_9675',
'edge_60495',
'edge_54420',
'edge_9111',
'edge_60559',
'edge_82190',
'edge_17571',
'edge_64988',
'edge_85051',
'edge_40812',
'edge_17114',
'edge_30207',
'edge_21929',
'edge_60776',
'edge_85640',
'edge_86050',
'edge_44713',
'edge_57571',
'edge_78123',
'edge_15184',
'edge_9748',
'edge_14428',
'edge_53132',
'edge_21803',
'edge_10898',
'edge_30839',
'edge_53016',
'edge_68603',
'edge_56603',
'edge_35369',
'edge_39843',
'edge_73855',
'edge_65197',
'edge_22924',
'edge_44781',
'edge_37037',
'edge_10426',
'edge_39022',
'edge_44300',
'edge_65337',
'edge_35357',
'edge_28241',
'edge_1386',
'edge_18964',
'edge_80738',
'edge_42527',
'edge_53663',
'edge_31659',
'edge_56660',
'edge_78146',
'edge_265',
'edge_43303',
'edge_44452',
'edge_74108',
'edge_15692',
'edge_28617',
'edge_13815',
'edge_56734',
'edge_84748',
'edge_75260',
'edge_68145',
'edge_5778',
'edge_42842',
'edge_4325',
'edge_47616',
'edge_36285',
'edge_60862',
'edge_54205',
'edge_17100',
'edge_43657',
'edge_54497',
'edge_75486',
'edge_27222',
'edge_53351',
'edge_49952',
'edge_61679',
'edge_32970',
'edge_70937',
'edge_75683',
'edge_62702',
'edge_10339',
'edge_66305',
'edge_70766',
'edge_25737',
'edge_74800',
'edge_80948',
'edge_72530',
'edge_69866',
'edge_4894',
'edge_19801',
'edge_10259',
'edge_46186',
'edge_18124',
'edge_20697',
'edge_61791',
'edge_43775',
'edge_68144',
'edge_59486',
'edge_6802',
'edge_25843',
'edge_40562',
'edge_10239',
'edge_79702',
'edge_83153',
'edge_83303',
'edge_33147',
'edge_47677',
'edge_80878',
'edge_74576',
'edge_70612',
'edge_61340',
...},
'slice_ids': {'carnival_inferred', 'default', 'omnipath_pkn'}}
21. PyG HeteroData export¶
Lossless export of vertex-by-edge incidence + attributes into PyG's heterogeneous format, ready for GNN training in §25.
export_rows = []
if EXPORT_PYG:
try:
pyg_data = to_pyg(G, hyperedge_mode='skip')
try:
import torch
torch.save(pyg_data, PYG_OUT)
export_rows.append(
{
'export': 'PyG HeteroData',
'ok': True,
'path': str(PYG_OUT),
'details': f'node_types={list(pyg_data.node_types)}, edge_types={list(pyg_data.edge_types)}',
}
)
except Exception as exc:
export_rows.append(
{
'export': 'PyG HeteroData',
'ok': False,
'path': str(PYG_OUT),
'details': f'torch save failed: {type(exc).__name__}: {exc}',
}
)
except Exception as exc:
export_rows.append(
{
'export': 'PyG HeteroData',
'ok': False,
'path': str(PYG_OUT),
'details': f'{type(exc).__name__}: {exc}',
}
)
export_summary = pd.DataFrame(export_rows)
export_summary.to_csv(TABLE_DIR / 'export_summary.csv', index=False)
export_summary
| export | ok | path | details | |
|---|---|---|---|---|
| 0 | PyG HeteroData | True | /mnt/c/Users/pc/desktop/annnet-remote/notebook... | node_types=['default'], edge_types=[('default'... |
22. Provenance — snapshot diff across pipeline stages¶
G.history.snapshot(label) records the structural state at each stage.
G.history.diff(a, b) returns the set differences between two
snapshots. The table below summarizes the per-stage delta — manuscript
artifact, auditable, machine-readable.
diff_rows = []
all_snapshots = list(G.history.list_snapshots())
labels = [s['label'] for s in all_snapshots]
for i in range(1, len(labels)):
d = G.history.diff(labels[i - 1], labels[i])
diff_rows.append(
{
'from': labels[i - 1],
'to': labels[i],
'vertices_added': len(d.vertices_added),
'vertices_removed': len(d.vertices_removed),
'edges_added': len(d.edges_added),
'edges_removed': len(d.edges_removed),
'slices_added': len(d.slices_added),
'slices_removed': len(d.slices_removed),
}
)
diff_df = pd.DataFrame(diff_rows)
diff_df.to_csv(TABLE_DIR / 'history_diff_table.csv', index=False)
snapshots = pd.DataFrame(all_snapshots)[['label', 'version']]
snapshots.to_csv(TABLE_DIR / 'history_snapshots.csv', index=False)
if EXPORT_HISTORY:
try:
G.history.export(str(HISTORY_OUT))
except Exception as exc:
print(f'History export failed: {type(exc).__name__}: {exc}')
print('Snapshots:')
display(snapshots)
print('\nStructural changes per pipeline stage:')
display(diff_df)
Snapshots:
| label | version | |
|---|---|---|
| 0 | after_pkn_load | 0 |
| 1 | after_patient_layers_defined | 1 |
| 2 | after_omics_loaded_into_layers | 104 |
| 3 | after_patient_specific_causal_networks | 147 |
| 4 | after_carnival_slice | 148 |
| 5 | after_consensus_layers | 153 |
| 6 | after_interlayer_coupling | 154 |
| 7 | after_backend_swap | 155 |
Structural changes per pipeline stage:
| from | to | vertices_added | vertices_removed | edges_added | edges_removed | slices_added | slices_removed | |
|---|---|---|---|---|---|---|---|---|
| 0 | after_pkn_load | after_patient_layers_defined | 0 | 0 | 0 | 0 | 0 | 0 |
| 1 | after_patient_layers_defined | after_omics_loaded_into_layers | 0 | 0 | 0 | 0 | 0 | 0 |
| 2 | after_omics_loaded_into_layers | after_patient_specific_causal_networks | 0 | 0 | 266 | 0 | 0 | 0 |
| 3 | after_patient_specific_causal_networks | after_carnival_slice | 0 | 0 | 0 | 0 | 1 | 0 |
| 4 | after_carnival_slice | after_consensus_layers | 0 | 0 | 6 | 0 | 0 | 0 |
| 5 | after_consensus_layers | after_interlayer_coupling | 0 | 0 | 1134 | 0 | 0 | 0 |
| 6 | after_interlayer_coupling | after_backend_swap | 0 | 0 | 0 | 0 | 0 | 0 |
23. CX2 export for Cytoscape¶
One call. The output .cx2 opens directly in Cytoscape (File → Import →
Network from File) for interactive review of the consensus subnetwork.
from annnet.io.cx2 import to_cx2
cx2_path = FIG_DIR / f'consensus_{PRIMARY_COHORT_LABEL}.cx2'
to_cx2(G_consensus, str(cx2_path))
print(f'Wrote {cx2_path}')
Wrote /mnt/c/Users/pc/desktop/annnet-remote/notebooks/uc1_fixed_outputs/figures/consensus_top10.cx2
24. Save the AnnNet snapshot¶
The .annnet archive is the canonical artifact; CSV tables under
tables/ make the per-stage numerics auditable without reopening the
graph.
if SAVE_ANNNET:
G.write(ANNNET_OUT, overwrite=True)
print(f'Wrote {ANNNET_OUT}')
else:
print('SAVE_ANNNET=False, skipping graph export.')
Wrote /mnt/c/Users/pc/desktop/annnet-remote/notebooks/uc1_fixed_outputs/UC1_fixed.annnet
25. Closing the loop — GNN training on the PyG export¶
Load the PyG export from §21 and train a 2-layer GraphSAGE on a binary node-classification task: predict CancerGeneCensus membership from PKN topology. We compare ROC-AUC against a degree-only logistic baseline to confirm the export carries structural signal beyond degree.
import torch
import torch.nn.functional as _F
from torch_geometric.nn import SAGEConv
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score
pyg_data = torch.load(PYG_OUT, weights_only=False)
node_type = next(iter(pyg_data.node_types))
edge_type = next(iter(pyg_data.edge_types))
n = pyg_data[node_type].num_nodes
ei = pyg_data[edge_type].edge_index
print(f'PyG: {n:,} nodes {ei.shape[1]:,} edges ({node_type=}, {edge_type=})')
vattr_df = G.vertex_attributes.to_pandas()
ccg_cols = [c for c in vattr_df.columns if 'CancerGeneCensus' in c]
if not ccg_cols:
print('No CancerGeneCensus annotation column — GNN demo skipped.')
else:
col = ccg_cols[0]
pos_vids = set(vattr_df.loc[vattr_df[col].notna(), 'vertex_id'])
node_idx_map = pyg_data.manifest['node_index'][node_type]
# node_idx_map is dict[vid -> idx]; produce a vid-ordered label tensor
idx_to_vid = {idx: vid for vid, idx in node_idx_map.items()}
y = torch.tensor(
[1.0 if idx_to_vid[i] in pos_vids else 0.0 for i in range(n)], dtype=torch.float
)
n_pos = int(y.sum())
print(f'Labels: {n_pos:,} CancerGeneCensus positive / {n:,} ({n_pos / n:.1%})')
# Features: log(1+degree) as a single scalar
deg = torch.zeros(n)
deg.index_add_(0, ei[0], torch.ones(ei.shape[1]))
deg.index_add_(0, ei[1], torch.ones(ei.shape[1]))
x = torch.log1p(deg).unsqueeze(-1)
torch.manual_seed(SEED)
perm = torch.randperm(n)
tr, va = perm[: int(0.7 * n)], perm[int(0.7 * n) :]
class _SAGE(torch.nn.Module):
def __init__(self):
super().__init__()
self.c1 = SAGEConv(1, 16)
self.c2 = SAGEConv(16, 1)
def forward(self, x, ei):
return self.c2(_F.relu(self.c1(x, ei)), ei)
model = _SAGE()
opt = torch.optim.Adam(model.parameters(), lr=1e-2, weight_decay=1e-4)
for _epoch in range(100):
model.train()
opt.zero_grad()
out = model(x, ei).squeeze(-1)
loss = _F.binary_cross_entropy_with_logits(out[tr], y[tr])
loss.backward()
opt.step()
model.eval()
with torch.no_grad():
scores = torch.sigmoid(model(x, ei).squeeze(-1))
auc_gnn = roc_auc_score(y[va].numpy(), scores[va].numpy())
# Degree-only logistic baseline
lr_clf = LogisticRegression(max_iter=1000).fit(x[tr].numpy(), y[tr].numpy())
auc_deg = roc_auc_score(y[va].numpy(), lr_clf.predict_proba(x[va].numpy())[:, 1])
print(f'GraphSAGE val ROC-AUC: {auc_gnn:.3f}')
print(f'Degree-only baseline : {auc_deg:.3f}')
print(f'Lift over degree : {auc_gnn - auc_deg:+.3f}')
PyG: 8,791 nodes 85,217 edges (node_type='default', edge_type=('default', 'edge', 'default'))
Labels: 628 CancerGeneCensus positive / 8,791 (7.1%)
GraphSAGE val ROC-AUC: 0.765
Degree-only baseline : 0.701
Lift over degree : +0.064
26. Notes and scope¶
This notebook is positioned as an AnnNet software case study. It shows how a single AnnNet object organises priors, omics-derived activities, patient-specific causal subnetworks, multilayer state, provenance, and export surfaces. It does not claim biological discovery; external validation and method-vs-method benchmarks are out of scope.