synomicsbench 1.0.0__py3-none-any.whl

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 (41) hide show
  1. synomicsbench/__init__.py +1 -0
  2. synomicsbench/metrics/__init__.py +13 -0
  3. synomicsbench/metrics/fidelity/BayesianComparison.py +356 -0
  4. synomicsbench/metrics/fidelity/MissingValueSimilarity.py +32 -0
  5. synomicsbench/metrics/fidelity/PairwiseSimilarity.py +643 -0
  6. synomicsbench/metrics/fidelity/UnivariateSimilarity.py +188 -0
  7. synomicsbench/metrics/fidelity/__init__.py +1 -0
  8. synomicsbench/metrics/fidelity/utils.py +196 -0
  9. synomicsbench/metrics/fidelity/visualization.py +288 -0
  10. synomicsbench/metrics/narrow_utility/BayesianComparison.py +547 -0
  11. synomicsbench/metrics/narrow_utility/DGE.py +399 -0
  12. synomicsbench/metrics/narrow_utility/GSEA.py +367 -0
  13. synomicsbench/metrics/narrow_utility/__init__.py +1 -0
  14. synomicsbench/metrics/narrow_utility/cell_deconvolution.py +86 -0
  15. synomicsbench/metrics/narrow_utility/predictive_model_comp.py +92 -0
  16. synomicsbench/metrics/narrow_utility/survival_analysis.py +418 -0
  17. synomicsbench/metrics/privacy/__init__.py +1 -0
  18. synomicsbench/metrics/privacy/inference.py +96 -0
  19. synomicsbench/metrics/privacy/linkability.py +90 -0
  20. synomicsbench/metrics/privacy/singling_out.py +107 -0
  21. synomicsbench/metrics/privacy/visualization.py +606 -0
  22. synomicsbench/processing/__init__.py +0 -0
  23. synomicsbench/processing/gene_query.py +202 -0
  24. synomicsbench/processing/metadata.py +264 -0
  25. synomicsbench/processing/pipeline.py +683 -0
  26. synomicsbench/processing/postprocessing.py +235 -0
  27. synomicsbench/processing/preprocessing.py +1004 -0
  28. synomicsbench/synthesizer/BaseSynthesizer.py +326 -0
  29. synomicsbench/synthesizer/CTGANsynthesizer.py +133 -0
  30. synomicsbench/synthesizer/GaussianCopulasynthesizer.py +351 -0
  31. synomicsbench/synthesizer/GaussianMultivariate_Parallel.py +463 -0
  32. synomicsbench/synthesizer/Synthpopsynthesizer.py +352 -0
  33. synomicsbench/synthesizer/TVAEsynthesizer.py +141 -0
  34. synomicsbench/synthesizer/__init__.py +0 -0
  35. synomicsbench/utils/__init__.py +1 -0
  36. synomicsbench/utils/correlations.py +799 -0
  37. synomicsbench/utils/monitoring.py +135 -0
  38. synomicsbench-1.0.0.dist-info/METADATA +162 -0
  39. synomicsbench-1.0.0.dist-info/RECORD +41 -0
  40. synomicsbench-1.0.0.dist-info/WHEEL +4 -0
  41. synomicsbench-1.0.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1 @@
