bsvae 0.4.0__tar.gz → 0.4.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. {bsvae-0.4.0 → bsvae-0.4.2}/PKG-INFO +1 -1
  2. {bsvae-0.4.0 → bsvae-0.4.2}/pyproject.toml +1 -1
  3. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/cli/networks.py +5 -1
  4. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/networks/targets.py +64 -19
  5. {bsvae-0.4.0 → bsvae-0.4.2}/LICENSE +0 -0
  6. {bsvae-0.4.0 → bsvae-0.4.2}/README.md +0 -0
  7. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/__init__.py +0 -0
  8. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/benchmark/__init__.py +0 -0
  9. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/benchmark/acorde.R +0 -0
  10. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/benchmark/edges.py +0 -0
  11. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/benchmark/harness.py +0 -0
  12. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/benchmark/methods.py +0 -0
  13. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/benchmark/runners.py +0 -0
  14. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/benchmark/twn.R +0 -0
  15. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/benchmark/wgcna.R +0 -0
  16. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/cli/__init__.py +0 -0
  17. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/cli/_common.py +0 -0
  18. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/cli/benchmark.py +0 -0
  19. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/cli/simulate.py +0 -0
  20. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/cli/sweep_k.py +0 -0
  21. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/cli/train.py +0 -0
  22. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/latent/__init__.py +0 -0
  23. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/latent/latent_analysis.py +0 -0
  24. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/latent/latent_export.py +0 -0
  25. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/models/__init__.py +0 -0
  26. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/models/decoder.py +0 -0
  27. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/models/encoder.py +0 -0
  28. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/models/gmm_prior.py +0 -0
  29. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/models/gmvae.py +0 -0
  30. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/models/losses.py +0 -0
  31. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/models/vae.py +0 -0
  32. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/networks/__init__.py +0 -0
  33. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/networks/extract_networks.py +0 -0
  34. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/networks/module_extraction.py +0 -0
  35. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/networks/postprocess.py +0 -0
  36. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/networks/quality.py +0 -0
  37. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/networks/utils.py +0 -0
  38. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/simulation/__init__.py +0 -0
  39. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/simulation/generate.py +0 -0
  40. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/simulation/isoform.py +0 -0
  41. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/simulation/metrics.py +0 -0
  42. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/simulation/scenario.py +0 -0
  43. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/utils/__init__.py +0 -0
  44. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/utils/datasets.py +0 -0
  45. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/utils/helpers.py +0 -0
  46. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/utils/hierarchy.py +0 -0
  47. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/utils/isoform.py +0 -0
  48. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/utils/modelIO.py +0 -0
  49. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/utils/preprocess.py +0 -0
  50. {bsvae-0.4.0 → bsvae-0.4.2}/src/bsvae/utils/training.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bsvae
3
- Version: 0.4.0
3
+ Version: 0.4.2
4
4
  Summary: Gaussian Mixture VAE for multi-modal biological module discovery in omics data.
5
5
  License: GPL-3.0-only
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "bsvae"
3
- version = "0.4.0" # dynamically resolved by poetry-dynamic-versioning
3
+ version = "0.4.2" # dynamically resolved by poetry-dynamic-versioning
4
4
  description = "Gaussian Mixture VAE for multi-modal biological module discovery in omics data."
5
5
  readme = "README.md"
6
6
  authors = ["Kynon J Benjamin <kj.benjamin90@gmail.com>"]
@@ -211,6 +211,10 @@ def build_parser() -> argparse.ArgumentParser:
211
211
  tg.add_argument("--covariates", default=None, help="Sample table (samples × columns) for --condition.")
212
212
  tg.add_argument("--condition", default=None, help="Two-level column of --covariates for a case/control interaction test.")
213
213
  tg.add_argument("--n-neighbours", type=int, default=20)
214
+ tg.add_argument("--max-target-corr", type=float, default=None,
215
+ help="Exclude as targets the modules whose eigengene correlates with the gene's own module above "
216
+ "this, in the observed statistic and in the null (default: none; 0.4 removes split components "
217
+ "of one programme, as for isoform-specific).")
214
218
  tg.add_argument("--no-residualize-eigengenes", action="store_true", default=False)
215
219
  tg.add_argument("--no-leave-one-out", action="store_true", default=False)
216
220
  tg.add_argument("--keep-tx-version", action="store_true", default=False)
@@ -646,7 +650,7 @@ def handle_target_genes(args, logger: logging.Logger) -> None:
646
650
  expr, tx2gene, labels, fids, targets, null=args.null, n_perm=args.n_perm, n_matched=args.n_matched,
647
651
  min_kme=args.min_kme, alpha=args.alpha, counts=counts, condition=condition, n_neighbours=args.n_neighbours,
648
652
  residualize_eigengenes=not args.no_residualize_eigengenes, leave_one_out=not args.no_leave_one_out,
649
- keep_version=args.keep_tx_version, seed=args.seed,
653
+ keep_version=args.keep_tx_version, seed=args.seed, max_target_corr=args.max_target_corr,
650
654
  )
651
655
  res["isoforms"].to_csv(out_dir / "target_isoforms.tsv", sep="\t")
652
656
  res["genes"].to_csv(out_dir / "target_genes.tsv", sep="\t")