1
+ __version__ = "1.0.0"
@@ -0,0 +1,13 @@
1
+ """
2
+ Metrics module for evaluating synthetic data quality.
3
+
4
+ This module provides comprehensive evaluation metrics across three dimensions:
5
+ - Statistical Fidelity: Univariate and bivariate similarity measures
6
+ - Biological Utility: Downstream bioinformatics task performance
7
+ - Privacy Risk: Attack-based privacy assessment
8
+
9
+ Submodules:
10
+ - fidelity: Statistical similarity metrics
11
+ - narrow_utility: Biological signal preservation metrics
12
+ - privacy: Privacy attack risk assessment
13
+ """
@@ -0,0 +1,356 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Dict, List, Mapping, Optional, Sequence, Tuple
5
+
6
+ import numpy as np
7
+ import pandas as pd
8
+ import seaborn as sns
9
+ import matplotlib.pyplot as plt
10
+
11
+ try:
12
+ import baycomp
13
+ except ImportError as e:
14
+ raise ImportError("baycomp is required. Install with: pip install baycomp") from e
15
+
16
+ from matplotlib.colors import LinearSegmentedColormap, TwoSlopeNorm
17
+
18
+
19
+ # -----------------------------
20
+ # Nature-style + consistent colormap
21
+ # -----------------------------
22
+
23
+ _FONT = {
24
+ "family": "sans-serif",
25
+ "sans-serif": ["Helvetica", "Arial", "DejaVu Sans"],
26
+ }
27
+
28
+ STABILITY_PLOT_COLORS = {
29
+ "grid": "#e6e6e6",
30
+ "heatmap_bg": "#f7f7f7",
31
+ }
32
+
33
+ PBETTER_FOCUS_CMAP = LinearSegmentedColormap.from_list(
34
+ "pbetter_focus",
35
+ [
36
+ (0.0, "#ffffff"),
37
+ (0.5, "#ffffff"),
38
+ (1.0, "#1a9850"),
39
+ ],
40
+ )
41
+
42
+ CANCER_COLORS = {
43
+ "ccRCC": "#4C72B0",
44
+ "Melanoma": "#DD8452",
45
+ "NSCLC": "#55A868",
46
+ }
47
+
48
+
49
+ def _set_nature_rcparams(fontsize: int = 11) -> None:
50
+ """
51
+ Apply Nature-like matplotlib rcParams (Helvetica/Arial and clean axes).
52
+
53
+ Args:
54
+ fontsize (int): Base font size for the figure.
55
+
56
+ Raises:
57
+ ValueError: If fontsize is not positive.
58
+ """
59
+ if fontsize <= 0:
60
+ raise ValueError("fontsize must be a positive integer.")
61
+
62
+ plt.rcParams.update(
63
+ {
64
+ "font.size": fontsize,
65
+ "font.family": _FONT["family"],
66
+ "font.sans-serif": _FONT["sans-serif"],
67
+ "pdf.fonttype": 42,
68
+ "ps.fonttype": 42,
69
+ "axes.titlesize": fontsize + 1,
70
+ "axes.labelsize": fontsize,
71
+ "xtick.labelsize": fontsize - 1,
72
+ "ytick.labelsize": fontsize - 1,
73
+ "legend.fontsize": fontsize - 1,
74
+ }
75
+ )
76
+
77
+
78
+ @dataclass(frozen=True)
79
+ class BaycompStyle:
80
+ """
81
+ Store manuscript-style visualization settings for baycomp heatmaps.
82
+
83
+ Args:
84
+ fontsize (int): Base font size used in matplotlib rcParams.
85
+ nature_font (Dict[str, Sequence[str]]): Font family configuration.
86
+ plot_colors (Dict[str, str]): Common background/grid colors.
87
+ pbetter_cmap (LinearSegmentedColormap): Colormap for P(Better) heatmaps.
88
+ cancer_colors (Dict[str, str]): Color strip mapping for each cancer panel.
89
+
90
+ Raises:
91
+ ValueError: If fontsize is not positive.
92
+ """
93
+ fontsize: int = 11
94
+ nature_font: Dict[str, Sequence[str]] = field(default_factory=lambda: dict(_FONT))
95
+ plot_colors: Dict[str, str] = field(default_factory=lambda: dict(STABILITY_PLOT_COLORS))
96
+ pbetter_cmap: LinearSegmentedColormap = field(default_factory=lambda: PBETTER_FOCUS_CMAP)
97
+ cancer_colors: Dict[str, str] = field(default_factory=lambda: dict(CANCER_COLORS))
98
+
99
+ def __post_init__(self) -> None:
100
+ if self.fontsize <= 0:
101
+ raise ValueError("fontsize must be a positive integer.")
102
+
103
+
104
+ @dataclass
105
+ class BayesianComparison:
106
+ """
107
+ Compute Bayesian pairwise comparisons using the external baycomp package.
108
+
109
+ Args:
110
+ rope (float): ROPE threshold for practical equivalence.
111
+ seed (int): Random seed for reproducibility in baycomp.
112
+ style (BaycompStyle): Plot styling settings for consistent manuscript figures.
113
+
114
+ Raises:
115
+ ValueError: If rope is not positive.
116
+ """
117
+ rope: float = 0.01
118
+ seed: int = 0
119
+ style: BaycompStyle = field(default_factory=BaycompStyle)
120
+
121
+ def __post_init__(self) -> None:
122
+ if self.rope <= 0:
123
+ raise ValueError("rope must be > 0.")
124
+
125
+ @staticmethod
126
+ def _validate_scores(scores: Sequence[float], method: str) -> np.ndarray:
127
+ """
128
+ Validate and coerce a score sequence to a finite numpy array.
129
+ """
130
+ x = np.asarray(scores, dtype=float)
131
+ x = x[np.isfinite(x)]
132
+ if x.size < 2:
133
+ raise ValueError(f"Method '{method}' must have at least 2 finite scores for baycomp.")
134
+ return x
135
+
136
+ @staticmethod
137
+ def _resolve_methods_order(
138
+ method_to_scores: Mapping[str, Sequence[float]],
139
+ methods_order: Optional[Sequence[str]],
140
+ ) -> List[str]:
141
+ """
142
+ Resolve and validate the method ordering.
143
+ """
144
+ if not method_to_scores or len(method_to_scores) < 2:
145
+ raise ValueError("method_to_scores must contain at least 2 methods.")
146
+
147
+ if methods_order is None:
148
+ return list(method_to_scores.keys())
149
+
150
+ resolved = list(methods_order)
151
+ missing = [m for m in resolved if m not in method_to_scores]
152
+ if missing:
153
+ raise ValueError(f"methods_order contains methods missing in method_to_scores: {missing}")
154
+ return resolved
155
+
156
+ def compare_methods(
157
+ self,
158
+ method_to_scores: Mapping[str, Sequence[float]],
159
+ methods_order: Optional[Sequence[str]] = None,
160
+ ) -> pd.DataFrame:
161
+ """
162
+ Compute ordered-pair Bayesian comparison probabilities for a set of methods.
163
+
164
+ Args:
165
+ method_to_scores (Mapping[str, Sequence[float]]): Mapping method -> scores across seeds/folds.
166
+ methods_order (Optional[Sequence[str]]): Optional ordering for methods.
167
+
168
+ Returns:
169
+ pd.DataFrame: Table with columns:
170
+ ["Method 1", "Method 2", "Better Prob", "Worse Prob", "Equivalent Prob"].
171
+
172
+ Raises:
173
+ ValueError: If fewer than 2 methods are provided.
174
+ ValueError: If any method has fewer than 2 finite scores.
175
+ """
176
+ methods = self._resolve_methods_order(method_to_scores, methods_order)
177
+ scores_np = {m: self._validate_scores(method_to_scores[m], method=m) for m in methods}
178
+
179
+ rows: List[dict] = []
180
+ for i, m1 in enumerate(methods):
181
+ for j, m2 in enumerate(methods):
182
+ if i == j:
183
+ continue
184
+
185
+ probs = baycomp.two_on_single(scores_np[m1], scores_np[m2], rope=self.rope)
186
+ p_better = float(probs[0])
187
+ p_equiv = float(probs[1])
188
+ p_worse = float(probs[2])
189
+
190
+ rows.append(
191
+ {
192
+ "Method 1": m1,
193
+ "Method 2": m2,
194
+ "Better Prob": p_better,
195
+ "Worse Prob": p_worse,
196
+ "Equivalent Prob": p_equiv,
197
+ }
198
+ )
199
+
200
+ return pd.DataFrame(rows)
201
+
202
+ @staticmethod
203
+ def comparison_to_matrix(
204
+ comparison_df: pd.DataFrame,
205
+ methods_order: Sequence[str],
206
+ value_col: str = "Better Prob",
207
+ nan_diagonal: bool = True,
208
+ ) -> pd.DataFrame:
209
+ """
210
+ Convert a comparison table into a square matrix suitable for heatmap plotting.
211
+
212
+ Args:
213
+ comparison_df (pd.DataFrame): Output of compare_methods.
214
+ methods_order (Sequence[str]): Method ordering for rows and columns.
215
+ value_col (str): Which column to visualize.
216
+ nan_diagonal (bool): If True, set diagonal values to NaN.
217
+
218
+ Returns:
219
+ pd.DataFrame: Square matrix with index=Method 1 and columns=Method 2.
220
+
221
+ Raises:
222
+ ValueError: If value_col is not present in comparison_df.
223
+ """
224
+ if value_col not in comparison_df.columns:
225
+ raise ValueError(f"value_col must be a column in comparison_df, got '{value_col}'.")
226
+
227
+ mat = comparison_df.pivot(index="Method 1", columns="Method 2", values=value_col)
228
+ mat = mat.reindex(index=list(methods_order), columns=list(methods_order))
229
+
230
+ if nan_diagonal:
231
+ for m in methods_order:
232
+ if m in mat.index and m in mat.columns:
233
+ mat.loc[m, m] = np.nan
234
+
235
+ return mat
236
+
237
+ def plot_pbetter_heatmap_grid(
238
+ self,
239
+ cancer_to_method_scores: Dict[str, Dict[str, Sequence[float]]],
240
+ cancers_order: Sequence[str] = ("ccRCC", "Melanoma", "NSCLC"),
241
+ methods_order: Optional[Sequence[str]] = None,
242
+ value_col: str = "Better Prob",
243
+ figsize: Tuple[int, int] = (18, 5.5),
244
+ annot: bool = True,
245
+ fmt: str = ".2f",
246
+ show: bool = True,
247
+ ) -> Tuple[plt.Figure, np.ndarray, Dict[str, pd.DataFrame], Dict[str, pd.DataFrame]]:
248
+ """
249
+ Plot a 1xN grid of Bayesian comparison probability heatmaps (one per cancer cohort).
250
+
251
+ Args:
252
+ cancer_to_method_scores (Dict[str, Dict[str, Sequence[float]]]): cancer -> {method -> scores across seeds/folds}.
253
+ cancers_order (Sequence[str]): Order of cohorts in the grid.
254
+ methods_order (Optional[Sequence[str]]): Global method ordering. If None, uses union over cancers.
255
+ value_col (str): One of ["Better Prob", "Worse Prob", "Equivalent Prob"] to visualize.
256
+ figsize (Tuple[int, int]): Figure size in inches.
257
+ annot (bool): If True, annotate each cell with numeric values.
258
+ fmt (str): Annotation format passed to seaborn.
259
+ show (bool): If True, calls plt.show().
260
+
261
+ Returns:
262
+ Tuple[plt.Figure, np.ndarray, Dict[str, pd.DataFrame], Dict[str, pd.DataFrame]]:
263
+ fig, axes, matrices_by_cancer, comparisons_by_cancer
264
+
265
+ Raises:
266
+ ValueError: If value_col is invalid.
267
+ ValueError: If cancers_order contains a cohort not present in cancer_to_method_scores.
268
+ """
269
+ allowed = {"Better Prob", "Worse Prob", "Equivalent Prob"}
270
+ if value_col not in allowed:
271
+ raise ValueError(f"value_col must be one of {sorted(allowed)}.")
272
+
273
+ cancers = list(cancers_order)
274
+ for c in cancers:
275
+ if c not in cancer_to_method_scores:
276
+ raise ValueError(f"Cancer '{c}' missing from cancer_to_method_scores.")
277
+
278
+ if methods_order is None:
279
+ union_methods: List[str] = []
280
+ for c in cancers:
281
+ for m in cancer_to_method_scores[c].keys():
282
+ if m not in union_methods:
283
+ union_methods.append(m)
284
+ methods = union_methods
285
+ else:
286
+ methods = list(methods_order)
287
+
288
+ _set_nature_rcparams(fontsize=self.style.fontsize)
289
+ sns.set(style="white", rc={"axes.facecolor": self.style.plot_colors["heatmap_bg"]})
290
+
291
+ fig, axes = plt.subplots(1, len(cancers), figsize=figsize)
292
+ if len(cancers) == 1:
293
+ axes = np.array([axes])
294
+
295
+ norm = TwoSlopeNorm(vmin=0.0, vcenter=0.5, vmax=1.0)
296
+
297
+ mats_by_cancer: Dict[str, pd.DataFrame] = {}
298
+ comps_by_cancer: Dict[str, pd.DataFrame] = {}
299
+
300
+ for ax, cancer in zip(axes, cancers):
301
+ comp_df = self.compare_methods(
302
+ method_to_scores=cancer_to_method_scores[cancer],
303
+ methods_order=methods,
304
+ )
305
+ comps_by_cancer[cancer] = comp_df
306
+
307
+ mat = self.comparison_to_matrix(
308
+ comparison_df=comp_df,
309
+ methods_order=methods,
310
+ value_col=value_col,
311
+ nan_diagonal=True,
312
+ )
313
+ mats_by_cancer[cancer] = mat
314
+
315
+ if value_col == "Better Prob":
316
+ cmap = self.style.pbetter_cmap.copy()
317
+ used_norm = norm
318
+ else:
319
+ cmap = plt.cm.viridis.copy()
320
+ used_norm = None
321
+ cmap.set_bad(color="#D3D3D3")
322
+
323
+ sns.heatmap(
324
+ mat,
325
+ ax=ax,
326
+ cmap=cmap,
327
+ norm=used_norm,
328
+ annot=annot,
329
+ fmt=fmt,
330
+ linewidths=0.5,
331
+ linecolor="lightgray",
332
+ cbar=(ax is axes[-1]),
333
+ cbar_kws={"label": value_col} if (ax is axes[-1]) else None,
334
+ square=True,
335
+ )
336
+
337
+ ax.set_title(cancer, fontsize=self.style.fontsize + 1, fontweight="bold", pad=10)
338
+
339
+ cancer_color = self.style.cancer_colors.get(cancer, "#333333")
340
+ ax.plot(
341
+ [0.02, 0.98],
342
+ [1.02, 1.02],
343
+ transform=ax.transAxes,
344
+ color=cancer_color,
345
+ lw=4,
346
+ clip_on=False,
347
+ )
348
+
349
+ ax.tick_params(axis="x", rotation=45)
350
+ ax.tick_params(axis="y", rotation=0)
351
+
352
+ plt.tight_layout()
353
+ if show:
354
+ plt.show()
355
+
356
+ return fig, axes, mats_by_cancer, comps_by_cancer
@@ -0,0 +1,32 @@
1
+ from sdmetrics.single_column import MissingValueSimilarity
2
+ import pandas as pd
3
+
4
+ def MissingValue_Similarity(
5
+ origin_data: pd.DataFrame, synthetic_data: pd.DataFrame, missing_indicators: list
6
+ ):
7
+ """
8
+ Compute missing value similarity scores for each target column.
9
+
10
+ Args:
11
+ origin_data (pd.DataFrame): Original data.
12
+ synthetic_data (pd.DataFrame): Synthetic data.
13
+ missing_indicators (list): List of column names with missing indicators.
14
+
15
+ Returns:
16
+ dict: Mapping from column name to similarity score.
17
+
18
+ Raises:
19
+ Exception: If computation fails for any column.
20
+ """
21
+ MissingValue_dict = {}
22
+ target_cols = [col.replace("missingindicator_", "") for col in missing_indicators]
23
+ for target_col in target_cols:
24
+ try:
25
+ simi_score = MissingValueSimilarity.compute(
26
+ real_data=origin_data[target_col], synthetic_data=synthetic_data[target_col]
27
+ )
28
+ MissingValue_dict[target_col] = simi_score
29
+ except Exception:
30
+ # Continue but note error in dict
31
+ MissingValue_dict[target_col] = None
32
+ return MissingValue_dict