@@ -195,10 +195,13 @@ def resolve_target_transcripts(
195
195
  Transcripts of the target genes (plus explicitly listed transcripts) that
196
196
  are present in ``tx_gene`` (feature_id → gene_id for the expression
197
197
  matrix). Gene symbols are resolved through ``annotation`` when given.
198
- Returns (feature_id → gene_id for the targets, list of unmatched inputs).
198
+ Returns (feature_id → gene_id for the targets, list of unmatched inputs);
199
+ the index keeps the matrix's own feature IDs (versions included), while
200
+ listed transcripts match with versions stripped unless ``keep_version``.
199
201
  """
200
202
  tx_gene = tx_gene.copy()
201
- tx_gene.index = [strip_feature_version(f, keep_version) for f in tx_gene.index]
203
+ tx_gene.index = tx_gene.index.astype(str)
204
+ by_id = {strip_feature_version(f, keep_version): f for f in tx_gene.index}
202
205
  unmatched: List[str] = []
203
206
  target_genes: set = set()
204
207
  if genes:
@@ -217,8 +220,8 @@ def resolve_target_transcripts(
217
220
  sel = tx_gene[tx_gene.isin(target_genes)] if target_genes else tx_gene.iloc[0:0]
218
221
  if transcripts:
219
222
  ids = [strip_feature_version(t, keep_version) for t in transcripts]
220
- present = [t for t in ids if t in tx_gene.index]
221
- unmatched.extend([t for t, i in zip(transcripts, ids) if i not in tx_gene.index])
223
+ present = [by_id[t] for t in ids if t in by_id]
224
+ unmatched.extend([t for t, i in zip(transcripts, ids) if i not in by_id])
222
225
  sel = pd.concat([sel, tx_gene.loc[present]])
223
226
  sel = sel[~sel.index.duplicated()]
224
227
  return sel, unmatched
@@ -300,6 +303,7 @@ def target_gene_tests(
300
303
  leave_one_out: bool = True,
301
304
  keep_version: bool = False,
302
305
  seed: int = 0,
306
+ max_target_corr: Optional[float] = None,
303
307
  ) -> Dict[str, pd.DataFrame]:
304
308
  """
305
309
  Test every isoform of the target genes for membership of a module other
@@ -327,6 +331,17 @@ def target_gene_tests(
327
331
  Two-level sample grouping (index = sample IDs); the best-other kME is
328
332
  then also estimated per group and the difference tested with
329
333
  Fisher's z (``p_interaction``).
334
+ max_target_corr
335
+ Exclude as targets the modules whose eigengene correlates with the
336
+ gene's own module eigengene above this (absolute Pearson correlation
337
+ of the eigengenes, as in
338
+ :func:`bsvae.utils.isoform.isoform_specific_calls`). The exclusion
339
+ is applied before the best module is chosen and inside every null
340
+ (the permutation maximum, the matched pool's best-other |kME| and the
341
+ analytic Bonferroni factor), so the test compares like with like.
342
+ ``n_targets`` reports the eligible modules per isoform; an isoform
343
+ with none gets ``best_other_module = -1``, ``kme_residual = 0`` and
344
+ ``p_value = 1``. Default ``None`` (no exclusion).
330
345
 
331
346
  min_kme, alpha
332
347
  Effect-size floor and BH FDR within the target set. The floor is
@@ -399,27 +414,56 @@ def target_gene_tests(
399
414
  K[rows_idx] = Zrows[rows_idx] @ Eg / (S - 1)
400
415
  return K
401
416
 
417
+ # eligible target modules per gene module: never the gene's own module and, with
418
+ # max_target_corr, never a module whose eigengene tracks the gene's module (a split
419
+ # component of the same programme rather than a divergence target)
420
+ Ecorr = None
421
+ if max_target_corr is not None and len(cols) > 1:
422
+ Ecorr = np.atleast_2d(np.nan_to_num(np.corrcoef(eigengenes.values.T.astype(np.float64))))
423
+
424
+ def eligible(g: int) -> np.ndarray:
425
+ ok = np.ones(len(cols), bool)
426
+ if g in cols:
427
+ j = cols.index(int(g))
428
+ ok[j] = False
429
+ if Ecorr is not None:
430
+ ok &= np.abs(Ecorr[j]) <= float(max_target_corr)
431
+ ok[j] = False
432
+ return ok
433
+
434
+ elig = {int(g): eligible(int(g)) for g in np.unique(gm)}
435
+
436
+ def mask_other(Kmat: np.ndarray, gmods: np.ndarray) -> np.ndarray:
437
+ Km = Kmat.copy()
438
+ for i, g in enumerate(gmods):
439
+ ok = elig.get(int(g))
440
+ if ok is None:
441
+ ok = elig[int(g)] = eligible(int(g))
442
+ Km[i, ~ok] = 0.0
443
+ return Km
444
+
402
445
  K = kme_matrix(Z, gm)
403
446
  kme_own = np.array([K[i, cols.index(int(g))] if g in cols else np.nan for i, g in enumerate(gm)])
404
- K_other = K.copy()
405
- for i, g in enumerate(gm):
406
- if g in cols:
407
- K_other[i, cols.index(int(g))] = 0.0
447
+ K_other = mask_other(K, gm)
448
+ n_targets = np.array([int(elig[int(g)].sum()) for g in gm])
408
449
  best_j = np.abs(K_other).argmax(axis=1)
409
450
  best = K_other[np.arange(len(targets)), best_j]
451
+ no_target = n_targets == 0
410
452
 
411
453
  # null distribution of the best-other |kME|
412
454
  if null == "analytic":
413
455
  from scipy import stats as sps
414
456
  rr = np.minimum(best ** 2, 1 - 1e-12)
415
457
  tval = np.abs(best) * np.sqrt(max(S - 2, 1) / (1 - rr))
416
- p = np.minimum(2.0 * sps.t.sf(tval, max(S - 2, 1)) * len(cols), 1.0)
458
+ p = np.minimum(2.0 * sps.t.sf(tval, max(S - 2, 1)) * np.maximum(n_targets, 1), 1.0)
417
459
  elif null == "permutation":
418
- p = np.zeros(len(targets))
460
+ p = np.ones(len(targets))
419
461
  for g in np.unique(gm):
420
462
  rows_idx = np.where(gm == g)[0]
421
463
  Eg = _standardize_cols(_residualize_eigengenes(eigengenes, cols.index(int(g))).values) if (residualize_eigengenes and g in cols) else E
422
- other = [j for j in range(len(cols)) if cols[j] != g]
464
+ other = np.where(elig[int(g)])[0]
465
+ if other.size == 0:
466
+ continue
423
467
  for i in rows_idx:
424
468
  perms = np.array([rng.permutation(S) for _ in range(int(n_perm))])
425
469
  null_k = (Z[i][perms] @ Eg[:, other]) / (S - 1)
@@ -437,10 +481,7 @@ def target_gene_tests(
437
481
  pool_feat = np.column_stack([pool_expr.mean(axis=1).values, (pool_expr.values == 0).mean(axis=1)])
438
482
  pool_Z = _zscore_rows(resid_all.loc[pool].values)
439
483
  pool_gm = np.array([gene_mod.get(g, -1) for g in stats_all.loc[pool, "gene_id"].values])
440
- pool_K = kme_matrix(pool_Z, pool_gm)
441
- for i, g in enumerate(pool_gm):
442
- if g in cols:
443
- pool_K[i, cols.index(int(g))] = 0.0
484
+ pool_K = mask_other(kme_matrix(pool_Z, pool_gm), pool_gm)
444
485
  pool_best = np.abs(pool_K).max(axis=1)
445
486
  tgt_expr = expr.reindex(targets.index)
446
487
  tgt_feat = np.column_stack([tgt_expr.mean(axis=1).values, (tgt_expr.values == 0).mean(axis=1)])
@@ -450,8 +491,10 @@ def target_gene_tests(
450
491
  d = (((pool_feat - tgt_feat[i]) / scale) ** 2).sum(axis=1)
451
492
  near = np.argsort(d, kind="stable")[: int(n_matched)]
452
493
  p[i] = (1.0 + (pool_best[near] >= abs(best[i])).sum()) / (len(near) + 1.0)
494
+ p = np.where(no_target, 1.0, p)
495
+ best = np.where(no_target, 0.0, best)
453
496
  q = _bh_adjust(p)
454
- called = (np.abs(best) >= float(min_kme)) & (q < float(alpha))
497
+ called = (np.abs(best) >= float(min_kme)) & (q < float(alpha)) & ~no_target
455
498
 
456
499
  out = pd.DataFrame({
457
500
  "gene_id": targets.values,
@@ -463,7 +506,8 @@ def target_gene_tests(
463
506
  "isoform_module": own,
464
507
  "label_discordant": (own >= 0) & (gm >= 0) & (own != gm),
465
508
  "kme_gene_module": kme_own,
466
- "best_other_module": [cols[j] for j in best_j],
509
+ "n_targets": n_targets,
510
+ "best_other_module": [-1 if nt else cols[j] for j, nt in zip(best_j, no_target)],
467
511
  "kme_residual": best,
468
512
  "p_value": p,
469
513
  "q_value": q,
@@ -498,9 +542,10 @@ def target_gene_tests(
498
542
  n1, n2 = masks[0].sum(), masks[1].sum()
499
543
  z1, z2 = np.arctanh(np.clip(k_grp[0], -0.999, 0.999)), np.arctanh(np.clip(k_grp[1], -0.999, 0.999))
500
544
  zdiff = (z1 - z2) / np.sqrt(1.0 / max(n1 - 3, 1) + 1.0 / max(n2 - 3, 1))
545
+ zdiff = np.where(no_target, np.nan, zdiff)
501
546
  from scipy import stats as sps
502
- out[f"kme_{levels[0]}"] = k_grp[0]
503
- out[f"kme_{levels[1]}"] = k_grp[1]
547
+ out[f"kme_{levels[0]}"] = np.where(no_target, np.nan, k_grp[0])
548
+ out[f"kme_{levels[1]}"] = np.where(no_target, np.nan, k_grp[1])
504
549
  out["z_interaction"] = zdiff
505
550
  out["p_interaction"] = 2.0 * sps.norm.sf(np.abs(zdiff))
506
551
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes