Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .pytest_cache/.gitignore +2 -0
- .pytest_cache/CACHEDIR.TAG +4 -0
- .pytest_cache/README.md +8 -0
- .pytest_cache/v/cache/lastfailed +3 -0
- .pytest_cache/v/cache/nodeids +110 -0
- analyses/download_eclip.py +451 -0
- analyses/run_all.py +302 -0
- analyses/run_cross_platform.py +278 -0
- analyses/run_deep_advantages.py +784 -0
- analyses/run_mirna_analysis.py +281 -0
- analyses/run_precedence.py +455 -0
- analyses/run_remaining_validation.py +494 -0
- analyses/run_tier1_fixes.py +942 -0
- analyses/run_weakness_fixes.py +892 -0
- analyses/run_weakness_improvements.py +640 -0
- output/cross_dataset/cross_dataset_consistency.csv +2 -0
- output/cross_dataset/summary.json +7 -0
- output/cross_platform/figures/cross_platform_gamma.png +0 -0
- output/cross_platform/results/cross_platform_results.json +37 -0
- output/deep_advantages/figures/pancreas_uncertainty_filtering.png +0 -0
- output/deep_advantages/results/combined_advantages.json +740 -0
- output/deep_advantages/results/dentate_gyrus_advantages.json +373 -0
- output/deep_advantages/results/pancreas_advantages.json +365 -0
- output/deep_benchmark/figures/dentate_gyrus_analytical_vs_deepptr.png +0 -0
- output/deep_benchmark/figures/dentate_gyrus_training_curves.png +0 -0
- output/deep_benchmark/figures/dentate_gyrus_uncertainty.png +0 -0
- output/deep_benchmark/figures/pancreas_analytical_vs_deepptr.png +0 -0
- output/deep_benchmark/figures/synthetic_training_curves.png +0 -0
- output/deep_benchmark/results/combined_benchmark.json +249 -0
- output/deep_benchmark/results/dentate_gyrus_benchmark.json +101 -0
- output/deep_benchmark/results/pancreas_benchmark.json +101 -0
- output/deep_benchmark/results/scifate_benchmark.json +34 -0
- output/deep_benchmark/results/synthetic_recovery.json +11 -0
- output/deep_benchmark_v2/results/combined_v2.json +342 -0
- output/deep_benchmark_v2/results/dentate_gyrus_v2.json +167 -0
- output/deep_benchmark_v2/results/pancreas_v2.json +125 -0
- output/deep_benchmark_v2/results/subsampling_robustness.json +26 -0
- output/deep_benchmarks/02_bootstrap_ci/figures/bootstrap_ci.png +0 -0
- output/deep_benchmarks/20_uncertainty_advantage/results/uncertainty_advantage.json +76 -0
- output/deep_benchmarks/24_fullgenome_gpu/results/fullgenome_gpu.json +112 -0
- output/figures/aim1/enrichment_barplot.png +0 -0
- output/figures/aim1/subsampling_robustness.png +0 -0
- output/figures/aim2/gamma_heatmap.png +0 -0
- output/figures/aim2/tf_ptf_scatter.png +0 -0
- output/gap_analysis/figures/network/network_dentate_gyrus.png +0 -0
- output/gap_analysis/results/invisible_states/dentate_gyrus/invisible_states.csv +12 -0
- output/gap_analysis/results/invisible_states/pancreas/invisible_states.csv +9 -0
- output/gap_analysis/results/network/dentate_gyrus/rbp_hub_counts.csv +31 -0
- output/gap_analysis/results/network/pancreas/network_edges.csv +0 -0
- output/gap_analysis/results/network/pancreas/rbp_hub_counts.csv +31 -0
.pytest_cache/.gitignore
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Created by pytest automatically.
|
| 2 |
+
*
|
.pytest_cache/CACHEDIR.TAG
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Signature: 8a477f597d28d172789f06886806bc55
|
| 2 |
+
# This file is a cache directory tag created by pytest.
|
| 3 |
+
# For information about cache directory tags, see:
|
| 4 |
+
# https://bford.info/cachedir/spec.html
|
.pytest_cache/README.md
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# pytest cache directory #
|
| 2 |
+
|
| 3 |
+
This directory contains data from the pytest's cache plugin,
|
| 4 |
+
which provides the `--lf` and `--ff` options, as well as the `cache` fixture.
|
| 5 |
+
|
| 6 |
+
**Do not** commit this to version control.
|
| 7 |
+
|
| 8 |
+
See [the docs](https://docs.pytest.org/en/stable/how-to/cache.html) for more information.
|
.pytest_cache/v/cache/lastfailed
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"tests/test_beta_groupby.py::test_groupby_consensus_is_median": true
|
| 3 |
+
}
|
.pytest_cache/v/cache/nodeids
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
"tests/test_benchmark.py::test_are_enrichment",
|
| 3 |
+
"tests/test_benchmark.py::test_correlate_no_overlap",
|
| 4 |
+
"tests/test_benchmark.py::test_correlate_with_halflives",
|
| 5 |
+
"tests/test_benchmark.py::test_cross_dataset_consistency",
|
| 6 |
+
"tests/test_benchmark.py::test_enrichment_barplot",
|
| 7 |
+
"tests/test_benchmark.py::test_halflife_scatter",
|
| 8 |
+
"tests/test_benchmark.py::test_nmd_enrichment",
|
| 9 |
+
"tests/test_benchmark.py::test_subsampling_robustness",
|
| 10 |
+
"tests/test_beta.py::test_estimate_beta",
|
| 11 |
+
"tests/test_beta.py::test_estimate_beta_quantile",
|
| 12 |
+
"tests/test_beta_groupby.py::test_groupby_consensus_is_clipped_median",
|
| 13 |
+
"tests/test_beta_groupby.py::test_groupby_consensus_is_median",
|
| 14 |
+
"tests/test_beta_groupby.py::test_groupby_missing_column_raises",
|
| 15 |
+
"tests/test_beta_groupby.py::test_groupby_produces_varm",
|
| 16 |
+
"tests/test_beta_groupby.py::test_groupby_single_group_matches_global",
|
| 17 |
+
"tests/test_datasets.py::test_dentate_gyrus_download",
|
| 18 |
+
"tests/test_datasets.py::test_herzog2017_halflives",
|
| 19 |
+
"tests/test_datasets.py::test_pancreas_download",
|
| 20 |
+
"tests/test_datasets.py::test_schofield2018_halflives",
|
| 21 |
+
"tests/test_deep_data.py::TestGetLibrarySizes::test_correct_sums",
|
| 22 |
+
"tests/test_deep_data.py::TestGetLibrarySizes::test_missing_layer_raises",
|
| 23 |
+
"tests/test_deep_data.py::TestGetLibrarySizes::test_positive",
|
| 24 |
+
"tests/test_deep_data.py::TestGetLibrarySizes::test_shapes",
|
| 25 |
+
"tests/test_deep_data.py::TestSetupDataloaders::test_batch_contents",
|
| 26 |
+
"tests/test_deep_data.py::TestSetupDataloaders::test_no_overlap",
|
| 27 |
+
"tests/test_deep_data.py::TestSetupDataloaders::test_reproducible",
|
| 28 |
+
"tests/test_deep_data.py::TestSetupDataloaders::test_returns_four",
|
| 29 |
+
"tests/test_deep_data.py::TestSetupDataloaders::test_stratified_split",
|
| 30 |
+
"tests/test_deep_distributions.py::TestLogNBPositive::test_batch_consistency",
|
| 31 |
+
"tests/test_deep_distributions.py::TestLogNBPositive::test_gradient_flows",
|
| 32 |
+
"tests/test_deep_distributions.py::TestLogNBPositive::test_higher_theta_less_variance",
|
| 33 |
+
"tests/test_deep_distributions.py::TestLogNBPositive::test_non_positive",
|
| 34 |
+
"tests/test_deep_distributions.py::TestLogNBPositive::test_output_shape",
|
| 35 |
+
"tests/test_deep_distributions.py::TestLogNBPositive::test_peak_at_mean",
|
| 36 |
+
"tests/test_deep_guide.py::TestExtractLatent::test_deterministic",
|
| 37 |
+
"tests/test_deep_guide.py::TestExtractLatent::test_no_nans",
|
| 38 |
+
"tests/test_deep_guide.py::TestExtractLatent::test_shapes",
|
| 39 |
+
"tests/test_deep_guide.py::TestPosteriorGamma::test_more_samples_lower_variance_of_mean",
|
| 40 |
+
"tests/test_deep_guide.py::TestPosteriorGamma::test_no_nans",
|
| 41 |
+
"tests/test_deep_guide.py::TestPosteriorGamma::test_positive_values",
|
| 42 |
+
"tests/test_deep_guide.py::TestPosteriorGamma::test_shapes",
|
| 43 |
+
"tests/test_deep_model.py::TestDeepPTR::test_backward",
|
| 44 |
+
"tests/test_deep_model.py::TestDeepPTR::test_forward_loss",
|
| 45 |
+
"tests/test_deep_model.py::TestDeepPTR::test_get_latent",
|
| 46 |
+
"tests/test_deep_model.py::TestDeepPTR::test_kl_weight_zero",
|
| 47 |
+
"tests/test_deep_model.py::TestDeepPTR::test_reparameterize_stochastic",
|
| 48 |
+
"tests/test_deep_model.py::TestEncoder::test_different_inputs_different_outputs",
|
| 49 |
+
"tests/test_deep_model.py::TestEncoder::test_output_shapes",
|
| 50 |
+
"tests/test_deep_model.py::TestKineticDecoder::test_beta_is_not_cell_specific",
|
| 51 |
+
"tests/test_deep_model.py::TestKineticDecoder::test_mu_scales_with_library_size",
|
| 52 |
+
"tests/test_deep_model.py::TestKineticDecoder::test_output_shapes",
|
| 53 |
+
"tests/test_deep_model.py::TestKineticDecoder::test_positive_outputs",
|
| 54 |
+
"tests/test_deep_synthetic.py::TestCICoverage::test_perfect_coverage",
|
| 55 |
+
"tests/test_deep_synthetic.py::TestCICoverage::test_returns_fraction",
|
| 56 |
+
"tests/test_deep_synthetic.py::TestCICoverage::test_zero_variance_coverage",
|
| 57 |
+
"tests/test_deep_synthetic.py::TestGammaRecovery::test_global_mode",
|
| 58 |
+
"tests/test_deep_synthetic.py::TestGammaRecovery::test_perfect_recovery",
|
| 59 |
+
"tests/test_deep_synthetic.py::TestGammaRecovery::test_random_is_low",
|
| 60 |
+
"tests/test_deep_synthetic.py::TestGenerateKineticData::test_cell_types",
|
| 61 |
+
"tests/test_deep_synthetic.py::TestGenerateKineticData::test_non_negative_counts",
|
| 62 |
+
"tests/test_deep_synthetic.py::TestGenerateKineticData::test_reproducible",
|
| 63 |
+
"tests/test_deep_synthetic.py::TestGenerateKineticData::test_shapes",
|
| 64 |
+
"tests/test_deep_synthetic.py::TestGenerateKineticData::test_sparsity",
|
| 65 |
+
"tests/test_deep_synthetic.py::TestGenerateKineticData::test_truth_keys",
|
| 66 |
+
"tests/test_deep_synthetic.py::TestGenerateKineticData::test_truth_shapes",
|
| 67 |
+
"tests/test_deep_synthetic.py::TestLatentRecovery::test_perfect_recovery",
|
| 68 |
+
"tests/test_deep_synthetic.py::TestLatentRecovery::test_random_is_lower",
|
| 69 |
+
"tests/test_deep_trainer.py::TestTrainer::test_early_stopping",
|
| 70 |
+
"tests/test_deep_trainer.py::TestTrainer::test_fit_runs",
|
| 71 |
+
"tests/test_deep_trainer.py::TestTrainer::test_history_fields",
|
| 72 |
+
"tests/test_deep_trainer.py::TestTrainer::test_kl_warmup",
|
| 73 |
+
"tests/test_deep_trainer.py::TestTrainer::test_loss_decreases",
|
| 74 |
+
"tests/test_gamma.py::test_estimate_gamma",
|
| 75 |
+
"tests/test_gamma.py::test_gamma_clipping",
|
| 76 |
+
"tests/test_gamma.py::test_gamma_requires_beta",
|
| 77 |
+
"tests/test_gamma_dynamic.py::test_dynamic_mode_logs_params",
|
| 78 |
+
"tests/test_gamma_dynamic.py::test_dynamic_mode_produces_gamma",
|
| 79 |
+
"tests/test_gamma_dynamic.py::test_dynamic_requires_velocity_layer",
|
| 80 |
+
"tests/test_gamma_dynamic.py::test_steady_state_mode_default",
|
| 81 |
+
"tests/test_gamma_dynamic.py::test_unknown_mode_raises",
|
| 82 |
+
"tests/test_integration.py::test_full_pipeline",
|
| 83 |
+
"tests/test_integration.py::test_network_inference",
|
| 84 |
+
"tests/test_network_priors.py::test_list_known_rbps",
|
| 85 |
+
"tests/test_network_priors.py::test_load_motif_priors_valid",
|
| 86 |
+
"tests/test_network_priors.py::test_load_motif_priors_validates_columns",
|
| 87 |
+
"tests/test_network_priors.py::test_network_prior_logs_has_prior",
|
| 88 |
+
"tests/test_network_priors.py::test_network_with_priors",
|
| 89 |
+
"tests/test_network_priors.py::test_network_without_priors_unchanged",
|
| 90 |
+
"tests/test_plotting.py::test_gamma_heatmap",
|
| 91 |
+
"tests/test_plotting.py::test_gamma_violin",
|
| 92 |
+
"tests/test_plotting.py::test_phase_portrait",
|
| 93 |
+
"tests/test_plotting.py::test_pt_comparison",
|
| 94 |
+
"tests/test_plotting.py::test_pt_umap",
|
| 95 |
+
"tests/test_plotting.py::test_pt_velocity_embedding",
|
| 96 |
+
"tests/test_plotting.py::test_pt_velocity_stream",
|
| 97 |
+
"tests/test_plotting.py::test_tf_ptf_scatter",
|
| 98 |
+
"tests/test_preprocessing.py::test_filter_cells",
|
| 99 |
+
"tests/test_preprocessing.py::test_filter_genes",
|
| 100 |
+
"tests/test_preprocessing.py::test_neighbors",
|
| 101 |
+
"tests/test_preprocessing.py::test_normalize_layers",
|
| 102 |
+
"tests/test_preprocessing.py::test_smooth_fixed_bandwidth",
|
| 103 |
+
"tests/test_preprocessing.py::test_smooth_layers",
|
| 104 |
+
"tests/test_pt_states.py::test_pt_states",
|
| 105 |
+
"tests/test_pt_states.py::test_rank_pt_genes",
|
| 106 |
+
"tests/test_readwrite.py::test_read_h5ad_no_warning_with_layers",
|
| 107 |
+
"tests/test_readwrite.py::test_read_h5ad_warns_missing_layers",
|
| 108 |
+
"tests/test_readwrite.py::test_validate_layers_warns_partial",
|
| 109 |
+
"tests/test_variance.py::test_variance_decomposition"
|
| 110 |
+
]
|
analyses/download_eclip.py
ADDED
|
@@ -0,0 +1,451 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Download ENCODE eCLIP data for specific RBPs and extract gene-level targets.
|
| 4 |
+
|
| 5 |
+
Steps:
|
| 6 |
+
1. Query ENCODE REST API for eCLIP experiments per RBP
|
| 7 |
+
2. Download IDR-merged peak files (GRCh38, bed narrowPeak)
|
| 8 |
+
3. Map peak coordinates to genes using Ensembl gene annotations (via pybiomart)
|
| 9 |
+
4. Save result as CSV: rbp, target_gene
|
| 10 |
+
|
| 11 |
+
For RBPs without direct ENCODE eCLIP data, close family members are used
|
| 12 |
+
as proxies where available (e.g., TRA2A for TRA2B, MBNL1 for MBNL2).
|
| 13 |
+
|
| 14 |
+
Author: auto-generated
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
import os
|
| 18 |
+
import sys
|
| 19 |
+
import time
|
| 20 |
+
import gzip
|
| 21 |
+
import bisect
|
| 22 |
+
import logging
|
| 23 |
+
from collections import defaultdict
|
| 24 |
+
|
| 25 |
+
import requests
|
| 26 |
+
import pandas as pd
|
| 27 |
+
from pybiomart import Server
|
| 28 |
+
|
| 29 |
+
# ---------------------------------------------------------------------------
|
| 30 |
+
# Configuration
|
| 31 |
+
# ---------------------------------------------------------------------------
|
| 32 |
+
|
| 33 |
+
# Primary RBPs requested by the user
|
| 34 |
+
RBPS = [
|
| 35 |
+
"HNRNPA1", "YBX1", "ELAVL1", "SRSF3", "RBFOX2", "FUS",
|
| 36 |
+
"HNRNPD", "CELF2", "MATR3", "HNRNPC", "HNRNPU", "RBFOX3",
|
| 37 |
+
"ELAVL3", "ELAVL4", "ZFP36L1", "TRA2B", "MBNL2",
|
| 38 |
+
]
|
| 39 |
+
|
| 40 |
+
# Mapping from requested RBP -> ENCODE target label.
|
| 41 |
+
# Where the exact gene is not in ENCODE eCLIP, we use a closely related
|
| 42 |
+
# family member as a proxy. These are noted in ENCODE_LABEL_NOTE.
|
| 43 |
+
ENCODE_LABEL = {
|
| 44 |
+
"HNRNPA1": "HNRNPA1",
|
| 45 |
+
"YBX1": None, # Not in ENCODE eCLIP (YBX3 is, but too divergent)
|
| 46 |
+
"ELAVL1": "ELAVL1",
|
| 47 |
+
"SRSF3": None, # Not in ENCODE eCLIP
|
| 48 |
+
"RBFOX2": "RBFOX2",
|
| 49 |
+
"FUS": "FUS",
|
| 50 |
+
"HNRNPD": None, # Not in ENCODE eCLIP
|
| 51 |
+
"CELF2": None, # Not in ENCODE eCLIP
|
| 52 |
+
"MATR3": "MATR3",
|
| 53 |
+
"HNRNPC": "HNRNPC",
|
| 54 |
+
"HNRNPU": "HNRNPU",
|
| 55 |
+
"RBFOX3": None, # Not in ENCODE eCLIP
|
| 56 |
+
"ELAVL3": None, # Not in ENCODE eCLIP (ELAVL1 too divergent in targets)
|
| 57 |
+
"ELAVL4": None, # Not in ENCODE eCLIP
|
| 58 |
+
"ZFP36L1": None, # Not in ENCODE eCLIP
|
| 59 |
+
"TRA2B": "TRA2A", # TRA2A is close paralog, same eCLIP binding profile
|
| 60 |
+
"MBNL2": "MBNL1", # MBNL1 is close paralog
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
# Notes about proxy usage
|
| 64 |
+
ENCODE_LABEL_NOTE = {
|
| 65 |
+
"TRA2B": "proxy:TRA2A",
|
| 66 |
+
"MBNL2": "proxy:MBNL1",
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
OUTPUT_CSV = "/home/bcheng/scPTR/src/scptr/benchmark/data/eclip_targets.csv"
|
| 70 |
+
|
| 71 |
+
ENCODE_BASE = "https://www.encodeproject.org"
|
| 72 |
+
ENCODE_HEADERS = {"Accept": "application/json"}
|
| 73 |
+
|
| 74 |
+
# Sleep between ENCODE API requests (seconds)
|
| 75 |
+
REQUEST_DELAY = 0.3
|
| 76 |
+
|
| 77 |
+
logging.basicConfig(
|
| 78 |
+
level=logging.INFO,
|
| 79 |
+
format="%(asctime)s [%(levelname)s] %(message)s",
|
| 80 |
+
)
|
| 81 |
+
log = logging.getLogger(__name__)
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
# ---------------------------------------------------------------------------
|
| 85 |
+
# Step 1: Build gene coordinate index from Ensembl (GRCh38)
|
| 86 |
+
# ---------------------------------------------------------------------------
|
| 87 |
+
|
| 88 |
+
def fetch_gene_annotations() -> pd.DataFrame:
|
| 89 |
+
"""
|
| 90 |
+
Retrieve gene annotations from Ensembl BioMart (GRCh38).
|
| 91 |
+
Keeps protein-coding genes and lncRNAs on standard chromosomes.
|
| 92 |
+
Returns a DataFrame with columns: chrom, start, end, strand, gene_name
|
| 93 |
+
"""
|
| 94 |
+
log.info("Fetching gene annotations from Ensembl BioMart ...")
|
| 95 |
+
server = Server(host="http://www.ensembl.org")
|
| 96 |
+
dataset = server["ENSEMBL_MART_ENSEMBL"]["hsapiens_gene_ensembl"]
|
| 97 |
+
|
| 98 |
+
result = dataset.query(
|
| 99 |
+
attributes=[
|
| 100 |
+
"chromosome_name",
|
| 101 |
+
"start_position",
|
| 102 |
+
"end_position",
|
| 103 |
+
"strand",
|
| 104 |
+
"external_gene_name",
|
| 105 |
+
"gene_biotype",
|
| 106 |
+
],
|
| 107 |
+
)
|
| 108 |
+
|
| 109 |
+
result.columns = ["chrom", "start", "end", "strand", "gene_name", "biotype"]
|
| 110 |
+
|
| 111 |
+
# Keep protein-coding genes and lncRNAs (commonly bound by RBPs)
|
| 112 |
+
keep_biotypes = {"protein_coding", "lncRNA"}
|
| 113 |
+
result = result[result["biotype"].isin(keep_biotypes)].copy()
|
| 114 |
+
|
| 115 |
+
# Only keep standard chromosomes (1-22, X, Y)
|
| 116 |
+
standard_chroms = {str(c) for c in range(1, 23)} | {"X", "Y"}
|
| 117 |
+
result = result[result["chrom"].isin(standard_chroms)].copy()
|
| 118 |
+
|
| 119 |
+
# Add 'chr' prefix to match ENCODE bed files
|
| 120 |
+
result["chrom"] = "chr" + result["chrom"].astype(str)
|
| 121 |
+
|
| 122 |
+
# Drop rows without gene names
|
| 123 |
+
result = result[result["gene_name"].notna() & (result["gene_name"] != "")].copy()
|
| 124 |
+
result = result.drop(columns=["biotype"]).reset_index(drop=True)
|
| 125 |
+
|
| 126 |
+
log.info(f" Retrieved {len(result):,} gene annotations")
|
| 127 |
+
return result
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
def build_gene_index(genes_df: pd.DataFrame) -> dict:
|
| 131 |
+
"""
|
| 132 |
+
Build a chromosome-indexed dict for fast overlap queries.
|
| 133 |
+
Returns: {chrom: list of (start, end, gene_name)} sorted by start.
|
| 134 |
+
"""
|
| 135 |
+
index = defaultdict(list)
|
| 136 |
+
for _, row in genes_df.iterrows():
|
| 137 |
+
index[row["chrom"]].append(
|
| 138 |
+
(int(row["start"]), int(row["end"]), row["gene_name"])
|
| 139 |
+
)
|
| 140 |
+
|
| 141 |
+
# Sort each chromosome by start position
|
| 142 |
+
for chrom in index:
|
| 143 |
+
index[chrom].sort(key=lambda x: x[0])
|
| 144 |
+
|
| 145 |
+
return dict(index)
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
def find_overlapping_genes(
|
| 149 |
+
chrom: str, peak_start: int, peak_end: int, gene_index: dict
|
| 150 |
+
) -> set:
|
| 151 |
+
"""
|
| 152 |
+
Find all genes whose genomic interval overlaps with a peak region.
|
| 153 |
+
Uses binary search on sorted gene starts for efficiency.
|
| 154 |
+
"""
|
| 155 |
+
genes = gene_index.get(chrom, [])
|
| 156 |
+
if not genes:
|
| 157 |
+
return set()
|
| 158 |
+
|
| 159 |
+
starts = [g[0] for g in genes]
|
| 160 |
+
# Find the index of the first gene whose start >= peak_end
|
| 161 |
+
right_idx = bisect.bisect_left(starts, peak_end)
|
| 162 |
+
|
| 163 |
+
overlapping = set()
|
| 164 |
+
|
| 165 |
+
# Scan backwards from right_idx to find all genes overlapping the peak.
|
| 166 |
+
# A gene overlaps if gene_start < peak_end AND gene_end > peak_start.
|
| 167 |
+
# Since genes are sorted by start, once gene_start drops well below
|
| 168 |
+
# peak_start we use a distance cutoff to stop (genes can be long).
|
| 169 |
+
for i in range(max(0, right_idx - 1), -1, -1):
|
| 170 |
+
g_start, g_end, g_name = genes[i]
|
| 171 |
+
if g_start < peak_end and g_end > peak_start:
|
| 172 |
+
overlapping.add(g_name)
|
| 173 |
+
# Safety cutoff: stop if gene starts > 2 Mb before peak start
|
| 174 |
+
if g_start < peak_start - 2_000_000:
|
| 175 |
+
break
|
| 176 |
+
|
| 177 |
+
# Also check a few genes forward (edge cases at the boundary)
|
| 178 |
+
for i in range(right_idx, min(len(genes), right_idx + 10)):
|
| 179 |
+
g_start, g_end, g_name = genes[i]
|
| 180 |
+
if g_start >= peak_end:
|
| 181 |
+
break
|
| 182 |
+
if g_start < peak_end and g_end > peak_start:
|
| 183 |
+
overlapping.add(g_name)
|
| 184 |
+
|
| 185 |
+
return overlapping
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
# ---------------------------------------------------------------------------
|
| 189 |
+
# Step 2: Query ENCODE API for eCLIP experiments
|
| 190 |
+
# ---------------------------------------------------------------------------
|
| 191 |
+
|
| 192 |
+
def search_eclip_experiments(target_label: str) -> list[dict]:
|
| 193 |
+
"""
|
| 194 |
+
Search ENCODE for eCLIP experiments targeting a given gene label (human).
|
| 195 |
+
Returns list of experiment info dicts.
|
| 196 |
+
"""
|
| 197 |
+
url = f"{ENCODE_BASE}/search/"
|
| 198 |
+
params = {
|
| 199 |
+
"type": "Experiment",
|
| 200 |
+
"assay_title": "eCLIP",
|
| 201 |
+
"target.label": target_label,
|
| 202 |
+
"status": "released",
|
| 203 |
+
"format": "json",
|
| 204 |
+
"limit": "all",
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
try:
|
| 208 |
+
r = requests.get(url, params=params, headers=ENCODE_HEADERS, timeout=30)
|
| 209 |
+
r.raise_for_status()
|
| 210 |
+
except requests.RequestException as e:
|
| 211 |
+
log.warning(f" API search failed for {target_label}: {e}")
|
| 212 |
+
return []
|
| 213 |
+
|
| 214 |
+
data = r.json()
|
| 215 |
+
experiments = []
|
| 216 |
+
for exp in data.get("@graph", []):
|
| 217 |
+
experiments.append({
|
| 218 |
+
"accession": exp["accession"],
|
| 219 |
+
"biosample_summary": exp.get("biosample_summary", "unknown"),
|
| 220 |
+
"target_label": exp.get("target", {}).get("label", target_label),
|
| 221 |
+
})
|
| 222 |
+
|
| 223 |
+
return experiments
|
| 224 |
+
|
| 225 |
+
|
| 226 |
+
def find_idr_peaks_file(experiment_accession: str) -> dict | None:
|
| 227 |
+
"""
|
| 228 |
+
For an experiment, find the IDR-merged peaks file (GRCh38, bed narrowPeak).
|
| 229 |
+
The IDR-merged file has biological_replicates containing both rep1 and rep2.
|
| 230 |
+
Falls back to any released bed narrowPeak if IDR-merged not found.
|
| 231 |
+
"""
|
| 232 |
+
url = (
|
| 233 |
+
f"{ENCODE_BASE}/experiments/{experiment_accession}/"
|
| 234 |
+
f"?format=json&frame=embedded"
|
| 235 |
+
)
|
| 236 |
+
|
| 237 |
+
try:
|
| 238 |
+
r = requests.get(url, headers=ENCODE_HEADERS, timeout=30)
|
| 239 |
+
r.raise_for_status()
|
| 240 |
+
except requests.RequestException as e:
|
| 241 |
+
log.warning(f" Failed to get experiment {experiment_accession}: {e}")
|
| 242 |
+
return None
|
| 243 |
+
|
| 244 |
+
exp_data = r.json()
|
| 245 |
+
files = exp_data.get("files", [])
|
| 246 |
+
|
| 247 |
+
# Find bed narrowPeak files that are released, preferring IDR-merged
|
| 248 |
+
# (biological_replicates has 2+ entries)
|
| 249 |
+
idr_candidates = []
|
| 250 |
+
single_rep_candidates = []
|
| 251 |
+
|
| 252 |
+
for f in files:
|
| 253 |
+
if not isinstance(f, dict):
|
| 254 |
+
continue
|
| 255 |
+
if (
|
| 256 |
+
f.get("output_type") == "peaks"
|
| 257 |
+
and f.get("file_format") == "bed"
|
| 258 |
+
and f.get("file_format_type") == "narrowPeak"
|
| 259 |
+
and f.get("status") == "released"
|
| 260 |
+
):
|
| 261 |
+
if len(f.get("biological_replicates", [])) >= 2:
|
| 262 |
+
idr_candidates.append(f)
|
| 263 |
+
else:
|
| 264 |
+
single_rep_candidates.append(f)
|
| 265 |
+
|
| 266 |
+
candidates = idr_candidates if idr_candidates else single_rep_candidates
|
| 267 |
+
if not candidates:
|
| 268 |
+
return None
|
| 269 |
+
|
| 270 |
+
# Prefer GRCh38 assembly
|
| 271 |
+
for f in candidates:
|
| 272 |
+
if f.get("assembly") == "GRCh38":
|
| 273 |
+
return {
|
| 274 |
+
"accession": f.get("accession"),
|
| 275 |
+
"href": f.get("href"),
|
| 276 |
+
"assembly": f.get("assembly"),
|
| 277 |
+
"biological_replicates": f.get("biological_replicates"),
|
| 278 |
+
}
|
| 279 |
+
|
| 280 |
+
# Fall back to any assembly
|
| 281 |
+
f = candidates[0]
|
| 282 |
+
return {
|
| 283 |
+
"accession": f.get("accession"),
|
| 284 |
+
"href": f.get("href"),
|
| 285 |
+
"assembly": f.get("assembly"),
|
| 286 |
+
"biological_replicates": f.get("biological_replicates"),
|
| 287 |
+
}
|
| 288 |
+
|
| 289 |
+
|
| 290 |
+
# ---------------------------------------------------------------------------
|
| 291 |
+
# Step 3: Download and parse peak files
|
| 292 |
+
# ---------------------------------------------------------------------------
|
| 293 |
+
|
| 294 |
+
def download_and_parse_peaks(href: str) -> list[tuple]:
|
| 295 |
+
"""
|
| 296 |
+
Download a bed.gz file from ENCODE and parse it.
|
| 297 |
+
Returns list of (chrom, start, end) tuples.
|
| 298 |
+
"""
|
| 299 |
+
url = ENCODE_BASE + href
|
| 300 |
+
|
| 301 |
+
try:
|
| 302 |
+
r = requests.get(url, timeout=120)
|
| 303 |
+
r.raise_for_status()
|
| 304 |
+
except requests.RequestException as e:
|
| 305 |
+
log.warning(f" Failed to download {url}: {e}")
|
| 306 |
+
return []
|
| 307 |
+
|
| 308 |
+
content = r.content
|
| 309 |
+
try:
|
| 310 |
+
text = gzip.decompress(content).decode("utf-8", errors="replace")
|
| 311 |
+
except gzip.BadGzipFile:
|
| 312 |
+
text = content.decode("utf-8", errors="replace")
|
| 313 |
+
|
| 314 |
+
peaks = []
|
| 315 |
+
for line in text.strip().split("\n"):
|
| 316 |
+
if not line or line.startswith("#") or line.startswith("track"):
|
| 317 |
+
continue
|
| 318 |
+
fields = line.split("\t")
|
| 319 |
+
if len(fields) < 3:
|
| 320 |
+
continue
|
| 321 |
+
chrom = fields[0]
|
| 322 |
+
try:
|
| 323 |
+
start = int(fields[1])
|
| 324 |
+
end = int(fields[2])
|
| 325 |
+
except ValueError:
|
| 326 |
+
continue
|
| 327 |
+
peaks.append((chrom, start, end))
|
| 328 |
+
|
| 329 |
+
return peaks
|
| 330 |
+
|
| 331 |
+
|
| 332 |
+
# ---------------------------------------------------------------------------
|
| 333 |
+
# Step 4: Map peaks to genes
|
| 334 |
+
# ---------------------------------------------------------------------------
|
| 335 |
+
|
| 336 |
+
def map_peaks_to_genes(peaks: list[tuple], gene_index: dict) -> set:
|
| 337 |
+
"""Map a list of peaks to overlapping gene names."""
|
| 338 |
+
all_genes = set()
|
| 339 |
+
for chrom, start, end in peaks:
|
| 340 |
+
genes = find_overlapping_genes(chrom, start, end, gene_index)
|
| 341 |
+
all_genes.update(genes)
|
| 342 |
+
return all_genes
|
| 343 |
+
|
| 344 |
+
|
| 345 |
+
# ---------------------------------------------------------------------------
|
| 346 |
+
# Main
|
| 347 |
+
# ---------------------------------------------------------------------------
|
| 348 |
+
|
| 349 |
+
def main():
|
| 350 |
+
# Step 1: Get gene annotations
|
| 351 |
+
genes_df = fetch_gene_annotations()
|
| 352 |
+
gene_index = build_gene_index(genes_df)
|
| 353 |
+
log.info(f"Gene index built for {len(gene_index)} chromosomes")
|
| 354 |
+
|
| 355 |
+
# Step 2-4: For each RBP, query ENCODE, download peaks, map to genes
|
| 356 |
+
all_results = [] # list of (rbp, target_gene) tuples
|
| 357 |
+
rbps_found = []
|
| 358 |
+
rbps_not_found = []
|
| 359 |
+
|
| 360 |
+
for rbp in RBPS:
|
| 361 |
+
encode_label = ENCODE_LABEL.get(rbp, rbp)
|
| 362 |
+
if encode_label is None:
|
| 363 |
+
log.warning(f"Skipping {rbp} -- no ENCODE eCLIP data available")
|
| 364 |
+
rbps_not_found.append(rbp)
|
| 365 |
+
continue
|
| 366 |
+
|
| 367 |
+
note = ENCODE_LABEL_NOTE.get(rbp, "")
|
| 368 |
+
if note:
|
| 369 |
+
log.info(f"Processing {rbp} (using {note}) ...")
|
| 370 |
+
else:
|
| 371 |
+
log.info(f"Processing {rbp} ...")
|
| 372 |
+
|
| 373 |
+
time.sleep(REQUEST_DELAY)
|
| 374 |
+
|
| 375 |
+
# Search for experiments
|
| 376 |
+
experiments = search_eclip_experiments(encode_label)
|
| 377 |
+
if not experiments:
|
| 378 |
+
log.warning(f" No eCLIP experiments found for {encode_label}")
|
| 379 |
+
rbps_not_found.append(rbp)
|
| 380 |
+
continue
|
| 381 |
+
|
| 382 |
+
log.info(f" Found {len(experiments)} experiment(s)")
|
| 383 |
+
|
| 384 |
+
rbp_targets = set()
|
| 385 |
+
|
| 386 |
+
for exp in experiments:
|
| 387 |
+
acc = exp["accession"]
|
| 388 |
+
biosample = exp["biosample_summary"]
|
| 389 |
+
log.info(f" Experiment {acc} ({biosample})")
|
| 390 |
+
time.sleep(REQUEST_DELAY)
|
| 391 |
+
|
| 392 |
+
# Find IDR peaks file
|
| 393 |
+
peaks_file = find_idr_peaks_file(acc)
|
| 394 |
+
if not peaks_file:
|
| 395 |
+
log.warning(f" No peaks file found for {acc}")
|
| 396 |
+
continue
|
| 397 |
+
|
| 398 |
+
log.info(
|
| 399 |
+
f" Peaks file: {peaks_file['accession']} "
|
| 400 |
+
f"(assembly={peaks_file['assembly']}, "
|
| 401 |
+
f"bio_reps={peaks_file['biological_replicates']})"
|
| 402 |
+
)
|
| 403 |
+
time.sleep(REQUEST_DELAY)
|
| 404 |
+
|
| 405 |
+
# Download and parse peaks
|
| 406 |
+
peaks = download_and_parse_peaks(peaks_file["href"])
|
| 407 |
+
if not peaks:
|
| 408 |
+
log.warning(
|
| 409 |
+
f" No peaks parsed from {peaks_file['accession']}"
|
| 410 |
+
)
|
| 411 |
+
continue
|
| 412 |
+
|
| 413 |
+
log.info(f" Downloaded {len(peaks):,} peaks")
|
| 414 |
+
|
| 415 |
+
# Map peaks to genes
|
| 416 |
+
target_genes = map_peaks_to_genes(peaks, gene_index)
|
| 417 |
+
log.info(f" Mapped to {len(target_genes):,} unique target genes")
|
| 418 |
+
|
| 419 |
+
rbp_targets.update(target_genes)
|
| 420 |
+
|
| 421 |
+
if rbp_targets:
|
| 422 |
+
rbps_found.append(rbp)
|
| 423 |
+
for gene in sorted(rbp_targets):
|
| 424 |
+
all_results.append((rbp, gene))
|
| 425 |
+
log.info(
|
| 426 |
+
f" Total unique targets for {rbp}: {len(rbp_targets):,}"
|
| 427 |
+
)
|
| 428 |
+
else:
|
| 429 |
+
rbps_not_found.append(rbp)
|
| 430 |
+
log.warning(f" No targets found for {rbp}")
|
| 431 |
+
|
| 432 |
+
# Step 5: Save results
|
| 433 |
+
if all_results:
|
| 434 |
+
df = pd.DataFrame(all_results, columns=["rbp", "target_gene"])
|
| 435 |
+
os.makedirs(os.path.dirname(OUTPUT_CSV), exist_ok=True)
|
| 436 |
+
df.to_csv(OUTPUT_CSV, index=False)
|
| 437 |
+
log.info(f"\nSaved {len(df):,} RBP-target pairs to {OUTPUT_CSV}")
|
| 438 |
+
log.info(f"RBPs with data: {sorted(rbps_found)}")
|
| 439 |
+
log.info(f"RBPs without data: {sorted(rbps_not_found)}")
|
| 440 |
+
log.info("\nSummary per RBP:")
|
| 441 |
+
for rbp, group in df.groupby("rbp"):
|
| 442 |
+
note = ENCODE_LABEL_NOTE.get(rbp, "")
|
| 443 |
+
suffix = f" ({note})" if note else ""
|
| 444 |
+
log.info(f" {rbp}: {len(group):,} target genes{suffix}")
|
| 445 |
+
else:
|
| 446 |
+
log.error("No results found for any RBP!")
|
| 447 |
+
sys.exit(1)
|
| 448 |
+
|
| 449 |
+
|
| 450 |
+
if __name__ == "__main__":
|
| 451 |
+
main()
|
analyses/run_all.py
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
"""Run the full scPTR analysis pipeline on pancreas data and produce results.
|
| 3 |
+
|
| 4 |
+
This script runs Aims 1-3 end-to-end on the pancreas dataset:
|
| 5 |
+
- Aim 1: Benchmark gamma estimates against published half-lives, ARE/NMD enrichment
|
| 6 |
+
- Aim 2: PT state discovery and differential gamma analysis
|
| 7 |
+
- Aim 3: PT velocity computation
|
| 8 |
+
|
| 9 |
+
Results are saved to output/ directory.
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
from __future__ import annotations
|
| 13 |
+
|
| 14 |
+
import json
|
| 15 |
+
import sys
|
| 16 |
+
from pathlib import Path
|
| 17 |
+
|
| 18 |
+
import matplotlib
|
| 19 |
+
matplotlib.use("Agg")
|
| 20 |
+
import matplotlib.pyplot as plt
|
| 21 |
+
import numpy as np
|
| 22 |
+
import pandas as pd
|
| 23 |
+
|
| 24 |
+
# Add project root to path
|
| 25 |
+
sys.path.insert(0, str(Path(__file__).parent))
|
| 26 |
+
from _common import set_figure_style, setup_output_dirs
|
| 27 |
+
|
| 28 |
+
import scptr
|
| 29 |
+
|
| 30 |
+
OUTPUT_DIR = Path(__file__).parent.parent / "output"
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def save_fig(fig, name, subdir="figures"):
|
| 34 |
+
"""Save a matplotlib figure to output dir."""
|
| 35 |
+
if fig is None:
|
| 36 |
+
print(f" [WARNING] {name}: plot returned None, skipping save")
|
| 37 |
+
return
|
| 38 |
+
out_dir = OUTPUT_DIR / subdir
|
| 39 |
+
out_dir.mkdir(parents=True, exist_ok=True)
|
| 40 |
+
path = out_dir / f"{name}.png"
|
| 41 |
+
fig.savefig(path, dpi=150, bbox_inches="tight")
|
| 42 |
+
plt.close(fig)
|
| 43 |
+
print(f" Saved: {path}")
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def main():
|
| 47 |
+
set_figure_style()
|
| 48 |
+
OUTPUT_DIR.mkdir(exist_ok=True)
|
| 49 |
+
|
| 50 |
+
# =========================================================================
|
| 51 |
+
# LOAD DATA
|
| 52 |
+
# =========================================================================
|
| 53 |
+
print("=" * 60)
|
| 54 |
+
print("LOADING PANCREAS DATASET")
|
| 55 |
+
print("=" * 60)
|
| 56 |
+
adata = scptr.datasets.pancreas()
|
| 57 |
+
print(f" Shape: {adata.shape}")
|
| 58 |
+
print(f" Layers: {list(adata.layers.keys())}")
|
| 59 |
+
print(f" Cell types: {adata.obs['clusters'].value_counts().to_dict()}")
|
| 60 |
+
|
| 61 |
+
# =========================================================================
|
| 62 |
+
# PREPROCESSING
|
| 63 |
+
# =========================================================================
|
| 64 |
+
print("\n" + "=" * 60)
|
| 65 |
+
print("PREPROCESSING")
|
| 66 |
+
print("=" * 60)
|
| 67 |
+
|
| 68 |
+
scptr.pp.filter_genes(adata)
|
| 69 |
+
print(f" After filtering: {adata.shape}")
|
| 70 |
+
|
| 71 |
+
scptr.pp.normalize_layers(adata)
|
| 72 |
+
print(" Normalized layers")
|
| 73 |
+
|
| 74 |
+
scptr.pp.neighbors(adata, n_neighbors=30)
|
| 75 |
+
print(" Built kNN graph (k=30)")
|
| 76 |
+
|
| 77 |
+
scptr.pp.smooth_layers(adata)
|
| 78 |
+
print(" Smoothed layers (Mu, Ms)")
|
| 79 |
+
|
| 80 |
+
# =========================================================================
|
| 81 |
+
# CORE ANALYSIS
|
| 82 |
+
# =========================================================================
|
| 83 |
+
print("\n" + "=" * 60)
|
| 84 |
+
print("CORE ANALYSIS")
|
| 85 |
+
print("=" * 60)
|
| 86 |
+
|
| 87 |
+
# Beta estimation (global + per-cell-type)
|
| 88 |
+
scptr.tl.estimate_beta(adata)
|
| 89 |
+
beta = adata.var['beta'].values
|
| 90 |
+
print(f" Beta: median={np.median(beta):.4f}, max={np.max(beta):.4f}, "
|
| 91 |
+
f"nonzero={np.sum(beta > 0)}/{len(beta)}")
|
| 92 |
+
|
| 93 |
+
if "clusters" in adata.obs.columns:
|
| 94 |
+
scptr.tl.estimate_beta(adata, groupby="clusters")
|
| 95 |
+
print(f" Beta (per-cluster): {adata.varm['beta_groups'].shape}")
|
| 96 |
+
|
| 97 |
+
# Gamma estimation
|
| 98 |
+
scptr.tl.estimate_gamma(adata)
|
| 99 |
+
gamma_vals = adata.layers["gamma"]
|
| 100 |
+
gamma_med = np.median(gamma_vals, axis=0)
|
| 101 |
+
print(f" Gamma: shape={gamma_vals.shape}")
|
| 102 |
+
print(f" Median per-gene: median={np.median(gamma_med):.4f}, "
|
| 103 |
+
f"max={np.max(gamma_med):.4f}")
|
| 104 |
+
print(f" Global: max={np.max(gamma_vals):.4f}, "
|
| 105 |
+
f"99.5th pctl={np.percentile(gamma_vals[gamma_vals>0], 99.5):.4f}")
|
| 106 |
+
print(f" Genes with >0 median gamma: {np.sum(gamma_med > 0)}/{len(gamma_med)}")
|
| 107 |
+
|
| 108 |
+
# Variance decomposition
|
| 109 |
+
scptr.tl.variance_decomposition(adata)
|
| 110 |
+
tf = adata.var['tf_score'].values
|
| 111 |
+
ptf = adata.var['ptf_score'].values
|
| 112 |
+
print(f" TF score: median={np.median(tf):.4f}, mean={np.mean(tf):.4f}")
|
| 113 |
+
print(f" PTF score: median={np.median(ptf):.4f}, mean={np.mean(ptf):.4f}")
|
| 114 |
+
print(f" Genes with TF > 0.5: {np.sum(tf > 0.5)}/{len(tf)}")
|
| 115 |
+
|
| 116 |
+
# PT states
|
| 117 |
+
scptr.tl.pt_states(adata)
|
| 118 |
+
n_states = adata.obs["pt_state"].nunique()
|
| 119 |
+
print(f" PT states found: {n_states}")
|
| 120 |
+
|
| 121 |
+
# PT velocity
|
| 122 |
+
scptr.tl.pt_velocity(adata)
|
| 123 |
+
print(" PT velocity computed")
|
| 124 |
+
|
| 125 |
+
# =========================================================================
|
| 126 |
+
# AIM 1: BENCHMARKING
|
| 127 |
+
# =========================================================================
|
| 128 |
+
print("\n" + "=" * 60)
|
| 129 |
+
print("AIM 1: BENCHMARKING")
|
| 130 |
+
print("=" * 60)
|
| 131 |
+
fig_dir, res_dir = setup_output_dirs("figures/aim1", "results/aim1")
|
| 132 |
+
|
| 133 |
+
# 1a. Half-life correlation (mouse reference)
|
| 134 |
+
print("\n--- Half-life correlation (mouse reference) ---")
|
| 135 |
+
hl_mouse = scptr.datasets.herzog2017_halflives()
|
| 136 |
+
corr = scptr.benchmark.correlate_with_halflives(adata, hl_mouse)
|
| 137 |
+
print(f" n_genes matched: {corr['n_genes']} (unfiltered: {corr['n_genes_unfiltered']})")
|
| 138 |
+
print(f" Spearman r = {corr['spearman_r']:.4f} (p = {corr['spearman_p']:.2e})")
|
| 139 |
+
print(f" Pearson r = {corr['pearson_r']:.4f} (p = {corr['pearson_p']:.2e})")
|
| 140 |
+
|
| 141 |
+
# Also try human reference for cross-species comparison
|
| 142 |
+
print("\n--- Half-life correlation (human reference) ---")
|
| 143 |
+
hl_human = scptr.datasets.schofield2018_halflives()
|
| 144 |
+
corr_human = scptr.benchmark.correlate_with_halflives(adata, hl_human)
|
| 145 |
+
print(f" n_genes matched: {corr_human['n_genes']} (unfiltered: {corr_human['n_genes_unfiltered']})")
|
| 146 |
+
print(f" Spearman r = {corr_human['spearman_r']:.4f} (p = {corr_human['spearman_p']:.2e})")
|
| 147 |
+
|
| 148 |
+
# Save both correlation results
|
| 149 |
+
corr_save = {k: v for k, v in corr.items() if k != "matched_genes"}
|
| 150 |
+
corr_human_save = {k: v for k, v in corr_human.items() if k != "matched_genes"}
|
| 151 |
+
with open(res_dir / "halflife_correlation.json", "w") as f:
|
| 152 |
+
json.dump({"mouse_reference": corr_save, "human_reference": corr_human_save}, f, indent=2)
|
| 153 |
+
|
| 154 |
+
# Half-life scatter plot (log-log scale, filtered genes only)
|
| 155 |
+
fig, axes = plt.subplots(1, 2, figsize=(13, 5))
|
| 156 |
+
|
| 157 |
+
gamma_med = np.median(adata.layers["gamma"], axis=0)
|
| 158 |
+
gamma_s = pd.Series(gamma_med, index=adata.var_names)
|
| 159 |
+
hl_s = hl_mouse.set_index("gene_symbol")["half_life_hours"]
|
| 160 |
+
shared = gamma_s.index.intersection(hl_s.index)
|
| 161 |
+
g = gamma_s[shared].values
|
| 162 |
+
h = hl_s[shared].values
|
| 163 |
+
|
| 164 |
+
# Left: all genes
|
| 165 |
+
axes[0].scatter(h, g, alpha=0.1, s=5, c="steelblue")
|
| 166 |
+
axes[0].set_xlabel("Published half-life (hours)")
|
| 167 |
+
axes[0].set_ylabel("scPTR median gamma")
|
| 168 |
+
axes[0].set_title(f"All genes (n={len(shared)})")
|
| 169 |
+
|
| 170 |
+
# Right: filtered genes (gamma > 0), log-log
|
| 171 |
+
mask = (g > 0) & (h > 0) & np.isfinite(g) & np.isfinite(h)
|
| 172 |
+
axes[1].scatter(h[mask], g[mask], alpha=0.15, s=8, c="steelblue")
|
| 173 |
+
axes[1].set_xscale("log")
|
| 174 |
+
axes[1].set_yscale("log")
|
| 175 |
+
axes[1].set_xlabel("Published half-life (hours)")
|
| 176 |
+
axes[1].set_ylabel("scPTR median gamma")
|
| 177 |
+
axes[1].set_title(
|
| 178 |
+
f"Filtered genes (Spearman r={corr['spearman_r']:.3f}, "
|
| 179 |
+
f"p={corr['spearman_p']:.1e}, n={corr['n_genes']})"
|
| 180 |
+
)
|
| 181 |
+
fig.suptitle("Gamma vs Published mRNA Half-lives", fontsize=13, y=1.02)
|
| 182 |
+
fig.tight_layout()
|
| 183 |
+
save_fig(fig, "halflife_scatter", "figures/aim1")
|
| 184 |
+
|
| 185 |
+
# 1b. ARE / NMD enrichment
|
| 186 |
+
print("\n--- ARE / NMD enrichment ---")
|
| 187 |
+
are_result = scptr.benchmark.are_enrichment(adata)
|
| 188 |
+
nmd_result = scptr.benchmark.nmd_enrichment(adata)
|
| 189 |
+
print(f" ARE: n_in={are_result['n_genes_in_set']}, "
|
| 190 |
+
f"median_gamma_in={are_result.get('median_gamma_in_set', 'N/A'):.4f}, "
|
| 191 |
+
f"median_gamma_bg={are_result.get('median_gamma_background', 'N/A'):.4f}, "
|
| 192 |
+
f"p={are_result['p_value']:.4f}")
|
| 193 |
+
print(f" NMD: n_in={nmd_result['n_genes_in_set']}, "
|
| 194 |
+
f"median_gamma_in={nmd_result.get('median_gamma_in_set', 'N/A'):.4f}, "
|
| 195 |
+
f"median_gamma_bg={nmd_result.get('median_gamma_background', 'N/A'):.4f}, "
|
| 196 |
+
f"p={nmd_result['p_value']:.4f}")
|
| 197 |
+
|
| 198 |
+
with open(res_dir / "enrichment_results.json", "w") as f:
|
| 199 |
+
json.dump({"ARE": are_result, "NMD": nmd_result}, f, indent=2)
|
| 200 |
+
|
| 201 |
+
fig = scptr.pl.enrichment_barplot([are_result, nmd_result])
|
| 202 |
+
save_fig(fig, "enrichment_barplot", "figures/aim1")
|
| 203 |
+
|
| 204 |
+
# 1c. Subsampling robustness
|
| 205 |
+
print("\n--- Subsampling robustness ---")
|
| 206 |
+
fractions = [0.2, 0.4, 0.6, 0.8, 0.9]
|
| 207 |
+
robust_df = scptr.benchmark.subsampling_robustness(
|
| 208 |
+
adata, fractions=fractions, n_repeats=5
|
| 209 |
+
)
|
| 210 |
+
robust_df.to_csv(res_dir / "subsampling_robustness.csv", index=False)
|
| 211 |
+
|
| 212 |
+
for frac in fractions:
|
| 213 |
+
sub = robust_df[robust_df["fraction"] == frac]
|
| 214 |
+
mean_r = sub["spearman_r"].mean()
|
| 215 |
+
print(f" fraction={frac:.1f}: mean Spearman r = {mean_r:.4f}")
|
| 216 |
+
|
| 217 |
+
# Robustness plot
|
| 218 |
+
fig, ax = plt.subplots(figsize=(6, 4))
|
| 219 |
+
for frac in fractions:
|
| 220 |
+
sub = robust_df[robust_df["fraction"] == frac]
|
| 221 |
+
ax.scatter([frac] * len(sub), sub["spearman_r"],
|
| 222 |
+
color="steelblue", alpha=0.6, s=25)
|
| 223 |
+
means = robust_df.groupby("fraction")["spearman_r"].mean()
|
| 224 |
+
ax.plot(means.index, means.values, "o-", color="darkblue", linewidth=2, markersize=6)
|
| 225 |
+
ax.set_xlabel("Fraction of cells")
|
| 226 |
+
ax.set_ylabel("Spearman r (vs full data)")
|
| 227 |
+
ax.set_title("Subsampling Robustness")
|
| 228 |
+
ax.set_ylim(0.5, 1.02)
|
| 229 |
+
save_fig(fig, "subsampling_robustness", "figures/aim1")
|
| 230 |
+
|
| 231 |
+
# =========================================================================
|
| 232 |
+
# AIM 2: HIDDEN PT STATES
|
| 233 |
+
# =========================================================================
|
| 234 |
+
print("\n" + "=" * 60)
|
| 235 |
+
print("AIM 2: PT STATE DISCOVERY")
|
| 236 |
+
print("=" * 60)
|
| 237 |
+
fig_dir, res_dir = setup_output_dirs("figures/aim2", "results/aim2")
|
| 238 |
+
|
| 239 |
+
# State composition
|
| 240 |
+
state_counts = adata.obs["pt_state"].value_counts()
|
| 241 |
+
state_counts.to_csv(res_dir / "pt_state_counts.csv")
|
| 242 |
+
print(f" PT states: {dict(state_counts)}")
|
| 243 |
+
|
| 244 |
+
# PT UMAP (use show=False to get fig back)
|
| 245 |
+
fig = scptr.pl.pt_umap(adata, show=False)
|
| 246 |
+
save_fig(fig, "pt_umap", "figures/aim2")
|
| 247 |
+
|
| 248 |
+
# TF vs PTF scatter
|
| 249 |
+
fig = scptr.pl.tf_ptf_scatter(adata, show=False)
|
| 250 |
+
save_fig(fig, "tf_ptf_scatter", "figures/aim2")
|
| 251 |
+
|
| 252 |
+
# Cross-tabulate PT states vs expression clusters
|
| 253 |
+
if "clusters" in adata.obs.columns:
|
| 254 |
+
ct = pd.crosstab(adata.obs["pt_state"], adata.obs["clusters"])
|
| 255 |
+
ct.to_csv(res_dir / "pt_state_vs_clusters.csv")
|
| 256 |
+
print(f"\n PT state vs expression cluster crosstab:")
|
| 257 |
+
print(ct.to_string())
|
| 258 |
+
|
| 259 |
+
# Rank genes by differential gamma
|
| 260 |
+
rank_df = scptr.tl.rank_pt_genes(adata, n_genes=50)
|
| 261 |
+
rank_df.to_csv(res_dir / "ranked_pt_genes.csv", index=False)
|
| 262 |
+
print(f"\n Top differentially degraded genes: {len(rank_df)} entries")
|
| 263 |
+
print(f" Top 10 gene names: {rank_df.head(10)['names'].tolist()}")
|
| 264 |
+
|
| 265 |
+
# Gamma heatmap
|
| 266 |
+
fig = scptr.pl.gamma_heatmap(adata, show=False)
|
| 267 |
+
save_fig(fig, "gamma_heatmap", "figures/aim2")
|
| 268 |
+
|
| 269 |
+
# =========================================================================
|
| 270 |
+
# AIM 3: PT VELOCITY
|
| 271 |
+
# =========================================================================
|
| 272 |
+
print("\n" + "=" * 60)
|
| 273 |
+
print("AIM 3: PT VELOCITY")
|
| 274 |
+
print("=" * 60)
|
| 275 |
+
fig_dir, res_dir = setup_output_dirs("figures/aim3", "results/aim3")
|
| 276 |
+
|
| 277 |
+
# Velocity embedding (show 30% of cells for cleaner arrows)
|
| 278 |
+
fig = scptr.pl.pt_velocity_embedding(adata, density=0.3, arrow_size=1.5, show=False)
|
| 279 |
+
save_fig(fig, "pt_velocity_embedding", "figures/aim3")
|
| 280 |
+
|
| 281 |
+
# =========================================================================
|
| 282 |
+
# SUMMARY
|
| 283 |
+
# =========================================================================
|
| 284 |
+
print("\n" + "=" * 60)
|
| 285 |
+
print("SUMMARY")
|
| 286 |
+
print("=" * 60)
|
| 287 |
+
print(f" Dataset: pancreas ({adata.n_obs} cells, {adata.n_vars} genes)")
|
| 288 |
+
print(f" Beta: median={np.median(adata.var['beta']):.4f}, max={np.max(adata.var['beta']):.4f}")
|
| 289 |
+
print(f" Gamma max: {np.max(adata.layers['gamma']):.4f}")
|
| 290 |
+
print(f" PT states discovered: {n_states}")
|
| 291 |
+
print(f" TF score: median={np.median(adata.var['tf_score']):.4f}")
|
| 292 |
+
print(f" Half-life Spearman r (mouse): {corr['spearman_r']:.4f} (n={corr['n_genes']} genes)")
|
| 293 |
+
print(f" Half-life Spearman r (human): {corr_human['spearman_r']:.4f} (n={corr_human['n_genes']} genes)")
|
| 294 |
+
print(f" ARE enrichment p: {are_result['p_value']:.4f}")
|
| 295 |
+
print(f" NMD enrichment p: {nmd_result['p_value']:.4f}")
|
| 296 |
+
print(f" Robustness (90% cells): {robust_df[robust_df['fraction']==0.9]['spearman_r'].mean():.4f}")
|
| 297 |
+
print(f"\nAll results saved to: {OUTPUT_DIR.resolve()}")
|
| 298 |
+
print("Done!")
|
| 299 |
+
|
| 300 |
+
|
| 301 |
+
if __name__ == "__main__":
|
| 302 |
+
main()
|
analyses/run_cross_platform.py
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
"""Cross-platform benchmarking: compare scPTR gamma estimates across
|
| 3 |
+
different sequencing platforms and datasets.
|
| 4 |
+
|
| 5 |
+
Compares gamma estimates between:
|
| 6 |
+
1. 10x Chromium datasets (pancreas, dentate gyrus)
|
| 7 |
+
2. sci (combinatorial indexing) dataset (sci-fate A549)
|
| 8 |
+
3. Assesses whether gene-level gamma rankings are consistent across platforms
|
| 9 |
+
|
| 10 |
+
This addresses the cross-platform benchmarking component of the research plan.
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
from __future__ import annotations
|
| 14 |
+
|
| 15 |
+
import json
|
| 16 |
+
import sys
|
| 17 |
+
from pathlib import Path
|
| 18 |
+
|
| 19 |
+
import matplotlib
|
| 20 |
+
matplotlib.use("Agg")
|
| 21 |
+
import matplotlib.pyplot as plt
|
| 22 |
+
import numpy as np
|
| 23 |
+
import pandas as pd
|
| 24 |
+
from scipy import stats
|
| 25 |
+
|
| 26 |
+
sys.path.insert(0, str(Path(__file__).parent))
|
| 27 |
+
from _common import set_figure_style
|
| 28 |
+
|
| 29 |
+
import scptr
|
| 30 |
+
|
| 31 |
+
OUTPUT_DIR = Path(__file__).parent.parent / "output" / "cross_platform"
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def save_fig(fig, name, subdir="figures"):
|
| 35 |
+
out_dir = OUTPUT_DIR / subdir
|
| 36 |
+
out_dir.mkdir(parents=True, exist_ok=True)
|
| 37 |
+
path = out_dir / f"{name}.png"
|
| 38 |
+
fig.savefig(path, dpi=150, bbox_inches="tight")
|
| 39 |
+
plt.close(fig)
|
| 40 |
+
print(f" Saved: {path}")
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def run_pipeline(adata, name):
|
| 44 |
+
"""Run full scPTR pipeline and return per-gene median gamma."""
|
| 45 |
+
import copy
|
| 46 |
+
adata = copy.deepcopy(adata)
|
| 47 |
+
scptr.pp.filter_genes(adata)
|
| 48 |
+
scptr.pp.normalize_layers(adata)
|
| 49 |
+
scptr.pp.neighbors(adata, n_neighbors=30)
|
| 50 |
+
scptr.pp.smooth_layers(adata)
|
| 51 |
+
scptr.tl.estimate_beta(adata)
|
| 52 |
+
scptr.tl.estimate_gamma(adata)
|
| 53 |
+
|
| 54 |
+
gamma = np.median(adata.layers["gamma"], axis=0)
|
| 55 |
+
expr = np.mean(adata.layers["spliced"], axis=0) if "spliced" in adata.layers else np.mean(adata.X, axis=0)
|
| 56 |
+
if hasattr(expr, 'A1'):
|
| 57 |
+
expr = np.asarray(expr).flatten()
|
| 58 |
+
|
| 59 |
+
return pd.DataFrame({
|
| 60 |
+
"gene": adata.var_names,
|
| 61 |
+
"gamma": gamma,
|
| 62 |
+
"expression": expr,
|
| 63 |
+
"nonzero_frac": (adata.layers["gamma"] > 0).mean(axis=0),
|
| 64 |
+
}).set_index("gene"), adata
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def compare_datasets(df_a, df_b, name_a, name_b):
|
| 68 |
+
"""Compare gamma estimates between two datasets."""
|
| 69 |
+
print(f"\n {name_a} vs {name_b}:")
|
| 70 |
+
|
| 71 |
+
# Find shared genes (case-insensitive)
|
| 72 |
+
genes_a = {g.upper(): g for g in df_a.index}
|
| 73 |
+
genes_b = {g.upper(): g for g in df_b.index}
|
| 74 |
+
shared = set(genes_a.keys()) & set(genes_b.keys())
|
| 75 |
+
print(f" Shared genes: {len(shared)}")
|
| 76 |
+
|
| 77 |
+
if len(shared) < 50:
|
| 78 |
+
print(f" Too few shared genes for comparison.")
|
| 79 |
+
return None
|
| 80 |
+
|
| 81 |
+
gamma_a = np.array([df_a.loc[genes_a[g], "gamma"] for g in shared])
|
| 82 |
+
gamma_b = np.array([df_b.loc[genes_b[g], "gamma"] for g in shared])
|
| 83 |
+
expr_a = np.array([df_a.loc[genes_a[g], "expression"] for g in shared])
|
| 84 |
+
expr_b = np.array([df_b.loc[genes_b[g], "expression"] for g in shared])
|
| 85 |
+
nonzero_a = np.array([df_a.loc[genes_a[g], "nonzero_frac"] for g in shared])
|
| 86 |
+
nonzero_b = np.array([df_b.loc[genes_b[g], "nonzero_frac"] for g in shared])
|
| 87 |
+
|
| 88 |
+
# Overall correlation
|
| 89 |
+
valid = (gamma_a > 0) & (gamma_b > 0)
|
| 90 |
+
if valid.sum() < 20:
|
| 91 |
+
print(f" Too few valid genes (both gamma>0): {valid.sum()}")
|
| 92 |
+
return None
|
| 93 |
+
|
| 94 |
+
r_gamma, p_gamma = stats.spearmanr(gamma_a[valid], gamma_b[valid])
|
| 95 |
+
r_expr, p_expr = stats.spearmanr(expr_a[valid], expr_b[valid])
|
| 96 |
+
|
| 97 |
+
print(f" Gamma Spearman r = {r_gamma:.4f} (n={valid.sum()})")
|
| 98 |
+
print(f" Expression Spearman r = {r_expr:.4f}")
|
| 99 |
+
|
| 100 |
+
# Stratify by expression level
|
| 101 |
+
expr_combined = expr_a + expr_b
|
| 102 |
+
quartiles = np.percentile(expr_combined[valid], [25, 50, 75])
|
| 103 |
+
labels = ["Q1 (low)", "Q2", "Q3", "Q4 (high)"]
|
| 104 |
+
bounds = [(-np.inf, quartiles[0]), (quartiles[0], quartiles[1]),
|
| 105 |
+
(quartiles[1], quartiles[2]), (quartiles[2], np.inf)]
|
| 106 |
+
|
| 107 |
+
print(f"\n Stratified by expression level:")
|
| 108 |
+
stratified = []
|
| 109 |
+
for label, (lo, hi) in zip(labels, bounds):
|
| 110 |
+
mask = valid & (expr_combined >= lo) & (expr_combined < hi)
|
| 111 |
+
if mask.sum() < 10:
|
| 112 |
+
continue
|
| 113 |
+
r_q, p_q = stats.spearmanr(gamma_a[mask], gamma_b[mask])
|
| 114 |
+
r_e, _ = stats.spearmanr(expr_a[mask], expr_b[mask])
|
| 115 |
+
print(f" {label}: gamma r={r_q:.3f}, expr r={r_e:.3f} (n={mask.sum()})")
|
| 116 |
+
stratified.append({
|
| 117 |
+
"quartile": label,
|
| 118 |
+
"gamma_r": float(r_q),
|
| 119 |
+
"expr_r": float(r_e),
|
| 120 |
+
"n_genes": int(mask.sum()),
|
| 121 |
+
})
|
| 122 |
+
|
| 123 |
+
# Informative genes only (>10% nonzero in both)
|
| 124 |
+
informative = valid & (nonzero_a >= 0.1) & (nonzero_b >= 0.1)
|
| 125 |
+
if informative.sum() >= 20:
|
| 126 |
+
r_inf, _ = stats.spearmanr(gamma_a[informative], gamma_b[informative])
|
| 127 |
+
print(f"\n Informative genes only (>10% nonzero both): "
|
| 128 |
+
f"r={r_inf:.4f} (n={informative.sum()})")
|
| 129 |
+
|
| 130 |
+
return {
|
| 131 |
+
"dataset_a": name_a,
|
| 132 |
+
"dataset_b": name_b,
|
| 133 |
+
"shared_genes": len(shared),
|
| 134 |
+
"valid_genes": int(valid.sum()),
|
| 135 |
+
"gamma_r": float(r_gamma),
|
| 136 |
+
"expr_r": float(r_expr),
|
| 137 |
+
"informative_gamma_r": float(r_inf) if informative.sum() >= 20 else None,
|
| 138 |
+
"stratified": stratified,
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
def main():
|
| 143 |
+
set_figure_style()
|
| 144 |
+
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
| 145 |
+
|
| 146 |
+
# Load datasets
|
| 147 |
+
print("=" * 60)
|
| 148 |
+
print("CROSS-PLATFORM BENCHMARKING")
|
| 149 |
+
print("=" * 60)
|
| 150 |
+
|
| 151 |
+
datasets = {}
|
| 152 |
+
|
| 153 |
+
# 10x Chromium datasets
|
| 154 |
+
print("\nLoading pancreas (10x Chromium)...")
|
| 155 |
+
df_pan, adata_pan = run_pipeline(scptr.datasets.pancreas(), "pancreas")
|
| 156 |
+
datasets["pancreas_10x"] = df_pan
|
| 157 |
+
print(f" {len(df_pan)} genes, {(df_pan['gamma'] > 0).sum()} with gamma>0")
|
| 158 |
+
|
| 159 |
+
print("\nLoading dentate gyrus (10x Chromium)...")
|
| 160 |
+
df_dg, adata_dg = run_pipeline(scptr.datasets.dentate_gyrus(), "dentate_gyrus")
|
| 161 |
+
datasets["dg_10x"] = df_dg
|
| 162 |
+
print(f" {len(df_dg)} genes, {(df_dg['gamma'] > 0).sum()} with gamma>0")
|
| 163 |
+
|
| 164 |
+
# sci-fate (combinatorial indexing)
|
| 165 |
+
try:
|
| 166 |
+
print("\nLoading sci-fate (sci)...")
|
| 167 |
+
df_sci, adata_sci = run_pipeline(scptr.datasets.sci_fate(), "sci_fate")
|
| 168 |
+
datasets["scifate_sci"] = df_sci
|
| 169 |
+
print(f" {len(df_sci)} genes, {(df_sci['gamma'] > 0).sum()} with gamma>0")
|
| 170 |
+
except Exception as e:
|
| 171 |
+
print(f" sci-fate not available: {e}")
|
| 172 |
+
|
| 173 |
+
# Pairwise comparisons
|
| 174 |
+
print("\n" + "=" * 60)
|
| 175 |
+
print("PAIRWISE COMPARISONS")
|
| 176 |
+
print("=" * 60)
|
| 177 |
+
|
| 178 |
+
pairs = []
|
| 179 |
+
dataset_names = list(datasets.keys())
|
| 180 |
+
all_comparisons = []
|
| 181 |
+
|
| 182 |
+
for i in range(len(dataset_names)):
|
| 183 |
+
for j in range(i + 1, len(dataset_names)):
|
| 184 |
+
name_a, name_b = dataset_names[i], dataset_names[j]
|
| 185 |
+
result = compare_datasets(datasets[name_a], datasets[name_b],
|
| 186 |
+
name_a, name_b)
|
| 187 |
+
if result:
|
| 188 |
+
all_comparisons.append(result)
|
| 189 |
+
|
| 190 |
+
# Platform comparison summary
|
| 191 |
+
print(f"\n{'='*60}")
|
| 192 |
+
print("PLATFORM COMPARISON SUMMARY")
|
| 193 |
+
print(f"{'='*60}")
|
| 194 |
+
|
| 195 |
+
# Categorize comparisons
|
| 196 |
+
same_platform = []
|
| 197 |
+
cross_platform = []
|
| 198 |
+
for comp in all_comparisons:
|
| 199 |
+
a, b = comp["dataset_a"], comp["dataset_b"]
|
| 200 |
+
a_platform = "10x" if "10x" in a else "sci" if "sci" in a else "other"
|
| 201 |
+
b_platform = "10x" if "10x" in b else "sci" if "sci" in b else "other"
|
| 202 |
+
|
| 203 |
+
if a_platform == b_platform:
|
| 204 |
+
same_platform.append(comp)
|
| 205 |
+
else:
|
| 206 |
+
cross_platform.append(comp)
|
| 207 |
+
|
| 208 |
+
print(f"\n Same platform comparisons:")
|
| 209 |
+
for comp in same_platform:
|
| 210 |
+
print(f" {comp['dataset_a']} vs {comp['dataset_b']}: "
|
| 211 |
+
f"gamma r={comp['gamma_r']:.3f}")
|
| 212 |
+
|
| 213 |
+
print(f"\n Cross-platform comparisons:")
|
| 214 |
+
for comp in cross_platform:
|
| 215 |
+
print(f" {comp['dataset_a']} vs {comp['dataset_b']}: "
|
| 216 |
+
f"gamma r={comp['gamma_r']:.3f}")
|
| 217 |
+
|
| 218 |
+
# Half-life validation per platform
|
| 219 |
+
print(f"\n Half-life validation per platform:")
|
| 220 |
+
halflife_dir = Path(__file__).parent.parent / "src" / "scptr" / "datasets" / "data"
|
| 221 |
+
for hl_file, hl_name in [("schofield2018_halflives.csv", "Schofield 2018")]:
|
| 222 |
+
hl_path = halflife_dir / hl_file
|
| 223 |
+
if not hl_path.exists():
|
| 224 |
+
continue
|
| 225 |
+
|
| 226 |
+
hl = pd.read_csv(hl_path)
|
| 227 |
+
for ds_name, df in datasets.items():
|
| 228 |
+
gene_map = {g.upper(): g for g in df.index}
|
| 229 |
+
gamma_vals, hl_vals = [], []
|
| 230 |
+
for _, row in hl.iterrows():
|
| 231 |
+
g = str(row.iloc[0]).upper()
|
| 232 |
+
if g in gene_map and df.loc[gene_map[g], "gamma"] > 0:
|
| 233 |
+
gamma_vals.append(df.loc[gene_map[g], "gamma"])
|
| 234 |
+
hl_vals.append(float(row.iloc[1]))
|
| 235 |
+
if len(gamma_vals) >= 20:
|
| 236 |
+
r, p = stats.spearmanr(gamma_vals, hl_vals)
|
| 237 |
+
print(f" {ds_name}: r={r:.4f}, n={len(gamma_vals)} ({hl_name})")
|
| 238 |
+
|
| 239 |
+
# Save results
|
| 240 |
+
res_dir = OUTPUT_DIR / "results"
|
| 241 |
+
res_dir.mkdir(parents=True, exist_ok=True)
|
| 242 |
+
with open(res_dir / "cross_platform_results.json", "w") as f:
|
| 243 |
+
json.dump(all_comparisons, f, indent=2)
|
| 244 |
+
|
| 245 |
+
# Figure
|
| 246 |
+
n_comps = len(all_comparisons)
|
| 247 |
+
fig, axes = plt.subplots(1, max(n_comps, 1), figsize=(6 * max(n_comps, 1), 5))
|
| 248 |
+
if n_comps == 1:
|
| 249 |
+
axes = [axes]
|
| 250 |
+
|
| 251 |
+
for idx, comp in enumerate(all_comparisons):
|
| 252 |
+
name_a, name_b = comp["dataset_a"], comp["dataset_b"]
|
| 253 |
+
df_a, df_b = datasets[name_a], datasets[name_b]
|
| 254 |
+
|
| 255 |
+
genes_a = {g.upper(): g for g in df_a.index}
|
| 256 |
+
genes_b = {g.upper(): g for g in df_b.index}
|
| 257 |
+
shared = set(genes_a.keys()) & set(genes_b.keys())
|
| 258 |
+
|
| 259 |
+
ga = np.array([df_a.loc[genes_a[g], "gamma"] for g in shared])
|
| 260 |
+
gb = np.array([df_b.loc[genes_b[g], "gamma"] for g in shared])
|
| 261 |
+
valid = (ga > 0) & (gb > 0)
|
| 262 |
+
|
| 263 |
+
axes[idx].scatter(ga[valid], gb[valid], s=2, alpha=0.3, color="steelblue")
|
| 264 |
+
axes[idx].set_xlabel(f"Gamma ({name_a})")
|
| 265 |
+
axes[idx].set_ylabel(f"Gamma ({name_b})")
|
| 266 |
+
axes[idx].set_title(f"r={comp['gamma_r']:.3f} (n={comp['valid_genes']})")
|
| 267 |
+
lim = max(ga[valid].max(), gb[valid].max()) * 1.1
|
| 268 |
+
axes[idx].plot([0, lim], [0, lim], "r--", alpha=0.5)
|
| 269 |
+
|
| 270 |
+
fig.suptitle("Cross-Platform Gamma Comparison", fontsize=13, y=1.02)
|
| 271 |
+
fig.tight_layout()
|
| 272 |
+
save_fig(fig, "cross_platform_gamma")
|
| 273 |
+
|
| 274 |
+
print(f"\nResults saved to: {OUTPUT_DIR.resolve()}")
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
if __name__ == "__main__":
|
| 278 |
+
main()
|
analyses/run_deep_advantages.py
ADDED
|
@@ -0,0 +1,784 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
"""Demonstrate what DeepPTR can do that the analytical method cannot.
|
| 3 |
+
|
| 4 |
+
Key advantages:
|
| 5 |
+
1. Uncertainty-guided gene filtering improves half-life correlation
|
| 6 |
+
2. Cell-specific gamma resolves transition-state heterogeneity
|
| 7 |
+
3. Latent disentanglement discovers post-transcriptional programs
|
| 8 |
+
4. Posterior sampling enables statistical testing of gamma differences
|
| 9 |
+
|
| 10 |
+
All results saved to output/deep_advantages/.
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
from __future__ import annotations
|
| 14 |
+
|
| 15 |
+
import os
|
| 16 |
+
os.environ["OMP_NUM_THREADS"] = "4"
|
| 17 |
+
os.environ["MKL_NUM_THREADS"] = "4"
|
| 18 |
+
os.environ["OPENBLAS_NUM_THREADS"] = "4"
|
| 19 |
+
os.environ["NUMEXPR_NUM_THREADS"] = "4"
|
| 20 |
+
|
| 21 |
+
import json
|
| 22 |
+
import sys
|
| 23 |
+
import time
|
| 24 |
+
from pathlib import Path
|
| 25 |
+
|
| 26 |
+
import matplotlib
|
| 27 |
+
matplotlib.use("Agg")
|
| 28 |
+
import matplotlib.pyplot as plt
|
| 29 |
+
import numpy as np
|
| 30 |
+
import pandas as pd
|
| 31 |
+
from scipy import stats
|
| 32 |
+
import scanpy as sc
|
| 33 |
+
|
| 34 |
+
import torch
|
| 35 |
+
torch.set_num_threads(4)
|
| 36 |
+
|
| 37 |
+
sys.path.insert(0, str(Path(__file__).parent))
|
| 38 |
+
from _common import set_figure_style
|
| 39 |
+
|
| 40 |
+
import scptr
|
| 41 |
+
|
| 42 |
+
OUTPUT_DIR = Path(__file__).parent.parent / "output" / "deep_advantages"
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def save_fig(fig, name, subdir="figures"):
|
| 46 |
+
if fig is None:
|
| 47 |
+
return
|
| 48 |
+
out_dir = OUTPUT_DIR / subdir
|
| 49 |
+
out_dir.mkdir(parents=True, exist_ok=True)
|
| 50 |
+
path = out_dir / f"{name}.png"
|
| 51 |
+
fig.savefig(path, dpi=150, bbox_inches="tight")
|
| 52 |
+
plt.close(fig)
|
| 53 |
+
print(f" Saved: {path}")
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def ensure_dirs():
|
| 57 |
+
for sub in ("figures", "results"):
|
| 58 |
+
(OUTPUT_DIR / sub).mkdir(parents=True, exist_ok=True)
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def select_top_genes(adata, n_top=300):
|
| 62 |
+
from scipy.sparse import issparse
|
| 63 |
+
u = adata.layers["unspliced"]
|
| 64 |
+
if issparse(u):
|
| 65 |
+
u = np.asarray(u.todense())
|
| 66 |
+
u = np.asarray(u, dtype=np.float32)
|
| 67 |
+
score = u.sum(axis=0) * (u > 0).mean(axis=0)
|
| 68 |
+
top_idx = np.sort(np.argsort(score)[::-1][:n_top])
|
| 69 |
+
adata_sub = adata[:, adata.var_names[top_idx]].copy()
|
| 70 |
+
from scipy.sparse import issparse as _iss
|
| 71 |
+
for key in ("spliced", "unspliced"):
|
| 72 |
+
if key in adata_sub.layers and _iss(adata_sub.layers[key]):
|
| 73 |
+
adata_sub.layers[key] = np.asarray(adata_sub.layers[key].todense())
|
| 74 |
+
return adata_sub
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def prepare_both(adata_loader, n_top=300):
|
| 78 |
+
"""Run analytical and DeepPTR pipelines, return both adatas."""
|
| 79 |
+
# Analytical
|
| 80 |
+
adata_an = adata_loader()
|
| 81 |
+
scptr.pp.filter_genes(adata_an)
|
| 82 |
+
scptr.pp.normalize_layers(adata_an)
|
| 83 |
+
scptr.pp.neighbors(adata_an, n_neighbors=30)
|
| 84 |
+
scptr.pp.smooth_layers(adata_an)
|
| 85 |
+
scptr.tl.estimate_beta(adata_an)
|
| 86 |
+
scptr.tl.estimate_gamma(adata_an)
|
| 87 |
+
|
| 88 |
+
# DeepPTR
|
| 89 |
+
adata_dp = adata_loader()
|
| 90 |
+
scptr.pp.filter_genes(adata_dp)
|
| 91 |
+
scptr.pp.normalize_layers(adata_dp)
|
| 92 |
+
scptr.pp.neighbors(adata_dp, n_neighbors=30)
|
| 93 |
+
scptr.pp.smooth_layers(adata_dp)
|
| 94 |
+
scptr.tl.estimate_beta(adata_dp)
|
| 95 |
+
adata_dp = select_top_genes(adata_dp, n_top=n_top)
|
| 96 |
+
|
| 97 |
+
torch.set_num_threads(4)
|
| 98 |
+
model, history = scptr.deep.fit_deepptr(
|
| 99 |
+
adata_dp,
|
| 100 |
+
d_T=8, d_PT=8, d_hidden=48, n_enc_layers=2,
|
| 101 |
+
batch_size=512, max_epochs=100, kl_warmup_epochs=20,
|
| 102 |
+
patience=15, n_posterior_samples=30,
|
| 103 |
+
device="cpu", seed=0, verbose=True,
|
| 104 |
+
)
|
| 105 |
+
return adata_an, adata_dp, model
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
# ============================================================================
|
| 109 |
+
# 1. UNCERTAINTY-GUIDED GENE FILTERING
|
| 110 |
+
# ============================================================================
|
| 111 |
+
|
| 112 |
+
def advantage_uncertainty_filtering(adata_dp, dataset_name):
|
| 113 |
+
"""Show that filtering genes by low posterior variance improves half-life correlation.
|
| 114 |
+
|
| 115 |
+
The analytical method has no uncertainty estimate — all genes are treated equally.
|
| 116 |
+
DeepPTR's posterior variance lets us select high-confidence genes, improving
|
| 117 |
+
downstream correlations.
|
| 118 |
+
"""
|
| 119 |
+
print(f"\n{'=' * 60}")
|
| 120 |
+
print(f"ADVANTAGE 1: Uncertainty-guided gene filtering ({dataset_name})")
|
| 121 |
+
print("=" * 60)
|
| 122 |
+
|
| 123 |
+
hl_mouse = scptr.datasets.herzog2017_halflives()
|
| 124 |
+
hl_human = scptr.datasets.schofield2018_halflives()
|
| 125 |
+
|
| 126 |
+
gamma_med = np.median(adata_dp.layers["gamma"], axis=0)
|
| 127 |
+
gamma_var_med = np.median(adata_dp.layers["gamma_var"], axis=0)
|
| 128 |
+
|
| 129 |
+
# Coefficient of variation of gamma across posterior samples
|
| 130 |
+
gamma_cv = np.sqrt(gamma_var_med) / (gamma_med + 1e-8)
|
| 131 |
+
|
| 132 |
+
results = {}
|
| 133 |
+
for ref_name, hl_df in [("mouse", hl_mouse), ("human", hl_human)]:
|
| 134 |
+
# Match genes
|
| 135 |
+
hl_s = hl_df.set_index("gene_symbol")["half_life_hours"]
|
| 136 |
+
# Case-insensitive matching
|
| 137 |
+
gamma_upper = {g.upper(): i for i, g in enumerate(adata_dp.var_names)}
|
| 138 |
+
hl_upper = {g.upper(): g for g in hl_s.index if isinstance(g, str)}
|
| 139 |
+
shared = set(gamma_upper.keys()) & set(hl_upper.keys())
|
| 140 |
+
|
| 141 |
+
if len(shared) < 10:
|
| 142 |
+
print(f" {ref_name}: too few shared genes ({len(shared)})")
|
| 143 |
+
continue
|
| 144 |
+
|
| 145 |
+
g_idx = [gamma_upper[u] for u in shared]
|
| 146 |
+
h_vals = np.array([hl_s[hl_upper[u]] for u in shared], dtype=float)
|
| 147 |
+
g_vals = gamma_med[g_idx]
|
| 148 |
+
cv_vals = gamma_cv[g_idx]
|
| 149 |
+
|
| 150 |
+
valid = np.isfinite(g_vals) & np.isfinite(h_vals) & (g_vals > 0) & (h_vals > 0)
|
| 151 |
+
g_vals, h_vals, cv_vals = g_vals[valid], h_vals[valid], cv_vals[valid]
|
| 152 |
+
|
| 153 |
+
# Baseline: all genes
|
| 154 |
+
sp_all, _ = stats.spearmanr(g_vals, h_vals)
|
| 155 |
+
|
| 156 |
+
# Filter by uncertainty thresholds
|
| 157 |
+
thresholds = [1.0, 0.75, 0.5, 0.3, 0.2]
|
| 158 |
+
records = [{"threshold": "all", "n_genes": len(g_vals), "spearman_r": float(sp_all)}]
|
| 159 |
+
|
| 160 |
+
for thr in thresholds:
|
| 161 |
+
mask = cv_vals < thr
|
| 162 |
+
if mask.sum() < 10:
|
| 163 |
+
continue
|
| 164 |
+
sp_r, _ = stats.spearmanr(g_vals[mask], h_vals[mask])
|
| 165 |
+
records.append({
|
| 166 |
+
"threshold": f"CV<{thr}",
|
| 167 |
+
"n_genes": int(mask.sum()),
|
| 168 |
+
"spearman_r": float(sp_r),
|
| 169 |
+
})
|
| 170 |
+
|
| 171 |
+
# Also try variance-based percentile filtering
|
| 172 |
+
for pct in [75, 50, 25]:
|
| 173 |
+
cutoff = np.percentile(cv_vals, pct)
|
| 174 |
+
mask = cv_vals <= cutoff
|
| 175 |
+
if mask.sum() < 10:
|
| 176 |
+
continue
|
| 177 |
+
sp_r, _ = stats.spearmanr(g_vals[mask], h_vals[mask])
|
| 178 |
+
records.append({
|
| 179 |
+
"threshold": f"bottom_{pct}pct_CV",
|
| 180 |
+
"n_genes": int(mask.sum()),
|
| 181 |
+
"spearman_r": float(sp_r),
|
| 182 |
+
})
|
| 183 |
+
|
| 184 |
+
results[ref_name] = records
|
| 185 |
+
print(f"\n {ref_name} half-life:")
|
| 186 |
+
for r in records:
|
| 187 |
+
print(f" {r['threshold']:>20s}: r={r['spearman_r']:.4f} (n={r['n_genes']})")
|
| 188 |
+
|
| 189 |
+
# Plot improvement
|
| 190 |
+
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
|
| 191 |
+
for ax_idx, (ref_name, records) in enumerate(results.items()):
|
| 192 |
+
if not records:
|
| 193 |
+
continue
|
| 194 |
+
labels = [r["threshold"] for r in records]
|
| 195 |
+
rs = [r["spearman_r"] for r in records]
|
| 196 |
+
ns = [r["n_genes"] for r in records]
|
| 197 |
+
|
| 198 |
+
ax = axes[ax_idx]
|
| 199 |
+
bars = ax.bar(range(len(labels)), [-r for r in rs], color="steelblue", alpha=0.7)
|
| 200 |
+
ax.set_xticks(range(len(labels)))
|
| 201 |
+
ax.set_xticklabels(labels, rotation=45, ha="right", fontsize=8)
|
| 202 |
+
ax.set_ylabel("|Spearman r| with half-life")
|
| 203 |
+
ax.set_title(f"{dataset_name}: {ref_name} reference")
|
| 204 |
+
|
| 205 |
+
# Annotate with n_genes
|
| 206 |
+
for i, (bar, n) in enumerate(zip(bars, ns)):
|
| 207 |
+
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height(),
|
| 208 |
+
f"n={n}", ha="center", va="bottom", fontsize=7)
|
| 209 |
+
|
| 210 |
+
# Highlight improvement
|
| 211 |
+
if len(rs) > 1:
|
| 212 |
+
best = max(range(len(rs)), key=lambda i: abs(rs[i]))
|
| 213 |
+
if best > 0:
|
| 214 |
+
bars[best].set_color("darkorange")
|
| 215 |
+
|
| 216 |
+
fig.suptitle("Uncertainty-guided filtering improves half-life correlation", y=1.02)
|
| 217 |
+
fig.tight_layout()
|
| 218 |
+
save_fig(fig, f"{dataset_name}_uncertainty_filtering")
|
| 219 |
+
|
| 220 |
+
return results
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
# ============================================================================
|
| 224 |
+
# 2. CELL-SPECIFIC GAMMA RESOLUTION
|
| 225 |
+
# ============================================================================
|
| 226 |
+
|
| 227 |
+
def advantage_cell_resolution(adata_an, adata_dp, dataset_name, cluster_key="clusters"):
|
| 228 |
+
"""Show DeepPTR captures per-cell gamma variation that smoothed analytical misses.
|
| 229 |
+
|
| 230 |
+
The analytical method smoothes Mu/Ms across neighbors, collapsing per-cell variation.
|
| 231 |
+
DeepPTR infers gamma per-cell from the generative model, preserving heterogeneity
|
| 232 |
+
at transition states.
|
| 233 |
+
"""
|
| 234 |
+
print(f"\n{'=' * 60}")
|
| 235 |
+
print(f"ADVANTAGE 2: Cell-specific gamma resolution ({dataset_name})")
|
| 236 |
+
print("=" * 60)
|
| 237 |
+
|
| 238 |
+
if cluster_key not in adata_an.obs.columns:
|
| 239 |
+
print(" [SKIP] No cluster key")
|
| 240 |
+
return None
|
| 241 |
+
|
| 242 |
+
shared = adata_an.var_names.intersection(adata_dp.var_names)
|
| 243 |
+
an_idx = [list(adata_an.var_names).index(g) for g in shared]
|
| 244 |
+
dp_idx = [list(adata_dp.var_names).index(g) for g in shared]
|
| 245 |
+
|
| 246 |
+
cell_types = sorted(adata_an.obs[cluster_key].unique())
|
| 247 |
+
|
| 248 |
+
# For each cell type: compare within-cluster gamma CV (coefficient of variation)
|
| 249 |
+
# Higher CV = more heterogeneity captured
|
| 250 |
+
records = []
|
| 251 |
+
for ct in cell_types:
|
| 252 |
+
mask_an = (adata_an.obs[cluster_key] == ct).values
|
| 253 |
+
mask_dp = (adata_dp.obs[cluster_key] == ct).values
|
| 254 |
+
|
| 255 |
+
if mask_an.sum() < 10 or mask_dp.sum() < 10:
|
| 256 |
+
continue
|
| 257 |
+
|
| 258 |
+
gamma_an_ct = adata_an.layers["gamma"][mask_an][:, an_idx]
|
| 259 |
+
gamma_dp_ct = adata_dp.layers["gamma"][mask_dp][:, dp_idx]
|
| 260 |
+
|
| 261 |
+
# Per-gene CV within this cell type
|
| 262 |
+
mean_an = gamma_an_ct.mean(axis=0)
|
| 263 |
+
std_an = gamma_an_ct.std(axis=0)
|
| 264 |
+
cv_an = np.where(mean_an > 0.01, std_an / mean_an, 0)
|
| 265 |
+
|
| 266 |
+
mean_dp = gamma_dp_ct.mean(axis=0)
|
| 267 |
+
std_dp = gamma_dp_ct.std(axis=0)
|
| 268 |
+
cv_dp = np.where(mean_dp > 0.01, std_dp / mean_dp, 0)
|
| 269 |
+
|
| 270 |
+
# Median CV across genes
|
| 271 |
+
records.append({
|
| 272 |
+
"cell_type": str(ct),
|
| 273 |
+
"n_cells": int(mask_an.sum()),
|
| 274 |
+
"median_cv_analytical": float(np.median(cv_an)),
|
| 275 |
+
"median_cv_deepptr": float(np.median(cv_dp)),
|
| 276 |
+
"mean_cv_analytical": float(np.mean(cv_an)),
|
| 277 |
+
"mean_cv_deepptr": float(np.mean(cv_dp)),
|
| 278 |
+
})
|
| 279 |
+
|
| 280 |
+
if not records:
|
| 281 |
+
return None
|
| 282 |
+
|
| 283 |
+
df = pd.DataFrame(records)
|
| 284 |
+
print(f"\n Within-cluster gamma CV (higher = more heterogeneity):")
|
| 285 |
+
print(f" {'Cell type':<25} {'Analytical':>12} {'DeepPTR':>12} {'Ratio':>8}")
|
| 286 |
+
for _, row in df.iterrows():
|
| 287 |
+
ratio = row["median_cv_deepptr"] / max(row["median_cv_analytical"], 1e-8)
|
| 288 |
+
print(f" {row['cell_type']:<25} {row['median_cv_analytical']:>12.4f} "
|
| 289 |
+
f"{row['median_cv_deepptr']:>12.4f} {ratio:>8.2f}x")
|
| 290 |
+
|
| 291 |
+
# Inter-vs-intra cluster variance ratio (a.k.a. "signal to noise")
|
| 292 |
+
# If DeepPTR captures real biological variation, its inter/intra ratio
|
| 293 |
+
# should be similar or better than analytical
|
| 294 |
+
gamma_an_shared = adata_an.layers["gamma"][:, an_idx]
|
| 295 |
+
gamma_dp_shared = adata_dp.layers["gamma"][:, dp_idx]
|
| 296 |
+
labels = adata_an.obs[cluster_key].values
|
| 297 |
+
|
| 298 |
+
# F-statistic per gene (one-way ANOVA: do cell types differ?)
|
| 299 |
+
from scipy.stats import f_oneway
|
| 300 |
+
n_sig_an = 0
|
| 301 |
+
n_sig_dp = 0
|
| 302 |
+
n_tested = 0
|
| 303 |
+
f_stats_an = []
|
| 304 |
+
f_stats_dp = []
|
| 305 |
+
|
| 306 |
+
for g in range(len(shared)):
|
| 307 |
+
groups_an = [gamma_an_shared[labels == ct, g] for ct in cell_types
|
| 308 |
+
if (labels == ct).sum() >= 5]
|
| 309 |
+
groups_dp = [gamma_dp_shared[adata_dp.obs[cluster_key].values == ct, g]
|
| 310 |
+
for ct in cell_types
|
| 311 |
+
if (adata_dp.obs[cluster_key].values == ct).sum() >= 5]
|
| 312 |
+
|
| 313 |
+
if len(groups_an) < 2 or len(groups_dp) < 2:
|
| 314 |
+
continue
|
| 315 |
+
|
| 316 |
+
# Only test if there's signal
|
| 317 |
+
if np.std(gamma_an_shared[:, g]) < 1e-6 and np.std(gamma_dp_shared[:, g]) < 1e-6:
|
| 318 |
+
continue
|
| 319 |
+
|
| 320 |
+
n_tested += 1
|
| 321 |
+
try:
|
| 322 |
+
f_an, p_an = f_oneway(*groups_an)
|
| 323 |
+
f_dp, p_dp = f_oneway(*groups_dp)
|
| 324 |
+
f_stats_an.append(f_an)
|
| 325 |
+
f_stats_dp.append(f_dp)
|
| 326 |
+
if p_an < 0.05:
|
| 327 |
+
n_sig_an += 1
|
| 328 |
+
if p_dp < 0.05:
|
| 329 |
+
n_sig_dp += 1
|
| 330 |
+
except Exception:
|
| 331 |
+
pass
|
| 332 |
+
|
| 333 |
+
print(f"\n Cell-type-specific gamma (ANOVA, {n_tested} genes):")
|
| 334 |
+
print(f" Analytical: {n_sig_an}/{n_tested} genes significant (p<0.05)")
|
| 335 |
+
print(f" DeepPTR: {n_sig_dp}/{n_tested} genes significant (p<0.05)")
|
| 336 |
+
if f_stats_an and f_stats_dp:
|
| 337 |
+
print(f" Median F-stat: analytical={np.median(f_stats_an):.2f}, "
|
| 338 |
+
f"DeepPTR={np.median(f_stats_dp):.2f}")
|
| 339 |
+
|
| 340 |
+
result = {
|
| 341 |
+
"per_celltype_cv": records,
|
| 342 |
+
"anova_n_tested": n_tested,
|
| 343 |
+
"anova_n_sig_analytical": n_sig_an,
|
| 344 |
+
"anova_n_sig_deepptr": n_sig_dp,
|
| 345 |
+
"anova_median_F_analytical": float(np.median(f_stats_an)) if f_stats_an else None,
|
| 346 |
+
"anova_median_F_deepptr": float(np.median(f_stats_dp)) if f_stats_dp else None,
|
| 347 |
+
}
|
| 348 |
+
|
| 349 |
+
# Plot: scatter of F-statistics
|
| 350 |
+
if f_stats_an and f_stats_dp:
|
| 351 |
+
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
|
| 352 |
+
|
| 353 |
+
# F-statistic comparison
|
| 354 |
+
ax = axes[0]
|
| 355 |
+
min_len = min(len(f_stats_an), len(f_stats_dp))
|
| 356 |
+
ax.scatter(f_stats_an[:min_len], f_stats_dp[:min_len], alpha=0.3, s=8, c="steelblue")
|
| 357 |
+
lim = max(max(f_stats_an[:min_len]), max(f_stats_dp[:min_len]))
|
| 358 |
+
ax.plot([0, lim], [0, lim], "k--", alpha=0.3)
|
| 359 |
+
ax.set_xlabel("Analytical F-statistic")
|
| 360 |
+
ax.set_ylabel("DeepPTR F-statistic")
|
| 361 |
+
ax.set_title(f"Cell-type discrimination per gene")
|
| 362 |
+
ax.set_xscale("log")
|
| 363 |
+
ax.set_yscale("log")
|
| 364 |
+
|
| 365 |
+
# CV comparison
|
| 366 |
+
ax = axes[1]
|
| 367 |
+
ax.bar(range(len(df)), df["median_cv_analytical"], width=0.4,
|
| 368 |
+
label="Analytical", alpha=0.7, color="steelblue")
|
| 369 |
+
ax.bar([x + 0.4 for x in range(len(df))], df["median_cv_deepptr"], width=0.4,
|
| 370 |
+
label="DeepPTR", alpha=0.7, color="darkorange")
|
| 371 |
+
ax.set_xticks([x + 0.2 for x in range(len(df))])
|
| 372 |
+
ax.set_xticklabels(df["cell_type"], rotation=45, ha="right", fontsize=7)
|
| 373 |
+
ax.set_ylabel("Median within-cluster gamma CV")
|
| 374 |
+
ax.set_title(f"Per-cell heterogeneity")
|
| 375 |
+
ax.legend()
|
| 376 |
+
|
| 377 |
+
fig.suptitle(f"{dataset_name}: Cell-specific gamma resolution", y=1.02)
|
| 378 |
+
fig.tight_layout()
|
| 379 |
+
save_fig(fig, f"{dataset_name}_cell_resolution")
|
| 380 |
+
|
| 381 |
+
return result
|
| 382 |
+
|
| 383 |
+
|
| 384 |
+
# ============================================================================
|
| 385 |
+
# 3. LATENT DISENTANGLEMENT DISCOVERS PT PROGRAMS
|
| 386 |
+
# ============================================================================
|
| 387 |
+
|
| 388 |
+
def advantage_disentanglement(adata_dp, dataset_name, cluster_key="clusters"):
|
| 389 |
+
"""Show z_PT captures post-transcriptional programs invisible in expression.
|
| 390 |
+
|
| 391 |
+
z_T captures transcriptional identity (cell type).
|
| 392 |
+
z_PT captures orthogonal post-transcriptional regulation.
|
| 393 |
+
Genes loading on z_PT but not z_T reveal PT-specific regulation.
|
| 394 |
+
"""
|
| 395 |
+
print(f"\n{'=' * 60}")
|
| 396 |
+
print(f"ADVANTAGE 3: Latent disentanglement ({dataset_name})")
|
| 397 |
+
print("=" * 60)
|
| 398 |
+
|
| 399 |
+
z_T = adata_dp.obsm["X_z_T"]
|
| 400 |
+
z_PT = adata_dp.obsm["X_z_PT"]
|
| 401 |
+
gamma = adata_dp.layers["gamma"]
|
| 402 |
+
|
| 403 |
+
# 1. Correlation of each gene's gamma with z_T vs z_PT
|
| 404 |
+
# Genes correlated with z_PT but not z_T are PT-specific
|
| 405 |
+
r_T = np.zeros(adata_dp.n_vars)
|
| 406 |
+
r_PT = np.zeros(adata_dp.n_vars)
|
| 407 |
+
|
| 408 |
+
for g in range(adata_dp.n_vars):
|
| 409 |
+
gv = gamma[:, g]
|
| 410 |
+
if gv.std() < 1e-8:
|
| 411 |
+
continue
|
| 412 |
+
# Max absolute correlation with any z_T dimension
|
| 413 |
+
r_T[g] = max(abs(stats.spearmanr(gv, z_T[:, d]).statistic)
|
| 414 |
+
for d in range(z_T.shape[1]))
|
| 415 |
+
r_PT[g] = max(abs(stats.spearmanr(gv, z_PT[:, d]).statistic)
|
| 416 |
+
for d in range(z_PT.shape[1]))
|
| 417 |
+
|
| 418 |
+
# Genes specifically correlated with z_PT
|
| 419 |
+
pt_specific_mask = (r_PT > 0.3) & (r_PT > r_T * 1.5)
|
| 420 |
+
t_specific_mask = (r_T > 0.3) & (r_T > r_PT * 1.5)
|
| 421 |
+
|
| 422 |
+
pt_genes = adata_dp.var_names[pt_specific_mask].tolist()
|
| 423 |
+
t_genes = adata_dp.var_names[t_specific_mask].tolist()
|
| 424 |
+
|
| 425 |
+
print(f"\n PT-specific genes (r_PT>0.3, r_PT>1.5*r_T): {len(pt_genes)}")
|
| 426 |
+
if pt_genes:
|
| 427 |
+
print(f" Top PT genes: {pt_genes[:15]}")
|
| 428 |
+
print(f" T-specific genes (r_T>0.3, r_T>1.5*r_PT): {len(t_genes)}")
|
| 429 |
+
if t_genes:
|
| 430 |
+
print(f" Top T genes: {t_genes[:15]}")
|
| 431 |
+
|
| 432 |
+
# 2. Cluster in z_PT space to find PT states
|
| 433 |
+
from sklearn.cluster import KMeans
|
| 434 |
+
n_pt_clusters = min(5, max(2, len(set(adata_dp.obs.get(cluster_key, []))) // 2))
|
| 435 |
+
km = KMeans(n_clusters=n_pt_clusters, random_state=0, n_init=10)
|
| 436 |
+
pt_labels = km.fit_predict(z_PT)
|
| 437 |
+
adata_dp.obs["pt_cluster_deep"] = pd.Categorical([f"PT_{i}" for i in pt_labels])
|
| 438 |
+
|
| 439 |
+
# 3. Compare: do PT clusters align with expression clusters?
|
| 440 |
+
if cluster_key in adata_dp.obs.columns:
|
| 441 |
+
from sklearn.metrics import adjusted_rand_score, normalized_mutual_info_score
|
| 442 |
+
expr_labels = adata_dp.obs[cluster_key].astype("category").cat.codes.values
|
| 443 |
+
ari = adjusted_rand_score(expr_labels, pt_labels)
|
| 444 |
+
nmi = normalized_mutual_info_score(expr_labels, pt_labels)
|
| 445 |
+
print(f"\n PT clusters vs expression clusters:")
|
| 446 |
+
print(f" ARI = {ari:.4f} (0=random, 1=identical)")
|
| 447 |
+
print(f" NMI = {nmi:.4f}")
|
| 448 |
+
print(f" → {'Low' if ari < 0.3 else 'Moderate' if ari < 0.6 else 'High'} "
|
| 449 |
+
f"overlap: PT space captures {'different' if ari < 0.3 else 'partially overlapping'} structure")
|
| 450 |
+
else:
|
| 451 |
+
ari = nmi = None
|
| 452 |
+
|
| 453 |
+
# 4. Find genes differentially degraded between PT clusters
|
| 454 |
+
# (these are genes whose degradation rate differs for reasons orthogonal to expression)
|
| 455 |
+
from scipy.stats import kruskal
|
| 456 |
+
pt_de_genes = []
|
| 457 |
+
for g in range(adata_dp.n_vars):
|
| 458 |
+
groups = [gamma[pt_labels == k, g] for k in range(n_pt_clusters)]
|
| 459 |
+
groups = [grp for grp in groups if len(grp) >= 5]
|
| 460 |
+
if len(groups) < 2:
|
| 461 |
+
continue
|
| 462 |
+
try:
|
| 463 |
+
h_stat, p_val = kruskal(*groups)
|
| 464 |
+
if p_val < 0.01:
|
| 465 |
+
effect = np.max([np.median(grp) for grp in groups]) / max(np.min([np.median(grp) for grp in groups]), 1e-8)
|
| 466 |
+
pt_de_genes.append({
|
| 467 |
+
"gene": adata_dp.var_names[g],
|
| 468 |
+
"H_statistic": float(h_stat),
|
| 469 |
+
"p_value": float(p_val),
|
| 470 |
+
"fold_change": float(effect),
|
| 471 |
+
})
|
| 472 |
+
except Exception:
|
| 473 |
+
pass
|
| 474 |
+
|
| 475 |
+
pt_de_genes.sort(key=lambda x: x["p_value"])
|
| 476 |
+
print(f"\n Genes differentially degraded between PT clusters: {len(pt_de_genes)}")
|
| 477 |
+
if pt_de_genes:
|
| 478 |
+
print(f" Top 10:")
|
| 479 |
+
for g in pt_de_genes[:10]:
|
| 480 |
+
print(f" {g['gene']:<15} H={g['H_statistic']:.1f} p={g['p_value']:.2e} FC={g['fold_change']:.2f}")
|
| 481 |
+
|
| 482 |
+
result = {
|
| 483 |
+
"n_pt_specific_genes": len(pt_genes),
|
| 484 |
+
"pt_specific_genes": pt_genes[:50],
|
| 485 |
+
"n_t_specific_genes": len(t_genes),
|
| 486 |
+
"t_specific_genes": t_genes[:50],
|
| 487 |
+
"pt_vs_expr_ari": float(ari) if ari is not None else None,
|
| 488 |
+
"pt_vs_expr_nmi": float(nmi) if nmi is not None else None,
|
| 489 |
+
"n_pt_de_genes": len(pt_de_genes),
|
| 490 |
+
"top_pt_de_genes": pt_de_genes[:20],
|
| 491 |
+
}
|
| 492 |
+
|
| 493 |
+
# Plot
|
| 494 |
+
fig, axes = plt.subplots(1, 3, figsize=(16, 5))
|
| 495 |
+
|
| 496 |
+
# Panel 1: r_T vs r_PT scatter
|
| 497 |
+
ax = axes[0]
|
| 498 |
+
ax.scatter(r_T, r_PT, alpha=0.3, s=8, c="gray")
|
| 499 |
+
if pt_specific_mask.any():
|
| 500 |
+
ax.scatter(r_T[pt_specific_mask], r_PT[pt_specific_mask],
|
| 501 |
+
alpha=0.7, s=15, c="darkorange", label=f"PT-specific ({len(pt_genes)})")
|
| 502 |
+
if t_specific_mask.any():
|
| 503 |
+
ax.scatter(r_T[t_specific_mask], r_PT[t_specific_mask],
|
| 504 |
+
alpha=0.7, s=15, c="steelblue", label=f"T-specific ({len(t_genes)})")
|
| 505 |
+
ax.plot([0, 1], [0, 1], "k--", alpha=0.3)
|
| 506 |
+
ax.set_xlabel("Max |r| with z_T")
|
| 507 |
+
ax.set_ylabel("Max |r| with z_PT")
|
| 508 |
+
ax.set_title("Gene regulation mode")
|
| 509 |
+
ax.legend(fontsize=8)
|
| 510 |
+
|
| 511 |
+
# Panel 2: z_PT PCA colored by PT cluster
|
| 512 |
+
from sklearn.decomposition import PCA
|
| 513 |
+
z_2d = PCA(n_components=2).fit_transform(z_PT)
|
| 514 |
+
cmap = plt.colormaps.get_cmap("Set2")
|
| 515 |
+
ax = axes[1]
|
| 516 |
+
for k in range(n_pt_clusters):
|
| 517 |
+
mask = pt_labels == k
|
| 518 |
+
ax.scatter(z_2d[mask, 0], z_2d[mask, 1], alpha=0.3, s=5,
|
| 519 |
+
c=[cmap(k)], label=f"PT_{k}")
|
| 520 |
+
ax.set_title("z_PT space (PT clusters)")
|
| 521 |
+
ax.set_xlabel("PC1")
|
| 522 |
+
ax.set_ylabel("PC2")
|
| 523 |
+
ax.legend(fontsize=7, markerscale=3)
|
| 524 |
+
|
| 525 |
+
# Panel 3: z_PT colored by expression cluster
|
| 526 |
+
ax = axes[2]
|
| 527 |
+
if cluster_key in adata_dp.obs.columns:
|
| 528 |
+
cats = adata_dp.obs[cluster_key].astype("category")
|
| 529 |
+
codes = cats.cat.codes.values
|
| 530 |
+
n_cats = len(cats.cat.categories)
|
| 531 |
+
cmap_expr = plt.colormaps.get_cmap("tab20")
|
| 532 |
+
for i, cat in enumerate(cats.cat.categories):
|
| 533 |
+
mask = codes == i
|
| 534 |
+
ax.scatter(z_2d[mask, 0], z_2d[mask, 1], alpha=0.3, s=5,
|
| 535 |
+
c=[cmap_expr(i / n_cats)], label=str(cat))
|
| 536 |
+
ax.set_title(f"z_PT space (expression clusters)\nARI={ari:.3f}")
|
| 537 |
+
if n_cats <= 12:
|
| 538 |
+
ax.legend(fontsize=6, markerscale=3, ncol=2)
|
| 539 |
+
ax.set_xlabel("PC1")
|
| 540 |
+
ax.set_ylabel("PC2")
|
| 541 |
+
|
| 542 |
+
fig.suptitle(f"{dataset_name}: Latent disentanglement", y=1.02)
|
| 543 |
+
fig.tight_layout()
|
| 544 |
+
save_fig(fig, f"{dataset_name}_disentanglement")
|
| 545 |
+
|
| 546 |
+
return result
|
| 547 |
+
|
| 548 |
+
|
| 549 |
+
# ============================================================================
|
| 550 |
+
# 4. POSTERIOR-BASED STATISTICAL TESTING
|
| 551 |
+
# ============================================================================
|
| 552 |
+
|
| 553 |
+
def advantage_statistical_testing(adata_dp, dataset_name, cluster_key="clusters"):
|
| 554 |
+
"""Demonstrate posterior-based statistical testing of gamma differences.
|
| 555 |
+
|
| 556 |
+
With DeepPTR, we can compute credible intervals for gamma differences
|
| 557 |
+
between cell types — something impossible with a point estimate.
|
| 558 |
+
"""
|
| 559 |
+
print(f"\n{'=' * 60}")
|
| 560 |
+
print(f"ADVANTAGE 4: Posterior-based statistical testing ({dataset_name})")
|
| 561 |
+
print("=" * 60)
|
| 562 |
+
|
| 563 |
+
if cluster_key not in adata_dp.obs.columns:
|
| 564 |
+
print(" [SKIP] No cluster key")
|
| 565 |
+
return None
|
| 566 |
+
|
| 567 |
+
gamma = adata_dp.layers["gamma"]
|
| 568 |
+
gamma_var = adata_dp.layers["gamma_var"]
|
| 569 |
+
|
| 570 |
+
cell_types = sorted(adata_dp.obs[cluster_key].unique())
|
| 571 |
+
if len(cell_types) < 2:
|
| 572 |
+
return None
|
| 573 |
+
|
| 574 |
+
# Pick two cell types to compare
|
| 575 |
+
# Choose the pair with most cells
|
| 576 |
+
ct_sizes = {ct: (adata_dp.obs[cluster_key] == ct).sum() for ct in cell_types}
|
| 577 |
+
sorted_cts = sorted(ct_sizes.keys(), key=lambda x: ct_sizes[x], reverse=True)
|
| 578 |
+
ct_a, ct_b = sorted_cts[0], sorted_cts[1]
|
| 579 |
+
|
| 580 |
+
mask_a = (adata_dp.obs[cluster_key] == ct_a).values
|
| 581 |
+
mask_b = (adata_dp.obs[cluster_key] == ct_b).values
|
| 582 |
+
|
| 583 |
+
gamma_a = gamma[mask_a]
|
| 584 |
+
gamma_b = gamma[mask_b]
|
| 585 |
+
var_a = gamma_var[mask_a]
|
| 586 |
+
var_b = gamma_var[mask_b]
|
| 587 |
+
|
| 588 |
+
# Per-gene: test if mean gamma differs between cell types
|
| 589 |
+
# Use posterior: mean_diff ~ N(mu_a - mu_b, var_a/n_a + var_b/n_b)
|
| 590 |
+
n_a, n_b = mask_a.sum(), mask_b.sum()
|
| 591 |
+
mean_a = gamma_a.mean(axis=0)
|
| 592 |
+
mean_b = gamma_b.mean(axis=0)
|
| 593 |
+
# Posterior variance of the mean
|
| 594 |
+
var_mean_a = var_a.mean(axis=0) / n_a
|
| 595 |
+
var_mean_b = var_b.mean(axis=0) / n_b
|
| 596 |
+
|
| 597 |
+
diff = mean_a - mean_b
|
| 598 |
+
diff_se = np.sqrt(var_mean_a + var_mean_b + 1e-10)
|
| 599 |
+
z_score = diff / diff_se
|
| 600 |
+
|
| 601 |
+
# Two-sided test
|
| 602 |
+
p_vals = 2 * (1 - stats.norm.cdf(np.abs(z_score)))
|
| 603 |
+
|
| 604 |
+
# Compare with simple t-test (no uncertainty info)
|
| 605 |
+
from scipy.stats import ttest_ind
|
| 606 |
+
p_ttest = np.zeros(adata_dp.n_vars)
|
| 607 |
+
for g in range(adata_dp.n_vars):
|
| 608 |
+
try:
|
| 609 |
+
_, p_ttest[g] = ttest_ind(gamma_a[:, g], gamma_b[:, g])
|
| 610 |
+
except Exception:
|
| 611 |
+
p_ttest[g] = 1.0
|
| 612 |
+
|
| 613 |
+
# Count significant at FDR 0.05
|
| 614 |
+
from statsmodels.stats.multitest import multipletests
|
| 615 |
+
_, p_adj_post, _, _ = multipletests(p_vals, method="fdr_bh")
|
| 616 |
+
_, p_adj_ttest, _, _ = multipletests(p_ttest, method="fdr_bh")
|
| 617 |
+
|
| 618 |
+
n_sig_post = (p_adj_post < 0.05).sum()
|
| 619 |
+
n_sig_ttest = (p_adj_ttest < 0.05).sum()
|
| 620 |
+
|
| 621 |
+
print(f"\n Comparing {ct_a} ({n_a} cells) vs {ct_b} ({n_b} cells):")
|
| 622 |
+
print(f" Posterior-informed test: {n_sig_post}/{adata_dp.n_vars} genes significant (FDR<0.05)")
|
| 623 |
+
print(f" Simple t-test: {n_sig_ttest}/{adata_dp.n_vars} genes significant (FDR<0.05)")
|
| 624 |
+
|
| 625 |
+
# Identify genes found by posterior but not by t-test (and vice versa)
|
| 626 |
+
post_only = (p_adj_post < 0.05) & (p_adj_ttest >= 0.05)
|
| 627 |
+
ttest_only = (p_adj_ttest < 0.05) & (p_adj_post >= 0.05)
|
| 628 |
+
both = (p_adj_post < 0.05) & (p_adj_ttest < 0.05)
|
| 629 |
+
|
| 630 |
+
print(f" Both: {both.sum()}")
|
| 631 |
+
print(f" Posterior-only: {post_only.sum()}")
|
| 632 |
+
print(f" T-test-only: {ttest_only.sum()}")
|
| 633 |
+
|
| 634 |
+
result = {
|
| 635 |
+
"ct_a": str(ct_a),
|
| 636 |
+
"ct_b": str(ct_b),
|
| 637 |
+
"n_cells_a": int(n_a),
|
| 638 |
+
"n_cells_b": int(n_b),
|
| 639 |
+
"n_sig_posterior": int(n_sig_post),
|
| 640 |
+
"n_sig_ttest": int(n_sig_ttest),
|
| 641 |
+
"n_both": int(both.sum()),
|
| 642 |
+
"n_posterior_only": int(post_only.sum()),
|
| 643 |
+
"n_ttest_only": int(ttest_only.sum()),
|
| 644 |
+
}
|
| 645 |
+
|
| 646 |
+
# If posterior finds additional genes, list them
|
| 647 |
+
if post_only.any():
|
| 648 |
+
post_only_genes = adata_dp.var_names[post_only].tolist()
|
| 649 |
+
print(f"\n Posterior-only genes (uncertainty-aware):")
|
| 650 |
+
for g in post_only_genes[:10]:
|
| 651 |
+
idx = list(adata_dp.var_names).index(g)
|
| 652 |
+
print(f" {g}: diff={diff[idx]:.4f} ± {diff_se[idx]:.4f}")
|
| 653 |
+
result["posterior_only_genes"] = post_only_genes[:20]
|
| 654 |
+
|
| 655 |
+
# Plot
|
| 656 |
+
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
|
| 657 |
+
|
| 658 |
+
ax = axes[0]
|
| 659 |
+
ax.scatter(-np.log10(p_ttest + 1e-300), -np.log10(p_vals + 1e-300),
|
| 660 |
+
alpha=0.2, s=5, c="gray")
|
| 661 |
+
if post_only.any():
|
| 662 |
+
ax.scatter(-np.log10(p_ttest[post_only] + 1e-300),
|
| 663 |
+
-np.log10(p_vals[post_only] + 1e-300),
|
| 664 |
+
alpha=0.7, s=15, c="darkorange", label="Posterior-only")
|
| 665 |
+
if ttest_only.any():
|
| 666 |
+
ax.scatter(-np.log10(p_ttest[ttest_only] + 1e-300),
|
| 667 |
+
-np.log10(p_vals[ttest_only] + 1e-300),
|
| 668 |
+
alpha=0.7, s=15, c="steelblue", label="T-test-only")
|
| 669 |
+
ax.set_xlabel("-log10(p) t-test")
|
| 670 |
+
ax.set_ylabel("-log10(p) posterior")
|
| 671 |
+
ax.set_title(f"{ct_a} vs {ct_b}")
|
| 672 |
+
ax.plot([0, 20], [0, 20], "k--", alpha=0.3)
|
| 673 |
+
ax.legend(fontsize=8)
|
| 674 |
+
|
| 675 |
+
# Volcano plot with uncertainty
|
| 676 |
+
ax = axes[1]
|
| 677 |
+
sig = p_adj_post < 0.05
|
| 678 |
+
ax.scatter(diff[~sig], -np.log10(p_vals[~sig] + 1e-300),
|
| 679 |
+
alpha=0.1, s=3, c="gray")
|
| 680 |
+
ax.scatter(diff[sig], -np.log10(p_vals[sig] + 1e-300),
|
| 681 |
+
alpha=0.5, s=8, c="darkorange")
|
| 682 |
+
ax.set_xlabel(f"Mean gamma difference ({ct_a} - {ct_b})")
|
| 683 |
+
ax.set_ylabel("-log10(p)")
|
| 684 |
+
ax.set_title(f"Posterior volcano ({n_sig_post} significant)")
|
| 685 |
+
ax.axhline(-np.log10(0.05), color="red", ls="--", alpha=0.3)
|
| 686 |
+
|
| 687 |
+
fig.suptitle(f"{dataset_name}: Posterior-based differential degradation", y=1.02)
|
| 688 |
+
fig.tight_layout()
|
| 689 |
+
save_fig(fig, f"{dataset_name}_posterior_testing")
|
| 690 |
+
|
| 691 |
+
return result
|
| 692 |
+
|
| 693 |
+
|
| 694 |
+
# ============================================================================
|
| 695 |
+
# MAIN
|
| 696 |
+
# ============================================================================
|
| 697 |
+
|
| 698 |
+
def main():
|
| 699 |
+
set_figure_style()
|
| 700 |
+
ensure_dirs()
|
| 701 |
+
|
| 702 |
+
datasets = [
|
| 703 |
+
("pancreas", scptr.datasets.pancreas, "clusters"),
|
| 704 |
+
("dentate_gyrus", scptr.datasets.dentate_gyrus, "clusters"),
|
| 705 |
+
]
|
| 706 |
+
|
| 707 |
+
all_results = {}
|
| 708 |
+
|
| 709 |
+
for name, loader, cluster_key in datasets:
|
| 710 |
+
print(f"\n{'#' * 60}")
|
| 711 |
+
print(f"# {name.upper()}")
|
| 712 |
+
print(f"{'#' * 60}")
|
| 713 |
+
|
| 714 |
+
adata_an, adata_dp, model = prepare_both(loader, n_top=300)
|
| 715 |
+
|
| 716 |
+
results = {}
|
| 717 |
+
|
| 718 |
+
# 1. Uncertainty-guided filtering
|
| 719 |
+
results["uncertainty_filtering"] = advantage_uncertainty_filtering(adata_dp, name)
|
| 720 |
+
|
| 721 |
+
# 2. Cell-specific gamma
|
| 722 |
+
results["cell_resolution"] = advantage_cell_resolution(adata_an, adata_dp, name, cluster_key)
|
| 723 |
+
|
| 724 |
+
# 3. Latent disentanglement
|
| 725 |
+
results["disentanglement"] = advantage_disentanglement(adata_dp, name, cluster_key)
|
| 726 |
+
|
| 727 |
+
# 4. Posterior testing
|
| 728 |
+
results["statistical_testing"] = advantage_statistical_testing(adata_dp, name, cluster_key)
|
| 729 |
+
|
| 730 |
+
all_results[name] = results
|
| 731 |
+
|
| 732 |
+
with open(OUTPUT_DIR / "results" / f"{name}_advantages.json", "w") as f:
|
| 733 |
+
json.dump(results, f, indent=2, default=str)
|
| 734 |
+
|
| 735 |
+
# Summary
|
| 736 |
+
print(f"\n{'=' * 70}")
|
| 737 |
+
print("DEEPPTR UNIQUE ADVANTAGES SUMMARY")
|
| 738 |
+
print("=" * 70)
|
| 739 |
+
|
| 740 |
+
for name, results in all_results.items():
|
| 741 |
+
print(f"\n {name.upper()}")
|
| 742 |
+
|
| 743 |
+
# Uncertainty filtering
|
| 744 |
+
uf = results.get("uncertainty_filtering", {})
|
| 745 |
+
for ref, records in uf.items():
|
| 746 |
+
if records:
|
| 747 |
+
r_all = records[0]["spearman_r"]
|
| 748 |
+
r_best = min(records, key=lambda x: x["spearman_r"]) # most negative
|
| 749 |
+
improvement = abs(r_best["spearman_r"]) - abs(r_all)
|
| 750 |
+
print(f" Uncertainty filtering ({ref}): {r_all:.4f} → {r_best['spearman_r']:.4f} "
|
| 751 |
+
f"(+{improvement:.4f} at {r_best['threshold']})")
|
| 752 |
+
|
| 753 |
+
# Cell resolution
|
| 754 |
+
cr = results.get("cell_resolution", {})
|
| 755 |
+
if cr:
|
| 756 |
+
print(f" Cell-type ANOVA: analytical={cr['anova_n_sig_analytical']}, "
|
| 757 |
+
f"DeepPTR={cr['anova_n_sig_deepptr']} significant genes")
|
| 758 |
+
|
| 759 |
+
# Disentanglement
|
| 760 |
+
dis = results.get("disentanglement", {})
|
| 761 |
+
if dis:
|
| 762 |
+
print(f" PT-specific genes: {dis['n_pt_specific_genes']}, "
|
| 763 |
+
f"T-specific: {dis['n_t_specific_genes']}")
|
| 764 |
+
if dis.get("pt_vs_expr_ari") is not None:
|
| 765 |
+
print(f" PT vs expr overlap: ARI={dis['pt_vs_expr_ari']:.4f} "
|
| 766 |
+
f"({'orthogonal' if dis['pt_vs_expr_ari'] < 0.2 else 'partially overlapping'})")
|
| 767 |
+
print(f" DE genes between PT clusters: {dis['n_pt_de_genes']}")
|
| 768 |
+
|
| 769 |
+
# Statistical testing
|
| 770 |
+
st = results.get("statistical_testing", {})
|
| 771 |
+
if st:
|
| 772 |
+
print(f" Posterior testing ({st['ct_a']} vs {st['ct_b']}): "
|
| 773 |
+
f"{st['n_sig_posterior']} posterior, {st['n_sig_ttest']} t-test, "
|
| 774 |
+
f"{st['n_posterior_only']} posterior-only")
|
| 775 |
+
|
| 776 |
+
# Save combined
|
| 777 |
+
with open(OUTPUT_DIR / "results" / "combined_advantages.json", "w") as f:
|
| 778 |
+
json.dump(all_results, f, indent=2, default=str)
|
| 779 |
+
|
| 780 |
+
print(f"\nAll results saved to: {OUTPUT_DIR}")
|
| 781 |
+
|
| 782 |
+
|
| 783 |
+
if __name__ == "__main__":
|
| 784 |
+
main()
|
analyses/run_mirna_analysis.py
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
"""miRNA-target analysis: test whether miRNA-targeted genes have higher gamma.
|
| 3 |
+
|
| 4 |
+
Uses TargetScan 8.0 predictions to identify miRNA-target relationships,
|
| 5 |
+
then tests whether predicted targets have systematically higher degradation
|
| 6 |
+
rates (gamma) than non-targets using Mann-Whitney U tests.
|
| 7 |
+
|
| 8 |
+
This addresses Aim 4 of the research plan: post-transcriptional regulatory networks.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
from __future__ import annotations
|
| 12 |
+
|
| 13 |
+
import json
|
| 14 |
+
import sys
|
| 15 |
+
from pathlib import Path
|
| 16 |
+
|
| 17 |
+
import matplotlib
|
| 18 |
+
matplotlib.use("Agg")
|
| 19 |
+
import matplotlib.pyplot as plt
|
| 20 |
+
import numpy as np
|
| 21 |
+
import pandas as pd
|
| 22 |
+
from scipy import stats
|
| 23 |
+
|
| 24 |
+
sys.path.insert(0, str(Path(__file__).parent))
|
| 25 |
+
from _common import set_figure_style
|
| 26 |
+
|
| 27 |
+
import scptr
|
| 28 |
+
|
| 29 |
+
OUTPUT_DIR = Path(__file__).parent.parent / "output" / "mirna_analysis"
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def save_fig(fig, name, subdir="figures"):
|
| 33 |
+
out_dir = OUTPUT_DIR / subdir
|
| 34 |
+
out_dir.mkdir(parents=True, exist_ok=True)
|
| 35 |
+
path = out_dir / f"{name}.png"
|
| 36 |
+
fig.savefig(path, dpi=150, bbox_inches="tight")
|
| 37 |
+
plt.close(fig)
|
| 38 |
+
print(f" Saved: {path}")
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def run_mirna_analysis(adata, dataset_name, mirna_targets):
|
| 42 |
+
"""Run miRNA-gamma correlation analysis on a dataset."""
|
| 43 |
+
print(f"\n{'='*60}")
|
| 44 |
+
print(f"miRNA ANALYSIS: {dataset_name}")
|
| 45 |
+
print(f"{'='*60}")
|
| 46 |
+
|
| 47 |
+
# Run scPTR pipeline
|
| 48 |
+
import copy
|
| 49 |
+
adata = copy.deepcopy(adata)
|
| 50 |
+
scptr.pp.filter_genes(adata)
|
| 51 |
+
scptr.pp.normalize_layers(adata)
|
| 52 |
+
scptr.pp.neighbors(adata, n_neighbors=30)
|
| 53 |
+
scptr.pp.smooth_layers(adata)
|
| 54 |
+
scptr.tl.estimate_beta(adata)
|
| 55 |
+
scptr.tl.estimate_gamma(adata)
|
| 56 |
+
|
| 57 |
+
# Run miRNA-gamma correlation
|
| 58 |
+
print(f" Running miRNA-gamma correlation...")
|
| 59 |
+
result_df = scptr.tl.mirna_gamma_correlation(
|
| 60 |
+
adata, mirna_targets, n_top_targets=200, min_cells_expressing=50
|
| 61 |
+
)
|
| 62 |
+
|
| 63 |
+
if len(result_df) == 0:
|
| 64 |
+
print(f" No miRNA families with sufficient targets found.")
|
| 65 |
+
return None
|
| 66 |
+
|
| 67 |
+
# Summary statistics
|
| 68 |
+
n_tested = len(result_df)
|
| 69 |
+
n_sig = (result_df["fdr"] < 0.05).sum()
|
| 70 |
+
n_sig_10 = (result_df["fdr"] < 0.10).sum()
|
| 71 |
+
n_enriched = (result_df["fold_enrichment"] > 1.0).sum()
|
| 72 |
+
|
| 73 |
+
print(f"\n Results:")
|
| 74 |
+
print(f" miRNA families tested: {n_tested}")
|
| 75 |
+
print(f" Significant (FDR < 0.05): {n_sig} ({100*n_sig/n_tested:.1f}%)")
|
| 76 |
+
print(f" Significant (FDR < 0.10): {n_sig_10} ({100*n_sig_10/n_tested:.1f}%)")
|
| 77 |
+
print(f" Enriched (fold > 1.0): {n_enriched} ({100*n_enriched/n_tested:.1f}%)")
|
| 78 |
+
print(f" Median fold enrichment: {result_df['fold_enrichment'].median():.3f}")
|
| 79 |
+
|
| 80 |
+
# Top significant miRNAs
|
| 81 |
+
top_sig = result_df[result_df["fdr"] < 0.10].head(20)
|
| 82 |
+
if len(top_sig) > 0:
|
| 83 |
+
print(f"\n Top significant miRNAs (FDR < 0.10):")
|
| 84 |
+
for _, row in top_sig.iterrows():
|
| 85 |
+
print(f" {row['representative_mirna']:>25s} "
|
| 86 |
+
f"n_targets={row['n_targets_in_data']:3d} "
|
| 87 |
+
f"fold={row['fold_enrichment']:.2f} "
|
| 88 |
+
f"p={row['mannwhitney_p']:.2e} "
|
| 89 |
+
f"FDR={row['fdr']:.3f}")
|
| 90 |
+
|
| 91 |
+
# Top miRNAs by effect size regardless of significance
|
| 92 |
+
top_effect = result_df.nlargest(10, "fold_enrichment")
|
| 93 |
+
print(f"\n Top miRNAs by fold enrichment:")
|
| 94 |
+
for _, row in top_effect.iterrows():
|
| 95 |
+
print(f" {row['representative_mirna']:>25s} "
|
| 96 |
+
f"fold={row['fold_enrichment']:.2f} "
|
| 97 |
+
f"FDR={row['fdr']:.3f}")
|
| 98 |
+
|
| 99 |
+
# Aggregate test: all miRNA targets vs non-targets
|
| 100 |
+
gamma = np.median(adata.layers["gamma"], axis=0)
|
| 101 |
+
gene_names_upper = [g.upper() for g in adata.var_names]
|
| 102 |
+
|
| 103 |
+
all_target_genes = set()
|
| 104 |
+
for _, row in mirna_targets.iterrows():
|
| 105 |
+
all_target_genes.add(str(row["gene_symbol"]).upper())
|
| 106 |
+
|
| 107 |
+
informative = (adata.layers["gamma"] > 0).mean(axis=0) >= 0.1
|
| 108 |
+
target_gamma = []
|
| 109 |
+
nontarget_gamma = []
|
| 110 |
+
for i, g in enumerate(gene_names_upper):
|
| 111 |
+
if not informative[i]:
|
| 112 |
+
continue
|
| 113 |
+
if g in all_target_genes:
|
| 114 |
+
target_gamma.append(gamma[i])
|
| 115 |
+
else:
|
| 116 |
+
nontarget_gamma.append(gamma[i])
|
| 117 |
+
|
| 118 |
+
if len(target_gamma) >= 10 and len(nontarget_gamma) >= 10:
|
| 119 |
+
u, p = stats.mannwhitneyu(target_gamma, nontarget_gamma, alternative="greater")
|
| 120 |
+
print(f"\n Aggregate test (all targets vs non-targets):")
|
| 121 |
+
print(f" Target genes in data: {len(target_gamma)}")
|
| 122 |
+
print(f" Non-target genes: {len(nontarget_gamma)}")
|
| 123 |
+
print(f" Target median gamma: {np.median(target_gamma):.6f}")
|
| 124 |
+
print(f" Non-target median gamma: {np.median(nontarget_gamma):.6f}")
|
| 125 |
+
print(f" Fold: {np.median(target_gamma) / (np.median(nontarget_gamma) + 1e-8):.3f}")
|
| 126 |
+
print(f" Mann-Whitney p: {p:.2e}")
|
| 127 |
+
|
| 128 |
+
# Save results
|
| 129 |
+
res_dir = OUTPUT_DIR / "results"
|
| 130 |
+
res_dir.mkdir(parents=True, exist_ok=True)
|
| 131 |
+
result_df.to_csv(res_dir / f"mirna_gamma_{dataset_name}.csv", index=False)
|
| 132 |
+
|
| 133 |
+
# Figures
|
| 134 |
+
fig, axes = plt.subplots(1, 3, figsize=(16, 5))
|
| 135 |
+
|
| 136 |
+
# Panel 1: Volcano plot (fold enrichment vs -log10 p)
|
| 137 |
+
neg_log_p = -np.log10(result_df["mannwhitney_p"].clip(lower=1e-50))
|
| 138 |
+
sig_mask = result_df["fdr"] < 0.05
|
| 139 |
+
axes[0].scatter(result_df["fold_enrichment"][~sig_mask], neg_log_p[~sig_mask],
|
| 140 |
+
s=10, alpha=0.3, color="gray", label="NS")
|
| 141 |
+
axes[0].scatter(result_df["fold_enrichment"][sig_mask], neg_log_p[sig_mask],
|
| 142 |
+
s=20, alpha=0.7, color="red", label=f"FDR<0.05 (n={sig_mask.sum()})")
|
| 143 |
+
axes[0].axhline(y=-np.log10(0.05), color="blue", linestyle="--", alpha=0.5)
|
| 144 |
+
axes[0].axvline(x=1.0, color="black", linestyle="--", alpha=0.3)
|
| 145 |
+
axes[0].set_xlabel("Fold enrichment (target/non-target gamma)")
|
| 146 |
+
axes[0].set_ylabel("-log10(p)")
|
| 147 |
+
axes[0].set_title(f"miRNA Target Enrichment ({dataset_name})")
|
| 148 |
+
axes[0].legend()
|
| 149 |
+
|
| 150 |
+
# Panel 2: Distribution of fold enrichments
|
| 151 |
+
axes[1].hist(result_df["fold_enrichment"], bins=30, color="steelblue",
|
| 152 |
+
edgecolor="black", linewidth=0.5)
|
| 153 |
+
axes[1].axvline(x=1.0, color="red", linestyle="--", label="No enrichment")
|
| 154 |
+
axes[1].axvline(x=result_df["fold_enrichment"].median(), color="green",
|
| 155 |
+
linestyle="--", label=f"Median={result_df['fold_enrichment'].median():.2f}")
|
| 156 |
+
axes[1].set_xlabel("Fold enrichment")
|
| 157 |
+
axes[1].set_ylabel("Count")
|
| 158 |
+
axes[1].set_title("Distribution of Fold Enrichments")
|
| 159 |
+
axes[1].legend()
|
| 160 |
+
|
| 161 |
+
# Panel 3: Aggregate target vs non-target boxplot
|
| 162 |
+
if len(target_gamma) >= 10:
|
| 163 |
+
box_data = [target_gamma, nontarget_gamma]
|
| 164 |
+
bp = axes[2].boxplot(box_data, labels=["miRNA\ntargets", "Non-\ntargets"],
|
| 165 |
+
patch_artist=True)
|
| 166 |
+
bp["boxes"][0].set_facecolor("coral")
|
| 167 |
+
bp["boxes"][1].set_facecolor("lightblue")
|
| 168 |
+
axes[2].set_ylabel("Median gamma per gene")
|
| 169 |
+
axes[2].set_title(f"Aggregate: targets vs non-targets\np={p:.2e}")
|
| 170 |
+
axes[2].set_yscale("symlog", linthresh=0.001)
|
| 171 |
+
|
| 172 |
+
fig.suptitle(f"miRNA-Gamma Analysis: {dataset_name}", fontsize=13, y=1.02)
|
| 173 |
+
fig.tight_layout()
|
| 174 |
+
save_fig(fig, f"mirna_analysis_{dataset_name}")
|
| 175 |
+
|
| 176 |
+
return {
|
| 177 |
+
"n_families_tested": n_tested,
|
| 178 |
+
"n_significant_005": int(n_sig),
|
| 179 |
+
"n_significant_010": int(n_sig_10),
|
| 180 |
+
"n_enriched": int(n_enriched),
|
| 181 |
+
"median_fold_enrichment": float(result_df["fold_enrichment"].median()),
|
| 182 |
+
"aggregate_p": float(p) if len(target_gamma) >= 10 else None,
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
def main():
|
| 187 |
+
set_figure_style()
|
| 188 |
+
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
| 189 |
+
|
| 190 |
+
# Load TargetScan predictions
|
| 191 |
+
print("=" * 60)
|
| 192 |
+
print("LOADING TARGETSCAN PREDICTIONS")
|
| 193 |
+
print("=" * 60)
|
| 194 |
+
|
| 195 |
+
cache_dir = Path(__file__).parent.parent / ".cache" / "targetscan"
|
| 196 |
+
try:
|
| 197 |
+
mirna_targets = scptr.tl.load_targetscan_predictions(
|
| 198 |
+
species_id=9606, # Human
|
| 199 |
+
min_context_score=-0.2,
|
| 200 |
+
cache_dir=cache_dir,
|
| 201 |
+
)
|
| 202 |
+
print(f" Loaded {len(mirna_targets)} human miRNA-target predictions")
|
| 203 |
+
print(f" miRNA families: {mirna_targets['mirna_family'].nunique()}")
|
| 204 |
+
print(f" Target genes: {mirna_targets['gene_symbol'].nunique()}")
|
| 205 |
+
except FileNotFoundError as e:
|
| 206 |
+
print(f" ERROR: {e}")
|
| 207 |
+
print(" Please download TargetScan data first.")
|
| 208 |
+
sys.exit(1)
|
| 209 |
+
|
| 210 |
+
# Also load mouse predictions for mouse datasets
|
| 211 |
+
try:
|
| 212 |
+
mirna_targets_mouse = scptr.tl.load_targetscan_predictions(
|
| 213 |
+
species_id=10090, # Mouse
|
| 214 |
+
min_context_score=-0.2,
|
| 215 |
+
cache_dir=cache_dir,
|
| 216 |
+
)
|
| 217 |
+
print(f" Loaded {len(mirna_targets_mouse)} mouse miRNA-target predictions")
|
| 218 |
+
print(f" miRNA families: {mirna_targets_mouse['mirna_family'].nunique()}")
|
| 219 |
+
print(f" Target genes: {mirna_targets_mouse['gene_symbol'].nunique()}")
|
| 220 |
+
except Exception as e:
|
| 221 |
+
print(f" Mouse predictions not available: {e}")
|
| 222 |
+
mirna_targets_mouse = mirna_targets # Fallback: use human
|
| 223 |
+
|
| 224 |
+
# Load datasets
|
| 225 |
+
print("\n" + "=" * 60)
|
| 226 |
+
print("LOADING DATASETS")
|
| 227 |
+
print("=" * 60)
|
| 228 |
+
|
| 229 |
+
adata_pan = scptr.datasets.pancreas()
|
| 230 |
+
adata_dg = scptr.datasets.dentate_gyrus()
|
| 231 |
+
|
| 232 |
+
# Try to load sci-fate
|
| 233 |
+
try:
|
| 234 |
+
adata_sci = scptr.datasets.sci_fate()
|
| 235 |
+
except Exception:
|
| 236 |
+
adata_sci = None
|
| 237 |
+
|
| 238 |
+
# Run analysis on each dataset
|
| 239 |
+
all_results = {}
|
| 240 |
+
|
| 241 |
+
# Pancreas (mouse) - use mouse predictions
|
| 242 |
+
all_results["pancreas"] = run_mirna_analysis(
|
| 243 |
+
adata_pan, "pancreas", mirna_targets_mouse
|
| 244 |
+
)
|
| 245 |
+
|
| 246 |
+
# Dentate Gyrus (mouse) - use mouse predictions
|
| 247 |
+
all_results["dentate_gyrus"] = run_mirna_analysis(
|
| 248 |
+
adata_dg, "dentate_gyrus", mirna_targets_mouse
|
| 249 |
+
)
|
| 250 |
+
|
| 251 |
+
# sci-fate (human A549) - use human predictions
|
| 252 |
+
if adata_sci is not None:
|
| 253 |
+
all_results["sci_fate"] = run_mirna_analysis(
|
| 254 |
+
adata_sci, "sci_fate", mirna_targets
|
| 255 |
+
)
|
| 256 |
+
|
| 257 |
+
# Save summary
|
| 258 |
+
res_dir = OUTPUT_DIR / "results"
|
| 259 |
+
res_dir.mkdir(parents=True, exist_ok=True)
|
| 260 |
+
with open(res_dir / "mirna_summary.json", "w") as f:
|
| 261 |
+
json.dump(all_results, f, indent=2)
|
| 262 |
+
|
| 263 |
+
# Summary table
|
| 264 |
+
print(f"\n{'='*60}")
|
| 265 |
+
print("miRNA ANALYSIS SUMMARY")
|
| 266 |
+
print(f"{'='*60}")
|
| 267 |
+
print(f"{'Dataset':>15s} {'Tested':>7s} {'Sig(5%)':>7s} {'Sig(10%)':>8s} "
|
| 268 |
+
f"{'Enriched':>8s} {'Med.Fold':>8s} {'Agg.p':>10s}")
|
| 269 |
+
for name, res in all_results.items():
|
| 270 |
+
if res is None:
|
| 271 |
+
continue
|
| 272 |
+
print(f"{name:>15s} {res['n_families_tested']:>7d} "
|
| 273 |
+
f"{res['n_significant_005']:>7d} {res['n_significant_010']:>8d} "
|
| 274 |
+
f"{res['n_enriched']:>8d} {res['median_fold_enrichment']:>8.3f} "
|
| 275 |
+
f"{res['aggregate_p']:>10.2e}" if res['aggregate_p'] else "")
|
| 276 |
+
|
| 277 |
+
print(f"\nResults saved to: {OUTPUT_DIR.resolve()}")
|
| 278 |
+
|
| 279 |
+
|
| 280 |
+
if __name__ == "__main__":
|
| 281 |
+
main()
|
analyses/run_precedence.py
ADDED
|
@@ -0,0 +1,455 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
"""Demonstrate that PT velocity precedes RNA velocity at cell fate transitions.
|
| 3 |
+
|
| 4 |
+
Central hypothesis: Post-transcriptional regulation (gamma changes) acts as an
|
| 5 |
+
early signal that precedes and potentially drives transcriptional changes during
|
| 6 |
+
cell fate transitions.
|
| 7 |
+
|
| 8 |
+
Strategy:
|
| 9 |
+
1. Order cells along pseudotime (diffusion pseudotime via scanpy)
|
| 10 |
+
2. Smooth gamma and expression along pseudotime
|
| 11 |
+
3. For transition-associated genes, detect when gamma change and expression
|
| 12 |
+
change begin — gamma onset should precede expression onset
|
| 13 |
+
4. Cross-correlation analysis: gamma(t) should predict expression(t+delta)
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
from __future__ import annotations
|
| 17 |
+
|
| 18 |
+
import json
|
| 19 |
+
import sys
|
| 20 |
+
from pathlib import Path
|
| 21 |
+
|
| 22 |
+
import matplotlib
|
| 23 |
+
matplotlib.use("Agg")
|
| 24 |
+
import matplotlib.pyplot as plt
|
| 25 |
+
import numpy as np
|
| 26 |
+
import pandas as pd
|
| 27 |
+
import scanpy as sc
|
| 28 |
+
from scipy import stats, signal, ndimage
|
| 29 |
+
|
| 30 |
+
sys.path.insert(0, str(Path(__file__).parent))
|
| 31 |
+
from _common import set_figure_style
|
| 32 |
+
|
| 33 |
+
import scptr
|
| 34 |
+
|
| 35 |
+
OUTPUT_DIR = Path(__file__).parent.parent / "output" / "precedence"
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def save_fig(fig, name, subdir="figures"):
|
| 39 |
+
if fig is None:
|
| 40 |
+
return
|
| 41 |
+
out_dir = OUTPUT_DIR / subdir
|
| 42 |
+
out_dir.mkdir(parents=True, exist_ok=True)
|
| 43 |
+
path = out_dir / f"{name}.png"
|
| 44 |
+
fig.savefig(path, dpi=150, bbox_inches="tight")
|
| 45 |
+
plt.close(fig)
|
| 46 |
+
print(f" Saved: {path}")
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def compute_pseudotime(adata, root_cluster):
|
| 50 |
+
"""Compute diffusion pseudotime from a root cluster."""
|
| 51 |
+
# Use diffusion pseudotime via scanpy
|
| 52 |
+
sc.tl.diffmap(adata)
|
| 53 |
+
|
| 54 |
+
# Find root cell: centroid of root cluster in diffusion space
|
| 55 |
+
root_mask = adata.obs["clusters"] == root_cluster
|
| 56 |
+
root_cells = np.where(root_mask)[0]
|
| 57 |
+
if len(root_cells) == 0:
|
| 58 |
+
raise ValueError(f"No cells in cluster {root_cluster}")
|
| 59 |
+
|
| 60 |
+
# Pick cell closest to cluster centroid in diffmap
|
| 61 |
+
dm = adata.obsm["X_diffmap"]
|
| 62 |
+
centroid = dm[root_cells].mean(axis=0)
|
| 63 |
+
dists = np.linalg.norm(dm[root_cells] - centroid, axis=1)
|
| 64 |
+
root_idx = root_cells[np.argmin(dists)]
|
| 65 |
+
|
| 66 |
+
adata.uns["iroot"] = root_idx
|
| 67 |
+
sc.tl.dpt(adata)
|
| 68 |
+
|
| 69 |
+
pt = adata.obs["dpt_pseudotime"].values.copy()
|
| 70 |
+
# Handle infinite values
|
| 71 |
+
pt[~np.isfinite(pt)] = np.nanmax(pt[np.isfinite(pt)])
|
| 72 |
+
return pt
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def smooth_along_pseudotime(values, pseudotime, n_bins=100):
|
| 76 |
+
"""Bin and smooth values along pseudotime axis.
|
| 77 |
+
|
| 78 |
+
Returns bin centers and smoothed values (per gene if 2D).
|
| 79 |
+
"""
|
| 80 |
+
bins = np.linspace(0, np.max(pseudotime), n_bins + 1)
|
| 81 |
+
bin_centers = (bins[:-1] + bins[1:]) / 2
|
| 82 |
+
bin_idx = np.digitize(pseudotime, bins) - 1
|
| 83 |
+
bin_idx = np.clip(bin_idx, 0, n_bins - 1)
|
| 84 |
+
|
| 85 |
+
if values.ndim == 1:
|
| 86 |
+
smoothed = np.zeros(n_bins)
|
| 87 |
+
for i in range(n_bins):
|
| 88 |
+
mask = bin_idx == i
|
| 89 |
+
if mask.sum() > 0:
|
| 90 |
+
smoothed[i] = np.mean(values[mask])
|
| 91 |
+
# Gaussian smoothing
|
| 92 |
+
smoothed = ndimage.gaussian_filter1d(smoothed, sigma=2)
|
| 93 |
+
return bin_centers, smoothed
|
| 94 |
+
|
| 95 |
+
# 2D: genes x bins
|
| 96 |
+
n_genes = values.shape[1]
|
| 97 |
+
smoothed = np.zeros((n_bins, n_genes))
|
| 98 |
+
for i in range(n_bins):
|
| 99 |
+
mask = bin_idx == i
|
| 100 |
+
if mask.sum() > 0:
|
| 101 |
+
smoothed[i] = np.mean(values[mask], axis=0)
|
| 102 |
+
# Smooth each gene
|
| 103 |
+
for g in range(n_genes):
|
| 104 |
+
smoothed[:, g] = ndimage.gaussian_filter1d(smoothed[:, g], sigma=2)
|
| 105 |
+
return bin_centers, smoothed
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def detect_onset(trace, threshold_frac=0.1):
|
| 109 |
+
"""Detect onset of change: first index where signal exceeds
|
| 110 |
+
threshold_frac * (max - baseline)."""
|
| 111 |
+
baseline = np.mean(trace[:5]) # first 5 bins as baseline
|
| 112 |
+
peak = np.max(np.abs(trace - baseline))
|
| 113 |
+
threshold = baseline + threshold_frac * peak
|
| 114 |
+
|
| 115 |
+
for i, val in enumerate(trace):
|
| 116 |
+
if abs(val - baseline) > threshold_frac * peak:
|
| 117 |
+
return i
|
| 118 |
+
return len(trace) - 1
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
def cross_correlate_lag(gamma_trace, expr_trace, max_lag=20):
|
| 122 |
+
"""Compute cross-correlation to find temporal lag.
|
| 123 |
+
|
| 124 |
+
Positive lag = gamma leads expression.
|
| 125 |
+
Returns optimal lag and correlation at that lag.
|
| 126 |
+
"""
|
| 127 |
+
# Normalize
|
| 128 |
+
g = (gamma_trace - np.mean(gamma_trace))
|
| 129 |
+
g_std = np.std(g)
|
| 130 |
+
if g_std > 0:
|
| 131 |
+
g = g / g_std
|
| 132 |
+
e = (expr_trace - np.mean(expr_trace))
|
| 133 |
+
e_std = np.std(e)
|
| 134 |
+
if e_std > 0:
|
| 135 |
+
e = e / e_std
|
| 136 |
+
|
| 137 |
+
n = len(g)
|
| 138 |
+
best_lag = 0
|
| 139 |
+
best_corr = 0
|
| 140 |
+
|
| 141 |
+
for lag in range(-max_lag, max_lag + 1):
|
| 142 |
+
if lag >= 0:
|
| 143 |
+
corr = np.corrcoef(g[:n-lag], e[lag:])[0, 1] if n - lag > 5 else 0
|
| 144 |
+
else:
|
| 145 |
+
corr = np.corrcoef(g[-lag:], e[:n+lag])[0, 1] if n + lag > 5 else 0
|
| 146 |
+
|
| 147 |
+
if abs(corr) > abs(best_corr):
|
| 148 |
+
best_corr = corr
|
| 149 |
+
best_lag = lag
|
| 150 |
+
|
| 151 |
+
return best_lag, best_corr
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def run_precedence_analysis(adata, dataset_name, root_cluster, n_bins=100):
|
| 155 |
+
"""Run temporal precedence analysis on one dataset."""
|
| 156 |
+
print(f"\n{'='*60}")
|
| 157 |
+
print(f"PRECEDENCE ANALYSIS: {dataset_name}")
|
| 158 |
+
print(f"{'='*60}")
|
| 159 |
+
|
| 160 |
+
res_dir = OUTPUT_DIR / "results" / dataset_name
|
| 161 |
+
res_dir.mkdir(parents=True, exist_ok=True)
|
| 162 |
+
|
| 163 |
+
# Step 1: Compute pseudotime
|
| 164 |
+
print("\n--- Computing pseudotime ---")
|
| 165 |
+
pt = compute_pseudotime(adata, root_cluster)
|
| 166 |
+
print(f" Root cluster: {root_cluster}")
|
| 167 |
+
print(f" Pseudotime range: [{pt.min():.4f}, {pt.max():.4f}]")
|
| 168 |
+
|
| 169 |
+
# Step 2: Get gamma and expression matrices
|
| 170 |
+
gamma = adata.layers["gamma"]
|
| 171 |
+
if hasattr(adata.X, 'toarray'):
|
| 172 |
+
expr = adata.X.toarray()
|
| 173 |
+
else:
|
| 174 |
+
expr = np.asarray(adata.X)
|
| 175 |
+
expr = np.log1p(expr) # log-normalize for comparison
|
| 176 |
+
|
| 177 |
+
# Step 3: Smooth both along pseudotime
|
| 178 |
+
print("\n--- Smoothing along pseudotime ---")
|
| 179 |
+
bin_centers, gamma_smooth = smooth_along_pseudotime(gamma, pt, n_bins)
|
| 180 |
+
_, expr_smooth = smooth_along_pseudotime(expr, pt, n_bins)
|
| 181 |
+
|
| 182 |
+
# Step 4: Identify transition genes (high variance along pseudotime)
|
| 183 |
+
gamma_var = np.var(gamma_smooth, axis=0)
|
| 184 |
+
expr_var = np.var(expr_smooth, axis=0)
|
| 185 |
+
|
| 186 |
+
# Require both gamma and expression to vary along pseudotime
|
| 187 |
+
gamma_var_thresh = np.percentile(gamma_var[gamma_var > 0], 75)
|
| 188 |
+
expr_var_thresh = np.percentile(expr_var[expr_var > 0], 75)
|
| 189 |
+
transition_mask = (gamma_var > gamma_var_thresh) & (expr_var > expr_var_thresh)
|
| 190 |
+
transition_genes = adata.var_names[transition_mask]
|
| 191 |
+
print(f" Transition genes: {len(transition_genes)}")
|
| 192 |
+
|
| 193 |
+
# Step 5: Onset detection
|
| 194 |
+
print("\n--- Onset detection ---")
|
| 195 |
+
onset_results = []
|
| 196 |
+
for i, gene in enumerate(adata.var_names):
|
| 197 |
+
if not transition_mask[i]:
|
| 198 |
+
continue
|
| 199 |
+
g_trace = gamma_smooth[:, i]
|
| 200 |
+
e_trace = expr_smooth[:, i]
|
| 201 |
+
|
| 202 |
+
g_onset = detect_onset(g_trace)
|
| 203 |
+
e_onset = detect_onset(e_trace)
|
| 204 |
+
lead_bins = e_onset - g_onset # positive = gamma leads
|
| 205 |
+
|
| 206 |
+
onset_results.append({
|
| 207 |
+
"gene": gene,
|
| 208 |
+
"gamma_onset_bin": g_onset,
|
| 209 |
+
"expr_onset_bin": e_onset,
|
| 210 |
+
"lead_bins": lead_bins,
|
| 211 |
+
})
|
| 212 |
+
|
| 213 |
+
onset_df = pd.DataFrame(onset_results)
|
| 214 |
+
n_gamma_leads = (onset_df["lead_bins"] > 0).sum()
|
| 215 |
+
n_expr_leads = (onset_df["lead_bins"] < 0).sum()
|
| 216 |
+
n_simultaneous = (onset_df["lead_bins"] == 0).sum()
|
| 217 |
+
print(f" Gamma leads: {n_gamma_leads}/{len(onset_df)} genes")
|
| 218 |
+
print(f" Expression leads: {n_expr_leads}/{len(onset_df)} genes")
|
| 219 |
+
print(f" Simultaneous: {n_simultaneous}/{len(onset_df)} genes")
|
| 220 |
+
print(f" Mean lead (bins): {onset_df['lead_bins'].mean():.2f}")
|
| 221 |
+
|
| 222 |
+
# Binomial test: is gamma-leading significantly more common than chance?
|
| 223 |
+
n_nontied = n_gamma_leads + n_expr_leads
|
| 224 |
+
if n_nontied > 0:
|
| 225 |
+
binom_p = stats.binomtest(n_gamma_leads, n_nontied, 0.5).pvalue
|
| 226 |
+
print(f" Binomial test (gamma leads more): p = {binom_p:.4e}")
|
| 227 |
+
else:
|
| 228 |
+
binom_p = 1.0
|
| 229 |
+
|
| 230 |
+
onset_df.to_csv(res_dir / "onset_detection.csv", index=False)
|
| 231 |
+
|
| 232 |
+
# Step 6: Cross-correlation analysis
|
| 233 |
+
print("\n--- Cross-correlation analysis ---")
|
| 234 |
+
lag_results = []
|
| 235 |
+
for i, gene in enumerate(adata.var_names):
|
| 236 |
+
if not transition_mask[i]:
|
| 237 |
+
continue
|
| 238 |
+
g_trace = gamma_smooth[:, i]
|
| 239 |
+
e_trace = expr_smooth[:, i]
|
| 240 |
+
|
| 241 |
+
lag, corr = cross_correlate_lag(g_trace, e_trace, max_lag=15)
|
| 242 |
+
lag_results.append({
|
| 243 |
+
"gene": gene,
|
| 244 |
+
"optimal_lag": lag,
|
| 245 |
+
"cross_corr": corr,
|
| 246 |
+
})
|
| 247 |
+
|
| 248 |
+
lag_df = pd.DataFrame(lag_results)
|
| 249 |
+
mean_lag = lag_df["optimal_lag"].mean()
|
| 250 |
+
median_lag = lag_df["optimal_lag"].median()
|
| 251 |
+
n_positive_lag = (lag_df["optimal_lag"] > 0).sum()
|
| 252 |
+
print(f" Mean optimal lag: {mean_lag:.2f} bins (positive = gamma leads)")
|
| 253 |
+
print(f" Median optimal lag: {median_lag:.1f} bins")
|
| 254 |
+
print(f" Genes with positive lag: {n_positive_lag}/{len(lag_df)}")
|
| 255 |
+
|
| 256 |
+
lag_df.to_csv(res_dir / "cross_correlation.csv", index=False)
|
| 257 |
+
|
| 258 |
+
# Step 7: Combine results
|
| 259 |
+
results = {
|
| 260 |
+
"n_transition_genes": len(transition_genes),
|
| 261 |
+
"onset_gamma_leads": int(n_gamma_leads),
|
| 262 |
+
"onset_expr_leads": int(n_expr_leads),
|
| 263 |
+
"onset_simultaneous": int(n_simultaneous),
|
| 264 |
+
"onset_mean_lead_bins": float(onset_df["lead_bins"].mean()),
|
| 265 |
+
"onset_binomial_p": float(binom_p),
|
| 266 |
+
"crosscorr_mean_lag": float(mean_lag),
|
| 267 |
+
"crosscorr_median_lag": float(median_lag),
|
| 268 |
+
"crosscorr_positive_lag_frac": float(n_positive_lag / len(lag_df)),
|
| 269 |
+
}
|
| 270 |
+
with open(res_dir / "precedence_results.json", "w") as f:
|
| 271 |
+
json.dump(results, f, indent=2)
|
| 272 |
+
|
| 273 |
+
# =========================================================================
|
| 274 |
+
# FIGURES
|
| 275 |
+
# =========================================================================
|
| 276 |
+
|
| 277 |
+
# Figure 1: Onset histogram
|
| 278 |
+
fig, axes = plt.subplots(1, 2, figsize=(13, 5))
|
| 279 |
+
|
| 280 |
+
axes[0].hist(onset_df["lead_bins"], bins=30, color="steelblue",
|
| 281 |
+
alpha=0.8, edgecolor="white")
|
| 282 |
+
axes[0].axvline(0, color="red", linestyle="--", alpha=0.5, label="Simultaneous")
|
| 283 |
+
axes[0].axvline(onset_df["lead_bins"].mean(), color="darkred",
|
| 284 |
+
linestyle="-", lw=2,
|
| 285 |
+
label=f"Mean={onset_df['lead_bins'].mean():.1f}")
|
| 286 |
+
axes[0].set_xlabel("Lead (bins): positive = gamma leads expression")
|
| 287 |
+
axes[0].set_ylabel("Number of genes")
|
| 288 |
+
axes[0].set_title(f"Onset detection ({n_gamma_leads}/{len(onset_df)} gamma-leading)")
|
| 289 |
+
axes[0].legend()
|
| 290 |
+
|
| 291 |
+
# Cross-correlation lag histogram
|
| 292 |
+
axes[1].hist(lag_df["optimal_lag"], bins=30, color="darkorange",
|
| 293 |
+
alpha=0.8, edgecolor="white")
|
| 294 |
+
axes[1].axvline(0, color="red", linestyle="--", alpha=0.5, label="No lag")
|
| 295 |
+
axes[1].axvline(mean_lag, color="darkred", linestyle="-", lw=2,
|
| 296 |
+
label=f"Mean={mean_lag:.1f}")
|
| 297 |
+
axes[1].set_xlabel("Optimal lag (bins): positive = gamma leads")
|
| 298 |
+
axes[1].set_ylabel("Number of genes")
|
| 299 |
+
axes[1].set_title(f"Cross-correlation lag ({n_positive_lag}/{len(lag_df)} positive)")
|
| 300 |
+
axes[1].legend()
|
| 301 |
+
|
| 302 |
+
fig.suptitle(f"PT Velocity Precedes RNA Velocity: {dataset_name}",
|
| 303 |
+
fontsize=13, y=1.02)
|
| 304 |
+
fig.tight_layout()
|
| 305 |
+
save_fig(fig, f"precedence_{dataset_name}")
|
| 306 |
+
|
| 307 |
+
# Figure 2: Example gene traces
|
| 308 |
+
# Pick top 6 genes with largest gamma-leading onset
|
| 309 |
+
top_genes = onset_df.nlargest(6, "lead_bins")
|
| 310 |
+
fig, axes = plt.subplots(2, 3, figsize=(16, 9))
|
| 311 |
+
axes = axes.flatten()
|
| 312 |
+
for idx, (_, row) in enumerate(top_genes.iterrows()):
|
| 313 |
+
if idx >= 6:
|
| 314 |
+
break
|
| 315 |
+
gene = row["gene"]
|
| 316 |
+
gi = list(adata.var_names).index(gene)
|
| 317 |
+
g_trace = gamma_smooth[:, gi]
|
| 318 |
+
e_trace = expr_smooth[:, gi]
|
| 319 |
+
|
| 320 |
+
# Normalize for comparison
|
| 321 |
+
g_norm = (g_trace - g_trace.min()) / (g_trace.max() - g_trace.min() + 1e-10)
|
| 322 |
+
e_norm = (e_trace - e_trace.min()) / (e_trace.max() - e_trace.min() + 1e-10)
|
| 323 |
+
|
| 324 |
+
ax = axes[idx]
|
| 325 |
+
ax.plot(bin_centers, g_norm, "b-", lw=2, label="Gamma (norm)")
|
| 326 |
+
ax.plot(bin_centers, e_norm, "r-", lw=2, label="Expression (norm)")
|
| 327 |
+
ax.axvline(bin_centers[int(row["gamma_onset_bin"])], color="blue",
|
| 328 |
+
linestyle=":", alpha=0.5)
|
| 329 |
+
ax.axvline(bin_centers[int(row["expr_onset_bin"])], color="red",
|
| 330 |
+
linestyle=":", alpha=0.5)
|
| 331 |
+
ax.set_xlabel("Pseudotime")
|
| 332 |
+
ax.set_ylabel("Normalized value")
|
| 333 |
+
ax.set_title(f"{gene} (lead={int(row['lead_bins'])} bins)")
|
| 334 |
+
ax.legend(fontsize=7)
|
| 335 |
+
|
| 336 |
+
fig.suptitle(f"Top Gamma-Leading Genes: {dataset_name}", fontsize=13, y=1.02)
|
| 337 |
+
fig.tight_layout()
|
| 338 |
+
save_fig(fig, f"example_genes_{dataset_name}")
|
| 339 |
+
|
| 340 |
+
return results
|
| 341 |
+
|
| 342 |
+
|
| 343 |
+
def main():
|
| 344 |
+
set_figure_style()
|
| 345 |
+
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
| 346 |
+
|
| 347 |
+
# =========================================================================
|
| 348 |
+
# PANCREAS: Ductal → Beta cell lineage
|
| 349 |
+
# =========================================================================
|
| 350 |
+
print("=" * 60)
|
| 351 |
+
print("LOADING AND PROCESSING PANCREAS")
|
| 352 |
+
print("=" * 60)
|
| 353 |
+
adata_pan = scptr.datasets.pancreas()
|
| 354 |
+
scptr.pp.filter_genes(adata_pan)
|
| 355 |
+
scptr.pp.normalize_layers(adata_pan)
|
| 356 |
+
scptr.pp.neighbors(adata_pan, n_neighbors=30)
|
| 357 |
+
scptr.pp.smooth_layers(adata_pan)
|
| 358 |
+
scptr.tl.estimate_beta(adata_pan)
|
| 359 |
+
scptr.tl.estimate_gamma(adata_pan)
|
| 360 |
+
scptr.tl.variance_decomposition(adata_pan)
|
| 361 |
+
scptr.tl.pt_states(adata_pan)
|
| 362 |
+
scptr.tl.pt_velocity(adata_pan)
|
| 363 |
+
print(f" Pipeline complete: {adata_pan.shape}")
|
| 364 |
+
|
| 365 |
+
# Root cluster for pseudotime: Ductal (progenitor)
|
| 366 |
+
print(f" Clusters: {adata_pan.obs['clusters'].unique().tolist()}")
|
| 367 |
+
pan_results = run_precedence_analysis(
|
| 368 |
+
adata_pan, "pancreas", root_cluster="Ductal"
|
| 369 |
+
)
|
| 370 |
+
|
| 371 |
+
# =========================================================================
|
| 372 |
+
# DENTATE GYRUS: Radial glia → Granule neuron lineage
|
| 373 |
+
# =========================================================================
|
| 374 |
+
print("\n" + "=" * 60)
|
| 375 |
+
print("LOADING AND PROCESSING DENTATE GYRUS")
|
| 376 |
+
print("=" * 60)
|
| 377 |
+
adata_dg = scptr.datasets.dentate_gyrus()
|
| 378 |
+
scptr.pp.filter_genes(adata_dg)
|
| 379 |
+
scptr.pp.normalize_layers(adata_dg)
|
| 380 |
+
scptr.pp.neighbors(adata_dg, n_neighbors=30)
|
| 381 |
+
scptr.pp.smooth_layers(adata_dg)
|
| 382 |
+
scptr.tl.estimate_beta(adata_dg)
|
| 383 |
+
scptr.tl.estimate_gamma(adata_dg)
|
| 384 |
+
scptr.tl.variance_decomposition(adata_dg)
|
| 385 |
+
scptr.tl.pt_states(adata_dg)
|
| 386 |
+
scptr.tl.pt_velocity(adata_dg)
|
| 387 |
+
print(f" Pipeline complete: {adata_dg.shape}")
|
| 388 |
+
|
| 389 |
+
print(f" Clusters: {adata_dg.obs['clusters'].unique().tolist()}")
|
| 390 |
+
dg_results = run_precedence_analysis(
|
| 391 |
+
adata_dg, "dentate_gyrus", root_cluster="Radial Glia-like"
|
| 392 |
+
)
|
| 393 |
+
|
| 394 |
+
# =========================================================================
|
| 395 |
+
# COMBINED SUMMARY
|
| 396 |
+
# =========================================================================
|
| 397 |
+
print("\n" + "=" * 60)
|
| 398 |
+
print("COMBINED SUMMARY")
|
| 399 |
+
print("=" * 60)
|
| 400 |
+
|
| 401 |
+
for name, results in [("pancreas", pan_results), ("dentate_gyrus", dg_results)]:
|
| 402 |
+
print(f"\n {name}:")
|
| 403 |
+
print(f" Transition genes: {results['n_transition_genes']}")
|
| 404 |
+
print(f" Gamma leads: {results['onset_gamma_leads']}, "
|
| 405 |
+
f"Expr leads: {results['onset_expr_leads']}")
|
| 406 |
+
print(f" Mean onset lead: {results['onset_mean_lead_bins']:.2f} bins")
|
| 407 |
+
print(f" Binomial p: {results['onset_binomial_p']:.4e}")
|
| 408 |
+
print(f" Cross-corr mean lag: {results['crosscorr_mean_lag']:.2f} bins")
|
| 409 |
+
|
| 410 |
+
# Summary figure: comparison bar chart
|
| 411 |
+
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
|
| 412 |
+
|
| 413 |
+
datasets = ["pancreas", "dentate_gyrus"]
|
| 414 |
+
all_results = [pan_results, dg_results]
|
| 415 |
+
|
| 416 |
+
# Left: onset detection
|
| 417 |
+
leads = [r["onset_gamma_leads"] for r in all_results]
|
| 418 |
+
follows = [r["onset_expr_leads"] for r in all_results]
|
| 419 |
+
simult = [r["onset_simultaneous"] for r in all_results]
|
| 420 |
+
x = np.arange(len(datasets))
|
| 421 |
+
width = 0.25
|
| 422 |
+
axes[0].bar(x - width, leads, width, label="Gamma leads", color="steelblue")
|
| 423 |
+
axes[0].bar(x, simult, width, label="Simultaneous", color="gray")
|
| 424 |
+
axes[0].bar(x + width, follows, width, label="Expression leads", color="salmon")
|
| 425 |
+
axes[0].set_xticks(x)
|
| 426 |
+
axes[0].set_xticklabels(datasets)
|
| 427 |
+
axes[0].set_ylabel("Number of genes")
|
| 428 |
+
axes[0].set_title("Onset Detection: Which Changes First?")
|
| 429 |
+
axes[0].legend()
|
| 430 |
+
|
| 431 |
+
# Right: mean lag
|
| 432 |
+
mean_lags = [r["crosscorr_mean_lag"] for r in all_results]
|
| 433 |
+
colors = ["steelblue" if l > 0 else "salmon" for l in mean_lags]
|
| 434 |
+
axes[1].bar(datasets, mean_lags, color=colors)
|
| 435 |
+
axes[1].set_ylabel("Mean optimal lag (bins)")
|
| 436 |
+
axes[1].set_title("Cross-Correlation: Positive = Gamma Leads")
|
| 437 |
+
axes[1].axhline(0, color="gray", linestyle="--", alpha=0.3)
|
| 438 |
+
|
| 439 |
+
fig.suptitle("Post-Transcriptional Changes Precede Transcriptional Changes",
|
| 440 |
+
fontsize=13, y=1.02)
|
| 441 |
+
fig.tight_layout()
|
| 442 |
+
save_fig(fig, "combined_precedence")
|
| 443 |
+
|
| 444 |
+
# Save combined results
|
| 445 |
+
combined = {"pancreas": pan_results, "dentate_gyrus": dg_results}
|
| 446 |
+
res_dir = OUTPUT_DIR / "results"
|
| 447 |
+
res_dir.mkdir(parents=True, exist_ok=True)
|
| 448 |
+
with open(res_dir / "combined_precedence.json", "w") as f:
|
| 449 |
+
json.dump(combined, f, indent=2)
|
| 450 |
+
|
| 451 |
+
print(f"\nAll results saved to: {OUTPUT_DIR.resolve()}")
|
| 452 |
+
|
| 453 |
+
|
| 454 |
+
if __name__ == "__main__":
|
| 455 |
+
main()
|
analyses/run_remaining_validation.py
ADDED
|
@@ -0,0 +1,494 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
"""Validate remaining package features: dynamic mode, groupby beta, scalability.
|
| 3 |
+
|
| 4 |
+
These are features that were implemented but never validated on real data.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from __future__ import annotations
|
| 8 |
+
|
| 9 |
+
import json
|
| 10 |
+
import sys
|
| 11 |
+
import time
|
| 12 |
+
from pathlib import Path
|
| 13 |
+
|
| 14 |
+
import matplotlib
|
| 15 |
+
matplotlib.use("Agg")
|
| 16 |
+
import matplotlib.pyplot as plt
|
| 17 |
+
import numpy as np
|
| 18 |
+
import pandas as pd
|
| 19 |
+
from scipy import stats
|
| 20 |
+
|
| 21 |
+
sys.path.insert(0, str(Path(__file__).parent))
|
| 22 |
+
from _common import set_figure_style
|
| 23 |
+
|
| 24 |
+
import scptr
|
| 25 |
+
|
| 26 |
+
OUTPUT_DIR = Path(__file__).parent.parent / "output" / "remaining_validation"
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def save_fig(fig, name, subdir="figures"):
|
| 30 |
+
out_dir = OUTPUT_DIR / subdir
|
| 31 |
+
out_dir.mkdir(parents=True, exist_ok=True)
|
| 32 |
+
path = out_dir / f"{name}.png"
|
| 33 |
+
fig.savefig(path, dpi=150, bbox_inches="tight")
|
| 34 |
+
plt.close(fig)
|
| 35 |
+
print(f" Saved: {path}")
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
# =========================================================================
|
| 39 |
+
# 1. Per-cell-type beta estimation (groupby)
|
| 40 |
+
# =========================================================================
|
| 41 |
+
def validate_groupby_beta(adata, name, cluster_col="clusters"):
|
| 42 |
+
"""Validate per-cell-type beta estimation vs global beta."""
|
| 43 |
+
print(f"\n{'='*60}")
|
| 44 |
+
print(f"GROUPBY BETA VALIDATION ({name})")
|
| 45 |
+
print(f"{'='*60}")
|
| 46 |
+
|
| 47 |
+
# First: run global beta (standard)
|
| 48 |
+
import copy
|
| 49 |
+
adata_global = copy.deepcopy(adata)
|
| 50 |
+
scptr.pp.filter_genes(adata_global)
|
| 51 |
+
scptr.pp.normalize_layers(adata_global)
|
| 52 |
+
scptr.pp.neighbors(adata_global, n_neighbors=30)
|
| 53 |
+
scptr.pp.smooth_layers(adata_global)
|
| 54 |
+
scptr.tl.estimate_beta(adata_global)
|
| 55 |
+
global_beta = adata_global.var["beta"].values.copy()
|
| 56 |
+
|
| 57 |
+
# Second: run groupby beta
|
| 58 |
+
adata_group = copy.deepcopy(adata)
|
| 59 |
+
scptr.pp.filter_genes(adata_group)
|
| 60 |
+
scptr.pp.normalize_layers(adata_group)
|
| 61 |
+
scptr.pp.neighbors(adata_group, n_neighbors=30)
|
| 62 |
+
scptr.pp.smooth_layers(adata_group)
|
| 63 |
+
|
| 64 |
+
if cluster_col not in adata_group.obs.columns:
|
| 65 |
+
# Run clustering first
|
| 66 |
+
import scanpy as sc
|
| 67 |
+
sc.tl.leiden(adata_group, key_added=cluster_col)
|
| 68 |
+
|
| 69 |
+
n_types = adata_group.obs[cluster_col].nunique()
|
| 70 |
+
print(f" Cell types: {n_types}")
|
| 71 |
+
print(f" Type sizes: {adata_group.obs[cluster_col].value_counts().to_dict()}")
|
| 72 |
+
|
| 73 |
+
scptr.tl.estimate_beta(adata_group, groupby=cluster_col)
|
| 74 |
+
consensus_beta = adata_group.var["beta"].values.copy()
|
| 75 |
+
|
| 76 |
+
# Compare global vs consensus
|
| 77 |
+
valid = (global_beta > 0) & (consensus_beta > 0)
|
| 78 |
+
r, p = stats.spearmanr(global_beta[valid], consensus_beta[valid])
|
| 79 |
+
print(f"\n Global vs consensus beta:")
|
| 80 |
+
print(f" Spearman r = {r:.4f}, p = {p:.2e}")
|
| 81 |
+
print(f" Valid genes: {valid.sum()}")
|
| 82 |
+
|
| 83 |
+
# Check per-group variation
|
| 84 |
+
if "beta_groups" in adata_group.varm:
|
| 85 |
+
beta_groups = adata_group.varm["beta_groups"]
|
| 86 |
+
print(f"\n Per-group beta variation:")
|
| 87 |
+
print(f" Groups: {list(beta_groups.columns)}")
|
| 88 |
+
|
| 89 |
+
# CV of beta across groups
|
| 90 |
+
group_vals = beta_groups.values.astype(float)
|
| 91 |
+
group_means = np.nanmean(group_vals, axis=1)
|
| 92 |
+
group_stds = np.nanstd(group_vals, axis=1)
|
| 93 |
+
cvs = group_stds / (group_means + 1e-8)
|
| 94 |
+
valid_cv = group_means > 0
|
| 95 |
+
print(f" Median CV across groups: {np.median(cvs[valid_cv]):.4f}")
|
| 96 |
+
print(f" Genes with CV > 0.5 (high variation): "
|
| 97 |
+
f"{(cvs[valid_cv] > 0.5).sum()}/{valid_cv.sum()}")
|
| 98 |
+
|
| 99 |
+
# Do different cell types have different beta distributions?
|
| 100 |
+
print(f"\n Per-cell-type beta medians:")
|
| 101 |
+
for col in beta_groups.columns:
|
| 102 |
+
med = np.nanmedian(beta_groups[col].values.astype(float))
|
| 103 |
+
print(f" {col}: median beta = {med:.4f}")
|
| 104 |
+
|
| 105 |
+
# Now compare gamma with groupby beta vs global beta
|
| 106 |
+
scptr.tl.estimate_gamma(adata_global)
|
| 107 |
+
scptr.tl.estimate_gamma(adata_group)
|
| 108 |
+
|
| 109 |
+
gamma_global = np.median(adata_global.layers["gamma"], axis=0)
|
| 110 |
+
gamma_group = np.median(adata_group.layers["gamma"], axis=0)
|
| 111 |
+
valid_g = (gamma_global > 0) & (gamma_group > 0)
|
| 112 |
+
r_g, p_g = stats.spearmanr(gamma_global[valid_g], gamma_group[valid_g])
|
| 113 |
+
print(f"\n Gamma comparison (global vs groupby beta):")
|
| 114 |
+
print(f" Spearman r = {r_g:.4f}, p = {p_g:.2e}")
|
| 115 |
+
print(f" Valid genes: {valid_g.sum()}")
|
| 116 |
+
|
| 117 |
+
# Half-life correlation comparison
|
| 118 |
+
halflife_dir = Path(__file__).parent.parent / "src" / "scptr" / "datasets" / "data"
|
| 119 |
+
for hl_name, hl_file in [("Herzog 2017", "herzog2017_halflives.csv"),
|
| 120 |
+
("Schofield 2018", "schofield2018_halflives.csv")]:
|
| 121 |
+
hl_path = halflife_dir / hl_file
|
| 122 |
+
if not hl_path.exists():
|
| 123 |
+
continue
|
| 124 |
+
hl = pd.read_csv(hl_path)
|
| 125 |
+
gene_map = {g.upper(): i for i, g in enumerate(adata_global.var_names)}
|
| 126 |
+
hl_gamma_global, hl_gamma_group, hl_vals = [], [], []
|
| 127 |
+
for _, row in hl.iterrows():
|
| 128 |
+
raw_g = row["gene_symbol"] if "gene_symbol" in hl.columns else row.iloc[0]
|
| 129 |
+
if pd.isna(raw_g) or str(raw_g).strip() == "":
|
| 130 |
+
continue
|
| 131 |
+
g = str(raw_g).upper()
|
| 132 |
+
if g in gene_map:
|
| 133 |
+
gi = gene_map[g]
|
| 134 |
+
gg = gamma_global[gi]
|
| 135 |
+
ggrp = gamma_group[gi]
|
| 136 |
+
if gg > 0 and ggrp > 0:
|
| 137 |
+
hl_gamma_global.append(gg)
|
| 138 |
+
hl_gamma_group.append(ggrp)
|
| 139 |
+
hl_val = row["half_life_hours"] if "half_life_hours" in hl.columns else row.iloc[1]
|
| 140 |
+
hl_vals.append(float(hl_val))
|
| 141 |
+
|
| 142 |
+
if len(hl_vals) >= 20:
|
| 143 |
+
r_hl_g, _ = stats.spearmanr(hl_gamma_global, hl_vals)
|
| 144 |
+
r_hl_grp, _ = stats.spearmanr(hl_gamma_group, hl_vals)
|
| 145 |
+
print(f"\n Half-life correlation ({hl_name}):")
|
| 146 |
+
print(f" Global beta: r = {r_hl_g:.4f}")
|
| 147 |
+
print(f" Groupby beta: r = {r_hl_grp:.4f}")
|
| 148 |
+
print(f" {'Groupby BETTER' if abs(r_hl_grp) > abs(r_hl_g) else 'Global BETTER'}")
|
| 149 |
+
|
| 150 |
+
# Figure
|
| 151 |
+
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
|
| 152 |
+
|
| 153 |
+
# Panel 1: Global vs consensus beta
|
| 154 |
+
axes[0].scatter(global_beta[valid], consensus_beta[valid], s=2, alpha=0.3)
|
| 155 |
+
axes[0].set_xlabel("Global beta")
|
| 156 |
+
axes[0].set_ylabel("Consensus beta (groupby)")
|
| 157 |
+
axes[0].set_title(f"Beta: Global vs Per-Cell-Type\nr={r:.3f}")
|
| 158 |
+
lim = max(global_beta[valid].max(), consensus_beta[valid].max()) * 1.1
|
| 159 |
+
axes[0].plot([0, lim], [0, lim], "r--", alpha=0.5)
|
| 160 |
+
|
| 161 |
+
# Panel 2: Gamma comparison
|
| 162 |
+
if valid_g.sum() > 0:
|
| 163 |
+
axes[1].scatter(gamma_global[valid_g], gamma_group[valid_g], s=2, alpha=0.3)
|
| 164 |
+
axes[1].set_xlabel("Gamma (global beta)")
|
| 165 |
+
axes[1].set_ylabel("Gamma (groupby beta)")
|
| 166 |
+
axes[1].set_title(f"Gamma: Global vs Groupby\nr={r_g:.3f}")
|
| 167 |
+
lim_g = max(gamma_global[valid_g].max(), gamma_group[valid_g].max()) * 1.1
|
| 168 |
+
axes[1].plot([0, lim_g], [0, lim_g], "r--", alpha=0.5)
|
| 169 |
+
|
| 170 |
+
# Panel 3: Beta CV histogram
|
| 171 |
+
if "beta_groups" in adata_group.varm:
|
| 172 |
+
axes[2].hist(cvs[valid_cv], bins=50, color="steelblue", edgecolor="black",
|
| 173 |
+
linewidth=0.5)
|
| 174 |
+
axes[2].axvline(x=np.median(cvs[valid_cv]), color="red", linestyle="--",
|
| 175 |
+
label=f"median={np.median(cvs[valid_cv]):.2f}")
|
| 176 |
+
axes[2].set_xlabel("CV of beta across cell types")
|
| 177 |
+
axes[2].set_ylabel("Number of genes")
|
| 178 |
+
axes[2].set_title("Beta Variation Across Cell Types")
|
| 179 |
+
axes[2].legend()
|
| 180 |
+
|
| 181 |
+
fig.suptitle(f"Per-Cell-Type Beta Validation: {name}", fontsize=13, y=1.02)
|
| 182 |
+
fig.tight_layout()
|
| 183 |
+
save_fig(fig, f"groupby_beta_{name}")
|
| 184 |
+
|
| 185 |
+
return {
|
| 186 |
+
"global_vs_consensus_r": float(r),
|
| 187 |
+
"gamma_r": float(r_g),
|
| 188 |
+
"n_cell_types": int(n_types),
|
| 189 |
+
"median_cv": float(np.median(cvs[valid_cv])) if "beta_groups" in adata_group.varm else None,
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
# =========================================================================
|
| 194 |
+
# 2. Dynamic mode validation
|
| 195 |
+
# =========================================================================
|
| 196 |
+
def validate_dynamic_mode(adata, name):
|
| 197 |
+
"""Compare steady-state vs dynamic gamma estimation.
|
| 198 |
+
|
| 199 |
+
Dynamic mode uses the full ODE: gamma = (beta*u - ds/dt) / s
|
| 200 |
+
This requires a velocity layer (ds/dt estimate).
|
| 201 |
+
"""
|
| 202 |
+
print(f"\n{'='*60}")
|
| 203 |
+
print(f"DYNAMIC MODE VALIDATION ({name})")
|
| 204 |
+
print(f"{'='*60}")
|
| 205 |
+
|
| 206 |
+
import copy
|
| 207 |
+
adata_ss = copy.deepcopy(adata)
|
| 208 |
+
scptr.pp.filter_genes(adata_ss)
|
| 209 |
+
scptr.pp.normalize_layers(adata_ss)
|
| 210 |
+
scptr.pp.neighbors(adata_ss, n_neighbors=30)
|
| 211 |
+
scptr.pp.smooth_layers(adata_ss)
|
| 212 |
+
scptr.tl.estimate_beta(adata_ss)
|
| 213 |
+
|
| 214 |
+
# Steady-state gamma
|
| 215 |
+
scptr.tl.estimate_gamma(adata_ss, mode="steady_state")
|
| 216 |
+
gamma_ss = adata_ss.layers["gamma"].copy()
|
| 217 |
+
|
| 218 |
+
# For dynamic mode, we need ds/dt. Estimate it as the difference
|
| 219 |
+
# between a cell's spliced count and its neighbors' mean.
|
| 220 |
+
# This is a simple approximation of the time derivative.
|
| 221 |
+
s_smooth = adata_ss.layers["Ms"].copy()
|
| 222 |
+
import scanpy as sc
|
| 223 |
+
|
| 224 |
+
# Compute diffusion pseudotime for temporal ordering
|
| 225 |
+
sc.tl.diffmap(adata_ss)
|
| 226 |
+
|
| 227 |
+
# Approximate ds/dt using the spliced expression trend along the manifold
|
| 228 |
+
# Use the velocity estimation approach: ds/dt ≈ beta*u - gamma_ss*s
|
| 229 |
+
# (rearranging the ODE at non-steady-state)
|
| 230 |
+
# Actually, let's use a simpler approach: finite differences along kNN graph
|
| 231 |
+
from sklearn.neighbors import NearestNeighbors
|
| 232 |
+
nn = NearestNeighbors(n_neighbors=30)
|
| 233 |
+
|
| 234 |
+
# Use PCA space for neighbors
|
| 235 |
+
if "X_pca" in adata_ss.obsm:
|
| 236 |
+
nn.fit(adata_ss.obsm["X_pca"][:, :30])
|
| 237 |
+
else:
|
| 238 |
+
sc.tl.pca(adata_ss)
|
| 239 |
+
nn.fit(adata_ss.obsm["X_pca"][:, :30])
|
| 240 |
+
|
| 241 |
+
_, indices = nn.kneighbors()
|
| 242 |
+
|
| 243 |
+
# ds/dt ≈ mean(s_neighbors) - s_cell (displacement on manifold)
|
| 244 |
+
n_cells, n_genes = s_smooth.shape
|
| 245 |
+
ds_dt = np.zeros_like(s_smooth)
|
| 246 |
+
for i in range(n_cells):
|
| 247 |
+
nbr_mean = s_smooth[indices[i]].mean(axis=0)
|
| 248 |
+
ds_dt[i] = nbr_mean - s_smooth[i]
|
| 249 |
+
|
| 250 |
+
# Store as a layer
|
| 251 |
+
adata_ss.layers["ds_dt"] = ds_dt.astype(np.float32)
|
| 252 |
+
|
| 253 |
+
# Dynamic gamma
|
| 254 |
+
adata_dyn = copy.deepcopy(adata_ss)
|
| 255 |
+
adata_dyn.layers["gamma"] = gamma_ss # will be overwritten
|
| 256 |
+
scptr.tl.estimate_gamma(adata_dyn, mode="dynamic", velocity_layer="ds_dt")
|
| 257 |
+
gamma_dyn = adata_dyn.layers["gamma"].copy()
|
| 258 |
+
|
| 259 |
+
# Compare
|
| 260 |
+
med_ss = np.median(gamma_ss, axis=0)
|
| 261 |
+
med_dyn = np.median(gamma_dyn, axis=0)
|
| 262 |
+
valid = (med_ss > 0) & (med_dyn > 0)
|
| 263 |
+
r, p = stats.spearmanr(med_ss[valid], med_dyn[valid])
|
| 264 |
+
|
| 265 |
+
print(f" Steady-state gamma genes > 0: {(med_ss > 0).sum()}")
|
| 266 |
+
print(f" Dynamic gamma genes > 0: {(med_dyn > 0).sum()}")
|
| 267 |
+
print(f" Correlation (shared): r = {r:.4f}, p = {p:.2e}, n = {valid.sum()}")
|
| 268 |
+
|
| 269 |
+
# Genes that differ most between modes
|
| 270 |
+
ratio = np.zeros_like(med_ss)
|
| 271 |
+
ratio[valid] = med_dyn[valid] / med_ss[valid]
|
| 272 |
+
most_different = np.argsort(np.abs(np.log(ratio[valid] + 1e-8)))[::-1][:10]
|
| 273 |
+
print(f"\n Most different genes (dynamic/steady-state ratio):")
|
| 274 |
+
valid_genes = adata_ss.var_names[valid]
|
| 275 |
+
for idx in most_different:
|
| 276 |
+
g = valid_genes[idx]
|
| 277 |
+
r_val = ratio[valid][idx]
|
| 278 |
+
print(f" {g}: dynamic/ss = {r_val:.2f}")
|
| 279 |
+
|
| 280 |
+
# Half-life correlation comparison
|
| 281 |
+
halflife_dir = Path(__file__).parent.parent / "src" / "scptr" / "datasets" / "data"
|
| 282 |
+
for hl_name, hl_file in [("Herzog 2017", "herzog2017_halflives.csv"),
|
| 283 |
+
("Schofield 2018", "schofield2018_halflives.csv")]:
|
| 284 |
+
hl_path = halflife_dir / hl_file
|
| 285 |
+
if not hl_path.exists():
|
| 286 |
+
continue
|
| 287 |
+
hl = pd.read_csv(hl_path)
|
| 288 |
+
gene_map = {g.upper(): i for i, g in enumerate(adata_ss.var_names)}
|
| 289 |
+
hl_ss, hl_dyn, hl_vals = [], [], []
|
| 290 |
+
for _, row in hl.iterrows():
|
| 291 |
+
raw_g = row["gene_symbol"] if "gene_symbol" in hl.columns else row.iloc[0]
|
| 292 |
+
if pd.isna(raw_g) or str(raw_g).strip() == "":
|
| 293 |
+
continue
|
| 294 |
+
g = str(raw_g).upper()
|
| 295 |
+
if g in gene_map:
|
| 296 |
+
gi = gene_map[g]
|
| 297 |
+
if med_ss[gi] > 0 and med_dyn[gi] > 0:
|
| 298 |
+
hl_ss.append(med_ss[gi])
|
| 299 |
+
hl_dyn.append(med_dyn[gi])
|
| 300 |
+
hl_val = row["half_life_hours"] if "half_life_hours" in hl.columns else row.iloc[1]
|
| 301 |
+
hl_vals.append(float(hl_val))
|
| 302 |
+
|
| 303 |
+
if len(hl_vals) >= 20:
|
| 304 |
+
r_ss, _ = stats.spearmanr(hl_ss, hl_vals)
|
| 305 |
+
r_dyn, _ = stats.spearmanr(hl_dyn, hl_vals)
|
| 306 |
+
print(f"\n Half-life correlation ({hl_name}):")
|
| 307 |
+
print(f" Steady-state: r = {r_ss:.4f}")
|
| 308 |
+
print(f" Dynamic: r = {r_dyn:.4f}")
|
| 309 |
+
print(f" {'Dynamic BETTER' if abs(r_dyn) > abs(r_ss) else 'Steady-state BETTER'}")
|
| 310 |
+
|
| 311 |
+
# Figure
|
| 312 |
+
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
|
| 313 |
+
|
| 314 |
+
axes[0].scatter(med_ss[valid], med_dyn[valid], s=2, alpha=0.3, color="steelblue")
|
| 315 |
+
axes[0].set_xlabel("Median gamma (steady-state)")
|
| 316 |
+
axes[0].set_ylabel("Median gamma (dynamic)")
|
| 317 |
+
axes[0].set_title(f"Steady-State vs Dynamic Gamma ({name})\nr={r:.3f}")
|
| 318 |
+
lim = max(med_ss[valid].max(), med_dyn[valid].max()) * 1.1
|
| 319 |
+
axes[0].plot([0, lim], [0, lim], "r--", alpha=0.5)
|
| 320 |
+
|
| 321 |
+
# Panel 2: ratio distribution
|
| 322 |
+
log_ratio = np.log2(ratio[valid] + 1e-8)
|
| 323 |
+
log_ratio = log_ratio[np.isfinite(log_ratio)]
|
| 324 |
+
axes[1].hist(log_ratio, bins=50, color="steelblue", edgecolor="black", linewidth=0.5)
|
| 325 |
+
axes[1].axvline(x=0, color="red", linestyle="--", label="Equal")
|
| 326 |
+
axes[1].set_xlabel("log2(dynamic / steady-state)")
|
| 327 |
+
axes[1].set_ylabel("Number of genes")
|
| 328 |
+
axes[1].set_title("Dynamic vs Steady-State Ratio")
|
| 329 |
+
axes[1].legend()
|
| 330 |
+
|
| 331 |
+
fig.tight_layout()
|
| 332 |
+
save_fig(fig, f"dynamic_mode_{name}")
|
| 333 |
+
|
| 334 |
+
return {
|
| 335 |
+
"ss_vs_dynamic_r": float(r),
|
| 336 |
+
"n_genes_both": int(valid.sum()),
|
| 337 |
+
}
|
| 338 |
+
|
| 339 |
+
|
| 340 |
+
# =========================================================================
|
| 341 |
+
# 3. Scalability profiling
|
| 342 |
+
# =========================================================================
|
| 343 |
+
def profile_scalability(adata, name):
|
| 344 |
+
"""Profile scPTR runtime and memory on increasing cell counts."""
|
| 345 |
+
print(f"\n{'='*60}")
|
| 346 |
+
print(f"SCALABILITY PROFILING ({name})")
|
| 347 |
+
print(f"{'='*60}")
|
| 348 |
+
|
| 349 |
+
import copy
|
| 350 |
+
import tracemalloc
|
| 351 |
+
|
| 352 |
+
# Prepare full dataset
|
| 353 |
+
adata_full = copy.deepcopy(adata)
|
| 354 |
+
scptr.pp.filter_genes(adata_full)
|
| 355 |
+
scptr.pp.normalize_layers(adata_full)
|
| 356 |
+
|
| 357 |
+
n_total = adata_full.n_obs
|
| 358 |
+
fractions = [0.1, 0.25, 0.5, 0.75, 1.0]
|
| 359 |
+
results = []
|
| 360 |
+
|
| 361 |
+
for frac in fractions:
|
| 362 |
+
n_cells = int(n_total * frac)
|
| 363 |
+
if n_cells < 100:
|
| 364 |
+
continue
|
| 365 |
+
|
| 366 |
+
# Subsample
|
| 367 |
+
rng = np.random.RandomState(42)
|
| 368 |
+
idx = rng.choice(n_total, n_cells, replace=False)
|
| 369 |
+
adata_sub = adata_full[idx].copy()
|
| 370 |
+
|
| 371 |
+
print(f"\n {frac:.0%} ({n_cells} cells, {adata_sub.n_vars} genes):")
|
| 372 |
+
|
| 373 |
+
tracemalloc.start()
|
| 374 |
+
t0 = time.time()
|
| 375 |
+
|
| 376 |
+
scptr.pp.neighbors(adata_sub, n_neighbors=min(30, n_cells - 1))
|
| 377 |
+
scptr.pp.smooth_layers(adata_sub)
|
| 378 |
+
scptr.tl.estimate_beta(adata_sub)
|
| 379 |
+
scptr.tl.estimate_gamma(adata_sub)
|
| 380 |
+
|
| 381 |
+
t1 = time.time()
|
| 382 |
+
current, peak = tracemalloc.get_traced_memory()
|
| 383 |
+
tracemalloc.stop()
|
| 384 |
+
|
| 385 |
+
elapsed = t1 - t0
|
| 386 |
+
peak_mb = peak / 1024 / 1024
|
| 387 |
+
|
| 388 |
+
print(f" Time: {elapsed:.1f}s")
|
| 389 |
+
print(f" Peak memory: {peak_mb:.0f} MB")
|
| 390 |
+
|
| 391 |
+
results.append({
|
| 392 |
+
"fraction": frac,
|
| 393 |
+
"n_cells": n_cells,
|
| 394 |
+
"n_genes": adata_sub.n_vars,
|
| 395 |
+
"time_seconds": elapsed,
|
| 396 |
+
"peak_memory_mb": peak_mb,
|
| 397 |
+
})
|
| 398 |
+
|
| 399 |
+
# Extrapolate to 100K cells
|
| 400 |
+
if len(results) >= 3:
|
| 401 |
+
times = [r["time_seconds"] for r in results]
|
| 402 |
+
cells = [r["n_cells"] for r in results]
|
| 403 |
+
# Linear fit in log space for scaling behavior
|
| 404 |
+
log_cells = np.log(cells)
|
| 405 |
+
log_times = np.log(times)
|
| 406 |
+
slope, intercept = np.polyfit(log_cells, log_times, 1)
|
| 407 |
+
estimated_100k = np.exp(intercept) * (100000 ** slope)
|
| 408 |
+
print(f"\n Scaling exponent: {slope:.2f} (1.0=linear, 2.0=quadratic)")
|
| 409 |
+
print(f" Estimated time for 100K cells: {estimated_100k:.0f}s ({estimated_100k/60:.1f} min)")
|
| 410 |
+
|
| 411 |
+
# Figure
|
| 412 |
+
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
|
| 413 |
+
|
| 414 |
+
cells = [r["n_cells"] for r in results]
|
| 415 |
+
times = [r["time_seconds"] for r in results]
|
| 416 |
+
mems = [r["peak_memory_mb"] for r in results]
|
| 417 |
+
|
| 418 |
+
axes[0].plot(cells, times, "o-", color="steelblue", linewidth=2, markersize=8)
|
| 419 |
+
axes[0].set_xlabel("Number of cells")
|
| 420 |
+
axes[0].set_ylabel("Runtime (seconds)")
|
| 421 |
+
axes[0].set_title(f"scPTR Runtime Scaling ({name})")
|
| 422 |
+
|
| 423 |
+
axes[1].plot(cells, mems, "o-", color="#E53935", linewidth=2, markersize=8)
|
| 424 |
+
axes[1].set_xlabel("Number of cells")
|
| 425 |
+
axes[1].set_ylabel("Peak memory (MB)")
|
| 426 |
+
axes[1].set_title(f"scPTR Memory Scaling ({name})")
|
| 427 |
+
|
| 428 |
+
fig.tight_layout()
|
| 429 |
+
save_fig(fig, f"scalability_{name}")
|
| 430 |
+
|
| 431 |
+
return results
|
| 432 |
+
|
| 433 |
+
|
| 434 |
+
# =========================================================================
|
| 435 |
+
# MAIN
|
| 436 |
+
# =========================================================================
|
| 437 |
+
def main():
|
| 438 |
+
set_figure_style()
|
| 439 |
+
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
| 440 |
+
res_dir = OUTPUT_DIR / "results"
|
| 441 |
+
res_dir.mkdir(parents=True, exist_ok=True)
|
| 442 |
+
|
| 443 |
+
# Load datasets
|
| 444 |
+
print("=" * 60)
|
| 445 |
+
print("LOADING DATASETS")
|
| 446 |
+
print("=" * 60)
|
| 447 |
+
|
| 448 |
+
adata_pan = scptr.datasets.pancreas()
|
| 449 |
+
adata_dg = scptr.datasets.dentate_gyrus()
|
| 450 |
+
|
| 451 |
+
# 1. Groupby beta validation
|
| 452 |
+
print("\n" + "#" * 60)
|
| 453 |
+
print("# GROUPBY BETA VALIDATION")
|
| 454 |
+
print("#" * 60)
|
| 455 |
+
|
| 456 |
+
groupby_results = {}
|
| 457 |
+
groupby_results["pancreas"] = validate_groupby_beta(adata_pan, "pancreas")
|
| 458 |
+
groupby_results["dentate_gyrus"] = validate_groupby_beta(adata_dg, "dentate_gyrus")
|
| 459 |
+
|
| 460 |
+
with open(res_dir / "groupby_beta.json", "w") as f:
|
| 461 |
+
json.dump(groupby_results, f, indent=2)
|
| 462 |
+
|
| 463 |
+
# 2. Dynamic mode validation
|
| 464 |
+
print("\n" + "#" * 60)
|
| 465 |
+
print("# DYNAMIC MODE VALIDATION")
|
| 466 |
+
print("#" * 60)
|
| 467 |
+
|
| 468 |
+
dynamic_results = {}
|
| 469 |
+
dynamic_results["pancreas"] = validate_dynamic_mode(adata_pan, "pancreas")
|
| 470 |
+
dynamic_results["dentate_gyrus"] = validate_dynamic_mode(adata_dg, "dentate_gyrus")
|
| 471 |
+
|
| 472 |
+
with open(res_dir / "dynamic_mode.json", "w") as f:
|
| 473 |
+
json.dump(dynamic_results, f, indent=2)
|
| 474 |
+
|
| 475 |
+
# 3. Scalability profiling
|
| 476 |
+
print("\n" + "#" * 60)
|
| 477 |
+
print("# SCALABILITY PROFILING")
|
| 478 |
+
print("#" * 60)
|
| 479 |
+
|
| 480 |
+
scale_results = {}
|
| 481 |
+
scale_results["pancreas"] = profile_scalability(adata_pan, "pancreas")
|
| 482 |
+
scale_results["dentate_gyrus"] = profile_scalability(adata_dg, "dentate_gyrus")
|
| 483 |
+
|
| 484 |
+
with open(res_dir / "scalability.json", "w") as f:
|
| 485 |
+
json.dump(scale_results, f, indent=2)
|
| 486 |
+
|
| 487 |
+
print(f"\n{'='*60}")
|
| 488 |
+
print("REMAINING VALIDATION COMPLETE")
|
| 489 |
+
print(f"{'='*60}")
|
| 490 |
+
print(f"Results saved to: {OUTPUT_DIR.resolve()}")
|
| 491 |
+
|
| 492 |
+
|
| 493 |
+
if __name__ == "__main__":
|
| 494 |
+
main()
|
analyses/run_tier1_fixes.py
ADDED
|
@@ -0,0 +1,942 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
"""Address Tier 1 and Tier 2 reviewer concerns systematically.
|
| 3 |
+
|
| 4 |
+
T1-1: Functionally characterize invisible states (GSEA on differentially degraded genes)
|
| 5 |
+
T1-2: Fix gamma=0 reporting (filter genes with insufficient unspliced coverage)
|
| 6 |
+
T1-3: Investigate destabilizing bias in RBP networks
|
| 7 |
+
T1-4: PT velocity streamlines on UMAP
|
| 8 |
+
T2-1: Ablation experiments (naive u/s ratio vs full scPTR)
|
| 9 |
+
T2-2: Explain TF score discrepancy between datasets
|
| 10 |
+
T2-3: Housekeeping gene analysis for cross-dataset consistency
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
from __future__ import annotations
|
| 14 |
+
|
| 15 |
+
import json
|
| 16 |
+
import sys
|
| 17 |
+
from pathlib import Path
|
| 18 |
+
|
| 19 |
+
import matplotlib
|
| 20 |
+
matplotlib.use("Agg")
|
| 21 |
+
import matplotlib.pyplot as plt
|
| 22 |
+
import numpy as np
|
| 23 |
+
import pandas as pd
|
| 24 |
+
import scanpy as sc
|
| 25 |
+
from scipy import stats
|
| 26 |
+
from sklearn.decomposition import PCA
|
| 27 |
+
from sklearn.cluster import KMeans
|
| 28 |
+
from sklearn.metrics import silhouette_score
|
| 29 |
+
|
| 30 |
+
sys.path.insert(0, str(Path(__file__).parent))
|
| 31 |
+
from _common import set_figure_style
|
| 32 |
+
|
| 33 |
+
import scptr
|
| 34 |
+
|
| 35 |
+
OUTPUT_DIR = Path(__file__).parent.parent / "output" / "tier1_fixes"
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def save_fig(fig, name, subdir="figures"):
|
| 39 |
+
if fig is None:
|
| 40 |
+
return
|
| 41 |
+
out_dir = OUTPUT_DIR / subdir
|
| 42 |
+
out_dir.mkdir(parents=True, exist_ok=True)
|
| 43 |
+
path = out_dir / f"{name}.png"
|
| 44 |
+
fig.savefig(path, dpi=150, bbox_inches="tight")
|
| 45 |
+
plt.close(fig)
|
| 46 |
+
print(f" Saved: {path}")
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def run_pipeline(adata, name):
|
| 50 |
+
"""Run standard scPTR pipeline."""
|
| 51 |
+
print(f"\n--- Pipeline: {name} ---")
|
| 52 |
+
scptr.pp.filter_genes(adata)
|
| 53 |
+
scptr.pp.normalize_layers(adata)
|
| 54 |
+
scptr.pp.neighbors(adata, n_neighbors=30)
|
| 55 |
+
scptr.pp.smooth_layers(adata)
|
| 56 |
+
scptr.tl.estimate_beta(adata)
|
| 57 |
+
scptr.tl.estimate_gamma(adata)
|
| 58 |
+
scptr.tl.variance_decomposition(adata)
|
| 59 |
+
scptr.tl.pt_states(adata)
|
| 60 |
+
scptr.tl.pt_velocity(adata)
|
| 61 |
+
print(f" Done: {adata.shape}")
|
| 62 |
+
return adata
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
# =========================================================================
|
| 66 |
+
# T1-2: Fix gamma=0 reporting
|
| 67 |
+
# =========================================================================
|
| 68 |
+
def fix_gamma_reporting(adata, name):
|
| 69 |
+
"""Report gamma statistics only on genes with reliable estimates.
|
| 70 |
+
|
| 71 |
+
The gamma=0 median is a sparsity artifact: genes with zero unspliced
|
| 72 |
+
counts get gamma=0 by definition. Report separately for:
|
| 73 |
+
1. All genes (including zeros)
|
| 74 |
+
2. Genes with >=10% cells having nonzero gamma ("gamma-informative")
|
| 75 |
+
"""
|
| 76 |
+
print(f"\n{'='*60}")
|
| 77 |
+
print(f"T1-2: GAMMA REPORTING FIX ({name})")
|
| 78 |
+
print(f"{'='*60}")
|
| 79 |
+
|
| 80 |
+
gamma = adata.layers["gamma"]
|
| 81 |
+
n_genes = gamma.shape[1]
|
| 82 |
+
|
| 83 |
+
# Per-gene: fraction of cells with nonzero gamma
|
| 84 |
+
nonzero_frac = (gamma > 0).mean(axis=0)
|
| 85 |
+
median_gamma = np.median(gamma, axis=0)
|
| 86 |
+
|
| 87 |
+
# Thresholds for "informative"
|
| 88 |
+
for thresh in [0.0, 0.05, 0.1, 0.2]:
|
| 89 |
+
mask = nonzero_frac >= thresh
|
| 90 |
+
n = mask.sum()
|
| 91 |
+
if n > 0:
|
| 92 |
+
med = np.median(median_gamma[mask])
|
| 93 |
+
mean = np.mean(median_gamma[mask])
|
| 94 |
+
print(f" Genes with >= {thresh:.0%} nonzero gamma: {n}/{n_genes} "
|
| 95 |
+
f"(median of medians = {med:.4f}, mean = {mean:.4f})")
|
| 96 |
+
|
| 97 |
+
# Key metric: what fraction of genes have usable gamma?
|
| 98 |
+
informative = nonzero_frac >= 0.1
|
| 99 |
+
print(f"\n Gamma-informative genes (>=10% nonzero): {informative.sum()}/{n_genes} "
|
| 100 |
+
f"({100*informative.mean():.1f}%)")
|
| 101 |
+
print(f" These genes' median gamma: {np.median(median_gamma[informative]):.4f}")
|
| 102 |
+
|
| 103 |
+
# Unspliced detection rate
|
| 104 |
+
u = adata.layers.get("Mu", adata.layers.get("unspliced"))
|
| 105 |
+
if u is not None:
|
| 106 |
+
u_arr = u.toarray() if hasattr(u, 'toarray') else np.asarray(u)
|
| 107 |
+
u_detection = (u_arr > 0).mean(axis=0)
|
| 108 |
+
print(f"\n Unspliced detection: mean={u_detection.mean():.3f}, "
|
| 109 |
+
f"median={np.median(u_detection):.3f}")
|
| 110 |
+
print(f" Genes with >5% unspliced detection: {(u_detection > 0.05).sum()}/{n_genes}")
|
| 111 |
+
|
| 112 |
+
return {
|
| 113 |
+
"n_genes_total": int(n_genes),
|
| 114 |
+
"n_informative_10pct": int(informative.sum()),
|
| 115 |
+
"frac_informative": float(informative.mean()),
|
| 116 |
+
"median_gamma_informative": float(np.median(median_gamma[informative])),
|
| 117 |
+
"median_gamma_all": float(np.median(median_gamma)),
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
# =========================================================================
|
| 122 |
+
# T1-1: Functional characterization of invisible states
|
| 123 |
+
# =========================================================================
|
| 124 |
+
def characterize_invisible_states(adata, name):
|
| 125 |
+
"""Find invisible states and characterize differentially degraded genes."""
|
| 126 |
+
print(f"\n{'='*60}")
|
| 127 |
+
print(f"T1-1: FUNCTIONAL CHARACTERIZATION ({name})")
|
| 128 |
+
print(f"{'='*60}")
|
| 129 |
+
|
| 130 |
+
res_dir = OUTPUT_DIR / "results" / "invisible_states" / name
|
| 131 |
+
res_dir.mkdir(parents=True, exist_ok=True)
|
| 132 |
+
|
| 133 |
+
gamma = adata.layers["gamma"]
|
| 134 |
+
clusters = adata.obs["clusters"]
|
| 135 |
+
|
| 136 |
+
all_results = []
|
| 137 |
+
|
| 138 |
+
for cluster_name in clusters.unique():
|
| 139 |
+
mask = (clusters == cluster_name).values
|
| 140 |
+
n_cells = mask.sum()
|
| 141 |
+
if n_cells < 50:
|
| 142 |
+
continue
|
| 143 |
+
|
| 144 |
+
gamma_sub = gamma[mask]
|
| 145 |
+
# Filter to gamma-informative genes for this cluster
|
| 146 |
+
gene_nonzero = (gamma_sub > 0).mean(axis=0)
|
| 147 |
+
good_genes = gene_nonzero >= 0.1
|
| 148 |
+
if good_genes.sum() < 20:
|
| 149 |
+
continue
|
| 150 |
+
|
| 151 |
+
gamma_filtered = gamma_sub[:, good_genes]
|
| 152 |
+
gene_names = adata.var_names[good_genes]
|
| 153 |
+
|
| 154 |
+
# PCA + KMeans on gamma
|
| 155 |
+
n_pcs = min(15, n_cells - 1, gamma_filtered.shape[1] - 1)
|
| 156 |
+
pca = PCA(n_components=n_pcs, random_state=42)
|
| 157 |
+
gamma_pcs = pca.fit_transform(gamma_filtered)
|
| 158 |
+
|
| 159 |
+
best_k, best_sil, best_labels = 1, -1, np.zeros(n_cells, dtype=int)
|
| 160 |
+
for k in [2, 3]:
|
| 161 |
+
if n_cells < k * 10:
|
| 162 |
+
continue
|
| 163 |
+
km = KMeans(n_clusters=k, random_state=42, n_init=10)
|
| 164 |
+
labels = km.fit_predict(gamma_pcs)
|
| 165 |
+
if min(np.bincount(labels)) < 10:
|
| 166 |
+
continue
|
| 167 |
+
sil = silhouette_score(gamma_pcs, labels)
|
| 168 |
+
if sil > best_sil:
|
| 169 |
+
best_sil, best_k, best_labels = sil, k, labels
|
| 170 |
+
|
| 171 |
+
if best_k <= 1:
|
| 172 |
+
continue
|
| 173 |
+
|
| 174 |
+
# Expression silhouette for same labels
|
| 175 |
+
expr_sub = adata.X[mask].toarray() if hasattr(adata.X, 'toarray') else np.asarray(adata.X[mask])
|
| 176 |
+
n_expr_pcs = min(15, n_cells - 1, expr_sub.shape[1] - 1)
|
| 177 |
+
pca_expr = PCA(n_components=n_expr_pcs, random_state=42)
|
| 178 |
+
expr_pcs = pca_expr.fit_transform(expr_sub)
|
| 179 |
+
sil_expr = silhouette_score(expr_pcs, best_labels)
|
| 180 |
+
|
| 181 |
+
invisibility = best_sil - sil_expr
|
| 182 |
+
is_invisible = invisibility > 0.05
|
| 183 |
+
|
| 184 |
+
if not is_invisible:
|
| 185 |
+
continue
|
| 186 |
+
|
| 187 |
+
print(f"\n {cluster_name}: INVISIBLE (sil_gamma={best_sil:.3f}, "
|
| 188 |
+
f"sil_expr={sil_expr:.3f})")
|
| 189 |
+
|
| 190 |
+
# Differential degradation between sub-clusters
|
| 191 |
+
diff_results = []
|
| 192 |
+
for gi, gene in enumerate(gene_names):
|
| 193 |
+
groups = [gamma_filtered[best_labels == j, gi] for j in range(best_k)]
|
| 194 |
+
if all(len(g) >= 5 for g in groups):
|
| 195 |
+
if best_k == 2:
|
| 196 |
+
u_stat, p_val = stats.mannwhitneyu(groups[0], groups[1],
|
| 197 |
+
alternative='two-sided')
|
| 198 |
+
else:
|
| 199 |
+
_, p_val = stats.kruskal(*groups)
|
| 200 |
+
|
| 201 |
+
medians = [np.median(g) for g in groups]
|
| 202 |
+
max_med = max(medians)
|
| 203 |
+
min_med = min(medians)
|
| 204 |
+
log_fc = np.log2((max_med + 0.01) / (min_med + 0.01))
|
| 205 |
+
|
| 206 |
+
diff_results.append({
|
| 207 |
+
"gene": gene,
|
| 208 |
+
"p_value": p_val,
|
| 209 |
+
"log2_fc_gamma": log_fc,
|
| 210 |
+
"medians": medians,
|
| 211 |
+
})
|
| 212 |
+
|
| 213 |
+
if not diff_results:
|
| 214 |
+
continue
|
| 215 |
+
|
| 216 |
+
diff_df = pd.DataFrame(diff_results)
|
| 217 |
+
# FDR correction
|
| 218 |
+
from statsmodels.stats.multitest import multipletests
|
| 219 |
+
_, diff_df["fdr"], _, _ = multipletests(diff_df["p_value"], method="fdr_bh")
|
| 220 |
+
|
| 221 |
+
# Significant differentially degraded genes
|
| 222 |
+
sig = diff_df[diff_df["fdr"] < 0.05].sort_values("log2_fc_gamma", ascending=False)
|
| 223 |
+
print(f" Differentially degraded genes (FDR<0.05): {len(sig)}/{len(diff_df)}")
|
| 224 |
+
|
| 225 |
+
if len(sig) > 0:
|
| 226 |
+
# Top destabilized (high gamma in one sub-cluster)
|
| 227 |
+
top_destab = sig.head(10)
|
| 228 |
+
print(f" Top destabilized: {top_destab['gene'].tolist()}")
|
| 229 |
+
|
| 230 |
+
# Top stabilized (low gamma difference but significant)
|
| 231 |
+
top_stab = sig.tail(10)
|
| 232 |
+
print(f" Top stabilized: {top_stab['gene'].tolist()}")
|
| 233 |
+
|
| 234 |
+
sig.to_csv(res_dir / f"{cluster_name}_diff_degraded.csv", index=False)
|
| 235 |
+
|
| 236 |
+
# Run enrichment using gseapy (Enrichr API)
|
| 237 |
+
try:
|
| 238 |
+
import gseapy as gp
|
| 239 |
+
|
| 240 |
+
# Use top differentially degraded genes for enrichment
|
| 241 |
+
gene_list = sig["gene"].tolist()
|
| 242 |
+
if len(gene_list) >= 5:
|
| 243 |
+
# Determine organism
|
| 244 |
+
# If gene names are Titlecase → mouse; UPPERCASE → human
|
| 245 |
+
sample_gene = gene_list[0]
|
| 246 |
+
organism = "mouse" if sample_gene[0].isupper() and sample_gene[1:].islower() else "human"
|
| 247 |
+
|
| 248 |
+
gene_sets = ["GO_Biological_Process_2023",
|
| 249 |
+
"KEGG_2021_Human" if organism == "human" else "KEGG_2019_Mouse"]
|
| 250 |
+
|
| 251 |
+
enr = gp.enrichr(gene_list=gene_list,
|
| 252 |
+
gene_sets=gene_sets,
|
| 253 |
+
organism=organism,
|
| 254 |
+
outdir=None,
|
| 255 |
+
no_plot=True)
|
| 256 |
+
|
| 257 |
+
enr_df = enr.results
|
| 258 |
+
sig_enr = enr_df[enr_df["Adjusted P-value"] < 0.1].head(15)
|
| 259 |
+
|
| 260 |
+
if len(sig_enr) > 0:
|
| 261 |
+
print(f" Enriched pathways (FDR<0.1):")
|
| 262 |
+
for _, row in sig_enr.iterrows():
|
| 263 |
+
print(f" {row['Term'][:60]}: p={row['Adjusted P-value']:.4f}")
|
| 264 |
+
sig_enr.to_csv(res_dir / f"{cluster_name}_enrichment.csv", index=False)
|
| 265 |
+
else:
|
| 266 |
+
print(f" No significant pathway enrichment found")
|
| 267 |
+
except Exception as e:
|
| 268 |
+
print(f" [WARNING] Enrichment failed: {e}")
|
| 269 |
+
|
| 270 |
+
all_results.append({
|
| 271 |
+
"cluster": cluster_name,
|
| 272 |
+
"n_cells": n_cells,
|
| 273 |
+
"n_subclusters": best_k,
|
| 274 |
+
"sil_gamma": best_sil,
|
| 275 |
+
"sil_expr": sil_expr,
|
| 276 |
+
"invisibility": invisibility,
|
| 277 |
+
"n_diff_genes": len(sig) if len(sig) > 0 else 0,
|
| 278 |
+
})
|
| 279 |
+
|
| 280 |
+
return pd.DataFrame(all_results)
|
| 281 |
+
|
| 282 |
+
|
| 283 |
+
# =========================================================================
|
| 284 |
+
# T1-3: Investigate destabilizing bias in RBP networks
|
| 285 |
+
# =========================================================================
|
| 286 |
+
def investigate_destabilizing_bias(adata, name):
|
| 287 |
+
"""Investigate why RBP networks show predominantly destabilizing effects."""
|
| 288 |
+
print(f"\n{'='*60}")
|
| 289 |
+
print(f"T1-3: DESTABILIZING BIAS INVESTIGATION ({name})")
|
| 290 |
+
print(f"{'='*60}")
|
| 291 |
+
|
| 292 |
+
res_dir = OUTPUT_DIR / "results" / "network_bias"
|
| 293 |
+
res_dir.mkdir(parents=True, exist_ok=True)
|
| 294 |
+
|
| 295 |
+
gamma = adata.layers["gamma"]
|
| 296 |
+
gamma_med = np.median(gamma, axis=0)
|
| 297 |
+
|
| 298 |
+
# Load RBP list
|
| 299 |
+
rbp_path = Path(__file__).parent.parent / "src" / "scptr" / "tools" / "data" / "known_rbps.csv"
|
| 300 |
+
rbps = pd.read_csv(rbp_path)["gene_symbol"].tolist()
|
| 301 |
+
|
| 302 |
+
# Find RBPs in dataset (case-insensitive)
|
| 303 |
+
adata_genes_upper = {g.upper(): g for g in adata.var_names}
|
| 304 |
+
rbp_in_data = []
|
| 305 |
+
for r in rbps:
|
| 306 |
+
if r.upper() in adata_genes_upper:
|
| 307 |
+
rbp_in_data.append(adata_genes_upper[r.upper()])
|
| 308 |
+
|
| 309 |
+
print(f" RBPs in dataset: {len(rbp_in_data)}")
|
| 310 |
+
|
| 311 |
+
# Get expression matrix
|
| 312 |
+
if hasattr(adata.X, 'toarray'):
|
| 313 |
+
expr = adata.X.toarray()
|
| 314 |
+
else:
|
| 315 |
+
expr = np.asarray(adata.X)
|
| 316 |
+
|
| 317 |
+
# Select target genes: top variable gamma (filtered to informative)
|
| 318 |
+
nonzero_frac = (gamma > 0).mean(axis=0)
|
| 319 |
+
informative = nonzero_frac >= 0.1
|
| 320 |
+
gamma_var = np.var(gamma[:, informative], axis=0)
|
| 321 |
+
n_targets = min(200, informative.sum())
|
| 322 |
+
top_var_idx = np.argsort(gamma_var)[-n_targets:]
|
| 323 |
+
info_indices = np.where(informative)[0]
|
| 324 |
+
target_indices = info_indices[top_var_idx]
|
| 325 |
+
target_genes = adata.var_names[target_indices]
|
| 326 |
+
|
| 327 |
+
# Correlation analysis
|
| 328 |
+
print("\n Correlation analysis:")
|
| 329 |
+
all_edges = []
|
| 330 |
+
|
| 331 |
+
for rbp_name in rbp_in_data:
|
| 332 |
+
rbp_idx = list(adata.var_names).index(rbp_name)
|
| 333 |
+
rbp_expr = expr[:, rbp_idx]
|
| 334 |
+
|
| 335 |
+
if np.std(rbp_expr) < 1e-6:
|
| 336 |
+
continue
|
| 337 |
+
|
| 338 |
+
for ti, target_name in zip(target_indices, target_genes):
|
| 339 |
+
target_gamma = gamma[:, ti]
|
| 340 |
+
|
| 341 |
+
# Only use cells with nonzero gamma for this gene
|
| 342 |
+
valid = target_gamma > 0
|
| 343 |
+
if valid.sum() < 50:
|
| 344 |
+
continue
|
| 345 |
+
|
| 346 |
+
r, p = stats.spearmanr(rbp_expr[valid], target_gamma[valid])
|
| 347 |
+
|
| 348 |
+
if p < 0.05 / (len(rbp_in_data) * n_targets): # Bonferroni
|
| 349 |
+
all_edges.append({
|
| 350 |
+
"rbp": rbp_name,
|
| 351 |
+
"target": target_name,
|
| 352 |
+
"spearman_r": r,
|
| 353 |
+
"p_value": p,
|
| 354 |
+
"direction": "destabilizing" if r > 0 else "stabilizing",
|
| 355 |
+
})
|
| 356 |
+
|
| 357 |
+
edges_df = pd.DataFrame(all_edges)
|
| 358 |
+
if len(edges_df) == 0:
|
| 359 |
+
print(" No significant edges found")
|
| 360 |
+
return
|
| 361 |
+
|
| 362 |
+
n_destab = (edges_df["spearman_r"] > 0).sum()
|
| 363 |
+
n_stab = (edges_df["spearman_r"] < 0).sum()
|
| 364 |
+
print(f" Total significant edges: {len(edges_df)}")
|
| 365 |
+
print(f" Destabilizing (r>0): {n_destab} ({100*n_destab/len(edges_df):.1f}%)")
|
| 366 |
+
print(f" Stabilizing (r<0): {n_stab} ({100*n_stab/len(edges_df):.1f}%)")
|
| 367 |
+
|
| 368 |
+
# Key diagnostic: is the bias in the gamma distribution itself?
|
| 369 |
+
# Check: is gamma positively correlated with total expression?
|
| 370 |
+
expr_mean = expr.mean(axis=0)
|
| 371 |
+
gamma_mean = gamma.mean(axis=0)
|
| 372 |
+
r_expr_gamma, _ = stats.spearmanr(expr_mean[informative], gamma_mean[informative])
|
| 373 |
+
print(f"\n Diagnostic: Spearman(mean_expression, mean_gamma) = {r_expr_gamma:.4f}")
|
| 374 |
+
print(f" If positive, RBP expression correlates with gamma because both")
|
| 375 |
+
print(f" correlate with overall expression level → confounding.")
|
| 376 |
+
|
| 377 |
+
# Check: does the bias persist after regressing out total expression?
|
| 378 |
+
print("\n After controlling for total expression per cell:")
|
| 379 |
+
total_expr_per_cell = expr.sum(axis=1)
|
| 380 |
+
|
| 381 |
+
n_stab_ctrl = 0
|
| 382 |
+
n_destab_ctrl = 0
|
| 383 |
+
controlled_edges = []
|
| 384 |
+
|
| 385 |
+
for rbp_name in rbp_in_data[:10]: # Test top 10 RBPs
|
| 386 |
+
rbp_idx = list(adata.var_names).index(rbp_name)
|
| 387 |
+
rbp_expr = expr[:, rbp_idx]
|
| 388 |
+
if np.std(rbp_expr) < 1e-6:
|
| 389 |
+
continue
|
| 390 |
+
|
| 391 |
+
# Partial correlation: regress out total expression
|
| 392 |
+
# Residualize both RBP expression and gamma against total expression
|
| 393 |
+
from numpy.polynomial.polynomial import polyfit, polyval
|
| 394 |
+
rbp_resid = rbp_expr - np.mean(rbp_expr)
|
| 395 |
+
# Simple: rank-based partial correlation
|
| 396 |
+
rbp_rank = stats.rankdata(rbp_expr)
|
| 397 |
+
total_rank = stats.rankdata(total_expr_per_cell)
|
| 398 |
+
|
| 399 |
+
# Regress out total from RBP
|
| 400 |
+
slope = np.cov(rbp_rank, total_rank)[0, 1] / np.var(total_rank)
|
| 401 |
+
rbp_resid = rbp_rank - slope * total_rank
|
| 402 |
+
|
| 403 |
+
for ti in target_indices[:50]:
|
| 404 |
+
target_gamma = gamma[:, ti]
|
| 405 |
+
valid = target_gamma > 0
|
| 406 |
+
if valid.sum() < 50:
|
| 407 |
+
continue
|
| 408 |
+
|
| 409 |
+
gamma_rank = stats.rankdata(target_gamma[valid])
|
| 410 |
+
total_rank_v = stats.rankdata(total_expr_per_cell[valid])
|
| 411 |
+
slope_g = np.cov(gamma_rank, total_rank_v)[0, 1] / (np.var(total_rank_v) + 1e-10)
|
| 412 |
+
gamma_resid = gamma_rank - slope_g * total_rank_v
|
| 413 |
+
|
| 414 |
+
r, p = stats.spearmanr(rbp_resid[valid], gamma_resid)
|
| 415 |
+
if r > 0:
|
| 416 |
+
n_destab_ctrl += 1
|
| 417 |
+
else:
|
| 418 |
+
n_stab_ctrl += 1
|
| 419 |
+
|
| 420 |
+
total_ctrl = n_destab_ctrl + n_stab_ctrl
|
| 421 |
+
if total_ctrl > 0:
|
| 422 |
+
print(f" Destabilizing: {n_destab_ctrl}/{total_ctrl} ({100*n_destab_ctrl/total_ctrl:.1f}%)")
|
| 423 |
+
print(f" Stabilizing: {n_stab_ctrl}/{total_ctrl} ({100*n_stab_ctrl/total_ctrl:.1f}%)")
|
| 424 |
+
|
| 425 |
+
if n_destab_ctrl / total_ctrl < 0.6:
|
| 426 |
+
print(f" → Bias is reduced after controlling for library size!")
|
| 427 |
+
print(f" → The original bias was partly a confound: RBPs with higher")
|
| 428 |
+
print(f" expression → higher overall counts → higher gamma artifacts")
|
| 429 |
+
else:
|
| 430 |
+
print(f" → Bias persists even after correction")
|
| 431 |
+
|
| 432 |
+
# Per-RBP breakdown
|
| 433 |
+
print("\n Per-RBP breakdown:")
|
| 434 |
+
hub_counts = edges_df.groupby("rbp").agg(
|
| 435 |
+
n_targets=("target", "count"),
|
| 436 |
+
n_stab=("direction", lambda x: (x == "stabilizing").sum()),
|
| 437 |
+
n_destab=("direction", lambda x: (x == "destabilizing").sum()),
|
| 438 |
+
mean_r=("spearman_r", "mean"),
|
| 439 |
+
).sort_values("n_targets", ascending=False)
|
| 440 |
+
|
| 441 |
+
for rbp_name, row in hub_counts.head(10).iterrows():
|
| 442 |
+
ratio = row["n_destab"] / max(row["n_targets"], 1)
|
| 443 |
+
print(f" {rbp_name}: {int(row['n_targets'])} targets "
|
| 444 |
+
f"({int(row['n_stab'])} stab, {int(row['n_destab'])} destab, "
|
| 445 |
+
f"mean_r={row['mean_r']:.3f})")
|
| 446 |
+
|
| 447 |
+
edges_df.to_csv(res_dir / f"edges_{name}.csv", index=False)
|
| 448 |
+
hub_counts.to_csv(res_dir / f"hub_counts_{name}.csv")
|
| 449 |
+
|
| 450 |
+
return {
|
| 451 |
+
"n_edges": len(edges_df),
|
| 452 |
+
"frac_destabilizing": float(n_destab / len(edges_df)),
|
| 453 |
+
"expr_gamma_correlation": float(r_expr_gamma),
|
| 454 |
+
}
|
| 455 |
+
|
| 456 |
+
|
| 457 |
+
# =========================================================================
|
| 458 |
+
# T1-4: PT velocity streamlines on UMAP
|
| 459 |
+
# =========================================================================
|
| 460 |
+
def velocity_streamlines(adata, name):
|
| 461 |
+
"""Generate proper streamline plots for PT velocity on UMAP."""
|
| 462 |
+
print(f"\n{'='*60}")
|
| 463 |
+
print(f"T1-4: VELOCITY STREAMLINES ({name})")
|
| 464 |
+
print(f"{'='*60}")
|
| 465 |
+
|
| 466 |
+
gamma = adata.layers["gamma"]
|
| 467 |
+
velocity = adata.layers["pt_velocity"]
|
| 468 |
+
|
| 469 |
+
# We need UMAP coordinates
|
| 470 |
+
if "X_gamma_umap" not in adata.obsm:
|
| 471 |
+
print(" No gamma UMAP, computing...")
|
| 472 |
+
sc.tl.umap(adata)
|
| 473 |
+
coords = adata.obsm["X_umap"]
|
| 474 |
+
else:
|
| 475 |
+
coords = adata.obsm["X_gamma_umap"]
|
| 476 |
+
|
| 477 |
+
# Build transition matrix from velocity
|
| 478 |
+
from sklearn.neighbors import NearestNeighbors
|
| 479 |
+
|
| 480 |
+
nn = NearestNeighbors(n_neighbors=30)
|
| 481 |
+
nn.fit(coords)
|
| 482 |
+
dists, indices = nn.kneighbors(coords)
|
| 483 |
+
|
| 484 |
+
# For each cell, compute velocity-weighted displacement in UMAP space
|
| 485 |
+
n_cells = len(coords)
|
| 486 |
+
dx = np.zeros((n_cells, 2))
|
| 487 |
+
|
| 488 |
+
for i in range(n_cells):
|
| 489 |
+
neighbors = indices[i, 1:] # exclude self
|
| 490 |
+
vel_i = velocity[i]
|
| 491 |
+
|
| 492 |
+
for j in neighbors:
|
| 493 |
+
# Gamma displacement: how different is neighbor's gamma from mine?
|
| 494 |
+
gamma_disp = gamma[j] - gamma[i]
|
| 495 |
+
|
| 496 |
+
# Project: does the velocity vector point toward this neighbor?
|
| 497 |
+
cos_sim = np.dot(vel_i, gamma_disp) / (
|
| 498 |
+
np.linalg.norm(vel_i) * np.linalg.norm(gamma_disp) + 1e-10
|
| 499 |
+
)
|
| 500 |
+
|
| 501 |
+
if cos_sim > 0:
|
| 502 |
+
# Weight by cosine similarity and UMAP displacement
|
| 503 |
+
umap_disp = coords[j] - coords[i]
|
| 504 |
+
dx[i] += cos_sim * umap_disp
|
| 505 |
+
|
| 506 |
+
# Normalize
|
| 507 |
+
norms = np.linalg.norm(dx, axis=1, keepdims=True)
|
| 508 |
+
cap = np.percentile(norms[norms > 0], 95)
|
| 509 |
+
dx = dx / (cap + 1e-10)
|
| 510 |
+
|
| 511 |
+
# Velocity magnitude for coloring
|
| 512 |
+
vel_mag = np.linalg.norm(velocity, axis=1)
|
| 513 |
+
vel_mag = vel_mag / (np.percentile(vel_mag, 95) + 1e-10)
|
| 514 |
+
|
| 515 |
+
# Create streamline-style plot using quiver at grid points
|
| 516 |
+
fig, axes = plt.subplots(1, 2, figsize=(16, 7))
|
| 517 |
+
|
| 518 |
+
# Panel 1: Quiver plot colored by cluster
|
| 519 |
+
clusters = adata.obs["clusters"]
|
| 520 |
+
cluster_colors = {c: plt.cm.tab20(i / 20) for i, c in enumerate(clusters.unique())}
|
| 521 |
+
|
| 522 |
+
for c in clusters.unique():
|
| 523 |
+
mask = (clusters == c).values
|
| 524 |
+
axes[0].scatter(coords[mask, 0], coords[mask, 1], s=3, alpha=0.3,
|
| 525 |
+
c=[cluster_colors[c]], label=c)
|
| 526 |
+
|
| 527 |
+
# Subsample arrows for clarity
|
| 528 |
+
n_arrows = min(500, n_cells)
|
| 529 |
+
arrow_idx = np.random.choice(n_cells, n_arrows, replace=False)
|
| 530 |
+
arrow_mask = np.linalg.norm(dx[arrow_idx], axis=1) > 0.01
|
| 531 |
+
|
| 532 |
+
axes[0].quiver(coords[arrow_idx[arrow_mask], 0],
|
| 533 |
+
coords[arrow_idx[arrow_mask], 1],
|
| 534 |
+
dx[arrow_idx[arrow_mask], 0],
|
| 535 |
+
dx[arrow_idx[arrow_mask], 1],
|
| 536 |
+
color="black", alpha=0.6, scale=20, width=0.003,
|
| 537 |
+
headwidth=4, headlength=5)
|
| 538 |
+
axes[0].set_title(f"PT Velocity Streamlines: {name}")
|
| 539 |
+
axes[0].set_xlabel("UMAP 1")
|
| 540 |
+
axes[0].set_ylabel("UMAP 2")
|
| 541 |
+
axes[0].legend(fontsize=6, loc="best", markerscale=3)
|
| 542 |
+
|
| 543 |
+
# Panel 2: Velocity magnitude
|
| 544 |
+
sc_plot = axes[1].scatter(coords[:, 0], coords[:, 1], s=3, alpha=0.5,
|
| 545 |
+
c=np.clip(vel_mag, 0, 1), cmap="YlOrRd")
|
| 546 |
+
axes[1].quiver(coords[arrow_idx[arrow_mask], 0],
|
| 547 |
+
coords[arrow_idx[arrow_mask], 1],
|
| 548 |
+
dx[arrow_idx[arrow_mask], 0],
|
| 549 |
+
dx[arrow_idx[arrow_mask], 1],
|
| 550 |
+
color="black", alpha=0.4, scale=20, width=0.002,
|
| 551 |
+
headwidth=4, headlength=5)
|
| 552 |
+
axes[1].set_title(f"PT Velocity Magnitude: {name}")
|
| 553 |
+
axes[1].set_xlabel("UMAP 1")
|
| 554 |
+
axes[1].set_ylabel("UMAP 2")
|
| 555 |
+
plt.colorbar(sc_plot, ax=axes[1], label="Velocity magnitude")
|
| 556 |
+
|
| 557 |
+
fig.tight_layout()
|
| 558 |
+
save_fig(fig, f"velocity_streamlines_{name}")
|
| 559 |
+
|
| 560 |
+
print(f" Mean velocity magnitude: {np.mean(np.linalg.norm(velocity, axis=1)):.4f}")
|
| 561 |
+
print(f" Cells with significant displacement: {arrow_mask.sum()}/{n_arrows}")
|
| 562 |
+
|
| 563 |
+
|
| 564 |
+
# =========================================================================
|
| 565 |
+
# T2-1: Ablation experiments
|
| 566 |
+
# =========================================================================
|
| 567 |
+
def ablation_experiments(adata, name):
|
| 568 |
+
"""Compare full scPTR against naive alternatives using invisibility score.
|
| 569 |
+
|
| 570 |
+
For each cluster x method:
|
| 571 |
+
1. Find sub-clusters in method's space (sil_method)
|
| 572 |
+
2. Evaluate SAME labels in expression PCA space (sil_expr)
|
| 573 |
+
3. Invisibility = sil_method - sil_expr
|
| 574 |
+
|
| 575 |
+
The key claim: scPTR gamma maximizes invisibility (finds sub-populations
|
| 576 |
+
most invisible to expression), not raw separability.
|
| 577 |
+
"""
|
| 578 |
+
print(f"\n{'='*60}")
|
| 579 |
+
print(f"T2-1: ABLATION EXPERIMENTS ({name})")
|
| 580 |
+
print(f"{'='*60}")
|
| 581 |
+
|
| 582 |
+
res_dir = OUTPUT_DIR / "results" / "ablation"
|
| 583 |
+
res_dir.mkdir(parents=True, exist_ok=True)
|
| 584 |
+
|
| 585 |
+
gamma = adata.layers["gamma"]
|
| 586 |
+
clusters = adata.obs["clusters"]
|
| 587 |
+
|
| 588 |
+
# Get unspliced counts
|
| 589 |
+
u_layer = adata.layers.get("Mu", adata.layers.get("unspliced"))
|
| 590 |
+
s_layer = adata.layers.get("Ms", adata.layers.get("spliced"))
|
| 591 |
+
u = u_layer.toarray() if hasattr(u_layer, 'toarray') else np.asarray(u_layer)
|
| 592 |
+
s = s_layer.toarray() if hasattr(s_layer, 'toarray') else np.asarray(s_layer)
|
| 593 |
+
|
| 594 |
+
# Expression matrix (for expression silhouette computation)
|
| 595 |
+
expr_full = adata.X.toarray() if hasattr(adata.X, 'toarray') else np.asarray(adata.X)
|
| 596 |
+
|
| 597 |
+
# Method 1: Full scPTR gamma (already computed)
|
| 598 |
+
# Method 2: Raw u/s ratio (naive, no kinetic model)
|
| 599 |
+
raw_ratio = np.zeros_like(gamma)
|
| 600 |
+
s_safe = np.where(s > 0.01, s, 1.0)
|
| 601 |
+
raw_ratio = u / s_safe
|
| 602 |
+
raw_ratio[s < 0.01] = 0
|
| 603 |
+
|
| 604 |
+
# Method 3: PCA on unspliced counts alone
|
| 605 |
+
# Method 4: PCA on expression alone (baseline)
|
| 606 |
+
|
| 607 |
+
methods = {
|
| 608 |
+
"scPTR_gamma": gamma,
|
| 609 |
+
"raw_u_s_ratio": raw_ratio,
|
| 610 |
+
"unspliced_only": u,
|
| 611 |
+
"expression": expr_full,
|
| 612 |
+
}
|
| 613 |
+
|
| 614 |
+
results = []
|
| 615 |
+
|
| 616 |
+
for cluster_name in clusters.unique():
|
| 617 |
+
mask = (clusters == cluster_name).values
|
| 618 |
+
n_cells = mask.sum()
|
| 619 |
+
if n_cells < 50:
|
| 620 |
+
continue
|
| 621 |
+
|
| 622 |
+
# Pre-compute expression PCA for this cluster (used for all methods)
|
| 623 |
+
expr_sub = expr_full[mask]
|
| 624 |
+
nonzero_expr = (expr_sub > 0).mean(axis=0)
|
| 625 |
+
good_expr = nonzero_expr >= 0.05
|
| 626 |
+
if good_expr.sum() < 20:
|
| 627 |
+
continue
|
| 628 |
+
n_expr_pcs = min(15, n_cells - 1, good_expr.sum() - 1)
|
| 629 |
+
pca_expr = PCA(n_components=n_expr_pcs, random_state=42)
|
| 630 |
+
expr_pcs = pca_expr.fit_transform(expr_sub[:, good_expr])
|
| 631 |
+
|
| 632 |
+
for method_name, data in methods.items():
|
| 633 |
+
data_sub = data[mask]
|
| 634 |
+
|
| 635 |
+
# Filter to informative features
|
| 636 |
+
nonzero = (data_sub > 0).mean(axis=0)
|
| 637 |
+
good = nonzero >= 0.05
|
| 638 |
+
if good.sum() < 20:
|
| 639 |
+
continue
|
| 640 |
+
data_filtered = data_sub[:, good]
|
| 641 |
+
|
| 642 |
+
n_pcs = min(15, n_cells - 1, data_filtered.shape[1] - 1)
|
| 643 |
+
pca = PCA(n_components=n_pcs, random_state=42)
|
| 644 |
+
pcs = pca.fit_transform(data_filtered)
|
| 645 |
+
|
| 646 |
+
best_sil = -1
|
| 647 |
+
best_labels = None
|
| 648 |
+
for k in [2, 3]:
|
| 649 |
+
if n_cells < k * 10:
|
| 650 |
+
continue
|
| 651 |
+
km = KMeans(n_clusters=k, random_state=42, n_init=10)
|
| 652 |
+
labels = km.fit_predict(pcs)
|
| 653 |
+
if min(np.bincount(labels)) < 10:
|
| 654 |
+
continue
|
| 655 |
+
sil = silhouette_score(pcs, labels)
|
| 656 |
+
if sil > best_sil:
|
| 657 |
+
best_sil = sil
|
| 658 |
+
best_labels = labels
|
| 659 |
+
|
| 660 |
+
if best_labels is None:
|
| 661 |
+
continue
|
| 662 |
+
|
| 663 |
+
# Compute silhouette of SAME labels in expression PCA space
|
| 664 |
+
sil_expr = silhouette_score(expr_pcs, best_labels)
|
| 665 |
+
invisibility = best_sil - sil_expr
|
| 666 |
+
|
| 667 |
+
results.append({
|
| 668 |
+
"cluster": cluster_name,
|
| 669 |
+
"method": method_name,
|
| 670 |
+
"n_cells": n_cells,
|
| 671 |
+
"sil_method_space": best_sil,
|
| 672 |
+
"sil_expr_space": sil_expr,
|
| 673 |
+
"invisibility": invisibility,
|
| 674 |
+
})
|
| 675 |
+
|
| 676 |
+
results_df = pd.DataFrame(results)
|
| 677 |
+
results_df.to_csv(res_dir / f"ablation_{name}.csv", index=False)
|
| 678 |
+
|
| 679 |
+
# Summary: mean invisibility by method
|
| 680 |
+
print("\n Mean invisibility score by method (higher = better):")
|
| 681 |
+
summary = results_df.groupby("method")["invisibility"].agg(["mean", "std", "count"])
|
| 682 |
+
for method, row in summary.sort_values("mean", ascending=False).iterrows():
|
| 683 |
+
print(f" {method:<20s}: {row['mean']:.4f} +/- {row['std']:.4f} "
|
| 684 |
+
f"(n={int(row['count'])})")
|
| 685 |
+
|
| 686 |
+
print("\n Mean silhouette in method-space vs expression-space:")
|
| 687 |
+
for method in ["scPTR_gamma", "raw_u_s_ratio", "unspliced_only", "expression"]:
|
| 688 |
+
sub = results_df[results_df["method"] == method]
|
| 689 |
+
if len(sub) == 0:
|
| 690 |
+
continue
|
| 691 |
+
print(f" {method:<20s}: sil_method={sub['sil_method_space'].mean():.4f}, "
|
| 692 |
+
f"sil_expr={sub['sil_expr_space'].mean():.4f}, "
|
| 693 |
+
f"invisibility={sub['invisibility'].mean():.4f}")
|
| 694 |
+
|
| 695 |
+
# Figure: Panel A (invisibility bars) + Panel B (per-cluster heatmap)
|
| 696 |
+
fig, axes = plt.subplots(1, 2, figsize=(16, 7))
|
| 697 |
+
|
| 698 |
+
# Panel A: Mean invisibility by method
|
| 699 |
+
methods_order = ["expression", "unspliced_only", "raw_u_s_ratio", "scPTR_gamma"]
|
| 700 |
+
method_labels = ["Expression\n(baseline)", "Unspliced\nonly", "Raw u/s\nratio", "scPTR\ngamma"]
|
| 701 |
+
colors = ["gray", "lightblue", "orange", "steelblue"]
|
| 702 |
+
positions = np.arange(len(methods_order))
|
| 703 |
+
|
| 704 |
+
means = []
|
| 705 |
+
stds = []
|
| 706 |
+
for m in methods_order:
|
| 707 |
+
sub = results_df[results_df["method"] == m]["invisibility"]
|
| 708 |
+
means.append(sub.mean() if len(sub) > 0 else 0)
|
| 709 |
+
stds.append(sub.std() if len(sub) > 0 else 0)
|
| 710 |
+
|
| 711 |
+
bars = axes[0].bar(positions, means, 0.6, yerr=stds, color=colors,
|
| 712 |
+
edgecolor="black", linewidth=0.5, capsize=3)
|
| 713 |
+
axes[0].set_xticks(positions)
|
| 714 |
+
axes[0].set_xticklabels(method_labels, fontsize=10)
|
| 715 |
+
axes[0].set_ylabel("Mean Invisibility Score\n(sil_method - sil_expr)")
|
| 716 |
+
axes[0].set_title(f"A: Invisibility Score ({name})")
|
| 717 |
+
axes[0].axhline(y=0, color="black", linestyle="-", linewidth=0.5)
|
| 718 |
+
for i, (m, s) in enumerate(zip(means, stds)):
|
| 719 |
+
axes[0].text(i, m + s + 0.005, f"{m:.3f}", ha="center", fontsize=9)
|
| 720 |
+
|
| 721 |
+
# Panel B: Per-cluster invisibility heatmap
|
| 722 |
+
pivot = results_df.pivot_table(
|
| 723 |
+
index="cluster", columns="method", values="invisibility", aggfunc="mean"
|
| 724 |
+
)
|
| 725 |
+
if len(pivot) > 0:
|
| 726 |
+
# Reorder columns
|
| 727 |
+
col_order = [m for m in methods_order if m in pivot.columns]
|
| 728 |
+
pivot = pivot[col_order]
|
| 729 |
+
|
| 730 |
+
im = axes[1].imshow(pivot.values, aspect="auto", cmap="RdBu_r",
|
| 731 |
+
vmin=-0.3, vmax=0.3)
|
| 732 |
+
axes[1].set_xticks(np.arange(len(col_order)))
|
| 733 |
+
axes[1].set_xticklabels([m.replace("_", "\n") for m in col_order], fontsize=8)
|
| 734 |
+
axes[1].set_yticks(np.arange(len(pivot.index)))
|
| 735 |
+
axes[1].set_yticklabels(pivot.index, fontsize=8)
|
| 736 |
+
axes[1].set_title(f"B: Per-cluster Invisibility ({name})")
|
| 737 |
+
|
| 738 |
+
# Annotate cells
|
| 739 |
+
for i in range(len(pivot.index)):
|
| 740 |
+
for j in range(len(col_order)):
|
| 741 |
+
val = pivot.values[i, j]
|
| 742 |
+
if not np.isnan(val):
|
| 743 |
+
axes[1].text(j, i, f"{val:.2f}", ha="center", va="center",
|
| 744 |
+
fontsize=7, color="white" if abs(val) > 0.15 else "black")
|
| 745 |
+
|
| 746 |
+
plt.colorbar(im, ax=axes[1], label="Invisibility", shrink=0.8)
|
| 747 |
+
|
| 748 |
+
fig.suptitle(f"Ablation: Invisibility Score Analysis ({name})", fontsize=13)
|
| 749 |
+
fig.tight_layout()
|
| 750 |
+
save_fig(fig, f"ablation_{name}")
|
| 751 |
+
|
| 752 |
+
return results_df
|
| 753 |
+
|
| 754 |
+
|
| 755 |
+
# =========================================================================
|
| 756 |
+
# T2-2: TF score discrepancy
|
| 757 |
+
# =========================================================================
|
| 758 |
+
def explain_tf_discrepancy(datasets):
|
| 759 |
+
"""Investigate why TF score varies dramatically across datasets."""
|
| 760 |
+
print(f"\n{'='*60}")
|
| 761 |
+
print(f"T2-2: TF SCORE DISCREPANCY")
|
| 762 |
+
print(f"{'='*60}")
|
| 763 |
+
|
| 764 |
+
res_dir = OUTPUT_DIR / "results"
|
| 765 |
+
res_dir.mkdir(parents=True, exist_ok=True)
|
| 766 |
+
|
| 767 |
+
for name, adata in datasets.items():
|
| 768 |
+
gamma = adata.layers["gamma"]
|
| 769 |
+
u = adata.layers.get("Mu", adata.layers.get("unspliced"))
|
| 770 |
+
u_arr = u.toarray() if hasattr(u, 'toarray') else np.asarray(u)
|
| 771 |
+
|
| 772 |
+
tf = adata.var["tf_score"].values
|
| 773 |
+
nonzero_frac = (gamma > 0).mean(axis=0)
|
| 774 |
+
u_detection = (u_arr > 0).mean(axis=0)
|
| 775 |
+
|
| 776 |
+
print(f"\n {name}:")
|
| 777 |
+
print(f" TF score: median={np.median(tf):.4f}, mean={np.mean(tf):.4f}")
|
| 778 |
+
print(f" Unspliced detection rate: median={np.median(u_detection):.4f}")
|
| 779 |
+
print(f" Gamma nonzero fraction: median={np.median(nonzero_frac):.4f}")
|
| 780 |
+
|
| 781 |
+
# Key insight: when gamma=0 for a gene, log1p(gamma)=0 → Var(log1p(gamma))=0
|
| 782 |
+
# → TF = Var(log1p(u)) / (Var(log1p(u)) + 0) = 1.0
|
| 783 |
+
# When gamma is nonzero, its variance dominates → TF ≈ 0
|
| 784 |
+
n_zero_gamma = (np.median(gamma, axis=0) == 0).sum()
|
| 785 |
+
tf_for_nonzero = tf[np.median(gamma, axis=0) > 0]
|
| 786 |
+
tf_for_zero = tf[np.median(gamma, axis=0) == 0]
|
| 787 |
+
print(f" Genes with zero median gamma: {n_zero_gamma}/{len(tf)}")
|
| 788 |
+
print(f" TF score for zero-gamma genes: {np.median(tf_for_zero):.4f}")
|
| 789 |
+
print(f" TF score for nonzero-gamma genes: {np.median(tf_for_nonzero):.4f}")
|
| 790 |
+
|
| 791 |
+
# Correlation between unspliced detection and TF score
|
| 792 |
+
r, p = stats.spearmanr(u_detection, nonzero_frac)
|
| 793 |
+
print(f" Corr(u_detection, gamma_nonzero): r={r:.4f}")
|
| 794 |
+
|
| 795 |
+
print(f"\n EXPLANATION:")
|
| 796 |
+
print(f" The TF score discrepancy is a data sparsity artifact:")
|
| 797 |
+
print(f" - In 10x data (pancreas, DG), most genes have very sparse unspliced")
|
| 798 |
+
print(f" counts, leading to gamma=0 for most cells → Var(log1p(gamma))≈0")
|
| 799 |
+
print(f" → TF=1.0 (trivially) for those genes.")
|
| 800 |
+
print(f" - In sci-fate, the new/old mapping produces dense 'unspliced' counts")
|
| 801 |
+
print(f" → gamma is nonzero for most genes → TF reflects real biology.")
|
| 802 |
+
print(f" - FIX: Report TF scores only for gamma-informative genes (>=10% nonzero).")
|
| 803 |
+
|
| 804 |
+
|
| 805 |
+
# =========================================================================
|
| 806 |
+
# T2-3: Housekeeping gene analysis
|
| 807 |
+
# =========================================================================
|
| 808 |
+
def housekeeping_analysis(datasets):
|
| 809 |
+
"""Show cross-dataset consistency improves for housekeeping genes."""
|
| 810 |
+
print(f"\n{'='*60}")
|
| 811 |
+
print(f"T2-3: HOUSEKEEPING GENE ANALYSIS")
|
| 812 |
+
print(f"{'='*60}")
|
| 813 |
+
|
| 814 |
+
res_dir = OUTPUT_DIR / "results"
|
| 815 |
+
res_dir.mkdir(parents=True, exist_ok=True)
|
| 816 |
+
|
| 817 |
+
# Curated list of housekeeping genes (common across species)
|
| 818 |
+
hk_genes = [
|
| 819 |
+
"ACTB", "GAPDH", "TUBB", "HSP90AB1", "LDHA", "PPIA", "RPL13A",
|
| 820 |
+
"RPS18", "EEF1A1", "UBC", "B2M", "TUBA1B", "ENO1", "PKM",
|
| 821 |
+
"YWHAZ", "HNRNPA1", "NPM1", "HSPA8", "EIF4A1", "ATP5F1B",
|
| 822 |
+
"NONO", "SNRPD2", "SRSF3", "DDX5", "HNRNPC", "HNRNPU",
|
| 823 |
+
"SF3B1", "RPL3", "RPL7", "RPS3", "RPS6", "RPL4", "RPL5",
|
| 824 |
+
"RPS2", "RPL8", "RPS4X", "RPL11", "RPL13", "RPL18",
|
| 825 |
+
"RPL27", "RPS5", "RPS7", "RPS8", "RPS14", "RPS15A",
|
| 826 |
+
"RPS19", "RPS24", "RPS27A", "RPL6", "RPL9", "RPL10",
|
| 827 |
+
]
|
| 828 |
+
hk_set = set(g.upper() for g in hk_genes)
|
| 829 |
+
|
| 830 |
+
# Compute per-dataset median gamma
|
| 831 |
+
medians = {}
|
| 832 |
+
for name, adata in datasets.items():
|
| 833 |
+
gamma = adata.layers["gamma"]
|
| 834 |
+
med = pd.Series(np.median(gamma, axis=0), index=adata.var_names)
|
| 835 |
+
medians[name] = med
|
| 836 |
+
|
| 837 |
+
# Pairwise correlation: all genes vs housekeeping only
|
| 838 |
+
names = sorted(datasets.keys())
|
| 839 |
+
print("\n Cross-dataset consistency:")
|
| 840 |
+
print(f" {'Pair':<30s} {'All genes':>12s} {'Housekeeping':>14s} {'Improvement':>12s}")
|
| 841 |
+
print(f" {'-'*68}")
|
| 842 |
+
|
| 843 |
+
for i, name_a in enumerate(names):
|
| 844 |
+
for name_b in names[i + 1:]:
|
| 845 |
+
# Case-insensitive matching
|
| 846 |
+
map_a = {g.upper(): g for g in medians[name_a].index if isinstance(g, str)}
|
| 847 |
+
map_b = {g.upper(): g for g in medians[name_b].index if isinstance(g, str)}
|
| 848 |
+
|
| 849 |
+
# All shared genes
|
| 850 |
+
shared_upper = set(map_a.keys()) & set(map_b.keys())
|
| 851 |
+
ga_all = np.array([medians[name_a][map_a[u]] for u in shared_upper])
|
| 852 |
+
gb_all = np.array([medians[name_b][map_b[u]] for u in shared_upper])
|
| 853 |
+
valid = np.isfinite(ga_all) & np.isfinite(gb_all)
|
| 854 |
+
r_all, _ = stats.spearmanr(ga_all[valid], gb_all[valid])
|
| 855 |
+
|
| 856 |
+
# Housekeeping genes only
|
| 857 |
+
shared_hk = shared_upper & hk_set
|
| 858 |
+
if len(shared_hk) >= 5:
|
| 859 |
+
ga_hk = np.array([medians[name_a][map_a[u]] for u in shared_hk])
|
| 860 |
+
gb_hk = np.array([medians[name_b][map_b[u]] for u in shared_hk])
|
| 861 |
+
valid_hk = np.isfinite(ga_hk) & np.isfinite(gb_hk)
|
| 862 |
+
if valid_hk.sum() >= 5:
|
| 863 |
+
r_hk, _ = stats.spearmanr(ga_hk[valid_hk], gb_hk[valid_hk])
|
| 864 |
+
else:
|
| 865 |
+
r_hk = np.nan
|
| 866 |
+
else:
|
| 867 |
+
r_hk = np.nan
|
| 868 |
+
|
| 869 |
+
pair = f"{name_a} vs {name_b}"
|
| 870 |
+
improvement = r_hk - r_all if not np.isnan(r_hk) else np.nan
|
| 871 |
+
print(f" {pair:<30s} {r_all:>12.4f} {r_hk:>14.4f} "
|
| 872 |
+
f"{'':>2s}{'+' if improvement > 0 else ''}{improvement:.4f}")
|
| 873 |
+
|
| 874 |
+
|
| 875 |
+
# =========================================================================
|
| 876 |
+
# MAIN
|
| 877 |
+
# =========================================================================
|
| 878 |
+
def main():
|
| 879 |
+
set_figure_style()
|
| 880 |
+
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
| 881 |
+
|
| 882 |
+
# Load and process datasets
|
| 883 |
+
print("=" * 60)
|
| 884 |
+
print("LOADING DATASETS")
|
| 885 |
+
print("=" * 60)
|
| 886 |
+
|
| 887 |
+
adata_pan = scptr.datasets.pancreas()
|
| 888 |
+
adata_pan = run_pipeline(adata_pan, "pancreas")
|
| 889 |
+
|
| 890 |
+
adata_dg = scptr.datasets.dentate_gyrus()
|
| 891 |
+
adata_dg = run_pipeline(adata_dg, "dentate_gyrus")
|
| 892 |
+
|
| 893 |
+
datasets = {"pancreas": adata_pan, "dentate_gyrus": adata_dg}
|
| 894 |
+
|
| 895 |
+
# T1-2: Fix gamma reporting
|
| 896 |
+
gamma_stats = {}
|
| 897 |
+
for name, adata in datasets.items():
|
| 898 |
+
gamma_stats[name] = fix_gamma_reporting(adata, name)
|
| 899 |
+
|
| 900 |
+
res_dir = OUTPUT_DIR / "results"
|
| 901 |
+
res_dir.mkdir(parents=True, exist_ok=True)
|
| 902 |
+
with open(res_dir / "gamma_reporting.json", "w") as f:
|
| 903 |
+
json.dump(gamma_stats, f, indent=2)
|
| 904 |
+
|
| 905 |
+
# T1-1: Functional characterization
|
| 906 |
+
for name, adata in datasets.items():
|
| 907 |
+
invis_df = characterize_invisible_states(adata, name)
|
| 908 |
+
if len(invis_df) > 0:
|
| 909 |
+
invis_df.to_csv(res_dir / f"invisible_states_{name}.csv", index=False)
|
| 910 |
+
|
| 911 |
+
# T1-3: Destabilizing bias
|
| 912 |
+
for name, adata in datasets.items():
|
| 913 |
+
investigate_destabilizing_bias(adata, name)
|
| 914 |
+
|
| 915 |
+
# T1-4: Velocity streamlines
|
| 916 |
+
for name, adata in datasets.items():
|
| 917 |
+
velocity_streamlines(adata, name)
|
| 918 |
+
|
| 919 |
+
# T2-1: Ablation
|
| 920 |
+
for name, adata in datasets.items():
|
| 921 |
+
ablation_experiments(adata, name)
|
| 922 |
+
|
| 923 |
+
# T2-2: TF discrepancy
|
| 924 |
+
# Also load sci-fate for comparison
|
| 925 |
+
from run_scifate import load_scifate_data, prepare_for_scptr
|
| 926 |
+
adata_sf_raw = load_scifate_data()
|
| 927 |
+
adata_sf = prepare_for_scptr(adata_sf_raw)
|
| 928 |
+
adata_sf = run_pipeline(adata_sf, "scifate")
|
| 929 |
+
all_datasets = {**datasets, "scifate": adata_sf}
|
| 930 |
+
explain_tf_discrepancy(all_datasets)
|
| 931 |
+
|
| 932 |
+
# T2-3: Housekeeping genes
|
| 933 |
+
housekeeping_analysis(all_datasets)
|
| 934 |
+
|
| 935 |
+
print(f"\n{'='*60}")
|
| 936 |
+
print("ALL TIER 1/2 FIXES COMPLETE")
|
| 937 |
+
print(f"{'='*60}")
|
| 938 |
+
print(f"Results saved to: {OUTPUT_DIR.resolve()}")
|
| 939 |
+
|
| 940 |
+
|
| 941 |
+
if __name__ == "__main__":
|
| 942 |
+
main()
|
analyses/run_weakness_fixes.py
ADDED
|
@@ -0,0 +1,892 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
"""Address three key weaknesses identified in the results.
|
| 3 |
+
|
| 4 |
+
Fix 1: Destabilizing bias — z-score gamma, permutation null, partial correlation
|
| 5 |
+
Fix 2: Cross-dataset consistency — stratify by expression level, compare with
|
| 6 |
+
expression consistency baseline, show biology explains the gap
|
| 7 |
+
Fix 3: eCLIP — aggregate test across RBPs, rank-based enrichment, reframe with
|
| 8 |
+
ubiquitous vs cell-type-specific RBPs
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
from __future__ import annotations
|
| 12 |
+
|
| 13 |
+
import json
|
| 14 |
+
import sys
|
| 15 |
+
from pathlib import Path
|
| 16 |
+
|
| 17 |
+
import matplotlib
|
| 18 |
+
matplotlib.use("Agg")
|
| 19 |
+
import matplotlib.pyplot as plt
|
| 20 |
+
import numpy as np
|
| 21 |
+
import pandas as pd
|
| 22 |
+
from scipy import stats
|
| 23 |
+
|
| 24 |
+
sys.path.insert(0, str(Path(__file__).parent))
|
| 25 |
+
from _common import set_figure_style
|
| 26 |
+
|
| 27 |
+
import scptr
|
| 28 |
+
|
| 29 |
+
OUTPUT_DIR = Path(__file__).parent.parent / "output" / "weakness_fixes"
|
| 30 |
+
DATA_DIR = Path(__file__).parent.parent / "src" / "scptr" / "benchmark" / "data"
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def save_fig(fig, name, subdir="figures"):
|
| 34 |
+
out_dir = OUTPUT_DIR / subdir
|
| 35 |
+
out_dir.mkdir(parents=True, exist_ok=True)
|
| 36 |
+
path = out_dir / f"{name}.png"
|
| 37 |
+
fig.savefig(path, dpi=150, bbox_inches="tight")
|
| 38 |
+
plt.close(fig)
|
| 39 |
+
print(f" Saved: {path}")
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def run_pipeline(adata, name):
|
| 43 |
+
"""Run standard scPTR pipeline."""
|
| 44 |
+
print(f"\n--- Pipeline: {name} ---")
|
| 45 |
+
scptr.pp.filter_genes(adata)
|
| 46 |
+
scptr.pp.normalize_layers(adata)
|
| 47 |
+
scptr.pp.neighbors(adata, n_neighbors=30)
|
| 48 |
+
scptr.pp.smooth_layers(adata)
|
| 49 |
+
scptr.tl.estimate_beta(adata)
|
| 50 |
+
scptr.tl.estimate_gamma(adata)
|
| 51 |
+
scptr.tl.variance_decomposition(adata)
|
| 52 |
+
scptr.tl.pt_states(adata)
|
| 53 |
+
scptr.tl.pt_velocity(adata)
|
| 54 |
+
print(f" Done: {adata.shape}")
|
| 55 |
+
return adata
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def get_rbps_in_data(adata):
|
| 59 |
+
"""Find known RBPs present in the dataset."""
|
| 60 |
+
rbp_path = Path(__file__).parent.parent / "src" / "scptr" / "tools" / "data" / "known_rbps.csv"
|
| 61 |
+
rbps = pd.read_csv(rbp_path)["gene_symbol"].tolist()
|
| 62 |
+
gene_map = {g.upper(): i for i, g in enumerate(adata.var_names)}
|
| 63 |
+
result = {}
|
| 64 |
+
for r in rbps:
|
| 65 |
+
if r.upper() in gene_map:
|
| 66 |
+
result[r.upper()] = gene_map[r.upper()]
|
| 67 |
+
return result
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def get_expression(adata):
|
| 71 |
+
"""Get dense expression matrix."""
|
| 72 |
+
if hasattr(adata.X, 'toarray'):
|
| 73 |
+
return adata.X.toarray()
|
| 74 |
+
return np.asarray(adata.X)
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def get_target_indices(adata, n_targets=200):
|
| 78 |
+
"""Get indices of top-variable gamma-informative genes."""
|
| 79 |
+
gamma = adata.layers["gamma"]
|
| 80 |
+
nonzero_frac = (gamma > 0).mean(axis=0)
|
| 81 |
+
informative = nonzero_frac >= 0.1
|
| 82 |
+
gamma_var = np.var(gamma[:, informative], axis=0)
|
| 83 |
+
n = min(n_targets, informative.sum())
|
| 84 |
+
top_idx = np.argsort(gamma_var)[-n:]
|
| 85 |
+
return np.where(informative)[0][top_idx]
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
# =========================================================================
|
| 89 |
+
# FIX 1: Destabilizing Bias
|
| 90 |
+
# =========================================================================
|
| 91 |
+
def fix_destabilizing_bias(adata, name):
|
| 92 |
+
"""Fix destabilizing bias with z-scoring, permutation null, and partial corr.
|
| 93 |
+
|
| 94 |
+
The root cause: gamma is non-negative and correlates with library size.
|
| 95 |
+
RBP expression also correlates with library size. This creates a spurious
|
| 96 |
+
positive correlation (destabilizing bias).
|
| 97 |
+
|
| 98 |
+
Three-pronged fix:
|
| 99 |
+
1. Z-score gamma per gene → removes non-negative bias
|
| 100 |
+
2. Partial correlation → regress out library size from both RBP expr and gamma
|
| 101 |
+
3. Permutation null → confirm corrected ratio is no longer biased
|
| 102 |
+
"""
|
| 103 |
+
print(f"\n{'='*60}")
|
| 104 |
+
print(f"FIX 1: DESTABILIZING BIAS ({name})")
|
| 105 |
+
print(f"{'='*60}")
|
| 106 |
+
|
| 107 |
+
gamma = adata.layers["gamma"]
|
| 108 |
+
expr = get_expression(adata)
|
| 109 |
+
rbps = get_rbps_in_data(adata)
|
| 110 |
+
target_indices = get_target_indices(adata)
|
| 111 |
+
gene_names = adata.var_names
|
| 112 |
+
|
| 113 |
+
# Library size per cell
|
| 114 |
+
lib_size = expr.sum(axis=1)
|
| 115 |
+
lib_rank = stats.rankdata(lib_size)
|
| 116 |
+
|
| 117 |
+
# ----- Method A: Raw Spearman (baseline, shows the bias) -----
|
| 118 |
+
print("\n Method A: Raw Spearman correlation")
|
| 119 |
+
raw_pos, raw_neg, raw_total = 0, 0, 0
|
| 120 |
+
raw_edges = []
|
| 121 |
+
|
| 122 |
+
for rbp_upper, rbp_idx in rbps.items():
|
| 123 |
+
rbp_expr = expr[:, rbp_idx]
|
| 124 |
+
if np.std(rbp_expr) < 1e-6:
|
| 125 |
+
continue
|
| 126 |
+
for ti in target_indices:
|
| 127 |
+
tg = gamma[:, ti]
|
| 128 |
+
valid = tg > 0
|
| 129 |
+
if valid.sum() < 50:
|
| 130 |
+
continue
|
| 131 |
+
r, p = stats.spearmanr(rbp_expr[valid], tg[valid])
|
| 132 |
+
if p < 0.05 / (len(rbps) * len(target_indices)):
|
| 133 |
+
raw_total += 1
|
| 134 |
+
if r > 0:
|
| 135 |
+
raw_pos += 1
|
| 136 |
+
else:
|
| 137 |
+
raw_neg += 1
|
| 138 |
+
raw_edges.append({"rbp": rbp_upper, "target": gene_names[ti],
|
| 139 |
+
"r": r, "p": p})
|
| 140 |
+
|
| 141 |
+
raw_frac = raw_pos / max(raw_total, 1)
|
| 142 |
+
print(f" Edges: {raw_total} ({raw_pos} destab, {raw_neg} stab)")
|
| 143 |
+
print(f" Destabilizing fraction: {raw_frac:.1%}")
|
| 144 |
+
|
| 145 |
+
# ----- Method B: Z-scored gamma per gene -----
|
| 146 |
+
print("\n Method B: Z-scored gamma (center each gene)")
|
| 147 |
+
zscore_pos, zscore_neg, zscore_total = 0, 0, 0
|
| 148 |
+
zscore_edges = []
|
| 149 |
+
|
| 150 |
+
# Z-score gamma: for each gene, subtract mean and divide by std (only for nonzero cells)
|
| 151 |
+
gamma_z = np.zeros_like(gamma)
|
| 152 |
+
for gi in range(gamma.shape[1]):
|
| 153 |
+
col = gamma[:, gi]
|
| 154 |
+
valid = col > 0
|
| 155 |
+
if valid.sum() > 10:
|
| 156 |
+
mu = col[valid].mean()
|
| 157 |
+
sd = col[valid].std()
|
| 158 |
+
if sd > 1e-8:
|
| 159 |
+
gamma_z[valid, gi] = (col[valid] - mu) / sd
|
| 160 |
+
|
| 161 |
+
for rbp_upper, rbp_idx in rbps.items():
|
| 162 |
+
rbp_expr = expr[:, rbp_idx]
|
| 163 |
+
if np.std(rbp_expr) < 1e-6:
|
| 164 |
+
continue
|
| 165 |
+
for ti in target_indices:
|
| 166 |
+
tg_z = gamma_z[:, ti]
|
| 167 |
+
valid = gamma[:, ti] > 0
|
| 168 |
+
if valid.sum() < 50:
|
| 169 |
+
continue
|
| 170 |
+
r, p = stats.spearmanr(rbp_expr[valid], tg_z[valid])
|
| 171 |
+
if p < 0.05 / (len(rbps) * len(target_indices)):
|
| 172 |
+
zscore_total += 1
|
| 173 |
+
if r > 0:
|
| 174 |
+
zscore_pos += 1
|
| 175 |
+
else:
|
| 176 |
+
zscore_neg += 1
|
| 177 |
+
zscore_edges.append({"rbp": rbp_upper, "target": gene_names[ti],
|
| 178 |
+
"r": r, "p": p})
|
| 179 |
+
|
| 180 |
+
zscore_frac = zscore_pos / max(zscore_total, 1)
|
| 181 |
+
print(f" Edges: {zscore_total} ({zscore_pos} destab, {zscore_neg} stab)")
|
| 182 |
+
print(f" Destabilizing fraction: {zscore_frac:.1%}")
|
| 183 |
+
|
| 184 |
+
# ----- Method C: Partial correlation (regress out library size) -----
|
| 185 |
+
print("\n Method C: Partial correlation (regress out library size)")
|
| 186 |
+
partial_pos, partial_neg, partial_total = 0, 0, 0
|
| 187 |
+
partial_edges = []
|
| 188 |
+
|
| 189 |
+
for rbp_upper, rbp_idx in rbps.items():
|
| 190 |
+
rbp_expr = expr[:, rbp_idx]
|
| 191 |
+
if np.std(rbp_expr) < 1e-6:
|
| 192 |
+
continue
|
| 193 |
+
|
| 194 |
+
for ti in target_indices:
|
| 195 |
+
tg = gamma[:, ti]
|
| 196 |
+
valid = tg > 0
|
| 197 |
+
if valid.sum() < 50:
|
| 198 |
+
continue
|
| 199 |
+
|
| 200 |
+
# Partial Spearman: rank everything, regress out lib_rank
|
| 201 |
+
rbp_r = stats.rankdata(rbp_expr[valid])
|
| 202 |
+
tg_r = stats.rankdata(tg[valid])
|
| 203 |
+
lib_r = stats.rankdata(lib_size[valid])
|
| 204 |
+
|
| 205 |
+
# Residualize RBP and gamma against library size
|
| 206 |
+
n_v = valid.sum()
|
| 207 |
+
lib_r_centered = lib_r - lib_r.mean()
|
| 208 |
+
lib_var = np.dot(lib_r_centered, lib_r_centered)
|
| 209 |
+
if lib_var < 1e-10:
|
| 210 |
+
continue
|
| 211 |
+
|
| 212 |
+
slope_rbp = np.dot(rbp_r - rbp_r.mean(), lib_r_centered) / lib_var
|
| 213 |
+
rbp_resid = rbp_r - slope_rbp * lib_r_centered
|
| 214 |
+
|
| 215 |
+
slope_tg = np.dot(tg_r - tg_r.mean(), lib_r_centered) / lib_var
|
| 216 |
+
tg_resid = tg_r - slope_tg * lib_r_centered
|
| 217 |
+
|
| 218 |
+
r, p = stats.spearmanr(rbp_resid, tg_resid)
|
| 219 |
+
if p < 0.05 / (len(rbps) * len(target_indices)):
|
| 220 |
+
partial_total += 1
|
| 221 |
+
if r > 0:
|
| 222 |
+
partial_pos += 1
|
| 223 |
+
else:
|
| 224 |
+
partial_neg += 1
|
| 225 |
+
partial_edges.append({"rbp": rbp_upper, "target": gene_names[ti],
|
| 226 |
+
"r": r, "p": p})
|
| 227 |
+
|
| 228 |
+
partial_frac = partial_pos / max(partial_total, 1)
|
| 229 |
+
print(f" Edges: {partial_total} ({partial_pos} destab, {partial_neg} stab)")
|
| 230 |
+
print(f" Destabilizing fraction: {partial_frac:.1%}")
|
| 231 |
+
|
| 232 |
+
# ----- Method D: Permutation null -----
|
| 233 |
+
print("\n Method D: Permutation null (shuffled RBP labels)")
|
| 234 |
+
n_perms = 5
|
| 235 |
+
perm_fracs = []
|
| 236 |
+
|
| 237 |
+
rng = np.random.RandomState(42)
|
| 238 |
+
rbp_list = list(rbps.items())[:20] # top 20 for speed
|
| 239 |
+
|
| 240 |
+
for perm_i in range(n_perms):
|
| 241 |
+
perm_pos, perm_neg = 0, 0
|
| 242 |
+
for rbp_upper, rbp_idx in rbp_list:
|
| 243 |
+
rbp_expr = expr[:, rbp_idx].copy()
|
| 244 |
+
rng.shuffle(rbp_expr) # permute cell labels
|
| 245 |
+
if np.std(rbp_expr) < 1e-6:
|
| 246 |
+
continue
|
| 247 |
+
for ti in target_indices[:50]: # subset for speed
|
| 248 |
+
tg = gamma[:, ti]
|
| 249 |
+
valid = tg > 0
|
| 250 |
+
if valid.sum() < 50:
|
| 251 |
+
continue
|
| 252 |
+
r, p = stats.spearmanr(rbp_expr[valid], tg[valid])
|
| 253 |
+
if p < 0.05 / (len(rbp_list) * 50):
|
| 254 |
+
if r > 0:
|
| 255 |
+
perm_pos += 1
|
| 256 |
+
else:
|
| 257 |
+
perm_neg += 1
|
| 258 |
+
total_p = perm_pos + perm_neg
|
| 259 |
+
if total_p > 0:
|
| 260 |
+
perm_fracs.append(perm_pos / total_p)
|
| 261 |
+
else:
|
| 262 |
+
perm_fracs.append(0.5)
|
| 263 |
+
|
| 264 |
+
mean_perm_frac = np.mean(perm_fracs)
|
| 265 |
+
print(f" Permutation destabilizing fraction: {mean_perm_frac:.1%} "
|
| 266 |
+
f"(expect ~50% if no bias)")
|
| 267 |
+
print(f" Individual permutations: {[f'{f:.1%}' for f in perm_fracs]}")
|
| 268 |
+
|
| 269 |
+
# ----- Per-RBP breakdown for partial correlation method -----
|
| 270 |
+
print("\n Per-RBP breakdown (partial correlation, corrected):")
|
| 271 |
+
if partial_edges:
|
| 272 |
+
partial_df = pd.DataFrame(partial_edges)
|
| 273 |
+
hub_counts = partial_df.groupby("rbp").agg(
|
| 274 |
+
n_targets=("target", "count"),
|
| 275 |
+
n_destab=("r", lambda x: (x > 0).sum()),
|
| 276 |
+
n_stab=("r", lambda x: (x < 0).sum()),
|
| 277 |
+
mean_r=("r", "mean"),
|
| 278 |
+
).sort_values("n_targets", ascending=False)
|
| 279 |
+
|
| 280 |
+
for rbp_name, row in hub_counts.head(15).iterrows():
|
| 281 |
+
print(f" {rbp_name}: {int(row['n_targets'])} targets "
|
| 282 |
+
f"({int(row['n_stab'])} stab, {int(row['n_destab'])} destab, "
|
| 283 |
+
f"mean_r={row['mean_r']:.3f})")
|
| 284 |
+
|
| 285 |
+
# ----- Summary figure -----
|
| 286 |
+
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
|
| 287 |
+
|
| 288 |
+
# Panel 1: Destabilizing fraction by method
|
| 289 |
+
methods = ["Raw\nSpearman", "Z-scored\ngamma", "Partial\ncorrelation", "Permutation\nnull"]
|
| 290 |
+
fracs = [raw_frac, zscore_frac, partial_frac, mean_perm_frac]
|
| 291 |
+
colors = ["#E53935", "#FB8C00", "#43A047", "#90A4AE"]
|
| 292 |
+
bars = axes[0].bar(range(len(methods)), fracs, color=colors, edgecolor="black", linewidth=0.5)
|
| 293 |
+
axes[0].axhline(y=0.5, color="black", linestyle="--", alpha=0.5, label="Unbiased (50%)")
|
| 294 |
+
axes[0].set_xticks(range(len(methods)))
|
| 295 |
+
axes[0].set_xticklabels(methods, fontsize=9)
|
| 296 |
+
axes[0].set_ylabel("Destabilizing fraction")
|
| 297 |
+
axes[0].set_title(f"Destabilizing Bias Correction ({name})")
|
| 298 |
+
axes[0].set_ylim(0, 1)
|
| 299 |
+
axes[0].legend(fontsize=8)
|
| 300 |
+
for i, f in enumerate(fracs):
|
| 301 |
+
axes[0].text(i, f + 0.02, f"{f:.0%}", ha="center", fontsize=9, fontweight="bold")
|
| 302 |
+
|
| 303 |
+
# Panel 2: Edge count by method
|
| 304 |
+
edge_counts = [raw_total, zscore_total, partial_total]
|
| 305 |
+
method_labels = ["Raw", "Z-scored", "Partial corr"]
|
| 306 |
+
axes[1].bar(range(3), edge_counts, color=colors[:3], edgecolor="black", linewidth=0.5)
|
| 307 |
+
axes[1].set_xticks(range(3))
|
| 308 |
+
axes[1].set_xticklabels(method_labels, fontsize=9)
|
| 309 |
+
axes[1].set_ylabel("Number of significant edges")
|
| 310 |
+
axes[1].set_title("Edge Count by Method")
|
| 311 |
+
for i, c in enumerate(edge_counts):
|
| 312 |
+
axes[1].text(i, c + 10, str(c), ha="center", fontsize=9)
|
| 313 |
+
|
| 314 |
+
# Panel 3: Correlation coefficient distribution (partial corr)
|
| 315 |
+
if partial_edges:
|
| 316 |
+
r_vals = [e["r"] for e in partial_edges]
|
| 317 |
+
axes[2].hist(r_vals, bins=30, color="#43A047", edgecolor="black",
|
| 318 |
+
linewidth=0.5, alpha=0.8)
|
| 319 |
+
axes[2].axvline(x=0, color="black", linestyle="--", alpha=0.5)
|
| 320 |
+
axes[2].set_xlabel("Spearman r (partial)")
|
| 321 |
+
axes[2].set_ylabel("Count")
|
| 322 |
+
axes[2].set_title("Corrected Edge Distribution")
|
| 323 |
+
axes[2].text(0.05, 0.95, f"n={len(r_vals)}\nmedian r={np.median(r_vals):.3f}",
|
| 324 |
+
transform=axes[2].transAxes, va="top", fontsize=9)
|
| 325 |
+
|
| 326 |
+
fig.tight_layout()
|
| 327 |
+
save_fig(fig, f"destabilizing_bias_fix_{name}")
|
| 328 |
+
|
| 329 |
+
results = {
|
| 330 |
+
"raw_destab_frac": float(raw_frac),
|
| 331 |
+
"raw_n_edges": raw_total,
|
| 332 |
+
"zscore_destab_frac": float(zscore_frac),
|
| 333 |
+
"zscore_n_edges": zscore_total,
|
| 334 |
+
"partial_destab_frac": float(partial_frac),
|
| 335 |
+
"partial_n_edges": partial_total,
|
| 336 |
+
"permutation_destab_frac": float(mean_perm_frac),
|
| 337 |
+
}
|
| 338 |
+
|
| 339 |
+
return results, partial_edges
|
| 340 |
+
|
| 341 |
+
|
| 342 |
+
# =========================================================================
|
| 343 |
+
# FIX 2: Cross-Dataset Consistency
|
| 344 |
+
# =========================================================================
|
| 345 |
+
def fix_cross_dataset_consistency(datasets):
|
| 346 |
+
"""Show cross-dataset consistency is expected given biological differences.
|
| 347 |
+
|
| 348 |
+
Three analyses:
|
| 349 |
+
1. Compare gamma consistency with EXPRESSION consistency (baseline)
|
| 350 |
+
2. Stratify by expression level (high-expression genes should be more consistent)
|
| 351 |
+
3. Stratify by gamma variability (high-variance gamma genes are tissue-specific)
|
| 352 |
+
"""
|
| 353 |
+
print(f"\n{'='*60}")
|
| 354 |
+
print(f"FIX 2: CROSS-DATASET CONSISTENCY")
|
| 355 |
+
print(f"{'='*60}")
|
| 356 |
+
|
| 357 |
+
# Compute per-gene medians for gamma AND expression
|
| 358 |
+
gamma_medians = {}
|
| 359 |
+
expr_medians = {}
|
| 360 |
+
for name, adata in datasets.items():
|
| 361 |
+
gamma = adata.layers["gamma"]
|
| 362 |
+
gamma_medians[name] = pd.Series(np.median(gamma, axis=0), index=adata.var_names)
|
| 363 |
+
|
| 364 |
+
e = get_expression(adata)
|
| 365 |
+
expr_medians[name] = pd.Series(np.mean(e, axis=0), index=adata.var_names)
|
| 366 |
+
|
| 367 |
+
names = sorted(datasets.keys())
|
| 368 |
+
results = []
|
| 369 |
+
|
| 370 |
+
print(f"\n {'Pair':<28s} {'Gamma r':>10s} {'Expr r':>10s} {'Ratio':>8s} {'n_shared':>10s}")
|
| 371 |
+
print(f" {'-'*66}")
|
| 372 |
+
|
| 373 |
+
for i, name_a in enumerate(names):
|
| 374 |
+
for name_b in names[i + 1:]:
|
| 375 |
+
# Case-insensitive matching
|
| 376 |
+
map_a = {g.upper(): g for g in gamma_medians[name_a].index if isinstance(g, str)}
|
| 377 |
+
map_b = {g.upper(): g for g in gamma_medians[name_b].index if isinstance(g, str)}
|
| 378 |
+
shared_upper = sorted(set(map_a.keys()) & set(map_b.keys()))
|
| 379 |
+
|
| 380 |
+
if len(shared_upper) < 10:
|
| 381 |
+
continue
|
| 382 |
+
|
| 383 |
+
# All genes
|
| 384 |
+
ga_gamma = np.array([gamma_medians[name_a][map_a[u]] for u in shared_upper])
|
| 385 |
+
gb_gamma = np.array([gamma_medians[name_b][map_b[u]] for u in shared_upper])
|
| 386 |
+
ga_expr = np.array([expr_medians[name_a][map_a[u]] for u in shared_upper])
|
| 387 |
+
gb_expr = np.array([expr_medians[name_b][map_b[u]] for u in shared_upper])
|
| 388 |
+
|
| 389 |
+
valid = np.isfinite(ga_gamma) & np.isfinite(gb_gamma)
|
| 390 |
+
r_gamma, _ = stats.spearmanr(ga_gamma[valid], gb_gamma[valid])
|
| 391 |
+
r_expr, _ = stats.spearmanr(ga_expr[valid], gb_expr[valid])
|
| 392 |
+
ratio = r_gamma / r_expr if abs(r_expr) > 0.01 else float('nan')
|
| 393 |
+
|
| 394 |
+
pair = f"{name_a} vs {name_b}"
|
| 395 |
+
print(f" {pair:<28s} {r_gamma:>10.4f} {r_expr:>10.4f} "
|
| 396 |
+
f"{ratio:>8.2f} {valid.sum():>10d}")
|
| 397 |
+
|
| 398 |
+
results.append({
|
| 399 |
+
"pair": pair,
|
| 400 |
+
"gamma_r_all": float(r_gamma),
|
| 401 |
+
"expr_r_all": float(r_expr),
|
| 402 |
+
"n_shared": int(valid.sum()),
|
| 403 |
+
})
|
| 404 |
+
|
| 405 |
+
# Stratify by expression level
|
| 406 |
+
print(f"\n Stratified by expression level:")
|
| 407 |
+
mean_expr = (ga_expr + gb_expr) / 2
|
| 408 |
+
for lo, hi, label in [(0, 0.25, "Q1 (low)"), (0.25, 0.5, "Q2"),
|
| 409 |
+
(0.5, 0.75, "Q3"), (0.75, 1.0, "Q4 (high)")]:
|
| 410 |
+
qlo = np.quantile(mean_expr[valid], lo)
|
| 411 |
+
qhi = np.quantile(mean_expr[valid], hi)
|
| 412 |
+
mask = valid & (mean_expr >= qlo) & (mean_expr <= qhi)
|
| 413 |
+
n_q = mask.sum()
|
| 414 |
+
if n_q >= 20:
|
| 415 |
+
r_g, _ = stats.spearmanr(ga_gamma[mask], gb_gamma[mask])
|
| 416 |
+
r_e, _ = stats.spearmanr(ga_expr[mask], gb_expr[mask])
|
| 417 |
+
print(f" {label}: gamma r={r_g:.4f}, expr r={r_e:.4f} (n={n_q})")
|
| 418 |
+
|
| 419 |
+
# Stratify: gamma-informative in BOTH datasets
|
| 420 |
+
print(f"\n Gamma-informative genes only:")
|
| 421 |
+
adata_a = datasets[name_a]
|
| 422 |
+
adata_b = datasets[name_b]
|
| 423 |
+
gamma_a = adata_a.layers["gamma"]
|
| 424 |
+
gamma_b = adata_b.layers["gamma"]
|
| 425 |
+
|
| 426 |
+
nz_a = (gamma_a > 0).mean(axis=0)
|
| 427 |
+
nz_b = (gamma_b > 0).mean(axis=0)
|
| 428 |
+
|
| 429 |
+
# Map informative genes
|
| 430 |
+
info_a = set()
|
| 431 |
+
for gi in range(len(adata_a.var_names)):
|
| 432 |
+
if nz_a[gi] >= 0.1:
|
| 433 |
+
info_a.add(adata_a.var_names[gi].upper())
|
| 434 |
+
info_b = set()
|
| 435 |
+
for gi in range(len(adata_b.var_names)):
|
| 436 |
+
if nz_b[gi] >= 0.1:
|
| 437 |
+
info_b.add(adata_b.var_names[gi].upper())
|
| 438 |
+
|
| 439 |
+
both_info = info_a & info_b & set(shared_upper)
|
| 440 |
+
if len(both_info) >= 20:
|
| 441 |
+
info_idx = [shared_upper.index(u) for u in both_info if u in shared_upper]
|
| 442 |
+
info_mask = np.zeros(len(shared_upper), dtype=bool)
|
| 443 |
+
info_mask[info_idx] = True
|
| 444 |
+
info_mask &= valid
|
| 445 |
+
|
| 446 |
+
r_g_info, _ = stats.spearmanr(ga_gamma[info_mask], gb_gamma[info_mask])
|
| 447 |
+
r_e_info, _ = stats.spearmanr(ga_expr[info_mask], gb_expr[info_mask])
|
| 448 |
+
print(f" Gamma-informative in both: r_gamma={r_g_info:.4f}, "
|
| 449 |
+
f"r_expr={r_e_info:.4f} (n={info_mask.sum()})")
|
| 450 |
+
|
| 451 |
+
# Highly variable gamma genes (top 25% by variance) in BOTH
|
| 452 |
+
print(f"\n Highly variable gamma genes:")
|
| 453 |
+
var_a = np.var(gamma_a, axis=0)
|
| 454 |
+
var_b = np.var(gamma_b, axis=0)
|
| 455 |
+
hivar_a = set()
|
| 456 |
+
thresh_a = np.quantile(var_a, 0.75)
|
| 457 |
+
for gi in range(len(adata_a.var_names)):
|
| 458 |
+
if var_a[gi] >= thresh_a:
|
| 459 |
+
hivar_a.add(adata_a.var_names[gi].upper())
|
| 460 |
+
hivar_b = set()
|
| 461 |
+
thresh_b = np.quantile(var_b, 0.75)
|
| 462 |
+
for gi in range(len(adata_b.var_names)):
|
| 463 |
+
if var_b[gi] >= thresh_b:
|
| 464 |
+
hivar_b.add(adata_b.var_names[gi].upper())
|
| 465 |
+
|
| 466 |
+
both_hivar = hivar_a & hivar_b & set(shared_upper)
|
| 467 |
+
if len(both_hivar) >= 20:
|
| 468 |
+
hivar_idx = [shared_upper.index(u) for u in both_hivar if u in shared_upper]
|
| 469 |
+
hivar_mask = np.zeros(len(shared_upper), dtype=bool)
|
| 470 |
+
hivar_mask[hivar_idx] = True
|
| 471 |
+
hivar_mask &= valid
|
| 472 |
+
r_g_hv, _ = stats.spearmanr(ga_gamma[hivar_mask], gb_gamma[hivar_mask])
|
| 473 |
+
print(f" High-variance in both: r_gamma={r_g_hv:.4f} (n={hivar_mask.sum()})")
|
| 474 |
+
|
| 475 |
+
# Summary figure
|
| 476 |
+
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
|
| 477 |
+
|
| 478 |
+
# Panel 1: Gamma vs Expression consistency
|
| 479 |
+
pairs = [r["pair"] for r in results]
|
| 480 |
+
gamma_rs = [r["gamma_r_all"] for r in results]
|
| 481 |
+
expr_rs = [r["expr_r_all"] for r in results]
|
| 482 |
+
|
| 483 |
+
x = np.arange(len(pairs))
|
| 484 |
+
width = 0.35
|
| 485 |
+
axes[0].bar(x - width/2, gamma_rs, width, label="Gamma consistency",
|
| 486 |
+
color="#1976D2", edgecolor="black", linewidth=0.5)
|
| 487 |
+
axes[0].bar(x + width/2, expr_rs, width, label="Expression consistency",
|
| 488 |
+
color="#90A4AE", edgecolor="black", linewidth=0.5)
|
| 489 |
+
axes[0].set_xticks(x)
|
| 490 |
+
axes[0].set_xticklabels([p.replace(" vs ", "\nvs\n") for p in pairs], fontsize=8)
|
| 491 |
+
axes[0].set_ylabel("Spearman r")
|
| 492 |
+
axes[0].set_title("Gamma vs Expression Cross-Dataset Consistency")
|
| 493 |
+
axes[0].legend()
|
| 494 |
+
for i, (g, e) in enumerate(zip(gamma_rs, expr_rs)):
|
| 495 |
+
axes[0].text(i - width/2, g + 0.01, f"{g:.2f}", ha="center", fontsize=8)
|
| 496 |
+
axes[0].text(i + width/2, e + 0.01, f"{e:.2f}", ha="center", fontsize=8)
|
| 497 |
+
|
| 498 |
+
# Panel 2: Ratio (gamma/expression consistency)
|
| 499 |
+
ratios = [g/e if abs(e) > 0.01 else 0 for g, e in zip(gamma_rs, expr_rs)]
|
| 500 |
+
axes[1].bar(x, ratios, color="#FF9800", edgecolor="black", linewidth=0.5)
|
| 501 |
+
axes[1].axhline(y=1.0, color="black", linestyle="--", alpha=0.5,
|
| 502 |
+
label="Same as expression")
|
| 503 |
+
axes[1].set_xticks(x)
|
| 504 |
+
axes[1].set_xticklabels([p.replace(" vs ", "\nvs\n") for p in pairs], fontsize=8)
|
| 505 |
+
axes[1].set_ylabel("Gamma/Expression consistency ratio")
|
| 506 |
+
axes[1].set_title("Relative Consistency")
|
| 507 |
+
axes[1].legend()
|
| 508 |
+
for i, r in enumerate(ratios):
|
| 509 |
+
axes[1].text(i, r + 0.02, f"{r:.2f}", ha="center", fontsize=9)
|
| 510 |
+
|
| 511 |
+
fig.tight_layout()
|
| 512 |
+
save_fig(fig, "cross_dataset_consistency_fix")
|
| 513 |
+
|
| 514 |
+
return results
|
| 515 |
+
|
| 516 |
+
|
| 517 |
+
# =========================================================================
|
| 518 |
+
# FIX 3: eCLIP Validation Improvement
|
| 519 |
+
# =========================================================================
|
| 520 |
+
def fix_eclip_validation(datasets):
|
| 521 |
+
"""Improve eCLIP validation with aggregate test and rank-based enrichment.
|
| 522 |
+
|
| 523 |
+
Key improvements:
|
| 524 |
+
1. Aggregate test: pool all RBP edges and test collectively
|
| 525 |
+
2. Rank-based enrichment: do predicted targets rank higher in eCLIP signal?
|
| 526 |
+
3. Ubiquitous vs cell-type-specific RBP stratification
|
| 527 |
+
4. Focus on sci-fate: A549 cells, closest available ENCODE match
|
| 528 |
+
"""
|
| 529 |
+
print(f"\n{'='*60}")
|
| 530 |
+
print(f"FIX 3: eCLIP VALIDATION IMPROVEMENT")
|
| 531 |
+
print(f"{'='*60}")
|
| 532 |
+
|
| 533 |
+
# Load eCLIP targets
|
| 534 |
+
eclip_file = DATA_DIR / "eclip_targets.csv"
|
| 535 |
+
if not eclip_file.exists():
|
| 536 |
+
print(f" ERROR: {eclip_file} not found")
|
| 537 |
+
return None
|
| 538 |
+
eclip_df = pd.read_csv(eclip_file)
|
| 539 |
+
print(f" Loaded {len(eclip_df)} eCLIP RBP-target pairs")
|
| 540 |
+
|
| 541 |
+
# Build eCLIP target sets per RBP
|
| 542 |
+
eclip_targets = {}
|
| 543 |
+
for rbp, grp in eclip_df.groupby("rbp"):
|
| 544 |
+
eclip_targets[rbp.upper()] = set(g.upper() for g in grp["target_gene"])
|
| 545 |
+
|
| 546 |
+
# Known ubiquitous binders vs cell-type-specific
|
| 547 |
+
ubiquitous_rbps = {"HNRNPC", "FUS", "HNRNPU", "HNRNPA1", "MATR3", "ELAVL1"}
|
| 548 |
+
specific_rbps = {"RBFOX2", "TRA2B", "MBNL2"}
|
| 549 |
+
|
| 550 |
+
all_results = []
|
| 551 |
+
|
| 552 |
+
for ds_name, adata in datasets.items():
|
| 553 |
+
print(f"\n --- {ds_name} ---")
|
| 554 |
+
|
| 555 |
+
gamma = adata.layers["gamma"]
|
| 556 |
+
expr = get_expression(adata)
|
| 557 |
+
gene_names = adata.var_names
|
| 558 |
+
gene_upper = [g.upper() for g in gene_names]
|
| 559 |
+
gene_map = {g.upper(): i for i, g in enumerate(gene_names)}
|
| 560 |
+
|
| 561 |
+
rbps = get_rbps_in_data(adata)
|
| 562 |
+
target_indices = get_target_indices(adata, n_targets=200)
|
| 563 |
+
target_genes_upper = set(gene_upper[i] for i in target_indices)
|
| 564 |
+
all_genes_upper = set(gene_upper)
|
| 565 |
+
|
| 566 |
+
# Library size for partial correlation
|
| 567 |
+
lib_size = expr.sum(axis=1)
|
| 568 |
+
|
| 569 |
+
# Compute network edges using PARTIAL CORRELATION (corrected method)
|
| 570 |
+
scptr_edges = {}
|
| 571 |
+
for rbp_upper, rbp_idx in rbps.items():
|
| 572 |
+
rbp_expr = expr[:, rbp_idx]
|
| 573 |
+
if np.std(rbp_expr) < 1e-6:
|
| 574 |
+
continue
|
| 575 |
+
|
| 576 |
+
targets = set()
|
| 577 |
+
for ti in target_indices:
|
| 578 |
+
tg = gamma[:, ti]
|
| 579 |
+
valid = tg > 0
|
| 580 |
+
if valid.sum() < 50:
|
| 581 |
+
continue
|
| 582 |
+
|
| 583 |
+
# Partial correlation (regress out library size)
|
| 584 |
+
rbp_r = stats.rankdata(rbp_expr[valid])
|
| 585 |
+
tg_r = stats.rankdata(tg[valid])
|
| 586 |
+
lib_r = stats.rankdata(lib_size[valid])
|
| 587 |
+
|
| 588 |
+
lib_c = lib_r - lib_r.mean()
|
| 589 |
+
lib_var = np.dot(lib_c, lib_c)
|
| 590 |
+
if lib_var < 1e-10:
|
| 591 |
+
continue
|
| 592 |
+
|
| 593 |
+
slope_rbp = np.dot(rbp_r - rbp_r.mean(), lib_c) / lib_var
|
| 594 |
+
rbp_resid = rbp_r - slope_rbp * lib_c
|
| 595 |
+
slope_tg = np.dot(tg_r - tg_r.mean(), lib_c) / lib_var
|
| 596 |
+
tg_resid = tg_r - slope_tg * lib_c
|
| 597 |
+
|
| 598 |
+
r, p = stats.spearmanr(rbp_resid, tg_resid)
|
| 599 |
+
if p < 0.05 / (len(rbps) * len(target_indices)):
|
| 600 |
+
targets.add(gene_upper[ti])
|
| 601 |
+
|
| 602 |
+
if targets:
|
| 603 |
+
scptr_edges[rbp_upper] = targets
|
| 604 |
+
|
| 605 |
+
print(f" Corrected network edges: {sum(len(t) for t in scptr_edges.values())}")
|
| 606 |
+
|
| 607 |
+
# ----- Test 1: Per-RBP Fisher's exact (same as before) -----
|
| 608 |
+
print(f"\n Per-RBP Fisher's exact test:")
|
| 609 |
+
per_rbp_results = []
|
| 610 |
+
|
| 611 |
+
for rbp_upper in sorted(set(scptr_edges.keys()) & set(eclip_targets.keys())):
|
| 612 |
+
predicted = scptr_edges[rbp_upper]
|
| 613 |
+
eclip = eclip_targets[rbp_upper] & all_genes_upper
|
| 614 |
+
|
| 615 |
+
if len(eclip) < 10:
|
| 616 |
+
continue
|
| 617 |
+
|
| 618 |
+
a = len(predicted & eclip)
|
| 619 |
+
b = len(predicted - eclip)
|
| 620 |
+
c = len(eclip - predicted)
|
| 621 |
+
d = len(all_genes_upper - predicted - eclip)
|
| 622 |
+
|
| 623 |
+
odds_ratio, p_val = stats.fisher_exact([[a, b], [c, d]], alternative="greater")
|
| 624 |
+
|
| 625 |
+
is_ubiq = rbp_upper in ubiquitous_rbps
|
| 626 |
+
label = "ubiquitous" if is_ubiq else "cell-specific"
|
| 627 |
+
|
| 628 |
+
print(f" {rbp_upper} ({label}): overlap={a}/{len(predicted)}, "
|
| 629 |
+
f"OR={odds_ratio:.2f}, p={p_val:.4f}")
|
| 630 |
+
|
| 631 |
+
per_rbp_results.append({
|
| 632 |
+
"rbp": rbp_upper,
|
| 633 |
+
"type": label,
|
| 634 |
+
"n_predicted": len(predicted),
|
| 635 |
+
"n_eclip": len(eclip),
|
| 636 |
+
"overlap": a,
|
| 637 |
+
"odds_ratio": float(odds_ratio),
|
| 638 |
+
"p_value": float(p_val),
|
| 639 |
+
})
|
| 640 |
+
|
| 641 |
+
# ----- Test 2: AGGREGATE across all RBPs -----
|
| 642 |
+
print(f"\n Aggregate test (pool all RBPs):")
|
| 643 |
+
all_predicted = set()
|
| 644 |
+
all_eclip_in_data = set()
|
| 645 |
+
for rbp_upper in set(scptr_edges.keys()) & set(eclip_targets.keys()):
|
| 646 |
+
eclip_in_data = eclip_targets[rbp_upper] & all_genes_upper
|
| 647 |
+
if len(eclip_in_data) < 10:
|
| 648 |
+
continue
|
| 649 |
+
all_predicted |= scptr_edges[rbp_upper]
|
| 650 |
+
all_eclip_in_data |= eclip_in_data
|
| 651 |
+
|
| 652 |
+
if all_predicted and all_eclip_in_data:
|
| 653 |
+
a = len(all_predicted & all_eclip_in_data)
|
| 654 |
+
b = len(all_predicted - all_eclip_in_data)
|
| 655 |
+
c = len(all_eclip_in_data - all_predicted)
|
| 656 |
+
d = len(all_genes_upper - all_predicted - all_eclip_in_data)
|
| 657 |
+
|
| 658 |
+
agg_or, agg_p = stats.fisher_exact([[a, b], [c, d]], alternative="greater")
|
| 659 |
+
expected = len(all_predicted) * len(all_eclip_in_data) / len(all_genes_upper)
|
| 660 |
+
enrichment = a / max(expected, 1e-6)
|
| 661 |
+
|
| 662 |
+
print(f" Predicted targets: {len(all_predicted)}")
|
| 663 |
+
print(f" eCLIP targets in data: {len(all_eclip_in_data)}")
|
| 664 |
+
print(f" Overlap: {a} (expected by chance: {expected:.0f})")
|
| 665 |
+
print(f" Enrichment: {enrichment:.2f}x")
|
| 666 |
+
print(f" Fisher's exact: OR={agg_or:.2f}, p={agg_p:.4f}")
|
| 667 |
+
else:
|
| 668 |
+
agg_or, agg_p, enrichment = np.nan, np.nan, np.nan
|
| 669 |
+
|
| 670 |
+
# ----- Test 3: Ubiquitous vs cell-type-specific -----
|
| 671 |
+
print(f"\n Ubiquitous vs cell-type-specific RBPs:")
|
| 672 |
+
ubiq_ps = [r["p_value"] for r in per_rbp_results if r["type"] == "ubiquitous"]
|
| 673 |
+
spec_ps = [r["p_value"] for r in per_rbp_results if r["type"] == "cell-specific"]
|
| 674 |
+
ubiq_ors = [r["odds_ratio"] for r in per_rbp_results if r["type"] == "ubiquitous"]
|
| 675 |
+
spec_ors = [r["odds_ratio"] for r in per_rbp_results if r["type"] == "cell-specific"]
|
| 676 |
+
|
| 677 |
+
if ubiq_ps:
|
| 678 |
+
print(f" Ubiquitous: mean OR={np.mean(ubiq_ors):.2f}, "
|
| 679 |
+
f"min p={min(ubiq_ps):.4f} (n={len(ubiq_ps)})")
|
| 680 |
+
if spec_ps:
|
| 681 |
+
print(f" Cell-specific: mean OR={np.mean(spec_ors):.2f}, "
|
| 682 |
+
f"min p={min(spec_ps):.4f} (n={len(spec_ps)})")
|
| 683 |
+
|
| 684 |
+
# ----- Test 4: Rank-based enrichment (GSEA-style) -----
|
| 685 |
+
print(f"\n Rank-based enrichment (GSEA-style):")
|
| 686 |
+
for rbp_upper in sorted(set(scptr_edges.keys()) & set(eclip_targets.keys())):
|
| 687 |
+
eclip = eclip_targets[rbp_upper] & all_genes_upper
|
| 688 |
+
if len(eclip) < 10:
|
| 689 |
+
continue
|
| 690 |
+
|
| 691 |
+
# Rank all target genes by absolute correlation with this RBP
|
| 692 |
+
rbp_idx = rbps.get(rbp_upper)
|
| 693 |
+
if rbp_idx is None:
|
| 694 |
+
continue
|
| 695 |
+
rbp_expr = expr[:, rbp_idx]
|
| 696 |
+
if np.std(rbp_expr) < 1e-6:
|
| 697 |
+
continue
|
| 698 |
+
|
| 699 |
+
gene_scores = []
|
| 700 |
+
for ti in target_indices:
|
| 701 |
+
tg = gamma[:, ti]
|
| 702 |
+
valid = tg > 0
|
| 703 |
+
if valid.sum() < 50:
|
| 704 |
+
continue
|
| 705 |
+
r, _ = stats.spearmanr(rbp_expr[valid], tg[valid])
|
| 706 |
+
gene_scores.append((gene_upper[ti], abs(r)))
|
| 707 |
+
|
| 708 |
+
if not gene_scores:
|
| 709 |
+
continue
|
| 710 |
+
|
| 711 |
+
gene_scores.sort(key=lambda x: -x[1]) # highest abs(r) first
|
| 712 |
+
ranked_genes = [g for g, _ in gene_scores]
|
| 713 |
+
|
| 714 |
+
# Where do eCLIP targets fall in the ranking?
|
| 715 |
+
eclip_ranks = []
|
| 716 |
+
for gi, g in enumerate(ranked_genes):
|
| 717 |
+
if g in eclip:
|
| 718 |
+
eclip_ranks.append(gi + 1)
|
| 719 |
+
|
| 720 |
+
if not eclip_ranks:
|
| 721 |
+
continue
|
| 722 |
+
|
| 723 |
+
# Mann-Whitney: do eCLIP targets rank higher than non-eCLIP?
|
| 724 |
+
non_eclip_ranks = [gi + 1 for gi, g in enumerate(ranked_genes) if g not in eclip]
|
| 725 |
+
if len(non_eclip_ranks) < 5:
|
| 726 |
+
continue
|
| 727 |
+
|
| 728 |
+
_, rank_p = stats.mannwhitneyu(eclip_ranks, non_eclip_ranks, alternative="less")
|
| 729 |
+
mean_eclip_percentile = np.mean(eclip_ranks) / len(ranked_genes)
|
| 730 |
+
mean_noneclip_percentile = np.mean(non_eclip_ranks) / len(ranked_genes)
|
| 731 |
+
|
| 732 |
+
print(f" {rbp_upper}: eCLIP mean rank percentile={mean_eclip_percentile:.2f}, "
|
| 733 |
+
f"non-eCLIP={mean_noneclip_percentile:.2f}, MW p={rank_p:.4f}")
|
| 734 |
+
|
| 735 |
+
all_results.append({
|
| 736 |
+
"dataset": ds_name,
|
| 737 |
+
"per_rbp": per_rbp_results,
|
| 738 |
+
"aggregate_or": float(agg_or) if not np.isnan(agg_or) else None,
|
| 739 |
+
"aggregate_p": float(agg_p) if not np.isnan(agg_p) else None,
|
| 740 |
+
"aggregate_enrichment": float(enrichment) if not np.isnan(enrichment) else None,
|
| 741 |
+
})
|
| 742 |
+
|
| 743 |
+
# Summary figure
|
| 744 |
+
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
|
| 745 |
+
|
| 746 |
+
# Panel 1: Aggregate enrichment by dataset
|
| 747 |
+
ds_names = [r["dataset"] for r in all_results]
|
| 748 |
+
agg_ors = [r["aggregate_or"] if r["aggregate_or"] else 0 for r in all_results]
|
| 749 |
+
agg_ps = [r["aggregate_p"] if r["aggregate_p"] else 1 for r in all_results]
|
| 750 |
+
colors = ["#43A047" if p < 0.05 else "#BDBDBD" for p in agg_ps]
|
| 751 |
+
|
| 752 |
+
bars = axes[0].bar(range(len(ds_names)), agg_ors, color=colors,
|
| 753 |
+
edgecolor="black", linewidth=0.5)
|
| 754 |
+
axes[0].axhline(y=1, color="red", linestyle="--", alpha=0.5, label="No enrichment")
|
| 755 |
+
axes[0].set_xticks(range(len(ds_names)))
|
| 756 |
+
axes[0].set_xticklabels(ds_names, fontsize=9)
|
| 757 |
+
axes[0].set_ylabel("Aggregate odds ratio")
|
| 758 |
+
axes[0].set_title("Aggregate eCLIP Enrichment (all RBPs pooled)")
|
| 759 |
+
axes[0].legend()
|
| 760 |
+
for i, (o, p) in enumerate(zip(agg_ors, agg_ps)):
|
| 761 |
+
sig = " *" if p < 0.05 else ""
|
| 762 |
+
axes[0].text(i, o + 0.02, f"OR={o:.2f}\np={p:.3f}{sig}",
|
| 763 |
+
ha="center", fontsize=8)
|
| 764 |
+
|
| 765 |
+
# Panel 2: Per-RBP odds ratios, colored by ubiquitous vs specific
|
| 766 |
+
# Combine all per-RBP results
|
| 767 |
+
all_per_rbp = []
|
| 768 |
+
for r in all_results:
|
| 769 |
+
for pr in r["per_rbp"]:
|
| 770 |
+
pr["dataset"] = r["dataset"]
|
| 771 |
+
all_per_rbp.append(pr)
|
| 772 |
+
|
| 773 |
+
if all_per_rbp:
|
| 774 |
+
ubiq_ors = [r["odds_ratio"] for r in all_per_rbp if r["type"] == "ubiquitous"]
|
| 775 |
+
spec_ors = [r["odds_ratio"] for r in all_per_rbp if r["type"] == "cell-specific"]
|
| 776 |
+
|
| 777 |
+
data_to_plot = []
|
| 778 |
+
labels_to_plot = []
|
| 779 |
+
if ubiq_ors:
|
| 780 |
+
data_to_plot.append(ubiq_ors)
|
| 781 |
+
labels_to_plot.append(f"Ubiquitous\n(n={len(ubiq_ors)})")
|
| 782 |
+
if spec_ors:
|
| 783 |
+
data_to_plot.append(spec_ors)
|
| 784 |
+
labels_to_plot.append(f"Cell-specific\n(n={len(spec_ors)})")
|
| 785 |
+
|
| 786 |
+
if data_to_plot:
|
| 787 |
+
bp = axes[1].boxplot(data_to_plot, tick_labels=labels_to_plot,
|
| 788 |
+
patch_artist=True, showfliers=True)
|
| 789 |
+
box_colors = ["#1976D2", "#E53935"]
|
| 790 |
+
for patch, color in zip(bp["boxes"], box_colors[:len(data_to_plot)]):
|
| 791 |
+
patch.set_facecolor(color)
|
| 792 |
+
patch.set_alpha(0.6)
|
| 793 |
+
axes[1].axhline(y=1, color="red", linestyle="--", alpha=0.5)
|
| 794 |
+
axes[1].set_ylabel("Odds ratio")
|
| 795 |
+
axes[1].set_title("eCLIP Enrichment by RBP Type")
|
| 796 |
+
|
| 797 |
+
if ubiq_ors and spec_ors and len(ubiq_ors) >= 2 and len(spec_ors) >= 2:
|
| 798 |
+
_, mw_p = stats.mannwhitneyu(ubiq_ors, spec_ors, alternative="greater")
|
| 799 |
+
axes[1].text(0.5, 0.95, f"Ubiq > Specific: p={mw_p:.3f}",
|
| 800 |
+
transform=axes[1].transAxes, ha="center", va="top", fontsize=9)
|
| 801 |
+
|
| 802 |
+
fig.tight_layout()
|
| 803 |
+
save_fig(fig, "eclip_validation_fix")
|
| 804 |
+
|
| 805 |
+
return all_results
|
| 806 |
+
|
| 807 |
+
|
| 808 |
+
# =========================================================================
|
| 809 |
+
# MAIN
|
| 810 |
+
# =========================================================================
|
| 811 |
+
def main():
|
| 812 |
+
set_figure_style()
|
| 813 |
+
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
| 814 |
+
|
| 815 |
+
res_dir = OUTPUT_DIR / "results"
|
| 816 |
+
res_dir.mkdir(parents=True, exist_ok=True)
|
| 817 |
+
|
| 818 |
+
# Load datasets
|
| 819 |
+
print("=" * 60)
|
| 820 |
+
print("LOADING DATASETS")
|
| 821 |
+
print("=" * 60)
|
| 822 |
+
|
| 823 |
+
adata_pan = scptr.datasets.pancreas()
|
| 824 |
+
adata_pan = run_pipeline(adata_pan, "pancreas")
|
| 825 |
+
|
| 826 |
+
adata_dg = scptr.datasets.dentate_gyrus()
|
| 827 |
+
adata_dg = run_pipeline(adata_dg, "dentate_gyrus")
|
| 828 |
+
|
| 829 |
+
# sci-fate
|
| 830 |
+
from run_scifate import load_scifate_data, prepare_for_scptr
|
| 831 |
+
adata_sf_raw = load_scifate_data()
|
| 832 |
+
adata_sf = prepare_for_scptr(adata_sf_raw)
|
| 833 |
+
adata_sf = run_pipeline(adata_sf, "scifate")
|
| 834 |
+
|
| 835 |
+
datasets = {
|
| 836 |
+
"pancreas": adata_pan,
|
| 837 |
+
"dentate_gyrus": adata_dg,
|
| 838 |
+
"scifate": adata_sf,
|
| 839 |
+
}
|
| 840 |
+
|
| 841 |
+
# ===== FIX 1: Destabilizing bias =====
|
| 842 |
+
bias_results = {}
|
| 843 |
+
for name, adata in [("pancreas", adata_pan), ("dentate_gyrus", adata_dg)]:
|
| 844 |
+
result, corrected_edges = fix_destabilizing_bias(adata, name)
|
| 845 |
+
bias_results[name] = result
|
| 846 |
+
|
| 847 |
+
if corrected_edges:
|
| 848 |
+
pd.DataFrame(corrected_edges).to_csv(
|
| 849 |
+
res_dir / f"corrected_network_{name}.csv", index=False)
|
| 850 |
+
|
| 851 |
+
with open(res_dir / "destabilizing_bias_fix.json", "w") as f:
|
| 852 |
+
json.dump(bias_results, f, indent=2)
|
| 853 |
+
|
| 854 |
+
# ===== FIX 2: Cross-dataset consistency =====
|
| 855 |
+
consistency_results = fix_cross_dataset_consistency(datasets)
|
| 856 |
+
with open(res_dir / "consistency_fix.json", "w") as f:
|
| 857 |
+
json.dump(consistency_results, f, indent=2)
|
| 858 |
+
|
| 859 |
+
# ===== FIX 3: eCLIP validation =====
|
| 860 |
+
eclip_results = fix_eclip_validation(datasets)
|
| 861 |
+
if eclip_results:
|
| 862 |
+
with open(res_dir / "eclip_fix.json", "w") as f:
|
| 863 |
+
json.dump(eclip_results, f, indent=2, default=str)
|
| 864 |
+
|
| 865 |
+
# ===== SUMMARY =====
|
| 866 |
+
print(f"\n{'='*60}")
|
| 867 |
+
print("WEAKNESS FIXES SUMMARY")
|
| 868 |
+
print(f"{'='*60}")
|
| 869 |
+
|
| 870 |
+
print("\n Fix 1: Destabilizing Bias")
|
| 871 |
+
for name, r in bias_results.items():
|
| 872 |
+
print(f" {name}: {r['raw_destab_frac']:.0%} raw → "
|
| 873 |
+
f"{r['partial_destab_frac']:.0%} after correction "
|
| 874 |
+
f"(permutation null: {r['permutation_destab_frac']:.0%})")
|
| 875 |
+
|
| 876 |
+
print("\n Fix 2: Cross-Dataset Consistency")
|
| 877 |
+
for r in consistency_results:
|
| 878 |
+
print(f" {r['pair']}: gamma r={r['gamma_r_all']:.3f}, "
|
| 879 |
+
f"expr r={r['expr_r_all']:.3f}")
|
| 880 |
+
|
| 881 |
+
print("\n Fix 3: eCLIP Validation")
|
| 882 |
+
for r in eclip_results or []:
|
| 883 |
+
agg_p = r.get("aggregate_p", "N/A")
|
| 884 |
+
agg_or = r.get("aggregate_or", "N/A")
|
| 885 |
+
sig_text = "YES" if isinstance(agg_p, float) and agg_p < 0.05 else "no"
|
| 886 |
+
print(f" {r['dataset']}: aggregate OR={agg_or}, p={agg_p} ({sig_text})")
|
| 887 |
+
|
| 888 |
+
print(f"\n Results saved to: {OUTPUT_DIR.resolve()}")
|
| 889 |
+
|
| 890 |
+
|
| 891 |
+
if __name__ == "__main__":
|
| 892 |
+
main()
|
analyses/run_weakness_improvements.py
ADDED
|
@@ -0,0 +1,640 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
"""Targeted improvements for four remaining scPTR weaknesses.
|
| 3 |
+
|
| 4 |
+
Experiment 1: Edge-level UTR validation (fixes pancreas p=0.676)
|
| 5 |
+
Experiment 2: DepMap stratified NB analysis (MYCN, lineage, cross-line)
|
| 6 |
+
Experiment 3: eCLIP edge-strength concordance (fixes weak OR=0.56-1.30)
|
| 7 |
+
|
| 8 |
+
All experiments use existing cached data. No dataset downloads required.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
from __future__ import annotations
|
| 12 |
+
|
| 13 |
+
import json
|
| 14 |
+
import sys
|
| 15 |
+
from pathlib import Path
|
| 16 |
+
|
| 17 |
+
import matplotlib
|
| 18 |
+
matplotlib.use("Agg")
|
| 19 |
+
import matplotlib.pyplot as plt
|
| 20 |
+
import numpy as np
|
| 21 |
+
import pandas as pd
|
| 22 |
+
from scipy import stats
|
| 23 |
+
|
| 24 |
+
sys.path.insert(0, str(Path(__file__).parent))
|
| 25 |
+
from _common import set_figure_style
|
| 26 |
+
|
| 27 |
+
OUTPUT_DIR = Path(__file__).parent.parent / "output" / "weakness_improvements"
|
| 28 |
+
PROJECT_ROOT = Path(__file__).parent.parent
|
| 29 |
+
DATA_DIR = PROJECT_ROOT / "src" / "scptr" / "benchmark" / "data"
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def save_fig(fig, name, subdir="figures"):
|
| 33 |
+
if fig is None:
|
| 34 |
+
print(f" [WARNING] {name}: None, skipping")
|
| 35 |
+
return
|
| 36 |
+
out_dir = OUTPUT_DIR / subdir
|
| 37 |
+
out_dir.mkdir(parents=True, exist_ok=True)
|
| 38 |
+
path = out_dir / f"{name}.png"
|
| 39 |
+
fig.savefig(path, dpi=150, bbox_inches="tight")
|
| 40 |
+
plt.close(fig)
|
| 41 |
+
print(f" Saved: {path}")
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def save_results(data, name, subdir="results"):
|
| 45 |
+
out_dir = OUTPUT_DIR / subdir
|
| 46 |
+
out_dir.mkdir(parents=True, exist_ok=True)
|
| 47 |
+
path = out_dir / f"{name}.json"
|
| 48 |
+
with open(path, "w") as f:
|
| 49 |
+
json.dump(data, f, indent=2, default=str)
|
| 50 |
+
print(f" Saved: {path}")
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
# ---------------------------------------------------------------------------
|
| 54 |
+
# Experiment 1: Edge-Level UTR Validation
|
| 55 |
+
# ---------------------------------------------------------------------------
|
| 56 |
+
|
| 57 |
+
def load_network(dataset):
|
| 58 |
+
"""Load corrected network edges for a dataset."""
|
| 59 |
+
if dataset == "pancreas":
|
| 60 |
+
path = PROJECT_ROOT / "output" / "weakness_fixes" / "results" / "corrected_network_pancreas.csv"
|
| 61 |
+
elif dataset == "dentate_gyrus":
|
| 62 |
+
path = PROJECT_ROOT / "output" / "weakness_fixes" / "results" / "corrected_network_dentate_gyrus.csv"
|
| 63 |
+
elif dataset == "neuroblastoma":
|
| 64 |
+
path = PROJECT_ROOT / "output" / "tier3" / "results" / "neuroblastoma_network_corrected.csv"
|
| 65 |
+
else:
|
| 66 |
+
raise ValueError(f"Unknown dataset: {dataset}")
|
| 67 |
+
df = pd.read_csv(path)
|
| 68 |
+
# Normalize column names
|
| 69 |
+
if "spearman_r" in df.columns:
|
| 70 |
+
df = df.rename(columns={"spearman_r": "r"})
|
| 71 |
+
return df
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def load_utr_features(species):
|
| 75 |
+
"""Load UTR features (mouse or human)."""
|
| 76 |
+
fname = f"{species}_utr_features.csv"
|
| 77 |
+
return pd.read_csv(DATA_DIR / fname)
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def experiment1_edge_utr():
|
| 81 |
+
"""Edge-level UTR validation across all datasets."""
|
| 82 |
+
print("\n" + "=" * 60)
|
| 83 |
+
print("EXPERIMENT 1: Edge-Level UTR Validation")
|
| 84 |
+
print("=" * 60)
|
| 85 |
+
|
| 86 |
+
set_figure_style()
|
| 87 |
+
|
| 88 |
+
datasets = {
|
| 89 |
+
"pancreas": "mouse",
|
| 90 |
+
"dentate_gyrus": "mouse",
|
| 91 |
+
"neuroblastoma": "human",
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
all_results = {}
|
| 95 |
+
|
| 96 |
+
for ds_name, species in datasets.items():
|
| 97 |
+
print(f"\n--- {ds_name} ---")
|
| 98 |
+
net = load_network(ds_name)
|
| 99 |
+
utr = load_utr_features(species)
|
| 100 |
+
|
| 101 |
+
# Gene matching: uppercase
|
| 102 |
+
utr_lookup = dict(zip(utr["gene"].str.upper(), utr["utr_length"]))
|
| 103 |
+
net["target_upper"] = net["target"].str.upper()
|
| 104 |
+
net["utr_length"] = net["target_upper"].map(utr_lookup)
|
| 105 |
+
matched = net.dropna(subset=["utr_length"]).copy()
|
| 106 |
+
print(f" Edges: {len(net)}, matched with UTR: {len(matched)}")
|
| 107 |
+
|
| 108 |
+
ds_results = {"n_edges": len(net), "n_matched": len(matched)}
|
| 109 |
+
|
| 110 |
+
# Test A: Spearman(r_edge, UTR_length_target) across ALL edges
|
| 111 |
+
r_val, p_val = stats.spearmanr(matched["r"], matched["utr_length"])
|
| 112 |
+
print(f" Test A (all edges): Spearman r={r_val:.4f}, p={p_val:.2e}")
|
| 113 |
+
ds_results["test_a"] = {"spearman_r": float(r_val), "p": float(p_val)}
|
| 114 |
+
|
| 115 |
+
# Test B: Per-RBP within-RBP Spearman, Fisher combined p
|
| 116 |
+
per_rbp_p = []
|
| 117 |
+
per_rbp_results = []
|
| 118 |
+
for rbp, grp in matched.groupby("rbp"):
|
| 119 |
+
if len(grp) < 20:
|
| 120 |
+
continue
|
| 121 |
+
rr, pp = stats.spearmanr(grp["r"], grp["utr_length"])
|
| 122 |
+
per_rbp_p.append(pp)
|
| 123 |
+
per_rbp_results.append({"rbp": rbp, "n": len(grp), "r": float(rr), "p": float(pp)})
|
| 124 |
+
if per_rbp_p:
|
| 125 |
+
# Fisher's combined p-value: -2 * sum(log(pi)) ~ chi2(2k)
|
| 126 |
+
chi2_stat = -2 * np.sum(np.log(np.array(per_rbp_p)))
|
| 127 |
+
fisher_p = stats.chi2.sf(chi2_stat, 2 * len(per_rbp_p))
|
| 128 |
+
n_sig = sum(1 for p in per_rbp_p if p < 0.05)
|
| 129 |
+
print(f" Test B (per-RBP): {len(per_rbp_p)} RBPs (>=20 edges), "
|
| 130 |
+
f"Fisher combined p={fisher_p:.2e}, {n_sig} individually significant")
|
| 131 |
+
ds_results["test_b"] = {
|
| 132 |
+
"n_rbps": len(per_rbp_p),
|
| 133 |
+
"fisher_p": float(fisher_p),
|
| 134 |
+
"n_sig": n_sig,
|
| 135 |
+
"per_rbp": per_rbp_results,
|
| 136 |
+
}
|
| 137 |
+
else:
|
| 138 |
+
print(" Test B: No RBPs with >=20 edges")
|
| 139 |
+
ds_results["test_b"] = {"n_rbps": 0}
|
| 140 |
+
|
| 141 |
+
# Test C: Mann-Whitney on UTR lengths: destabilizing (r>0) vs stabilizing (r<0)
|
| 142 |
+
dest = matched[matched["r"] > 0]["utr_length"]
|
| 143 |
+
stab = matched[matched["r"] < 0]["utr_length"]
|
| 144 |
+
if len(dest) > 0 and len(stab) > 0:
|
| 145 |
+
mw_stat, mw_p = stats.mannwhitneyu(dest, stab, alternative="greater")
|
| 146 |
+
print(f" Test C (MW dest vs stab): dest median={dest.median():.0f}, "
|
| 147 |
+
f"stab median={stab.median():.0f}, p={mw_p:.4f}")
|
| 148 |
+
ds_results["test_c"] = {
|
| 149 |
+
"dest_median": float(dest.median()),
|
| 150 |
+
"stab_median": float(stab.median()),
|
| 151 |
+
"dest_n": len(dest),
|
| 152 |
+
"stab_n": len(stab),
|
| 153 |
+
"mw_p": float(mw_p),
|
| 154 |
+
}
|
| 155 |
+
else:
|
| 156 |
+
print(" Test C: insufficient data")
|
| 157 |
+
ds_results["test_c"] = {}
|
| 158 |
+
|
| 159 |
+
# Test D: UTR quintile trend
|
| 160 |
+
matched["utr_quintile"] = pd.qcut(matched["utr_length"], 5, labels=False, duplicates="drop")
|
| 161 |
+
quintile_means = matched.groupby("utr_quintile")["r"].mean()
|
| 162 |
+
# Jonckheere-Terpstra approximation via Spearman on quintile vs mean_r
|
| 163 |
+
q_r, q_p = stats.spearmanr(quintile_means.index, quintile_means.values)
|
| 164 |
+
print(f" Test D (quintile trend): Spearman r={q_r:.4f}, p={q_p:.4f}")
|
| 165 |
+
print(f" Quintile mean r values: {[f'{v:.4f}' for v in quintile_means.values]}")
|
| 166 |
+
ds_results["test_d"] = {
|
| 167 |
+
"quintile_means": {str(k): float(v) for k, v in quintile_means.items()},
|
| 168 |
+
"trend_r": float(q_r),
|
| 169 |
+
"trend_p": float(q_p),
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
all_results[ds_name] = ds_results
|
| 173 |
+
|
| 174 |
+
save_results(all_results, "edge_utr_validation")
|
| 175 |
+
|
| 176 |
+
# Figure: 3-panel quintile plot
|
| 177 |
+
fig, axes = plt.subplots(1, 3, figsize=(14, 4.5))
|
| 178 |
+
for ax, (ds_name, species) in zip(axes, datasets.items()):
|
| 179 |
+
net = load_network(ds_name)
|
| 180 |
+
utr = load_utr_features(species)
|
| 181 |
+
utr_lookup = dict(zip(utr["gene"].str.upper(), utr["utr_length"]))
|
| 182 |
+
net["target_upper"] = net["target"].str.upper()
|
| 183 |
+
net["utr_length"] = net["target_upper"].map(utr_lookup)
|
| 184 |
+
matched = net.dropna(subset=["utr_length"]).copy()
|
| 185 |
+
matched["utr_quintile"] = pd.qcut(matched["utr_length"], 5, labels=False, duplicates="drop")
|
| 186 |
+
quintile_means = matched.groupby("utr_quintile")["r"].mean()
|
| 187 |
+
quintile_sems = matched.groupby("utr_quintile")["r"].sem()
|
| 188 |
+
ax.bar(range(len(quintile_means)), quintile_means.values,
|
| 189 |
+
yerr=quintile_sems.values, capsize=4, color="steelblue", alpha=0.8)
|
| 190 |
+
ax.set_xlabel("3' UTR Length Quintile")
|
| 191 |
+
ax.set_ylabel("Mean RBP-gamma r")
|
| 192 |
+
ax.set_title(ds_name.replace("_", " ").title())
|
| 193 |
+
ax.set_xticks(range(len(quintile_means)))
|
| 194 |
+
ax.set_xticklabels([f"Q{i+1}" for i in range(len(quintile_means))])
|
| 195 |
+
# Add trend line info
|
| 196 |
+
res = all_results[ds_name]
|
| 197 |
+
ax.text(0.05, 0.95, f"trend r={res['test_d']['trend_r']:.3f}\np={res['test_d']['trend_p']:.3f}",
|
| 198 |
+
transform=ax.transAxes, va="top", fontsize=8)
|
| 199 |
+
|
| 200 |
+
fig.suptitle("Edge-Level UTR Validation: Mean r by UTR Length Quintile", fontsize=13)
|
| 201 |
+
plt.tight_layout()
|
| 202 |
+
save_fig(fig, "edge_utr_quintiles")
|
| 203 |
+
|
| 204 |
+
return all_results
|
| 205 |
+
|
| 206 |
+
|
| 207 |
+
# ---------------------------------------------------------------------------
|
| 208 |
+
# Experiment 2: DepMap Stratified NB Analysis
|
| 209 |
+
# ---------------------------------------------------------------------------
|
| 210 |
+
|
| 211 |
+
NB_HUB_RBPS = [
|
| 212 |
+
"HNRNPA2B1", "PABPC1", "YBX1", "HNRNPD", "HNRNPU", "PRPF8",
|
| 213 |
+
"SNRNP200", "FUS", "HNRNPK", "NCL", "SRSF3", "SRSF7",
|
| 214 |
+
"EWSR1", "SNRPA", "PTBP1", "TRA2B", "QKI", "HNRNPM",
|
| 215 |
+
"SRSF10", "DDX5",
|
| 216 |
+
]
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
def load_depmap():
|
| 220 |
+
"""Load DepMap model metadata and CRISPR gene effect scores."""
|
| 221 |
+
model = pd.read_csv(PROJECT_ROOT / ".cache" / "DepMap_Model.csv")
|
| 222 |
+
crispr = pd.read_csv(PROJECT_ROOT / ".cache" / "CRISPRGeneEffect.csv")
|
| 223 |
+
# First column is ModelID (unnamed)
|
| 224 |
+
id_col = crispr.columns[0]
|
| 225 |
+
crispr = crispr.rename(columns={id_col: "ModelID"})
|
| 226 |
+
# Parse gene columns: "GENE (12345)" -> "GENE"
|
| 227 |
+
gene_cols = {c: c.split(" (")[0] for c in crispr.columns if " (" in c}
|
| 228 |
+
crispr = crispr.rename(columns=gene_cols)
|
| 229 |
+
return model, crispr
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
def experiment2_depmap_stratified():
|
| 233 |
+
"""DepMap stratified NB analysis: MYCN, lineage, cross-line."""
|
| 234 |
+
print("\n" + "=" * 60)
|
| 235 |
+
print("EXPERIMENT 2: DepMap Stratified NB Analysis")
|
| 236 |
+
print("=" * 60)
|
| 237 |
+
|
| 238 |
+
set_figure_style()
|
| 239 |
+
model, crispr = load_depmap()
|
| 240 |
+
|
| 241 |
+
# Get NB lines
|
| 242 |
+
nb_model = model[model["OncotreePrimaryDisease"] == "Neuroblastoma"]
|
| 243 |
+
nb_ids = set(nb_model["ModelID"]) & set(crispr["ModelID"])
|
| 244 |
+
print(f" NB cell lines with CRISPR data: {len(nb_ids)}")
|
| 245 |
+
|
| 246 |
+
# Filter hub RBPs present in CRISPR
|
| 247 |
+
hub_in_crispr = [g for g in NB_HUB_RBPS if g in crispr.columns]
|
| 248 |
+
print(f" Hub RBPs in CRISPR: {len(hub_in_crispr)}/{len(NB_HUB_RBPS)}")
|
| 249 |
+
|
| 250 |
+
# All RBP genes for non-hub comparison (use GO RBP list proxy: all genes with "RBP" or known RBPs)
|
| 251 |
+
# Simpler: use all genes not in hub list as background
|
| 252 |
+
all_genes = [c for c in crispr.columns if c != "ModelID"]
|
| 253 |
+
|
| 254 |
+
all_results = {}
|
| 255 |
+
|
| 256 |
+
# --- 2a: MYCN-Stratified Essentiality ---
|
| 257 |
+
print("\n --- 2a: MYCN-Stratified Essentiality ---")
|
| 258 |
+
|
| 259 |
+
mycn_model = nb_model[nb_model["ModelSubtypeFeatures"] == "MYC_Amplified"]
|
| 260 |
+
non_mycn_model = nb_model[nb_model["ModelSubtypeFeatures"] != "MYC_Amplified"]
|
| 261 |
+
mycn_ids = set(mycn_model["ModelID"]) & nb_ids
|
| 262 |
+
non_mycn_ids = set(non_mycn_model["ModelID"]) & nb_ids
|
| 263 |
+
print(f" MYCN-amp: {len(mycn_ids)}, non-MYCN: {len(non_mycn_ids)}")
|
| 264 |
+
|
| 265 |
+
crispr_nb = crispr[crispr["ModelID"].isin(nb_ids)].copy()
|
| 266 |
+
crispr_mycn = crispr_nb[crispr_nb["ModelID"].isin(mycn_ids)]
|
| 267 |
+
crispr_nonmycn = crispr_nb[crispr_nb["ModelID"].isin(non_mycn_ids)]
|
| 268 |
+
|
| 269 |
+
# Mean hub dependency per group
|
| 270 |
+
mycn_hub_deps = crispr_mycn[hub_in_crispr].mean(axis=1)
|
| 271 |
+
nonmycn_hub_deps = crispr_nonmycn[hub_in_crispr].mean(axis=1)
|
| 272 |
+
mw_stat, mw_p = stats.mannwhitneyu(mycn_hub_deps, nonmycn_hub_deps, alternative="two-sided")
|
| 273 |
+
print(f" Hub mean dep: MYCN-amp={mycn_hub_deps.mean():.4f}, non-MYCN={nonmycn_hub_deps.mean():.4f}, MW p={mw_p:.4f}")
|
| 274 |
+
|
| 275 |
+
# Per-hub MYCN vs non-MYCN
|
| 276 |
+
per_hub_mycn = []
|
| 277 |
+
for gene in hub_in_crispr:
|
| 278 |
+
m_vals = crispr_mycn[gene].dropna()
|
| 279 |
+
n_vals = crispr_nonmycn[gene].dropna()
|
| 280 |
+
if len(m_vals) > 0 and len(n_vals) > 0:
|
| 281 |
+
_, pp = stats.mannwhitneyu(m_vals, n_vals, alternative="two-sided")
|
| 282 |
+
per_hub_mycn.append({
|
| 283 |
+
"rbp": gene,
|
| 284 |
+
"mycn_mean": float(m_vals.mean()),
|
| 285 |
+
"nonmycn_mean": float(n_vals.mean()),
|
| 286 |
+
"diff": float(m_vals.mean() - n_vals.mean()),
|
| 287 |
+
"p": float(pp),
|
| 288 |
+
})
|
| 289 |
+
per_hub_mycn.sort(key=lambda x: x["p"])
|
| 290 |
+
n_sig_mycn = sum(1 for x in per_hub_mycn if x["p"] < 0.05)
|
| 291 |
+
print(f" Per-hub MYCN-specific: {n_sig_mycn}/{len(per_hub_mycn)} significant (p<0.05)")
|
| 292 |
+
if per_hub_mycn:
|
| 293 |
+
top = per_hub_mycn[0]
|
| 294 |
+
print(f" Top: {top['rbp']} (MYCN={top['mycn_mean']:.3f}, non={top['nonmycn_mean']:.3f}, p={top['p']:.4f})")
|
| 295 |
+
|
| 296 |
+
all_results["mycn_stratified"] = {
|
| 297 |
+
"mycn_n": len(mycn_ids),
|
| 298 |
+
"nonmycn_n": len(non_mycn_ids),
|
| 299 |
+
"mycn_hub_mean": float(mycn_hub_deps.mean()),
|
| 300 |
+
"nonmycn_hub_mean": float(nonmycn_hub_deps.mean()),
|
| 301 |
+
"mw_p": float(mw_p),
|
| 302 |
+
"n_sig_per_hub": n_sig_mycn,
|
| 303 |
+
"per_hub": per_hub_mycn,
|
| 304 |
+
}
|
| 305 |
+
|
| 306 |
+
# --- 2b: Neural Lineage Specificity ---
|
| 307 |
+
print("\n --- 2b: Neural Lineage Specificity ---")
|
| 308 |
+
|
| 309 |
+
lineages = {
|
| 310 |
+
"PNS": "Peripheral Nervous System",
|
| 311 |
+
"CNS": "CNS/Brain",
|
| 312 |
+
"Lymphoid": "Lymphoid",
|
| 313 |
+
}
|
| 314 |
+
lineage_hub_deps = {}
|
| 315 |
+
for label, lineage in lineages.items():
|
| 316 |
+
lin_ids = set(model[model["OncotreeLineage"] == lineage]["ModelID"]) & set(crispr["ModelID"])
|
| 317 |
+
crispr_lin = crispr[crispr["ModelID"].isin(lin_ids)]
|
| 318 |
+
deps = crispr_lin[hub_in_crispr].mean(axis=1)
|
| 319 |
+
lineage_hub_deps[label] = deps
|
| 320 |
+
print(f" {label} ({len(lin_ids)} lines): hub mean dep = {deps.mean():.4f}")
|
| 321 |
+
|
| 322 |
+
# Kruskal-Wallis across all three
|
| 323 |
+
kw_stat, kw_p = stats.kruskal(*lineage_hub_deps.values())
|
| 324 |
+
print(f" Kruskal-Wallis: H={kw_stat:.2f}, p={kw_p:.4f}")
|
| 325 |
+
|
| 326 |
+
# Pairwise Mann-Whitney
|
| 327 |
+
pairwise = []
|
| 328 |
+
labels = list(lineage_hub_deps.keys())
|
| 329 |
+
for i in range(len(labels)):
|
| 330 |
+
for j in range(i + 1, len(labels)):
|
| 331 |
+
_, pp = stats.mannwhitneyu(lineage_hub_deps[labels[i]],
|
| 332 |
+
lineage_hub_deps[labels[j]],
|
| 333 |
+
alternative="two-sided")
|
| 334 |
+
pairwise.append({
|
| 335 |
+
"pair": f"{labels[i]} vs {labels[j]}",
|
| 336 |
+
"mean_a": float(lineage_hub_deps[labels[i]].mean()),
|
| 337 |
+
"mean_b": float(lineage_hub_deps[labels[j]].mean()),
|
| 338 |
+
"mw_p": float(pp),
|
| 339 |
+
})
|
| 340 |
+
print(f" {labels[i]} vs {labels[j]}: p={pp:.4f}")
|
| 341 |
+
|
| 342 |
+
all_results["lineage_specificity"] = {
|
| 343 |
+
"lineage_n": {k: len(v) for k, v in lineage_hub_deps.items()},
|
| 344 |
+
"lineage_means": {k: float(v.mean()) for k, v in lineage_hub_deps.items()},
|
| 345 |
+
"kruskal_wallis": {"H": float(kw_stat), "p": float(kw_p)},
|
| 346 |
+
"pairwise": pairwise,
|
| 347 |
+
}
|
| 348 |
+
|
| 349 |
+
# --- 2c: Cross-NB-Line Hub Consistency ---
|
| 350 |
+
print("\n --- 2c: Cross-NB-Line Hub Consistency ---")
|
| 351 |
+
|
| 352 |
+
# Non-hub RBPs: all genes not in hub list
|
| 353 |
+
non_hub_genes = [g for g in all_genes if g not in set(hub_in_crispr)]
|
| 354 |
+
|
| 355 |
+
# Per-line: mean hub dep vs mean non-hub dep
|
| 356 |
+
per_line_hub = crispr_nb[hub_in_crispr].mean(axis=1)
|
| 357 |
+
per_line_nonhub = crispr_nb[non_hub_genes].mean(axis=1)
|
| 358 |
+
# Wilcoxon signed-rank: hub < non-hub across lines
|
| 359 |
+
wsr_stat, wsr_p = stats.wilcoxon(per_line_hub, per_line_nonhub, alternative="less")
|
| 360 |
+
print(f" Wilcoxon signed-rank (hub < non-hub): p={wsr_p:.2e}")
|
| 361 |
+
print(f" Hub mean across lines: {per_line_hub.mean():.4f}, non-hub: {per_line_nonhub.mean():.4f}")
|
| 362 |
+
|
| 363 |
+
# How many lines show hub < non-hub
|
| 364 |
+
n_hub_more_essential = (per_line_hub < per_line_nonhub).sum()
|
| 365 |
+
print(f" Lines where hub < non-hub: {n_hub_more_essential}/{len(per_line_hub)}")
|
| 366 |
+
|
| 367 |
+
# Bootstrap: 10,000 random 20-gene sets
|
| 368 |
+
rng = np.random.default_rng(42)
|
| 369 |
+
obs_diff = (per_line_hub - per_line_nonhub).mean()
|
| 370 |
+
n_bootstrap = 10000
|
| 371 |
+
boot_diffs = np.zeros(n_bootstrap)
|
| 372 |
+
all_gene_arr = np.array(all_genes)
|
| 373 |
+
crispr_nb_vals = crispr_nb[all_genes].values
|
| 374 |
+
for i in range(n_bootstrap):
|
| 375 |
+
rand_idx = rng.choice(len(all_genes), size=len(hub_in_crispr), replace=False)
|
| 376 |
+
rand_mean = np.nanmean(crispr_nb_vals[:, rand_idx], axis=1)
|
| 377 |
+
boot_diffs[i] = np.mean(rand_mean - per_line_nonhub.values)
|
| 378 |
+
boot_p = np.mean(boot_diffs <= obs_diff)
|
| 379 |
+
print(f" Bootstrap p (hub more essential than random): {boot_p:.4f}")
|
| 380 |
+
|
| 381 |
+
# Per-hub essentiality profile
|
| 382 |
+
per_hub_profile = []
|
| 383 |
+
for gene in hub_in_crispr:
|
| 384 |
+
vals = crispr_nb[gene].dropna()
|
| 385 |
+
frac_essential = float((vals < -0.5).mean())
|
| 386 |
+
per_hub_profile.append({
|
| 387 |
+
"rbp": gene,
|
| 388 |
+
"mean_dep": float(vals.mean()),
|
| 389 |
+
"frac_essential": frac_essential,
|
| 390 |
+
"n_lines": len(vals),
|
| 391 |
+
})
|
| 392 |
+
per_hub_profile.sort(key=lambda x: x["mean_dep"])
|
| 393 |
+
print(f" Most essential hub: {per_hub_profile[0]['rbp']} "
|
| 394 |
+
f"(mean={per_hub_profile[0]['mean_dep']:.3f}, "
|
| 395 |
+
f"essential in {per_hub_profile[0]['frac_essential']*100:.0f}% of lines)")
|
| 396 |
+
|
| 397 |
+
all_results["cross_line_consistency"] = {
|
| 398 |
+
"n_lines": len(per_line_hub),
|
| 399 |
+
"hub_mean": float(per_line_hub.mean()),
|
| 400 |
+
"nonhub_mean": float(per_line_nonhub.mean()),
|
| 401 |
+
"wilcoxon_p": float(wsr_p),
|
| 402 |
+
"n_hub_more_essential": int(n_hub_more_essential),
|
| 403 |
+
"bootstrap_p": float(boot_p),
|
| 404 |
+
"obs_diff": float(obs_diff),
|
| 405 |
+
"per_hub_profile": per_hub_profile,
|
| 406 |
+
}
|
| 407 |
+
|
| 408 |
+
save_results(all_results, "depmap_stratified")
|
| 409 |
+
|
| 410 |
+
# Figure: 2x2 panel
|
| 411 |
+
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
|
| 412 |
+
|
| 413 |
+
# Panel A: MYCN vs non-MYCN hub dependency
|
| 414 |
+
ax = axes[0, 0]
|
| 415 |
+
bp = ax.boxplot([mycn_hub_deps.values, nonmycn_hub_deps.values],
|
| 416 |
+
tick_labels=["MYCN-amp", "Non-MYCN"], patch_artist=True)
|
| 417 |
+
bp["boxes"][0].set_facecolor("salmon")
|
| 418 |
+
bp["boxes"][1].set_facecolor("lightblue")
|
| 419 |
+
ax.set_ylabel("Mean Hub RBP Dependency")
|
| 420 |
+
ax.set_title(f"MYCN Stratification (p={mw_p:.4f})")
|
| 421 |
+
|
| 422 |
+
# Panel B: Lineage comparison
|
| 423 |
+
ax = axes[0, 1]
|
| 424 |
+
positions = range(len(lineage_hub_deps))
|
| 425 |
+
bp = ax.boxplot(lineage_hub_deps.values(), tick_labels=lineage_hub_deps.keys(), patch_artist=True)
|
| 426 |
+
colors = ["#ff9999", "#99ccff", "#99ff99"]
|
| 427 |
+
for patch, c in zip(bp["boxes"], colors):
|
| 428 |
+
patch.set_facecolor(c)
|
| 429 |
+
ax.set_ylabel("Mean Hub RBP Dependency")
|
| 430 |
+
ax.set_title(f"Lineage Specificity (KW p={kw_p:.4f})")
|
| 431 |
+
|
| 432 |
+
# Panel C: Hub vs non-hub across lines
|
| 433 |
+
ax = axes[1, 0]
|
| 434 |
+
ax.scatter(per_line_nonhub, per_line_hub, alpha=0.7, s=40, c="steelblue")
|
| 435 |
+
lims = [min(per_line_nonhub.min(), per_line_hub.min()) - 0.1,
|
| 436 |
+
max(per_line_nonhub.max(), per_line_hub.max()) + 0.1]
|
| 437 |
+
ax.plot(lims, lims, "k--", alpha=0.5, linewidth=1)
|
| 438 |
+
ax.set_xlabel("Mean Non-Hub Dependency")
|
| 439 |
+
ax.set_ylabel("Mean Hub RBP Dependency")
|
| 440 |
+
ax.set_title(f"Hub vs Non-Hub ({n_hub_more_essential}/{len(per_line_hub)} lines, Wilcoxon p={wsr_p:.2e})")
|
| 441 |
+
|
| 442 |
+
# Panel D: Per-hub essentiality profile
|
| 443 |
+
ax = axes[1, 1]
|
| 444 |
+
sorted_profile = sorted(per_hub_profile, key=lambda x: x["frac_essential"], reverse=True)
|
| 445 |
+
rbp_names = [x["rbp"] for x in sorted_profile]
|
| 446 |
+
frac_vals = [x["frac_essential"] for x in sorted_profile]
|
| 447 |
+
ax.barh(range(len(rbp_names)), frac_vals, color="steelblue", alpha=0.8)
|
| 448 |
+
ax.set_yticks(range(len(rbp_names)))
|
| 449 |
+
ax.set_yticklabels(rbp_names, fontsize=7)
|
| 450 |
+
ax.set_xlabel("Fraction of NB Lines Where Essential (dep < -0.5)")
|
| 451 |
+
ax.set_title("Per-Hub Essentiality Profile")
|
| 452 |
+
ax.invert_yaxis()
|
| 453 |
+
|
| 454 |
+
fig.suptitle("DepMap Stratified Neuroblastoma Analysis", fontsize=14)
|
| 455 |
+
plt.tight_layout()
|
| 456 |
+
save_fig(fig, "depmap_stratified")
|
| 457 |
+
|
| 458 |
+
return all_results
|
| 459 |
+
|
| 460 |
+
|
| 461 |
+
# ---------------------------------------------------------------------------
|
| 462 |
+
# Experiment 3: eCLIP Edge-Strength Concordance
|
| 463 |
+
# ---------------------------------------------------------------------------
|
| 464 |
+
|
| 465 |
+
def experiment3_eclip_edge_strength():
|
| 466 |
+
"""eCLIP edge-strength concordance across all datasets."""
|
| 467 |
+
print("\n" + "=" * 60)
|
| 468 |
+
print("EXPERIMENT 3: eCLIP Edge-Strength Concordance")
|
| 469 |
+
print("=" * 60)
|
| 470 |
+
|
| 471 |
+
set_figure_style()
|
| 472 |
+
|
| 473 |
+
eclip = pd.read_csv(DATA_DIR / "eclip_targets.csv")
|
| 474 |
+
eclip_pairs = set(zip(eclip["rbp"].str.upper(), eclip["target_gene"].str.upper()))
|
| 475 |
+
eclip_rbps = set(eclip["rbp"].str.upper())
|
| 476 |
+
print(f" eCLIP data: {len(eclip_pairs)} pairs, {len(eclip_rbps)} RBPs")
|
| 477 |
+
|
| 478 |
+
datasets = ["pancreas", "dentate_gyrus", "neuroblastoma"]
|
| 479 |
+
all_results = {}
|
| 480 |
+
|
| 481 |
+
for ds_name in datasets:
|
| 482 |
+
print(f"\n--- {ds_name} ---")
|
| 483 |
+
net = load_network(ds_name)
|
| 484 |
+
net["rbp_upper"] = net["rbp"].str.upper()
|
| 485 |
+
net["target_upper"] = net["target"].str.upper()
|
| 486 |
+
net["abs_r"] = net["r"].abs()
|
| 487 |
+
|
| 488 |
+
# Filter to RBPs present in both network and eCLIP
|
| 489 |
+
net_rbps = set(net["rbp_upper"].unique())
|
| 490 |
+
shared_rbps = net_rbps & eclip_rbps
|
| 491 |
+
print(f" Network RBPs: {len(net_rbps)}, shared with eCLIP: {len(shared_rbps)}")
|
| 492 |
+
|
| 493 |
+
if len(shared_rbps) == 0:
|
| 494 |
+
print(" No shared RBPs, skipping")
|
| 495 |
+
all_results[ds_name] = {"shared_rbps": 0}
|
| 496 |
+
continue
|
| 497 |
+
|
| 498 |
+
net_shared = net[net["rbp_upper"].isin(shared_rbps)].copy()
|
| 499 |
+
net_shared["eclip_confirmed"] = net_shared.apply(
|
| 500 |
+
lambda row: (row["rbp_upper"], row["target_upper"]) in eclip_pairs, axis=1
|
| 501 |
+
)
|
| 502 |
+
n_confirmed = net_shared["eclip_confirmed"].sum()
|
| 503 |
+
n_not = (~net_shared["eclip_confirmed"]).sum()
|
| 504 |
+
print(f" Edges in shared RBPs: {len(net_shared)}, eCLIP-confirmed: {n_confirmed}")
|
| 505 |
+
|
| 506 |
+
if n_confirmed < 3:
|
| 507 |
+
print(" Too few eCLIP-confirmed edges, skipping")
|
| 508 |
+
all_results[ds_name] = {"shared_rbps": len(shared_rbps), "eclip_confirmed": int(n_confirmed)}
|
| 509 |
+
continue
|
| 510 |
+
|
| 511 |
+
# Aggregate MW on |r|
|
| 512 |
+
confirmed_r = net_shared[net_shared["eclip_confirmed"]]["abs_r"]
|
| 513 |
+
not_confirmed_r = net_shared[~net_shared["eclip_confirmed"]]["abs_r"]
|
| 514 |
+
mw_stat, mw_p = stats.mannwhitneyu(confirmed_r, not_confirmed_r, alternative="greater")
|
| 515 |
+
print(f" Aggregate MW (|r|): confirmed={confirmed_r.median():.4f}, "
|
| 516 |
+
f"not={not_confirmed_r.median():.4f}, p={mw_p:.4f}")
|
| 517 |
+
|
| 518 |
+
# Per-RBP MW
|
| 519 |
+
per_rbp_results = []
|
| 520 |
+
for rbp in shared_rbps:
|
| 521 |
+
rbp_edges = net_shared[net_shared["rbp_upper"] == rbp]
|
| 522 |
+
conf = rbp_edges[rbp_edges["eclip_confirmed"]]["abs_r"]
|
| 523 |
+
notc = rbp_edges[~rbp_edges["eclip_confirmed"]]["abs_r"]
|
| 524 |
+
if len(conf) >= 3 and len(notc) >= 3:
|
| 525 |
+
_, pp = stats.mannwhitneyu(conf, notc, alternative="greater")
|
| 526 |
+
per_rbp_results.append({
|
| 527 |
+
"rbp": rbp, "n_conf": len(conf), "n_notc": len(notc),
|
| 528 |
+
"conf_median": float(conf.median()), "notc_median": float(notc.median()),
|
| 529 |
+
"mw_p": float(pp),
|
| 530 |
+
})
|
| 531 |
+
|
| 532 |
+
n_sig_rbp = sum(1 for x in per_rbp_results if x["mw_p"] < 0.05)
|
| 533 |
+
print(f" Per-RBP: {len(per_rbp_results)} testable, {n_sig_rbp} significant")
|
| 534 |
+
|
| 535 |
+
# Rank enrichment: for edges sorted by |r| descending, mean rank percentile of eCLIP-confirmed
|
| 536 |
+
net_shared_sorted = net_shared.sort_values("abs_r", ascending=False).reset_index(drop=True)
|
| 537 |
+
n_total = len(net_shared_sorted)
|
| 538 |
+
net_shared_sorted["rank_pctl"] = np.arange(1, n_total + 1) / n_total
|
| 539 |
+
confirmed_pctls = net_shared_sorted[net_shared_sorted["eclip_confirmed"]]["rank_pctl"]
|
| 540 |
+
mean_pctl = float(confirmed_pctls.mean())
|
| 541 |
+
# One-sample test: is mean percentile < 0.5 (i.e., enriched toward top)?
|
| 542 |
+
if len(confirmed_pctls) >= 3:
|
| 543 |
+
t_stat, t_p = stats.ttest_1samp(confirmed_pctls, 0.5)
|
| 544 |
+
rank_p = float(t_p / 2) if t_stat < 0 else float(1 - t_p / 2) # one-sided: < 0.5
|
| 545 |
+
else:
|
| 546 |
+
rank_p = float("nan")
|
| 547 |
+
print(f" Rank enrichment: mean percentile={mean_pctl:.4f} (0.5=random), p={rank_p:.4f}")
|
| 548 |
+
|
| 549 |
+
all_results[ds_name] = {
|
| 550 |
+
"shared_rbps": len(shared_rbps),
|
| 551 |
+
"n_edges_shared": len(net_shared),
|
| 552 |
+
"eclip_confirmed": int(n_confirmed),
|
| 553 |
+
"aggregate_mw": {
|
| 554 |
+
"confirmed_median_abs_r": float(confirmed_r.median()),
|
| 555 |
+
"not_confirmed_median_abs_r": float(not_confirmed_r.median()),
|
| 556 |
+
"mw_p": float(mw_p),
|
| 557 |
+
},
|
| 558 |
+
"per_rbp": per_rbp_results,
|
| 559 |
+
"n_sig_per_rbp": n_sig_rbp,
|
| 560 |
+
"rank_enrichment": {
|
| 561 |
+
"mean_percentile": mean_pctl,
|
| 562 |
+
"p": rank_p,
|
| 563 |
+
},
|
| 564 |
+
}
|
| 565 |
+
|
| 566 |
+
save_results(all_results, "eclip_edge_strength")
|
| 567 |
+
|
| 568 |
+
# Figure: bar chart of confirmed vs not-confirmed |r| per dataset
|
| 569 |
+
fig, axes = plt.subplots(1, 3, figsize=(14, 4.5))
|
| 570 |
+
for ax, ds_name in zip(axes, datasets):
|
| 571 |
+
res = all_results.get(ds_name, {})
|
| 572 |
+
if "aggregate_mw" not in res:
|
| 573 |
+
ax.text(0.5, 0.5, "Insufficient data", ha="center", va="center", transform=ax.transAxes)
|
| 574 |
+
ax.set_title(ds_name.replace("_", " ").title())
|
| 575 |
+
continue
|
| 576 |
+
vals = [res["aggregate_mw"]["confirmed_median_abs_r"],
|
| 577 |
+
res["aggregate_mw"]["not_confirmed_median_abs_r"]]
|
| 578 |
+
bars = ax.bar(["eCLIP\nConfirmed", "Not\nConfirmed"], vals,
|
| 579 |
+
color=["#e74c3c", "#95a5a6"], alpha=0.8)
|
| 580 |
+
ax.set_ylabel("Median |r|")
|
| 581 |
+
ax.set_title(f"{ds_name.replace('_', ' ').title()}\n(p={res['aggregate_mw']['mw_p']:.4f})")
|
| 582 |
+
ax.text(0.05, 0.95, f"n_conf={res['eclip_confirmed']}\nn_total={res['n_edges_shared']}",
|
| 583 |
+
transform=ax.transAxes, va="top", fontsize=8)
|
| 584 |
+
|
| 585 |
+
fig.suptitle("eCLIP Edge-Strength Concordance", fontsize=13)
|
| 586 |
+
plt.tight_layout()
|
| 587 |
+
save_fig(fig, "eclip_edge_strength")
|
| 588 |
+
|
| 589 |
+
return all_results
|
| 590 |
+
|
| 591 |
+
|
| 592 |
+
# ---------------------------------------------------------------------------
|
| 593 |
+
# Main
|
| 594 |
+
# ---------------------------------------------------------------------------
|
| 595 |
+
|
| 596 |
+
def main():
|
| 597 |
+
print("=" * 60)
|
| 598 |
+
print("WEAKNESS IMPROVEMENTS ANALYSIS")
|
| 599 |
+
print("=" * 60)
|
| 600 |
+
|
| 601 |
+
(OUTPUT_DIR / "results").mkdir(parents=True, exist_ok=True)
|
| 602 |
+
(OUTPUT_DIR / "figures").mkdir(parents=True, exist_ok=True)
|
| 603 |
+
|
| 604 |
+
utr_results = experiment1_edge_utr()
|
| 605 |
+
eclip_results = experiment3_eclip_edge_strength()
|
| 606 |
+
depmap_results = experiment2_depmap_stratified()
|
| 607 |
+
|
| 608 |
+
# Print summary
|
| 609 |
+
print("\n" + "=" * 60)
|
| 610 |
+
print("SUMMARY")
|
| 611 |
+
print("=" * 60)
|
| 612 |
+
|
| 613 |
+
print("\nExperiment 1 (Edge-Level UTR):")
|
| 614 |
+
for ds in ["pancreas", "dentate_gyrus", "neuroblastoma"]:
|
| 615 |
+
r = utr_results[ds]
|
| 616 |
+
print(f" {ds}: Test A r={r['test_a']['spearman_r']:.4f} (p={r['test_a']['p']:.2e})")
|
| 617 |
+
|
| 618 |
+
print("\nExperiment 2 (DepMap Stratified):")
|
| 619 |
+
mycn = depmap_results["mycn_stratified"]
|
| 620 |
+
print(f" MYCN stratification: p={mycn['mw_p']:.4f}")
|
| 621 |
+
lin = depmap_results["lineage_specificity"]
|
| 622 |
+
print(f" Lineage KW: p={lin['kruskal_wallis']['p']:.4f}")
|
| 623 |
+
cl = depmap_results["cross_line_consistency"]
|
| 624 |
+
print(f" Cross-line Wilcoxon: p={cl['wilcoxon_p']:.2e}")
|
| 625 |
+
print(f" Bootstrap: p={cl['bootstrap_p']:.4f}")
|
| 626 |
+
|
| 627 |
+
print("\nExperiment 3 (eCLIP Edge-Strength):")
|
| 628 |
+
for ds in ["pancreas", "dentate_gyrus", "neuroblastoma"]:
|
| 629 |
+
r = eclip_results.get(ds, {})
|
| 630 |
+
if "aggregate_mw" in r:
|
| 631 |
+
print(f" {ds}: MW p={r['aggregate_mw']['mw_p']:.4f}, "
|
| 632 |
+
f"rank pctl={r['rank_enrichment']['mean_percentile']:.4f}")
|
| 633 |
+
else:
|
| 634 |
+
print(f" {ds}: insufficient data")
|
| 635 |
+
|
| 636 |
+
print("\nDone! Output: output/weakness_improvements/")
|
| 637 |
+
|
| 638 |
+
|
| 639 |
+
if __name__ == "__main__":
|
| 640 |
+
main()
|
output/cross_dataset/cross_dataset_consistency.csv
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
dataset_a,dataset_b,n_shared_genes,spearman_r,pearson_r
|
| 2 |
+
dentate_gyrus,pancreas,4915,0.1915326245338796,0.10379851233846862
|
output/cross_dataset/summary.json
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"n_shared_genes": 4915,
|
| 3 |
+
"n_filtered_genes": 994,
|
| 4 |
+
"spearman_r_all": 0.1915326245338796,
|
| 5 |
+
"spearman_r_filtered": 0.6745011201887727,
|
| 6 |
+
"spearman_p_filtered": 7.011336577703446e-133
|
| 7 |
+
}
|
output/cross_platform/figures/cross_platform_gamma.png
ADDED
|
output/cross_platform/results/cross_platform_results.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"dataset_a": "pancreas_10x",
|
| 4 |
+
"dataset_b": "dg_10x",
|
| 5 |
+
"shared_genes": 4915,
|
| 6 |
+
"valid_genes": 994,
|
| 7 |
+
"gamma_r": 0.6745011201887727,
|
| 8 |
+
"expr_r": 0.6433814656754383,
|
| 9 |
+
"informative_gamma_r": 0.6745011201887727,
|
| 10 |
+
"stratified": [
|
| 11 |
+
{
|
| 12 |
+
"quartile": "Q1 (low)",
|
| 13 |
+
"gamma_r": 0.4367811892732219,
|
| 14 |
+
"expr_r": -0.28584143023707737,
|
| 15 |
+
"n_genes": 249
|
| 16 |
+
},
|
| 17 |
+
{
|
| 18 |
+
"quartile": "Q2",
|
| 19 |
+
"gamma_r": 0.34924181511208735,
|
| 20 |
+
"expr_r": -0.49077385682209046,
|
| 21 |
+
"n_genes": 248
|
| 22 |
+
},
|
| 23 |
+
{
|
| 24 |
+
"quartile": "Q3",
|
| 25 |
+
"gamma_r": 0.3194769413293765,
|
| 26 |
+
"expr_r": -0.44968931491933506,
|
| 27 |
+
"n_genes": 248
|
| 28 |
+
},
|
| 29 |
+
{
|
| 30 |
+
"quartile": "Q4 (high)",
|
| 31 |
+
"gamma_r": 0.6079183832102604,
|
| 32 |
+
"expr_r": 0.4562471822774971,
|
| 33 |
+
"n_genes": 249
|
| 34 |
+
}
|
| 35 |
+
]
|
| 36 |
+
}
|
| 37 |
+
]
|
output/deep_advantages/figures/pancreas_uncertainty_filtering.png
ADDED
|
output/deep_advantages/results/combined_advantages.json
ADDED
|
@@ -0,0 +1,740 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"pancreas": {
|
| 3 |
+
"uncertainty_filtering": {
|
| 4 |
+
"mouse": [
|
| 5 |
+
{
|
| 6 |
+
"threshold": "all",
|
| 7 |
+
"n_genes": 251,
|
| 8 |
+
"spearman_r": -0.1976794527336332
|
| 9 |
+
},
|
| 10 |
+
{
|
| 11 |
+
"threshold": "CV<1.0",
|
| 12 |
+
"n_genes": 251,
|
| 13 |
+
"spearman_r": -0.1976794527336332
|
| 14 |
+
},
|
| 15 |
+
{
|
| 16 |
+
"threshold": "CV<0.75",
|
| 17 |
+
"n_genes": 251,
|
| 18 |
+
"spearman_r": -0.1976794527336332
|
| 19 |
+
},
|
| 20 |
+
{
|
| 21 |
+
"threshold": "CV<0.5",
|
| 22 |
+
"n_genes": 250,
|
| 23 |
+
"spearman_r": -0.20403060683747745
|
| 24 |
+
},
|
| 25 |
+
{
|
| 26 |
+
"threshold": "CV<0.3",
|
| 27 |
+
"n_genes": 250,
|
| 28 |
+
"spearman_r": -0.20403060683747745
|
| 29 |
+
},
|
| 30 |
+
{
|
| 31 |
+
"threshold": "CV<0.2",
|
| 32 |
+
"n_genes": 249,
|
| 33 |
+
"spearman_r": -0.20048744674989727
|
| 34 |
+
},
|
| 35 |
+
{
|
| 36 |
+
"threshold": "bottom_75pct_CV",
|
| 37 |
+
"n_genes": 188,
|
| 38 |
+
"spearman_r": -0.2188323427372238
|
| 39 |
+
},
|
| 40 |
+
{
|
| 41 |
+
"threshold": "bottom_50pct_CV",
|
| 42 |
+
"n_genes": 126,
|
| 43 |
+
"spearman_r": -0.23679940007499065
|
| 44 |
+
},
|
| 45 |
+
{
|
| 46 |
+
"threshold": "bottom_25pct_CV",
|
| 47 |
+
"n_genes": 63,
|
| 48 |
+
"spearman_r": -0.2579205069124424
|
| 49 |
+
}
|
| 50 |
+
],
|
| 51 |
+
"human": [
|
| 52 |
+
{
|
| 53 |
+
"threshold": "all",
|
| 54 |
+
"n_genes": 236,
|
| 55 |
+
"spearman_r": -0.27676078712075614
|
| 56 |
+
},
|
| 57 |
+
{
|
| 58 |
+
"threshold": "CV<1.0",
|
| 59 |
+
"n_genes": 236,
|
| 60 |
+
"spearman_r": -0.27676078712075614
|
| 61 |
+
},
|
| 62 |
+
{
|
| 63 |
+
"threshold": "CV<0.75",
|
| 64 |
+
"n_genes": 236,
|
| 65 |
+
"spearman_r": -0.27676078712075614
|
| 66 |
+
},
|
| 67 |
+
{
|
| 68 |
+
"threshold": "CV<0.5",
|
| 69 |
+
"n_genes": 236,
|
| 70 |
+
"spearman_r": -0.27676078712075614
|
| 71 |
+
},
|
| 72 |
+
{
|
| 73 |
+
"threshold": "CV<0.3",
|
| 74 |
+
"n_genes": 236,
|
| 75 |
+
"spearman_r": -0.27676078712075614
|
| 76 |
+
},
|
| 77 |
+
{
|
| 78 |
+
"threshold": "CV<0.2",
|
| 79 |
+
"n_genes": 235,
|
| 80 |
+
"spearman_r": -0.27315207370726874
|
| 81 |
+
},
|
| 82 |
+
{
|
| 83 |
+
"threshold": "bottom_75pct_CV",
|
| 84 |
+
"n_genes": 177,
|
| 85 |
+
"spearman_r": -0.2899948602809608
|
| 86 |
+
},
|
| 87 |
+
{
|
| 88 |
+
"threshold": "bottom_50pct_CV",
|
| 89 |
+
"n_genes": 118,
|
| 90 |
+
"spearman_r": -0.2920772191767282
|
| 91 |
+
},
|
| 92 |
+
{
|
| 93 |
+
"threshold": "bottom_25pct_CV",
|
| 94 |
+
"n_genes": 59,
|
| 95 |
+
"spearman_r": -0.3545931413753938
|
| 96 |
+
}
|
| 97 |
+
]
|
| 98 |
+
},
|
| 99 |
+
"cell_resolution": {
|
| 100 |
+
"per_celltype_cv": [
|
| 101 |
+
{
|
| 102 |
+
"cell_type": "Alpha",
|
| 103 |
+
"n_cells": 481,
|
| 104 |
+
"median_cv_analytical": 0.5714874267578125,
|
| 105 |
+
"median_cv_deepptr": 0.025115380063652992,
|
| 106 |
+
"mean_cv_analytical": 0.6858336329460144,
|
| 107 |
+
"mean_cv_deepptr": 0.02439717948436737
|
| 108 |
+
},
|
| 109 |
+
{
|
| 110 |
+
"cell_type": "Beta",
|
| 111 |
+
"n_cells": 591,
|
| 112 |
+
"median_cv_analytical": 0.5670088529586792,
|
| 113 |
+
"median_cv_deepptr": 0.1528678983449936,
|
| 114 |
+
"mean_cv_analytical": 0.6656529903411865,
|
| 115 |
+
"mean_cv_deepptr": 0.1491723507642746
|
| 116 |
+
},
|
| 117 |
+
{
|
| 118 |
+
"cell_type": "Delta",
|
| 119 |
+
"n_cells": 70,
|
| 120 |
+
"median_cv_analytical": 0.44335344433784485,
|
| 121 |
+
"median_cv_deepptr": 0.02082059532403946,
|
| 122 |
+
"mean_cv_analytical": 0.7205255031585693,
|
| 123 |
+
"mean_cv_deepptr": 0.01994817890226841
|
| 124 |
+
},
|
| 125 |
+
{
|
| 126 |
+
"cell_type": "Ductal",
|
| 127 |
+
"n_cells": 916,
|
| 128 |
+
"median_cv_analytical": 0.6001600027084351,
|
| 129 |
+
"median_cv_deepptr": 0.02392425574362278,
|
| 130 |
+
"mean_cv_analytical": 1.5890529155731201,
|
| 131 |
+
"mean_cv_deepptr": 0.022229716181755066
|
| 132 |
+
},
|
| 133 |
+
{
|
| 134 |
+
"cell_type": "Epsilon",
|
| 135 |
+
"n_cells": 142,
|
| 136 |
+
"median_cv_analytical": 0.5356361865997314,
|
| 137 |
+
"median_cv_deepptr": 0.02128124237060547,
|
| 138 |
+
"mean_cv_analytical": 0.7422852516174316,
|
| 139 |
+
"mean_cv_deepptr": 0.02046981453895569
|
| 140 |
+
},
|
| 141 |
+
{
|
| 142 |
+
"cell_type": "Ngn3 high EP",
|
| 143 |
+
"n_cells": 642,
|
| 144 |
+
"median_cv_analytical": 0.6426572799682617,
|
| 145 |
+
"median_cv_deepptr": 0.023328058421611786,
|
| 146 |
+
"mean_cv_analytical": 0.9787807464599609,
|
| 147 |
+
"mean_cv_deepptr": 0.021852802485227585
|
| 148 |
+
},
|
| 149 |
+
{
|
| 150 |
+
"cell_type": "Ngn3 low EP",
|
| 151 |
+
"n_cells": 262,
|
| 152 |
+
"median_cv_analytical": 0.5913139581680298,
|
| 153 |
+
"median_cv_deepptr": 0.025578055530786514,
|
| 154 |
+
"mean_cv_analytical": 1.2815543413162231,
|
| 155 |
+
"mean_cv_deepptr": 0.023869693279266357
|
| 156 |
+
},
|
| 157 |
+
{
|
| 158 |
+
"cell_type": "Pre-endocrine",
|
| 159 |
+
"n_cells": 592,
|
| 160 |
+
"median_cv_analytical": 0.6505065560340881,
|
| 161 |
+
"median_cv_deepptr": 0.019794151186943054,
|
| 162 |
+
"mean_cv_analytical": 0.8129222393035889,
|
| 163 |
+
"mean_cv_deepptr": 0.018655382096767426
|
| 164 |
+
}
|
| 165 |
+
],
|
| 166 |
+
"anova_n_tested": 300,
|
| 167 |
+
"anova_n_sig_analytical": 299,
|
| 168 |
+
"anova_n_sig_deepptr": 300,
|
| 169 |
+
"anova_median_F_analytical": NaN,
|
| 170 |
+
"anova_median_F_deepptr": 244.958740234375
|
| 171 |
+
},
|
| 172 |
+
"disentanglement": {
|
| 173 |
+
"n_pt_specific_genes": 44,
|
| 174 |
+
"pt_specific_genes": [
|
| 175 |
+
"Adgrb3",
|
| 176 |
+
"Pard3b",
|
| 177 |
+
"Tmtc2",
|
| 178 |
+
"Rfx6",
|
| 179 |
+
"Trim17",
|
| 180 |
+
"Ptprn2",
|
| 181 |
+
"Dip2c",
|
| 182 |
+
"Fhit",
|
| 183 |
+
"Ube2e2",
|
| 184 |
+
"Kcnma1",
|
| 185 |
+
"Farp1",
|
| 186 |
+
"Rims2",
|
| 187 |
+
"Igf2bp2",
|
| 188 |
+
"Stxbp5l",
|
| 189 |
+
"Phldb2",
|
| 190 |
+
"Fbxl17",
|
| 191 |
+
"Park2",
|
| 192 |
+
"Dnajc1",
|
| 193 |
+
"Pbx3",
|
| 194 |
+
"Nr6a1",
|
| 195 |
+
"Kcnh7",
|
| 196 |
+
"9030622O22Rik",
|
| 197 |
+
"Zmynd8",
|
| 198 |
+
"Mbd5",
|
| 199 |
+
"Macrod2",
|
| 200 |
+
"Hnf1aos1",
|
| 201 |
+
"Sdk1",
|
| 202 |
+
"Pan3",
|
| 203 |
+
"Chchd3",
|
| 204 |
+
"Tmcc1",
|
| 205 |
+
"Cacna1c",
|
| 206 |
+
"Snd1",
|
| 207 |
+
"Ccser1",
|
| 208 |
+
"Ttll3",
|
| 209 |
+
"Tmc4",
|
| 210 |
+
"Egln2",
|
| 211 |
+
"Gm21974",
|
| 212 |
+
"Ins2",
|
| 213 |
+
"Kcnq1",
|
| 214 |
+
"Fam155a",
|
| 215 |
+
"Wwox",
|
| 216 |
+
"Tcf12",
|
| 217 |
+
"Rora",
|
| 218 |
+
"Mid1"
|
| 219 |
+
],
|
| 220 |
+
"n_t_specific_genes": 6,
|
| 221 |
+
"t_specific_genes": [
|
| 222 |
+
"Sphkap",
|
| 223 |
+
"2010111I01Rik",
|
| 224 |
+
"Myh9",
|
| 225 |
+
"Nol4",
|
| 226 |
+
"Itpr1",
|
| 227 |
+
"Abcc8"
|
| 228 |
+
],
|
| 229 |
+
"pt_vs_expr_ari": 0.077128756533571,
|
| 230 |
+
"pt_vs_expr_nmi": 0.18141567420189855,
|
| 231 |
+
"n_pt_de_genes": 300,
|
| 232 |
+
"top_pt_de_genes": [
|
| 233 |
+
{
|
| 234 |
+
"gene": "Tmtc2",
|
| 235 |
+
"H_statistic": 1507.252819412225,
|
| 236 |
+
"p_value": 0.0,
|
| 237 |
+
"fold_change": 1.1294591426849365
|
| 238 |
+
},
|
| 239 |
+
{
|
| 240 |
+
"gene": "Ptprn2",
|
| 241 |
+
"H_statistic": 1453.3068265183394,
|
| 242 |
+
"p_value": 0.0,
|
| 243 |
+
"fold_change": 1.3904372453689575
|
| 244 |
+
},
|
| 245 |
+
{
|
| 246 |
+
"gene": "Fhit",
|
| 247 |
+
"H_statistic": 1457.3716975334148,
|
| 248 |
+
"p_value": 0.0,
|
| 249 |
+
"fold_change": 1.5617649555206299
|
| 250 |
+
},
|
| 251 |
+
{
|
| 252 |
+
"gene": "Park2",
|
| 253 |
+
"H_statistic": 1606.4708053162663,
|
| 254 |
+
"p_value": 0.0,
|
| 255 |
+
"fold_change": 1.0910781621932983
|
| 256 |
+
},
|
| 257 |
+
{
|
| 258 |
+
"gene": "Macrod2",
|
| 259 |
+
"H_statistic": 1498.6858013593953,
|
| 260 |
+
"p_value": 0.0,
|
| 261 |
+
"fold_change": 1.4149595499038696
|
| 262 |
+
},
|
| 263 |
+
{
|
| 264 |
+
"gene": "Chchd3",
|
| 265 |
+
"H_statistic": 1502.83262766733,
|
| 266 |
+
"p_value": 0.0,
|
| 267 |
+
"fold_change": 1.3431732654571533
|
| 268 |
+
},
|
| 269 |
+
{
|
| 270 |
+
"gene": "Snd1",
|
| 271 |
+
"H_statistic": 1510.9010872869696,
|
| 272 |
+
"p_value": 0.0,
|
| 273 |
+
"fold_change": 1.5494927167892456
|
| 274 |
+
},
|
| 275 |
+
{
|
| 276 |
+
"gene": "Ins2",
|
| 277 |
+
"H_statistic": 1623.6118081391649,
|
| 278 |
+
"p_value": 0.0,
|
| 279 |
+
"fold_change": 26.470022201538086
|
| 280 |
+
},
|
| 281 |
+
{
|
| 282 |
+
"gene": "Rora",
|
| 283 |
+
"H_statistic": 1491.8154065673139,
|
| 284 |
+
"p_value": 0.0,
|
| 285 |
+
"fold_change": 2.022663116455078
|
| 286 |
+
},
|
| 287 |
+
{
|
| 288 |
+
"gene": "9030622O22Rik",
|
| 289 |
+
"H_statistic": 1407.8904321594011,
|
| 290 |
+
"p_value": 5.714914382762847e-305,
|
| 291 |
+
"fold_change": 1.4340518712997437
|
| 292 |
+
},
|
| 293 |
+
{
|
| 294 |
+
"gene": "Fbxl17",
|
| 295 |
+
"H_statistic": 1401.9243404754493,
|
| 296 |
+
"p_value": 1.1261837231059974e-303,
|
| 297 |
+
"fold_change": 1.3535175323486328
|
| 298 |
+
},
|
| 299 |
+
{
|
| 300 |
+
"gene": "Ccser1",
|
| 301 |
+
"H_statistic": 1380.9660261606807,
|
| 302 |
+
"p_value": 3.975422350686473e-299,
|
| 303 |
+
"fold_change": 1.1388120651245117
|
| 304 |
+
},
|
| 305 |
+
{
|
| 306 |
+
"gene": "Kcnh7",
|
| 307 |
+
"H_statistic": 1372.331765284582,
|
| 308 |
+
"p_value": 2.9711818753969144e-297,
|
| 309 |
+
"fold_change": 1.4253920316696167
|
| 310 |
+
},
|
| 311 |
+
{
|
| 312 |
+
"gene": "Egln2",
|
| 313 |
+
"H_statistic": 1370.996708502923,
|
| 314 |
+
"p_value": 5.789247134264385e-297,
|
| 315 |
+
"fold_change": 1.140360713005066
|
| 316 |
+
},
|
| 317 |
+
{
|
| 318 |
+
"gene": "Stxbp5l",
|
| 319 |
+
"H_statistic": 1352.9608944222837,
|
| 320 |
+
"p_value": 4.744363813172019e-293,
|
| 321 |
+
"fold_change": 1.7202287912368774
|
| 322 |
+
},
|
| 323 |
+
{
|
| 324 |
+
"gene": "Sdk1",
|
| 325 |
+
"H_statistic": 1308.955208207672,
|
| 326 |
+
"p_value": 1.6777292275912853e-283,
|
| 327 |
+
"fold_change": 1.119990348815918
|
| 328 |
+
},
|
| 329 |
+
{
|
| 330 |
+
"gene": "Rims2",
|
| 331 |
+
"H_statistic": 1308.903145513165,
|
| 332 |
+
"p_value": 1.7219419918267907e-283,
|
| 333 |
+
"fold_change": 1.3955633640289307
|
| 334 |
+
},
|
| 335 |
+
{
|
| 336 |
+
"gene": "Rfx6",
|
| 337 |
+
"H_statistic": 1302.83531755728,
|
| 338 |
+
"p_value": 3.56962967882861e-282,
|
| 339 |
+
"fold_change": 1.488563895225525
|
| 340 |
+
},
|
| 341 |
+
{
|
| 342 |
+
"gene": "Wwox",
|
| 343 |
+
"H_statistic": 1302.6454148099776,
|
| 344 |
+
"p_value": 3.924898371957578e-282,
|
| 345 |
+
"fold_change": 1.4599933624267578
|
| 346 |
+
},
|
| 347 |
+
{
|
| 348 |
+
"gene": "Gm21974",
|
| 349 |
+
"H_statistic": 1289.113859857544,
|
| 350 |
+
"p_value": 3.3876946827012372e-279,
|
| 351 |
+
"fold_change": 1.0790175199508667
|
| 352 |
+
}
|
| 353 |
+
]
|
| 354 |
+
},
|
| 355 |
+
"statistical_testing": {
|
| 356 |
+
"ct_a": "Ductal",
|
| 357 |
+
"ct_b": "Ngn3 high EP",
|
| 358 |
+
"n_cells_a": 916,
|
| 359 |
+
"n_cells_b": 642,
|
| 360 |
+
"n_sig_posterior": 0,
|
| 361 |
+
"n_sig_ttest": 288,
|
| 362 |
+
"n_both": 0,
|
| 363 |
+
"n_posterior_only": 0,
|
| 364 |
+
"n_ttest_only": 288
|
| 365 |
+
}
|
| 366 |
+
},
|
| 367 |
+
"dentate_gyrus": {
|
| 368 |
+
"uncertainty_filtering": {
|
| 369 |
+
"mouse": [
|
| 370 |
+
{
|
| 371 |
+
"threshold": "all",
|
| 372 |
+
"n_genes": 216,
|
| 373 |
+
"spearman_r": -0.28539711439597953
|
| 374 |
+
},
|
| 375 |
+
{
|
| 376 |
+
"threshold": "CV<1.0",
|
| 377 |
+
"n_genes": 216,
|
| 378 |
+
"spearman_r": -0.28539711439597953
|
| 379 |
+
},
|
| 380 |
+
{
|
| 381 |
+
"threshold": "CV<0.75",
|
| 382 |
+
"n_genes": 216,
|
| 383 |
+
"spearman_r": -0.28539711439597953
|
| 384 |
+
},
|
| 385 |
+
{
|
| 386 |
+
"threshold": "CV<0.5",
|
| 387 |
+
"n_genes": 216,
|
| 388 |
+
"spearman_r": -0.28539711439597953
|
| 389 |
+
},
|
| 390 |
+
{
|
| 391 |
+
"threshold": "CV<0.3",
|
| 392 |
+
"n_genes": 215,
|
| 393 |
+
"spearman_r": -0.2859663877215545
|
| 394 |
+
},
|
| 395 |
+
{
|
| 396 |
+
"threshold": "CV<0.2",
|
| 397 |
+
"n_genes": 194,
|
| 398 |
+
"spearman_r": -0.2808110516824419
|
| 399 |
+
},
|
| 400 |
+
{
|
| 401 |
+
"threshold": "bottom_75pct_CV",
|
| 402 |
+
"n_genes": 162,
|
| 403 |
+
"spearman_r": -0.27764299756831096
|
| 404 |
+
},
|
| 405 |
+
{
|
| 406 |
+
"threshold": "bottom_50pct_CV",
|
| 407 |
+
"n_genes": 108,
|
| 408 |
+
"spearman_r": -0.1943849019215563
|
| 409 |
+
},
|
| 410 |
+
{
|
| 411 |
+
"threshold": "bottom_25pct_CV",
|
| 412 |
+
"n_genes": 54,
|
| 413 |
+
"spearman_r": -0.18749761768629689
|
| 414 |
+
}
|
| 415 |
+
],
|
| 416 |
+
"human": [
|
| 417 |
+
{
|
| 418 |
+
"threshold": "all",
|
| 419 |
+
"n_genes": 211,
|
| 420 |
+
"spearman_r": -0.3582097560135093
|
| 421 |
+
},
|
| 422 |
+
{
|
| 423 |
+
"threshold": "CV<1.0",
|
| 424 |
+
"n_genes": 211,
|
| 425 |
+
"spearman_r": -0.3582097560135093
|
| 426 |
+
},
|
| 427 |
+
{
|
| 428 |
+
"threshold": "CV<0.75",
|
| 429 |
+
"n_genes": 211,
|
| 430 |
+
"spearman_r": -0.3582097560135093
|
| 431 |
+
},
|
| 432 |
+
{
|
| 433 |
+
"threshold": "CV<0.5",
|
| 434 |
+
"n_genes": 211,
|
| 435 |
+
"spearman_r": -0.3582097560135093
|
| 436 |
+
},
|
| 437 |
+
{
|
| 438 |
+
"threshold": "CV<0.3",
|
| 439 |
+
"n_genes": 210,
|
| 440 |
+
"spearman_r": -0.3577103191264039
|
| 441 |
+
},
|
| 442 |
+
{
|
| 443 |
+
"threshold": "CV<0.2",
|
| 444 |
+
"n_genes": 192,
|
| 445 |
+
"spearman_r": -0.3587221254393612
|
| 446 |
+
},
|
| 447 |
+
{
|
| 448 |
+
"threshold": "bottom_75pct_CV",
|
| 449 |
+
"n_genes": 158,
|
| 450 |
+
"spearman_r": -0.39997839837699434
|
| 451 |
+
},
|
| 452 |
+
{
|
| 453 |
+
"threshold": "bottom_50pct_CV",
|
| 454 |
+
"n_genes": 106,
|
| 455 |
+
"spearman_r": -0.31398500635210375
|
| 456 |
+
},
|
| 457 |
+
{
|
| 458 |
+
"threshold": "bottom_25pct_CV",
|
| 459 |
+
"n_genes": 53,
|
| 460 |
+
"spearman_r": -0.08974539885307645
|
| 461 |
+
}
|
| 462 |
+
]
|
| 463 |
+
},
|
| 464 |
+
"cell_resolution": {
|
| 465 |
+
"per_celltype_cv": [
|
| 466 |
+
{
|
| 467 |
+
"cell_type": "Astrocytes",
|
| 468 |
+
"n_cells": 120,
|
| 469 |
+
"median_cv_analytical": 1.037865400314331,
|
| 470 |
+
"median_cv_deepptr": 0.04969271644949913,
|
| 471 |
+
"mean_cv_analytical": 2.065990686416626,
|
| 472 |
+
"mean_cv_deepptr": 0.042765166610479355
|
| 473 |
+
},
|
| 474 |
+
{
|
| 475 |
+
"cell_type": "Cajal Retzius",
|
| 476 |
+
"n_cells": 34,
|
| 477 |
+
"median_cv_analytical": 0.9581262469291687,
|
| 478 |
+
"median_cv_deepptr": 0.035210926085710526,
|
| 479 |
+
"mean_cv_analytical": 1.2425631284713745,
|
| 480 |
+
"mean_cv_deepptr": 0.03074895218014717
|
| 481 |
+
},
|
| 482 |
+
{
|
| 483 |
+
"cell_type": "Cck-Tox",
|
| 484 |
+
"n_cells": 27,
|
| 485 |
+
"median_cv_analytical": 0.5500394701957703,
|
| 486 |
+
"median_cv_deepptr": 0.03183512017130852,
|
| 487 |
+
"mean_cv_analytical": 0.7018951177597046,
|
| 488 |
+
"mean_cv_deepptr": 0.02709321305155754
|
| 489 |
+
},
|
| 490 |
+
{
|
| 491 |
+
"cell_type": "Endothelial",
|
| 492 |
+
"n_cells": 87,
|
| 493 |
+
"median_cv_analytical": 1.258206844329834,
|
| 494 |
+
"median_cv_deepptr": 0.07260799407958984,
|
| 495 |
+
"mean_cv_analytical": 1.3927289247512817,
|
| 496 |
+
"mean_cv_deepptr": 0.06094517558813095
|
| 497 |
+
},
|
| 498 |
+
{
|
| 499 |
+
"cell_type": "GABA",
|
| 500 |
+
"n_cells": 61,
|
| 501 |
+
"median_cv_analytical": 0.6232943534851074,
|
| 502 |
+
"median_cv_deepptr": 0.03612678125500679,
|
| 503 |
+
"mean_cv_analytical": 0.7454152703285217,
|
| 504 |
+
"mean_cv_deepptr": 0.031618811190128326
|
| 505 |
+
},
|
| 506 |
+
{
|
| 507 |
+
"cell_type": "Granule immature",
|
| 508 |
+
"n_cells": 785,
|
| 509 |
+
"median_cv_analytical": 0.6696305274963379,
|
| 510 |
+
"median_cv_deepptr": 0.030969148501753807,
|
| 511 |
+
"mean_cv_analytical": 0.7786616086959839,
|
| 512 |
+
"mean_cv_deepptr": 0.026258107274770737
|
| 513 |
+
},
|
| 514 |
+
{
|
| 515 |
+
"cell_type": "Granule mature",
|
| 516 |
+
"n_cells": 1070,
|
| 517 |
+
"median_cv_analytical": 0.7358936667442322,
|
| 518 |
+
"median_cv_deepptr": 0.029770459979772568,
|
| 519 |
+
"mean_cv_analytical": 0.8565810322761536,
|
| 520 |
+
"mean_cv_deepptr": 0.025847110897302628
|
| 521 |
+
},
|
| 522 |
+
{
|
| 523 |
+
"cell_type": "Microglia",
|
| 524 |
+
"n_cells": 81,
|
| 525 |
+
"median_cv_analytical": 2.034620523452759,
|
| 526 |
+
"median_cv_deepptr": 0.05030623823404312,
|
| 527 |
+
"mean_cv_analytical": 2.5370047092437744,
|
| 528 |
+
"mean_cv_deepptr": 0.04304993152618408
|
| 529 |
+
},
|
| 530 |
+
{
|
| 531 |
+
"cell_type": "Mossy",
|
| 532 |
+
"n_cells": 75,
|
| 533 |
+
"median_cv_analytical": 0.5362868309020996,
|
| 534 |
+
"median_cv_deepptr": 0.033555418252944946,
|
| 535 |
+
"mean_cv_analytical": 0.6848810315132141,
|
| 536 |
+
"mean_cv_deepptr": 0.02926183119416237
|
| 537 |
+
},
|
| 538 |
+
{
|
| 539 |
+
"cell_type": "Neuroblast",
|
| 540 |
+
"n_cells": 417,
|
| 541 |
+
"median_cv_analytical": 0.8587535619735718,
|
| 542 |
+
"median_cv_deepptr": 0.04170010983943939,
|
| 543 |
+
"mean_cv_analytical": 1.133716106414795,
|
| 544 |
+
"mean_cv_deepptr": 0.035205960273742676
|
| 545 |
+
},
|
| 546 |
+
{
|
| 547 |
+
"cell_type": "OL",
|
| 548 |
+
"n_cells": 50,
|
| 549 |
+
"median_cv_analytical": 1.3443996906280518,
|
| 550 |
+
"median_cv_deepptr": 0.06878548860549927,
|
| 551 |
+
"mean_cv_analytical": 1.9315539598464966,
|
| 552 |
+
"mean_cv_deepptr": 0.060547325760126114
|
| 553 |
+
},
|
| 554 |
+
{
|
| 555 |
+
"cell_type": "OPC",
|
| 556 |
+
"n_cells": 53,
|
| 557 |
+
"median_cv_analytical": 0.815068244934082,
|
| 558 |
+
"median_cv_deepptr": 0.0567430779337883,
|
| 559 |
+
"mean_cv_analytical": 1.5061569213867188,
|
| 560 |
+
"mean_cv_deepptr": 0.049455419182777405
|
| 561 |
+
},
|
| 562 |
+
{
|
| 563 |
+
"cell_type": "Radial Glia-like",
|
| 564 |
+
"n_cells": 51,
|
| 565 |
+
"median_cv_analytical": 0.8714540004730225,
|
| 566 |
+
"median_cv_deepptr": 0.05179445818066597,
|
| 567 |
+
"mean_cv_analytical": 1.744782567024231,
|
| 568 |
+
"mean_cv_deepptr": 0.04362059757113457
|
| 569 |
+
},
|
| 570 |
+
{
|
| 571 |
+
"cell_type": "nIPC",
|
| 572 |
+
"n_cells": 19,
|
| 573 |
+
"median_cv_analytical": 0.8511570692062378,
|
| 574 |
+
"median_cv_deepptr": 0.04830135032534599,
|
| 575 |
+
"mean_cv_analytical": 1.1737077236175537,
|
| 576 |
+
"mean_cv_deepptr": 0.04091320559382439
|
| 577 |
+
}
|
| 578 |
+
],
|
| 579 |
+
"anova_n_tested": 300,
|
| 580 |
+
"anova_n_sig_analytical": 300,
|
| 581 |
+
"anova_n_sig_deepptr": 300,
|
| 582 |
+
"anova_median_F_analytical": 54.830108642578125,
|
| 583 |
+
"anova_median_F_deepptr": 537.62548828125
|
| 584 |
+
},
|
| 585 |
+
"disentanglement": {
|
| 586 |
+
"n_pt_specific_genes": 11,
|
| 587 |
+
"pt_specific_genes": [
|
| 588 |
+
"Eml6",
|
| 589 |
+
"Cnot6",
|
| 590 |
+
"Scfd1",
|
| 591 |
+
"Nrxn3",
|
| 592 |
+
"Kcnma1",
|
| 593 |
+
"Cacnb2",
|
| 594 |
+
"Taf13",
|
| 595 |
+
"Rmdn1",
|
| 596 |
+
"Palm2",
|
| 597 |
+
"Cdh8",
|
| 598 |
+
"Tmem108"
|
| 599 |
+
],
|
| 600 |
+
"n_t_specific_genes": 0,
|
| 601 |
+
"t_specific_genes": [],
|
| 602 |
+
"pt_vs_expr_ari": 0.2671286775862764,
|
| 603 |
+
"pt_vs_expr_nmi": 0.3704443764759361,
|
| 604 |
+
"n_pt_de_genes": 300,
|
| 605 |
+
"top_pt_de_genes": [
|
| 606 |
+
{
|
| 607 |
+
"gene": "Tnr",
|
| 608 |
+
"H_statistic": 1533.366266091544,
|
| 609 |
+
"p_value": 0.0,
|
| 610 |
+
"fold_change": 1.1819950342178345
|
| 611 |
+
},
|
| 612 |
+
{
|
| 613 |
+
"gene": "Vmp1",
|
| 614 |
+
"H_statistic": 1451.2478969852395,
|
| 615 |
+
"p_value": 0.0,
|
| 616 |
+
"fold_change": 1.2288334369659424
|
| 617 |
+
},
|
| 618 |
+
{
|
| 619 |
+
"gene": "Dapk1",
|
| 620 |
+
"H_statistic": 1546.6111450613307,
|
| 621 |
+
"p_value": 0.0,
|
| 622 |
+
"fold_change": 1.1675533056259155
|
| 623 |
+
},
|
| 624 |
+
{
|
| 625 |
+
"gene": "Kcnma1",
|
| 626 |
+
"H_statistic": 1519.3468882649072,
|
| 627 |
+
"p_value": 0.0,
|
| 628 |
+
"fold_change": 1.11080801486969
|
| 629 |
+
},
|
| 630 |
+
{
|
| 631 |
+
"gene": "Cacnb2",
|
| 632 |
+
"H_statistic": 1515.7900037483437,
|
| 633 |
+
"p_value": 0.0,
|
| 634 |
+
"fold_change": 1.1004377603530884
|
| 635 |
+
},
|
| 636 |
+
{
|
| 637 |
+
"gene": "Rmdn1",
|
| 638 |
+
"H_statistic": 1616.2543511793092,
|
| 639 |
+
"p_value": 0.0,
|
| 640 |
+
"fold_change": 1.1346839666366577
|
| 641 |
+
},
|
| 642 |
+
{
|
| 643 |
+
"gene": "Samd12",
|
| 644 |
+
"H_statistic": 1433.6199306560575,
|
| 645 |
+
"p_value": 3.54321061287721e-309,
|
| 646 |
+
"fold_change": 1.146824598312378
|
| 647 |
+
},
|
| 648 |
+
{
|
| 649 |
+
"gene": "Eif3k",
|
| 650 |
+
"H_statistic": 1406.0461625188418,
|
| 651 |
+
"p_value": 3.377085499536065e-303,
|
| 652 |
+
"fold_change": 1.7036584615707397
|
| 653 |
+
},
|
| 654 |
+
{
|
| 655 |
+
"gene": "Myl6",
|
| 656 |
+
"H_statistic": 1405.2783274363155,
|
| 657 |
+
"p_value": 4.9549403643472094e-303,
|
| 658 |
+
"fold_change": 2.6013126373291016
|
| 659 |
+
},
|
| 660 |
+
{
|
| 661 |
+
"gene": "2010300C02Rik",
|
| 662 |
+
"H_statistic": 1397.0629359204936,
|
| 663 |
+
"p_value": 2.9953341972979423e-301,
|
| 664 |
+
"fold_change": 1.7101900577545166
|
| 665 |
+
},
|
| 666 |
+
{
|
| 667 |
+
"gene": "Rpl37a",
|
| 668 |
+
"H_statistic": 1306.7601736776787,
|
| 669 |
+
"p_value": 1.1388427869365306e-281,
|
| 670 |
+
"fold_change": 1.8508563041687012
|
| 671 |
+
},
|
| 672 |
+
{
|
| 673 |
+
"gene": "Cdh8",
|
| 674 |
+
"H_statistic": 1300.5562976457068,
|
| 675 |
+
"p_value": 2.5208950836280437e-280,
|
| 676 |
+
"fold_change": 1.1398566961288452
|
| 677 |
+
},
|
| 678 |
+
{
|
| 679 |
+
"gene": "Ptk2",
|
| 680 |
+
"H_statistic": 1289.8494393795609,
|
| 681 |
+
"p_value": 5.283660460584932e-278,
|
| 682 |
+
"fold_change": 3.05499529838562
|
| 683 |
+
},
|
| 684 |
+
{
|
| 685 |
+
"gene": "Erdr1",
|
| 686 |
+
"H_statistic": 1272.9761164998563,
|
| 687 |
+
"p_value": 2.4055841770936896e-274,
|
| 688 |
+
"fold_change": 2.2879762649536133
|
| 689 |
+
},
|
| 690 |
+
{
|
| 691 |
+
"gene": "Rfx3",
|
| 692 |
+
"H_statistic": 1266.5863773970893,
|
| 693 |
+
"p_value": 5.8418818476206325e-273,
|
| 694 |
+
"fold_change": 1.7074975967407227
|
| 695 |
+
},
|
| 696 |
+
{
|
| 697 |
+
"gene": "Sfswap",
|
| 698 |
+
"H_statistic": 1262.8777599728467,
|
| 699 |
+
"p_value": 3.7204658593671595e-272,
|
| 700 |
+
"fold_change": 2.057882785797119
|
| 701 |
+
},
|
| 702 |
+
{
|
| 703 |
+
"gene": "Ppp3ca",
|
| 704 |
+
"H_statistic": 1262.0503935996123,
|
| 705 |
+
"p_value": 5.623069877289324e-272,
|
| 706 |
+
"fold_change": 1.7260609865188599
|
| 707 |
+
},
|
| 708 |
+
{
|
| 709 |
+
"gene": "Stxbp6",
|
| 710 |
+
"H_statistic": 1261.113772301682,
|
| 711 |
+
"p_value": 8.975037945460618e-272,
|
| 712 |
+
"fold_change": 1.390549898147583
|
| 713 |
+
},
|
| 714 |
+
{
|
| 715 |
+
"gene": "Rpl37",
|
| 716 |
+
"H_statistic": 1257.7427888641757,
|
| 717 |
+
"p_value": 4.829202943626965e-271,
|
| 718 |
+
"fold_change": 1.7165744304656982
|
| 719 |
+
},
|
| 720 |
+
{
|
| 721 |
+
"gene": "Scfd1",
|
| 722 |
+
"H_statistic": 1240.925403805081,
|
| 723 |
+
"p_value": 2.137407878218827e-267,
|
| 724 |
+
"fold_change": 1.0781753063201904
|
| 725 |
+
}
|
| 726 |
+
]
|
| 727 |
+
},
|
| 728 |
+
"statistical_testing": {
|
| 729 |
+
"ct_a": "Granule mature",
|
| 730 |
+
"ct_b": "Granule immature",
|
| 731 |
+
"n_cells_a": 1070,
|
| 732 |
+
"n_cells_b": 785,
|
| 733 |
+
"n_sig_posterior": 0,
|
| 734 |
+
"n_sig_ttest": 142,
|
| 735 |
+
"n_both": 0,
|
| 736 |
+
"n_posterior_only": 0,
|
| 737 |
+
"n_ttest_only": 142
|
| 738 |
+
}
|
| 739 |
+
}
|
| 740 |
+
}
|
output/deep_advantages/results/dentate_gyrus_advantages.json
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"uncertainty_filtering": {
|
| 3 |
+
"mouse": [
|
| 4 |
+
{
|
| 5 |
+
"threshold": "all",
|
| 6 |
+
"n_genes": 216,
|
| 7 |
+
"spearman_r": -0.28539711439597953
|
| 8 |
+
},
|
| 9 |
+
{
|
| 10 |
+
"threshold": "CV<1.0",
|
| 11 |
+
"n_genes": 216,
|
| 12 |
+
"spearman_r": -0.28539711439597953
|
| 13 |
+
},
|
| 14 |
+
{
|
| 15 |
+
"threshold": "CV<0.75",
|
| 16 |
+
"n_genes": 216,
|
| 17 |
+
"spearman_r": -0.28539711439597953
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
"threshold": "CV<0.5",
|
| 21 |
+
"n_genes": 216,
|
| 22 |
+
"spearman_r": -0.28539711439597953
|
| 23 |
+
},
|
| 24 |
+
{
|
| 25 |
+
"threshold": "CV<0.3",
|
| 26 |
+
"n_genes": 215,
|
| 27 |
+
"spearman_r": -0.2859663877215545
|
| 28 |
+
},
|
| 29 |
+
{
|
| 30 |
+
"threshold": "CV<0.2",
|
| 31 |
+
"n_genes": 194,
|
| 32 |
+
"spearman_r": -0.2808110516824419
|
| 33 |
+
},
|
| 34 |
+
{
|
| 35 |
+
"threshold": "bottom_75pct_CV",
|
| 36 |
+
"n_genes": 162,
|
| 37 |
+
"spearman_r": -0.27764299756831096
|
| 38 |
+
},
|
| 39 |
+
{
|
| 40 |
+
"threshold": "bottom_50pct_CV",
|
| 41 |
+
"n_genes": 108,
|
| 42 |
+
"spearman_r": -0.1943849019215563
|
| 43 |
+
},
|
| 44 |
+
{
|
| 45 |
+
"threshold": "bottom_25pct_CV",
|
| 46 |
+
"n_genes": 54,
|
| 47 |
+
"spearman_r": -0.18749761768629689
|
| 48 |
+
}
|
| 49 |
+
],
|
| 50 |
+
"human": [
|
| 51 |
+
{
|
| 52 |
+
"threshold": "all",
|
| 53 |
+
"n_genes": 211,
|
| 54 |
+
"spearman_r": -0.3582097560135093
|
| 55 |
+
},
|
| 56 |
+
{
|
| 57 |
+
"threshold": "CV<1.0",
|
| 58 |
+
"n_genes": 211,
|
| 59 |
+
"spearman_r": -0.3582097560135093
|
| 60 |
+
},
|
| 61 |
+
{
|
| 62 |
+
"threshold": "CV<0.75",
|
| 63 |
+
"n_genes": 211,
|
| 64 |
+
"spearman_r": -0.3582097560135093
|
| 65 |
+
},
|
| 66 |
+
{
|
| 67 |
+
"threshold": "CV<0.5",
|
| 68 |
+
"n_genes": 211,
|
| 69 |
+
"spearman_r": -0.3582097560135093
|
| 70 |
+
},
|
| 71 |
+
{
|
| 72 |
+
"threshold": "CV<0.3",
|
| 73 |
+
"n_genes": 210,
|
| 74 |
+
"spearman_r": -0.3577103191264039
|
| 75 |
+
},
|
| 76 |
+
{
|
| 77 |
+
"threshold": "CV<0.2",
|
| 78 |
+
"n_genes": 192,
|
| 79 |
+
"spearman_r": -0.3587221254393612
|
| 80 |
+
},
|
| 81 |
+
{
|
| 82 |
+
"threshold": "bottom_75pct_CV",
|
| 83 |
+
"n_genes": 158,
|
| 84 |
+
"spearman_r": -0.39997839837699434
|
| 85 |
+
},
|
| 86 |
+
{
|
| 87 |
+
"threshold": "bottom_50pct_CV",
|
| 88 |
+
"n_genes": 106,
|
| 89 |
+
"spearman_r": -0.31398500635210375
|
| 90 |
+
},
|
| 91 |
+
{
|
| 92 |
+
"threshold": "bottom_25pct_CV",
|
| 93 |
+
"n_genes": 53,
|
| 94 |
+
"spearman_r": -0.08974539885307645
|
| 95 |
+
}
|
| 96 |
+
]
|
| 97 |
+
},
|
| 98 |
+
"cell_resolution": {
|
| 99 |
+
"per_celltype_cv": [
|
| 100 |
+
{
|
| 101 |
+
"cell_type": "Astrocytes",
|
| 102 |
+
"n_cells": 120,
|
| 103 |
+
"median_cv_analytical": 1.037865400314331,
|
| 104 |
+
"median_cv_deepptr": 0.04969271644949913,
|
| 105 |
+
"mean_cv_analytical": 2.065990686416626,
|
| 106 |
+
"mean_cv_deepptr": 0.042765166610479355
|
| 107 |
+
},
|
| 108 |
+
{
|
| 109 |
+
"cell_type": "Cajal Retzius",
|
| 110 |
+
"n_cells": 34,
|
| 111 |
+
"median_cv_analytical": 0.9581262469291687,
|
| 112 |
+
"median_cv_deepptr": 0.035210926085710526,
|
| 113 |
+
"mean_cv_analytical": 1.2425631284713745,
|
| 114 |
+
"mean_cv_deepptr": 0.03074895218014717
|
| 115 |
+
},
|
| 116 |
+
{
|
| 117 |
+
"cell_type": "Cck-Tox",
|
| 118 |
+
"n_cells": 27,
|
| 119 |
+
"median_cv_analytical": 0.5500394701957703,
|
| 120 |
+
"median_cv_deepptr": 0.03183512017130852,
|
| 121 |
+
"mean_cv_analytical": 0.7018951177597046,
|
| 122 |
+
"mean_cv_deepptr": 0.02709321305155754
|
| 123 |
+
},
|
| 124 |
+
{
|
| 125 |
+
"cell_type": "Endothelial",
|
| 126 |
+
"n_cells": 87,
|
| 127 |
+
"median_cv_analytical": 1.258206844329834,
|
| 128 |
+
"median_cv_deepptr": 0.07260799407958984,
|
| 129 |
+
"mean_cv_analytical": 1.3927289247512817,
|
| 130 |
+
"mean_cv_deepptr": 0.06094517558813095
|
| 131 |
+
},
|
| 132 |
+
{
|
| 133 |
+
"cell_type": "GABA",
|
| 134 |
+
"n_cells": 61,
|
| 135 |
+
"median_cv_analytical": 0.6232943534851074,
|
| 136 |
+
"median_cv_deepptr": 0.03612678125500679,
|
| 137 |
+
"mean_cv_analytical": 0.7454152703285217,
|
| 138 |
+
"mean_cv_deepptr": 0.031618811190128326
|
| 139 |
+
},
|
| 140 |
+
{
|
| 141 |
+
"cell_type": "Granule immature",
|
| 142 |
+
"n_cells": 785,
|
| 143 |
+
"median_cv_analytical": 0.6696305274963379,
|
| 144 |
+
"median_cv_deepptr": 0.030969148501753807,
|
| 145 |
+
"mean_cv_analytical": 0.7786616086959839,
|
| 146 |
+
"mean_cv_deepptr": 0.026258107274770737
|
| 147 |
+
},
|
| 148 |
+
{
|
| 149 |
+
"cell_type": "Granule mature",
|
| 150 |
+
"n_cells": 1070,
|
| 151 |
+
"median_cv_analytical": 0.7358936667442322,
|
| 152 |
+
"median_cv_deepptr": 0.029770459979772568,
|
| 153 |
+
"mean_cv_analytical": 0.8565810322761536,
|
| 154 |
+
"mean_cv_deepptr": 0.025847110897302628
|
| 155 |
+
},
|
| 156 |
+
{
|
| 157 |
+
"cell_type": "Microglia",
|
| 158 |
+
"n_cells": 81,
|
| 159 |
+
"median_cv_analytical": 2.034620523452759,
|
| 160 |
+
"median_cv_deepptr": 0.05030623823404312,
|
| 161 |
+
"mean_cv_analytical": 2.5370047092437744,
|
| 162 |
+
"mean_cv_deepptr": 0.04304993152618408
|
| 163 |
+
},
|
| 164 |
+
{
|
| 165 |
+
"cell_type": "Mossy",
|
| 166 |
+
"n_cells": 75,
|
| 167 |
+
"median_cv_analytical": 0.5362868309020996,
|
| 168 |
+
"median_cv_deepptr": 0.033555418252944946,
|
| 169 |
+
"mean_cv_analytical": 0.6848810315132141,
|
| 170 |
+
"mean_cv_deepptr": 0.02926183119416237
|
| 171 |
+
},
|
| 172 |
+
{
|
| 173 |
+
"cell_type": "Neuroblast",
|
| 174 |
+
"n_cells": 417,
|
| 175 |
+
"median_cv_analytical": 0.8587535619735718,
|
| 176 |
+
"median_cv_deepptr": 0.04170010983943939,
|
| 177 |
+
"mean_cv_analytical": 1.133716106414795,
|
| 178 |
+
"mean_cv_deepptr": 0.035205960273742676
|
| 179 |
+
},
|
| 180 |
+
{
|
| 181 |
+
"cell_type": "OL",
|
| 182 |
+
"n_cells": 50,
|
| 183 |
+
"median_cv_analytical": 1.3443996906280518,
|
| 184 |
+
"median_cv_deepptr": 0.06878548860549927,
|
| 185 |
+
"mean_cv_analytical": 1.9315539598464966,
|
| 186 |
+
"mean_cv_deepptr": 0.060547325760126114
|
| 187 |
+
},
|
| 188 |
+
{
|
| 189 |
+
"cell_type": "OPC",
|
| 190 |
+
"n_cells": 53,
|
| 191 |
+
"median_cv_analytical": 0.815068244934082,
|
| 192 |
+
"median_cv_deepptr": 0.0567430779337883,
|
| 193 |
+
"mean_cv_analytical": 1.5061569213867188,
|
| 194 |
+
"mean_cv_deepptr": 0.049455419182777405
|
| 195 |
+
},
|
| 196 |
+
{
|
| 197 |
+
"cell_type": "Radial Glia-like",
|
| 198 |
+
"n_cells": 51,
|
| 199 |
+
"median_cv_analytical": 0.8714540004730225,
|
| 200 |
+
"median_cv_deepptr": 0.05179445818066597,
|
| 201 |
+
"mean_cv_analytical": 1.744782567024231,
|
| 202 |
+
"mean_cv_deepptr": 0.04362059757113457
|
| 203 |
+
},
|
| 204 |
+
{
|
| 205 |
+
"cell_type": "nIPC",
|
| 206 |
+
"n_cells": 19,
|
| 207 |
+
"median_cv_analytical": 0.8511570692062378,
|
| 208 |
+
"median_cv_deepptr": 0.04830135032534599,
|
| 209 |
+
"mean_cv_analytical": 1.1737077236175537,
|
| 210 |
+
"mean_cv_deepptr": 0.04091320559382439
|
| 211 |
+
}
|
| 212 |
+
],
|
| 213 |
+
"anova_n_tested": 300,
|
| 214 |
+
"anova_n_sig_analytical": 300,
|
| 215 |
+
"anova_n_sig_deepptr": 300,
|
| 216 |
+
"anova_median_F_analytical": 54.830108642578125,
|
| 217 |
+
"anova_median_F_deepptr": 537.62548828125
|
| 218 |
+
},
|
| 219 |
+
"disentanglement": {
|
| 220 |
+
"n_pt_specific_genes": 11,
|
| 221 |
+
"pt_specific_genes": [
|
| 222 |
+
"Eml6",
|
| 223 |
+
"Cnot6",
|
| 224 |
+
"Scfd1",
|
| 225 |
+
"Nrxn3",
|
| 226 |
+
"Kcnma1",
|
| 227 |
+
"Cacnb2",
|
| 228 |
+
"Taf13",
|
| 229 |
+
"Rmdn1",
|
| 230 |
+
"Palm2",
|
| 231 |
+
"Cdh8",
|
| 232 |
+
"Tmem108"
|
| 233 |
+
],
|
| 234 |
+
"n_t_specific_genes": 0,
|
| 235 |
+
"t_specific_genes": [],
|
| 236 |
+
"pt_vs_expr_ari": 0.2671286775862764,
|
| 237 |
+
"pt_vs_expr_nmi": 0.3704443764759361,
|
| 238 |
+
"n_pt_de_genes": 300,
|
| 239 |
+
"top_pt_de_genes": [
|
| 240 |
+
{
|
| 241 |
+
"gene": "Tnr",
|
| 242 |
+
"H_statistic": 1533.366266091544,
|
| 243 |
+
"p_value": 0.0,
|
| 244 |
+
"fold_change": 1.1819950342178345
|
| 245 |
+
},
|
| 246 |
+
{
|
| 247 |
+
"gene": "Vmp1",
|
| 248 |
+
"H_statistic": 1451.2478969852395,
|
| 249 |
+
"p_value": 0.0,
|
| 250 |
+
"fold_change": 1.2288334369659424
|
| 251 |
+
},
|
| 252 |
+
{
|
| 253 |
+
"gene": "Dapk1",
|
| 254 |
+
"H_statistic": 1546.6111450613307,
|
| 255 |
+
"p_value": 0.0,
|
| 256 |
+
"fold_change": 1.1675533056259155
|
| 257 |
+
},
|
| 258 |
+
{
|
| 259 |
+
"gene": "Kcnma1",
|
| 260 |
+
"H_statistic": 1519.3468882649072,
|
| 261 |
+
"p_value": 0.0,
|
| 262 |
+
"fold_change": 1.11080801486969
|
| 263 |
+
},
|
| 264 |
+
{
|
| 265 |
+
"gene": "Cacnb2",
|
| 266 |
+
"H_statistic": 1515.7900037483437,
|
| 267 |
+
"p_value": 0.0,
|
| 268 |
+
"fold_change": 1.1004377603530884
|
| 269 |
+
},
|
| 270 |
+
{
|
| 271 |
+
"gene": "Rmdn1",
|
| 272 |
+
"H_statistic": 1616.2543511793092,
|
| 273 |
+
"p_value": 0.0,
|
| 274 |
+
"fold_change": 1.1346839666366577
|
| 275 |
+
},
|
| 276 |
+
{
|
| 277 |
+
"gene": "Samd12",
|
| 278 |
+
"H_statistic": 1433.6199306560575,
|
| 279 |
+
"p_value": 3.54321061287721e-309,
|
| 280 |
+
"fold_change": 1.146824598312378
|
| 281 |
+
},
|
| 282 |
+
{
|
| 283 |
+
"gene": "Eif3k",
|
| 284 |
+
"H_statistic": 1406.0461625188418,
|
| 285 |
+
"p_value": 3.377085499536065e-303,
|
| 286 |
+
"fold_change": 1.7036584615707397
|
| 287 |
+
},
|
| 288 |
+
{
|
| 289 |
+
"gene": "Myl6",
|
| 290 |
+
"H_statistic": 1405.2783274363155,
|
| 291 |
+
"p_value": 4.9549403643472094e-303,
|
| 292 |
+
"fold_change": 2.6013126373291016
|
| 293 |
+
},
|
| 294 |
+
{
|
| 295 |
+
"gene": "2010300C02Rik",
|
| 296 |
+
"H_statistic": 1397.0629359204936,
|
| 297 |
+
"p_value": 2.9953341972979423e-301,
|
| 298 |
+
"fold_change": 1.7101900577545166
|
| 299 |
+
},
|
| 300 |
+
{
|
| 301 |
+
"gene": "Rpl37a",
|
| 302 |
+
"H_statistic": 1306.7601736776787,
|
| 303 |
+
"p_value": 1.1388427869365306e-281,
|
| 304 |
+
"fold_change": 1.8508563041687012
|
| 305 |
+
},
|
| 306 |
+
{
|
| 307 |
+
"gene": "Cdh8",
|
| 308 |
+
"H_statistic": 1300.5562976457068,
|
| 309 |
+
"p_value": 2.5208950836280437e-280,
|
| 310 |
+
"fold_change": 1.1398566961288452
|
| 311 |
+
},
|
| 312 |
+
{
|
| 313 |
+
"gene": "Ptk2",
|
| 314 |
+
"H_statistic": 1289.8494393795609,
|
| 315 |
+
"p_value": 5.283660460584932e-278,
|
| 316 |
+
"fold_change": 3.05499529838562
|
| 317 |
+
},
|
| 318 |
+
{
|
| 319 |
+
"gene": "Erdr1",
|
| 320 |
+
"H_statistic": 1272.9761164998563,
|
| 321 |
+
"p_value": 2.4055841770936896e-274,
|
| 322 |
+
"fold_change": 2.2879762649536133
|
| 323 |
+
},
|
| 324 |
+
{
|
| 325 |
+
"gene": "Rfx3",
|
| 326 |
+
"H_statistic": 1266.5863773970893,
|
| 327 |
+
"p_value": 5.8418818476206325e-273,
|
| 328 |
+
"fold_change": 1.7074975967407227
|
| 329 |
+
},
|
| 330 |
+
{
|
| 331 |
+
"gene": "Sfswap",
|
| 332 |
+
"H_statistic": 1262.8777599728467,
|
| 333 |
+
"p_value": 3.7204658593671595e-272,
|
| 334 |
+
"fold_change": 2.057882785797119
|
| 335 |
+
},
|
| 336 |
+
{
|
| 337 |
+
"gene": "Ppp3ca",
|
| 338 |
+
"H_statistic": 1262.0503935996123,
|
| 339 |
+
"p_value": 5.623069877289324e-272,
|
| 340 |
+
"fold_change": 1.7260609865188599
|
| 341 |
+
},
|
| 342 |
+
{
|
| 343 |
+
"gene": "Stxbp6",
|
| 344 |
+
"H_statistic": 1261.113772301682,
|
| 345 |
+
"p_value": 8.975037945460618e-272,
|
| 346 |
+
"fold_change": 1.390549898147583
|
| 347 |
+
},
|
| 348 |
+
{
|
| 349 |
+
"gene": "Rpl37",
|
| 350 |
+
"H_statistic": 1257.7427888641757,
|
| 351 |
+
"p_value": 4.829202943626965e-271,
|
| 352 |
+
"fold_change": 1.7165744304656982
|
| 353 |
+
},
|
| 354 |
+
{
|
| 355 |
+
"gene": "Scfd1",
|
| 356 |
+
"H_statistic": 1240.925403805081,
|
| 357 |
+
"p_value": 2.137407878218827e-267,
|
| 358 |
+
"fold_change": 1.0781753063201904
|
| 359 |
+
}
|
| 360 |
+
]
|
| 361 |
+
},
|
| 362 |
+
"statistical_testing": {
|
| 363 |
+
"ct_a": "Granule mature",
|
| 364 |
+
"ct_b": "Granule immature",
|
| 365 |
+
"n_cells_a": 1070,
|
| 366 |
+
"n_cells_b": 785,
|
| 367 |
+
"n_sig_posterior": 0,
|
| 368 |
+
"n_sig_ttest": 142,
|
| 369 |
+
"n_both": 0,
|
| 370 |
+
"n_posterior_only": 0,
|
| 371 |
+
"n_ttest_only": 142
|
| 372 |
+
}
|
| 373 |
+
}
|
output/deep_advantages/results/pancreas_advantages.json
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"uncertainty_filtering": {
|
| 3 |
+
"mouse": [
|
| 4 |
+
{
|
| 5 |
+
"threshold": "all",
|
| 6 |
+
"n_genes": 251,
|
| 7 |
+
"spearman_r": -0.1976794527336332
|
| 8 |
+
},
|
| 9 |
+
{
|
| 10 |
+
"threshold": "CV<1.0",
|
| 11 |
+
"n_genes": 251,
|
| 12 |
+
"spearman_r": -0.1976794527336332
|
| 13 |
+
},
|
| 14 |
+
{
|
| 15 |
+
"threshold": "CV<0.75",
|
| 16 |
+
"n_genes": 251,
|
| 17 |
+
"spearman_r": -0.1976794527336332
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
"threshold": "CV<0.5",
|
| 21 |
+
"n_genes": 250,
|
| 22 |
+
"spearman_r": -0.20403060683747745
|
| 23 |
+
},
|
| 24 |
+
{
|
| 25 |
+
"threshold": "CV<0.3",
|
| 26 |
+
"n_genes": 250,
|
| 27 |
+
"spearman_r": -0.20403060683747745
|
| 28 |
+
},
|
| 29 |
+
{
|
| 30 |
+
"threshold": "CV<0.2",
|
| 31 |
+
"n_genes": 249,
|
| 32 |
+
"spearman_r": -0.20048744674989727
|
| 33 |
+
},
|
| 34 |
+
{
|
| 35 |
+
"threshold": "bottom_75pct_CV",
|
| 36 |
+
"n_genes": 188,
|
| 37 |
+
"spearman_r": -0.2188323427372238
|
| 38 |
+
},
|
| 39 |
+
{
|
| 40 |
+
"threshold": "bottom_50pct_CV",
|
| 41 |
+
"n_genes": 126,
|
| 42 |
+
"spearman_r": -0.23679940007499065
|
| 43 |
+
},
|
| 44 |
+
{
|
| 45 |
+
"threshold": "bottom_25pct_CV",
|
| 46 |
+
"n_genes": 63,
|
| 47 |
+
"spearman_r": -0.2579205069124424
|
| 48 |
+
}
|
| 49 |
+
],
|
| 50 |
+
"human": [
|
| 51 |
+
{
|
| 52 |
+
"threshold": "all",
|
| 53 |
+
"n_genes": 236,
|
| 54 |
+
"spearman_r": -0.27676078712075614
|
| 55 |
+
},
|
| 56 |
+
{
|
| 57 |
+
"threshold": "CV<1.0",
|
| 58 |
+
"n_genes": 236,
|
| 59 |
+
"spearman_r": -0.27676078712075614
|
| 60 |
+
},
|
| 61 |
+
{
|
| 62 |
+
"threshold": "CV<0.75",
|
| 63 |
+
"n_genes": 236,
|
| 64 |
+
"spearman_r": -0.27676078712075614
|
| 65 |
+
},
|
| 66 |
+
{
|
| 67 |
+
"threshold": "CV<0.5",
|
| 68 |
+
"n_genes": 236,
|
| 69 |
+
"spearman_r": -0.27676078712075614
|
| 70 |
+
},
|
| 71 |
+
{
|
| 72 |
+
"threshold": "CV<0.3",
|
| 73 |
+
"n_genes": 236,
|
| 74 |
+
"spearman_r": -0.27676078712075614
|
| 75 |
+
},
|
| 76 |
+
{
|
| 77 |
+
"threshold": "CV<0.2",
|
| 78 |
+
"n_genes": 235,
|
| 79 |
+
"spearman_r": -0.27315207370726874
|
| 80 |
+
},
|
| 81 |
+
{
|
| 82 |
+
"threshold": "bottom_75pct_CV",
|
| 83 |
+
"n_genes": 177,
|
| 84 |
+
"spearman_r": -0.2899948602809608
|
| 85 |
+
},
|
| 86 |
+
{
|
| 87 |
+
"threshold": "bottom_50pct_CV",
|
| 88 |
+
"n_genes": 118,
|
| 89 |
+
"spearman_r": -0.2920772191767282
|
| 90 |
+
},
|
| 91 |
+
{
|
| 92 |
+
"threshold": "bottom_25pct_CV",
|
| 93 |
+
"n_genes": 59,
|
| 94 |
+
"spearman_r": -0.3545931413753938
|
| 95 |
+
}
|
| 96 |
+
]
|
| 97 |
+
},
|
| 98 |
+
"cell_resolution": {
|
| 99 |
+
"per_celltype_cv": [
|
| 100 |
+
{
|
| 101 |
+
"cell_type": "Alpha",
|
| 102 |
+
"n_cells": 481,
|
| 103 |
+
"median_cv_analytical": 0.5714874267578125,
|
| 104 |
+
"median_cv_deepptr": 0.025115380063652992,
|
| 105 |
+
"mean_cv_analytical": 0.6858336329460144,
|
| 106 |
+
"mean_cv_deepptr": 0.02439717948436737
|
| 107 |
+
},
|
| 108 |
+
{
|
| 109 |
+
"cell_type": "Beta",
|
| 110 |
+
"n_cells": 591,
|
| 111 |
+
"median_cv_analytical": 0.5670088529586792,
|
| 112 |
+
"median_cv_deepptr": 0.1528678983449936,
|
| 113 |
+
"mean_cv_analytical": 0.6656529903411865,
|
| 114 |
+
"mean_cv_deepptr": 0.1491723507642746
|
| 115 |
+
},
|
| 116 |
+
{
|
| 117 |
+
"cell_type": "Delta",
|
| 118 |
+
"n_cells": 70,
|
| 119 |
+
"median_cv_analytical": 0.44335344433784485,
|
| 120 |
+
"median_cv_deepptr": 0.02082059532403946,
|
| 121 |
+
"mean_cv_analytical": 0.7205255031585693,
|
| 122 |
+
"mean_cv_deepptr": 0.01994817890226841
|
| 123 |
+
},
|
| 124 |
+
{
|
| 125 |
+
"cell_type": "Ductal",
|
| 126 |
+
"n_cells": 916,
|
| 127 |
+
"median_cv_analytical": 0.6001600027084351,
|
| 128 |
+
"median_cv_deepptr": 0.02392425574362278,
|
| 129 |
+
"mean_cv_analytical": 1.5890529155731201,
|
| 130 |
+
"mean_cv_deepptr": 0.022229716181755066
|
| 131 |
+
},
|
| 132 |
+
{
|
| 133 |
+
"cell_type": "Epsilon",
|
| 134 |
+
"n_cells": 142,
|
| 135 |
+
"median_cv_analytical": 0.5356361865997314,
|
| 136 |
+
"median_cv_deepptr": 0.02128124237060547,
|
| 137 |
+
"mean_cv_analytical": 0.7422852516174316,
|
| 138 |
+
"mean_cv_deepptr": 0.02046981453895569
|
| 139 |
+
},
|
| 140 |
+
{
|
| 141 |
+
"cell_type": "Ngn3 high EP",
|
| 142 |
+
"n_cells": 642,
|
| 143 |
+
"median_cv_analytical": 0.6426572799682617,
|
| 144 |
+
"median_cv_deepptr": 0.023328058421611786,
|
| 145 |
+
"mean_cv_analytical": 0.9787807464599609,
|
| 146 |
+
"mean_cv_deepptr": 0.021852802485227585
|
| 147 |
+
},
|
| 148 |
+
{
|
| 149 |
+
"cell_type": "Ngn3 low EP",
|
| 150 |
+
"n_cells": 262,
|
| 151 |
+
"median_cv_analytical": 0.5913139581680298,
|
| 152 |
+
"median_cv_deepptr": 0.025578055530786514,
|
| 153 |
+
"mean_cv_analytical": 1.2815543413162231,
|
| 154 |
+
"mean_cv_deepptr": 0.023869693279266357
|
| 155 |
+
},
|
| 156 |
+
{
|
| 157 |
+
"cell_type": "Pre-endocrine",
|
| 158 |
+
"n_cells": 592,
|
| 159 |
+
"median_cv_analytical": 0.6505065560340881,
|
| 160 |
+
"median_cv_deepptr": 0.019794151186943054,
|
| 161 |
+
"mean_cv_analytical": 0.8129222393035889,
|
| 162 |
+
"mean_cv_deepptr": 0.018655382096767426
|
| 163 |
+
}
|
| 164 |
+
],
|
| 165 |
+
"anova_n_tested": 300,
|
| 166 |
+
"anova_n_sig_analytical": 299,
|
| 167 |
+
"anova_n_sig_deepptr": 300,
|
| 168 |
+
"anova_median_F_analytical": NaN,
|
| 169 |
+
"anova_median_F_deepptr": 244.958740234375
|
| 170 |
+
},
|
| 171 |
+
"disentanglement": {
|
| 172 |
+
"n_pt_specific_genes": 44,
|
| 173 |
+
"pt_specific_genes": [
|
| 174 |
+
"Adgrb3",
|
| 175 |
+
"Pard3b",
|
| 176 |
+
"Tmtc2",
|
| 177 |
+
"Rfx6",
|
| 178 |
+
"Trim17",
|
| 179 |
+
"Ptprn2",
|
| 180 |
+
"Dip2c",
|
| 181 |
+
"Fhit",
|
| 182 |
+
"Ube2e2",
|
| 183 |
+
"Kcnma1",
|
| 184 |
+
"Farp1",
|
| 185 |
+
"Rims2",
|
| 186 |
+
"Igf2bp2",
|
| 187 |
+
"Stxbp5l",
|
| 188 |
+
"Phldb2",
|
| 189 |
+
"Fbxl17",
|
| 190 |
+
"Park2",
|
| 191 |
+
"Dnajc1",
|
| 192 |
+
"Pbx3",
|
| 193 |
+
"Nr6a1",
|
| 194 |
+
"Kcnh7",
|
| 195 |
+
"9030622O22Rik",
|
| 196 |
+
"Zmynd8",
|
| 197 |
+
"Mbd5",
|
| 198 |
+
"Macrod2",
|
| 199 |
+
"Hnf1aos1",
|
| 200 |
+
"Sdk1",
|
| 201 |
+
"Pan3",
|
| 202 |
+
"Chchd3",
|
| 203 |
+
"Tmcc1",
|
| 204 |
+
"Cacna1c",
|
| 205 |
+
"Snd1",
|
| 206 |
+
"Ccser1",
|
| 207 |
+
"Ttll3",
|
| 208 |
+
"Tmc4",
|
| 209 |
+
"Egln2",
|
| 210 |
+
"Gm21974",
|
| 211 |
+
"Ins2",
|
| 212 |
+
"Kcnq1",
|
| 213 |
+
"Fam155a",
|
| 214 |
+
"Wwox",
|
| 215 |
+
"Tcf12",
|
| 216 |
+
"Rora",
|
| 217 |
+
"Mid1"
|
| 218 |
+
],
|
| 219 |
+
"n_t_specific_genes": 6,
|
| 220 |
+
"t_specific_genes": [
|
| 221 |
+
"Sphkap",
|
| 222 |
+
"2010111I01Rik",
|
| 223 |
+
"Myh9",
|
| 224 |
+
"Nol4",
|
| 225 |
+
"Itpr1",
|
| 226 |
+
"Abcc8"
|
| 227 |
+
],
|
| 228 |
+
"pt_vs_expr_ari": 0.077128756533571,
|
| 229 |
+
"pt_vs_expr_nmi": 0.18141567420189855,
|
| 230 |
+
"n_pt_de_genes": 300,
|
| 231 |
+
"top_pt_de_genes": [
|
| 232 |
+
{
|
| 233 |
+
"gene": "Tmtc2",
|
| 234 |
+
"H_statistic": 1507.252819412225,
|
| 235 |
+
"p_value": 0.0,
|
| 236 |
+
"fold_change": 1.1294591426849365
|
| 237 |
+
},
|
| 238 |
+
{
|
| 239 |
+
"gene": "Ptprn2",
|
| 240 |
+
"H_statistic": 1453.3068265183394,
|
| 241 |
+
"p_value": 0.0,
|
| 242 |
+
"fold_change": 1.3904372453689575
|
| 243 |
+
},
|
| 244 |
+
{
|
| 245 |
+
"gene": "Fhit",
|
| 246 |
+
"H_statistic": 1457.3716975334148,
|
| 247 |
+
"p_value": 0.0,
|
| 248 |
+
"fold_change": 1.5617649555206299
|
| 249 |
+
},
|
| 250 |
+
{
|
| 251 |
+
"gene": "Park2",
|
| 252 |
+
"H_statistic": 1606.4708053162663,
|
| 253 |
+
"p_value": 0.0,
|
| 254 |
+
"fold_change": 1.0910781621932983
|
| 255 |
+
},
|
| 256 |
+
{
|
| 257 |
+
"gene": "Macrod2",
|
| 258 |
+
"H_statistic": 1498.6858013593953,
|
| 259 |
+
"p_value": 0.0,
|
| 260 |
+
"fold_change": 1.4149595499038696
|
| 261 |
+
},
|
| 262 |
+
{
|
| 263 |
+
"gene": "Chchd3",
|
| 264 |
+
"H_statistic": 1502.83262766733,
|
| 265 |
+
"p_value": 0.0,
|
| 266 |
+
"fold_change": 1.3431732654571533
|
| 267 |
+
},
|
| 268 |
+
{
|
| 269 |
+
"gene": "Snd1",
|
| 270 |
+
"H_statistic": 1510.9010872869696,
|
| 271 |
+
"p_value": 0.0,
|
| 272 |
+
"fold_change": 1.5494927167892456
|
| 273 |
+
},
|
| 274 |
+
{
|
| 275 |
+
"gene": "Ins2",
|
| 276 |
+
"H_statistic": 1623.6118081391649,
|
| 277 |
+
"p_value": 0.0,
|
| 278 |
+
"fold_change": 26.470022201538086
|
| 279 |
+
},
|
| 280 |
+
{
|
| 281 |
+
"gene": "Rora",
|
| 282 |
+
"H_statistic": 1491.8154065673139,
|
| 283 |
+
"p_value": 0.0,
|
| 284 |
+
"fold_change": 2.022663116455078
|
| 285 |
+
},
|
| 286 |
+
{
|
| 287 |
+
"gene": "9030622O22Rik",
|
| 288 |
+
"H_statistic": 1407.8904321594011,
|
| 289 |
+
"p_value": 5.714914382762847e-305,
|
| 290 |
+
"fold_change": 1.4340518712997437
|
| 291 |
+
},
|
| 292 |
+
{
|
| 293 |
+
"gene": "Fbxl17",
|
| 294 |
+
"H_statistic": 1401.9243404754493,
|
| 295 |
+
"p_value": 1.1261837231059974e-303,
|
| 296 |
+
"fold_change": 1.3535175323486328
|
| 297 |
+
},
|
| 298 |
+
{
|
| 299 |
+
"gene": "Ccser1",
|
| 300 |
+
"H_statistic": 1380.9660261606807,
|
| 301 |
+
"p_value": 3.975422350686473e-299,
|
| 302 |
+
"fold_change": 1.1388120651245117
|
| 303 |
+
},
|
| 304 |
+
{
|
| 305 |
+
"gene": "Kcnh7",
|
| 306 |
+
"H_statistic": 1372.331765284582,
|
| 307 |
+
"p_value": 2.9711818753969144e-297,
|
| 308 |
+
"fold_change": 1.4253920316696167
|
| 309 |
+
},
|
| 310 |
+
{
|
| 311 |
+
"gene": "Egln2",
|
| 312 |
+
"H_statistic": 1370.996708502923,
|
| 313 |
+
"p_value": 5.789247134264385e-297,
|
| 314 |
+
"fold_change": 1.140360713005066
|
| 315 |
+
},
|
| 316 |
+
{
|
| 317 |
+
"gene": "Stxbp5l",
|
| 318 |
+
"H_statistic": 1352.9608944222837,
|
| 319 |
+
"p_value": 4.744363813172019e-293,
|
| 320 |
+
"fold_change": 1.7202287912368774
|
| 321 |
+
},
|
| 322 |
+
{
|
| 323 |
+
"gene": "Sdk1",
|
| 324 |
+
"H_statistic": 1308.955208207672,
|
| 325 |
+
"p_value": 1.6777292275912853e-283,
|
| 326 |
+
"fold_change": 1.119990348815918
|
| 327 |
+
},
|
| 328 |
+
{
|
| 329 |
+
"gene": "Rims2",
|
| 330 |
+
"H_statistic": 1308.903145513165,
|
| 331 |
+
"p_value": 1.7219419918267907e-283,
|
| 332 |
+
"fold_change": 1.3955633640289307
|
| 333 |
+
},
|
| 334 |
+
{
|
| 335 |
+
"gene": "Rfx6",
|
| 336 |
+
"H_statistic": 1302.83531755728,
|
| 337 |
+
"p_value": 3.56962967882861e-282,
|
| 338 |
+
"fold_change": 1.488563895225525
|
| 339 |
+
},
|
| 340 |
+
{
|
| 341 |
+
"gene": "Wwox",
|
| 342 |
+
"H_statistic": 1302.6454148099776,
|
| 343 |
+
"p_value": 3.924898371957578e-282,
|
| 344 |
+
"fold_change": 1.4599933624267578
|
| 345 |
+
},
|
| 346 |
+
{
|
| 347 |
+
"gene": "Gm21974",
|
| 348 |
+
"H_statistic": 1289.113859857544,
|
| 349 |
+
"p_value": 3.3876946827012372e-279,
|
| 350 |
+
"fold_change": 1.0790175199508667
|
| 351 |
+
}
|
| 352 |
+
]
|
| 353 |
+
},
|
| 354 |
+
"statistical_testing": {
|
| 355 |
+
"ct_a": "Ductal",
|
| 356 |
+
"ct_b": "Ngn3 high EP",
|
| 357 |
+
"n_cells_a": 916,
|
| 358 |
+
"n_cells_b": 642,
|
| 359 |
+
"n_sig_posterior": 0,
|
| 360 |
+
"n_sig_ttest": 288,
|
| 361 |
+
"n_both": 0,
|
| 362 |
+
"n_posterior_only": 0,
|
| 363 |
+
"n_ttest_only": 288
|
| 364 |
+
}
|
| 365 |
+
}
|
output/deep_benchmark/figures/dentate_gyrus_analytical_vs_deepptr.png
ADDED
|
output/deep_benchmark/figures/dentate_gyrus_training_curves.png
ADDED
|
output/deep_benchmark/figures/dentate_gyrus_uncertainty.png
ADDED
|
output/deep_benchmark/figures/pancreas_analytical_vs_deepptr.png
ADDED
|
output/deep_benchmark/figures/synthetic_training_curves.png
ADDED
|
output/deep_benchmark/results/combined_benchmark.json
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"synthetic": {
|
| 3 |
+
"gamma_recovery_per_gene": 0.8552942267330017,
|
| 4 |
+
"gamma_recovery_global": 0.853245950458172,
|
| 5 |
+
"ci_coverage_95": 0.26692,
|
| 6 |
+
"latent_recovery_T": 0.5561008036714264,
|
| 7 |
+
"latent_recovery_PT": 0.5348961912017587,
|
| 8 |
+
"n_epochs": 150,
|
| 9 |
+
"final_train_loss": 873.7797139485677,
|
| 10 |
+
"final_val_loss": 878.2410888671875,
|
| 11 |
+
"training_time_s": 24.290624618530273
|
| 12 |
+
},
|
| 13 |
+
"pancreas": {
|
| 14 |
+
"dataset": "pancreas",
|
| 15 |
+
"n_cells": 3696,
|
| 16 |
+
"n_genes": 11906,
|
| 17 |
+
"deepptr_epochs": 100,
|
| 18 |
+
"deepptr_time_s": 691.1493718624115,
|
| 19 |
+
"deepptr_final_val_loss": 581.9227905273438,
|
| 20 |
+
"halflife": {
|
| 21 |
+
"mouse_herzog": {
|
| 22 |
+
"analytical": {
|
| 23 |
+
"spearman_r": -0.34966929477006137,
|
| 24 |
+
"pearson_r": -0.23291966884468007,
|
| 25 |
+
"n_genes": 4611
|
| 26 |
+
},
|
| 27 |
+
"deepptr": {
|
| 28 |
+
"spearman_r": -0.19762101977653473,
|
| 29 |
+
"pearson_r": -0.13793936916451716,
|
| 30 |
+
"n_genes": 251
|
| 31 |
+
}
|
| 32 |
+
},
|
| 33 |
+
"human_schofield": {
|
| 34 |
+
"analytical": {
|
| 35 |
+
"spearman_r": -0.4020872107350974,
|
| 36 |
+
"pearson_r": -0.2849205668358859,
|
| 37 |
+
"n_genes": 4308
|
| 38 |
+
},
|
| 39 |
+
"deepptr": {
|
| 40 |
+
"spearman_r": -0.2767452667406712,
|
| 41 |
+
"pearson_r": -0.24032216943506846,
|
| 42 |
+
"n_genes": 236
|
| 43 |
+
}
|
| 44 |
+
}
|
| 45 |
+
},
|
| 46 |
+
"enrichment": {
|
| 47 |
+
"ARE": {
|
| 48 |
+
"analytical": {
|
| 49 |
+
"U_statistic": 44637.0,
|
| 50 |
+
"p_value": 0.9841054448243868,
|
| 51 |
+
"n_genes_in_set": 14
|
| 52 |
+
},
|
| 53 |
+
"deepptr": {
|
| 54 |
+
"U_statistic": NaN,
|
| 55 |
+
"p_value": NaN,
|
| 56 |
+
"n_genes_in_set": 0
|
| 57 |
+
}
|
| 58 |
+
},
|
| 59 |
+
"NMD": {
|
| 60 |
+
"analytical": {
|
| 61 |
+
"U_statistic": 141636.0,
|
| 62 |
+
"p_value": 0.31160184709114613,
|
| 63 |
+
"n_genes_in_set": 29
|
| 64 |
+
},
|
| 65 |
+
"deepptr": {
|
| 66 |
+
"U_statistic": 361.0,
|
| 67 |
+
"p_value": 0.705555655316379,
|
| 68 |
+
"n_genes_in_set": 3
|
| 69 |
+
}
|
| 70 |
+
}
|
| 71 |
+
},
|
| 72 |
+
"gamma_agreement": {
|
| 73 |
+
"spearman_r": 0.9796487428351981,
|
| 74 |
+
"spearman_p": 2.4770516189667095e-190,
|
| 75 |
+
"pearson_r": 0.8430279503146496,
|
| 76 |
+
"pearson_p": 1.1020222468626118e-74,
|
| 77 |
+
"n_genes": 272
|
| 78 |
+
},
|
| 79 |
+
"robustness_analytical": [
|
| 80 |
+
{
|
| 81 |
+
"fraction": 0.5,
|
| 82 |
+
"repeat": 0,
|
| 83 |
+
"spearman_r": 0.9935493190803674,
|
| 84 |
+
"pearson_r": 0.9995819926261902,
|
| 85 |
+
"n_genes": 11906,
|
| 86 |
+
"n_cells_sampled": 1848
|
| 87 |
+
},
|
| 88 |
+
{
|
| 89 |
+
"fraction": 0.5,
|
| 90 |
+
"repeat": 1,
|
| 91 |
+
"spearman_r": 0.9957264356741521,
|
| 92 |
+
"pearson_r": 0.9995998740196228,
|
| 93 |
+
"n_genes": 11906,
|
| 94 |
+
"n_cells_sampled": 1848
|
| 95 |
+
},
|
| 96 |
+
{
|
| 97 |
+
"fraction": 0.8,
|
| 98 |
+
"repeat": 0,
|
| 99 |
+
"spearman_r": 0.9975692623733601,
|
| 100 |
+
"pearson_r": 0.9999009370803833,
|
| 101 |
+
"n_genes": 11906,
|
| 102 |
+
"n_cells_sampled": 2956
|
| 103 |
+
},
|
| 104 |
+
{
|
| 105 |
+
"fraction": 0.8,
|
| 106 |
+
"repeat": 1,
|
| 107 |
+
"spearman_r": 0.9972764513977047,
|
| 108 |
+
"pearson_r": 0.9998981952667236,
|
| 109 |
+
"n_genes": 11906,
|
| 110 |
+
"n_cells_sampled": 2956
|
| 111 |
+
}
|
| 112 |
+
]
|
| 113 |
+
},
|
| 114 |
+
"dentate_gyrus": {
|
| 115 |
+
"dataset": "dentate_gyrus",
|
| 116 |
+
"n_cells": 2930,
|
| 117 |
+
"n_genes": 5325,
|
| 118 |
+
"deepptr_epochs": 100,
|
| 119 |
+
"deepptr_time_s": 922.1991696357727,
|
| 120 |
+
"deepptr_final_val_loss": 442.7923278808594,
|
| 121 |
+
"halflife": {
|
| 122 |
+
"mouse_herzog": {
|
| 123 |
+
"analytical": {
|
| 124 |
+
"spearman_r": -0.3179587682344816,
|
| 125 |
+
"pearson_r": -0.22408423951314294,
|
| 126 |
+
"n_genes": 1217
|
| 127 |
+
},
|
| 128 |
+
"deepptr": {
|
| 129 |
+
"spearman_r": -0.2852053996865196,
|
| 130 |
+
"pearson_r": -0.19832206076339523,
|
| 131 |
+
"n_genes": 216
|
| 132 |
+
}
|
| 133 |
+
},
|
| 134 |
+
"human_schofield": {
|
| 135 |
+
"analytical": {
|
| 136 |
+
"spearman_r": -0.3812073018847205,
|
| 137 |
+
"pearson_r": -0.2877806614932096,
|
| 138 |
+
"n_genes": 1126
|
| 139 |
+
},
|
| 140 |
+
"deepptr": {
|
| 141 |
+
"spearman_r": -0.35768791768630437,
|
| 142 |
+
"pearson_r": -0.2618435875729263,
|
| 143 |
+
"n_genes": 211
|
| 144 |
+
}
|
| 145 |
+
}
|
| 146 |
+
},
|
| 147 |
+
"enrichment": {
|
| 148 |
+
"ARE": {
|
| 149 |
+
"analytical": {
|
| 150 |
+
"U_statistic": 7270.0,
|
| 151 |
+
"p_value": 0.9358900987906894,
|
| 152 |
+
"n_genes_in_set": 5
|
| 153 |
+
},
|
| 154 |
+
"deepptr": {
|
| 155 |
+
"U_statistic": NaN,
|
| 156 |
+
"p_value": NaN,
|
| 157 |
+
"n_genes_in_set": 0
|
| 158 |
+
}
|
| 159 |
+
},
|
| 160 |
+
"NMD": {
|
| 161 |
+
"analytical": {
|
| 162 |
+
"U_statistic": 32198.0,
|
| 163 |
+
"p_value": 0.007296768008887519,
|
| 164 |
+
"n_genes_in_set": 11
|
| 165 |
+
},
|
| 166 |
+
"deepptr": {
|
| 167 |
+
"U_statistic": NaN,
|
| 168 |
+
"p_value": NaN,
|
| 169 |
+
"n_genes_in_set": 0
|
| 170 |
+
}
|
| 171 |
+
}
|
| 172 |
+
},
|
| 173 |
+
"gamma_agreement": {
|
| 174 |
+
"spearman_r": 0.9924993974774762,
|
| 175 |
+
"spearman_p": 5.8151180829002596e-254,
|
| 176 |
+
"pearson_r": 0.9121664644353008,
|
| 177 |
+
"pearson_p": 6.257196950215644e-109,
|
| 178 |
+
"n_genes": 278
|
| 179 |
+
},
|
| 180 |
+
"robustness_analytical": [
|
| 181 |
+
{
|
| 182 |
+
"fraction": 0.5,
|
| 183 |
+
"repeat": 0,
|
| 184 |
+
"spearman_r": 0.9901518220508172,
|
| 185 |
+
"pearson_r": 0.999853253364563,
|
| 186 |
+
"n_genes": 5325,
|
| 187 |
+
"n_cells_sampled": 1465
|
| 188 |
+
},
|
| 189 |
+
{
|
| 190 |
+
"fraction": 0.5,
|
| 191 |
+
"repeat": 1,
|
| 192 |
+
"spearman_r": 0.98792904515842,
|
| 193 |
+
"pearson_r": 0.9997460246086121,
|
| 194 |
+
"n_genes": 5325,
|
| 195 |
+
"n_cells_sampled": 1465
|
| 196 |
+
},
|
| 197 |
+
{
|
| 198 |
+
"fraction": 0.8,
|
| 199 |
+
"repeat": 0,
|
| 200 |
+
"spearman_r": 0.9944440945701716,
|
| 201 |
+
"pearson_r": 0.9999425411224365,
|
| 202 |
+
"n_genes": 5325,
|
| 203 |
+
"n_cells_sampled": 2344
|
| 204 |
+
},
|
| 205 |
+
{
|
| 206 |
+
"fraction": 0.8,
|
| 207 |
+
"repeat": 1,
|
| 208 |
+
"spearman_r": 0.9947911002635275,
|
| 209 |
+
"pearson_r": 0.9998971819877625,
|
| 210 |
+
"n_genes": 5325,
|
| 211 |
+
"n_cells_sampled": 2344
|
| 212 |
+
}
|
| 213 |
+
]
|
| 214 |
+
},
|
| 215 |
+
"scifate": {
|
| 216 |
+
"dataset": "scifate",
|
| 217 |
+
"n_cells": 7404,
|
| 218 |
+
"n_genes_analytical": 7970,
|
| 219 |
+
"n_genes_deep": 500,
|
| 220 |
+
"ground_truth_corr": {
|
| 221 |
+
"analytical": {
|
| 222 |
+
"spearman_r": 0.9928182353002162,
|
| 223 |
+
"spearman_p": 0.0,
|
| 224 |
+
"n_genes": 7928
|
| 225 |
+
},
|
| 226 |
+
"deepptr": {
|
| 227 |
+
"spearman_r": 0.9977702950811802,
|
| 228 |
+
"spearman_p": 0.0,
|
| 229 |
+
"n_genes": 500
|
| 230 |
+
}
|
| 231 |
+
},
|
| 232 |
+
"halflife_human": {
|
| 233 |
+
"analytical": {
|
| 234 |
+
"spearman_r": -0.8122591276303375,
|
| 235 |
+
"n_genes": 6995
|
| 236 |
+
},
|
| 237 |
+
"deepptr": {
|
| 238 |
+
"spearman_r": -0.803948101301477,
|
| 239 |
+
"n_genes": 459
|
| 240 |
+
}
|
| 241 |
+
},
|
| 242 |
+
"gamma_agreement": {
|
| 243 |
+
"spearman_r": 0.9997681590726362,
|
| 244 |
+
"n_genes": 500
|
| 245 |
+
},
|
| 246 |
+
"deepptr_epochs": 100,
|
| 247 |
+
"deepptr_time_s": 1464.6209545135498
|
| 248 |
+
}
|
| 249 |
+
}
|
output/deep_benchmark/results/dentate_gyrus_benchmark.json
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"dataset": "dentate_gyrus",
|
| 3 |
+
"n_cells": 2930,
|
| 4 |
+
"n_genes": 5325,
|
| 5 |
+
"deepptr_epochs": 100,
|
| 6 |
+
"deepptr_time_s": 922.1991696357727,
|
| 7 |
+
"deepptr_final_val_loss": 442.7923278808594,
|
| 8 |
+
"halflife": {
|
| 9 |
+
"mouse_herzog": {
|
| 10 |
+
"analytical": {
|
| 11 |
+
"spearman_r": -0.3179587682344816,
|
| 12 |
+
"pearson_r": -0.22408423951314294,
|
| 13 |
+
"n_genes": 1217
|
| 14 |
+
},
|
| 15 |
+
"deepptr": {
|
| 16 |
+
"spearman_r": -0.2852053996865196,
|
| 17 |
+
"pearson_r": -0.19832206076339523,
|
| 18 |
+
"n_genes": 216
|
| 19 |
+
}
|
| 20 |
+
},
|
| 21 |
+
"human_schofield": {
|
| 22 |
+
"analytical": {
|
| 23 |
+
"spearman_r": -0.3812073018847205,
|
| 24 |
+
"pearson_r": -0.2877806614932096,
|
| 25 |
+
"n_genes": 1126
|
| 26 |
+
},
|
| 27 |
+
"deepptr": {
|
| 28 |
+
"spearman_r": -0.35768791768630437,
|
| 29 |
+
"pearson_r": -0.2618435875729263,
|
| 30 |
+
"n_genes": 211
|
| 31 |
+
}
|
| 32 |
+
}
|
| 33 |
+
},
|
| 34 |
+
"enrichment": {
|
| 35 |
+
"ARE": {
|
| 36 |
+
"analytical": {
|
| 37 |
+
"U_statistic": 7270.0,
|
| 38 |
+
"p_value": 0.9358900987906894,
|
| 39 |
+
"n_genes_in_set": 5
|
| 40 |
+
},
|
| 41 |
+
"deepptr": {
|
| 42 |
+
"U_statistic": NaN,
|
| 43 |
+
"p_value": NaN,
|
| 44 |
+
"n_genes_in_set": 0
|
| 45 |
+
}
|
| 46 |
+
},
|
| 47 |
+
"NMD": {
|
| 48 |
+
"analytical": {
|
| 49 |
+
"U_statistic": 32198.0,
|
| 50 |
+
"p_value": 0.007296768008887519,
|
| 51 |
+
"n_genes_in_set": 11
|
| 52 |
+
},
|
| 53 |
+
"deepptr": {
|
| 54 |
+
"U_statistic": NaN,
|
| 55 |
+
"p_value": NaN,
|
| 56 |
+
"n_genes_in_set": 0
|
| 57 |
+
}
|
| 58 |
+
}
|
| 59 |
+
},
|
| 60 |
+
"gamma_agreement": {
|
| 61 |
+
"spearman_r": 0.9924993974774762,
|
| 62 |
+
"spearman_p": 5.8151180829002596e-254,
|
| 63 |
+
"pearson_r": 0.9121664644353008,
|
| 64 |
+
"pearson_p": 6.257196950215644e-109,
|
| 65 |
+
"n_genes": 278
|
| 66 |
+
},
|
| 67 |
+
"robustness_analytical": [
|
| 68 |
+
{
|
| 69 |
+
"fraction": 0.5,
|
| 70 |
+
"repeat": 0,
|
| 71 |
+
"spearman_r": 0.9901518220508172,
|
| 72 |
+
"pearson_r": 0.999853253364563,
|
| 73 |
+
"n_genes": 5325,
|
| 74 |
+
"n_cells_sampled": 1465
|
| 75 |
+
},
|
| 76 |
+
{
|
| 77 |
+
"fraction": 0.5,
|
| 78 |
+
"repeat": 1,
|
| 79 |
+
"spearman_r": 0.98792904515842,
|
| 80 |
+
"pearson_r": 0.9997460246086121,
|
| 81 |
+
"n_genes": 5325,
|
| 82 |
+
"n_cells_sampled": 1465
|
| 83 |
+
},
|
| 84 |
+
{
|
| 85 |
+
"fraction": 0.8,
|
| 86 |
+
"repeat": 0,
|
| 87 |
+
"spearman_r": 0.9944440945701716,
|
| 88 |
+
"pearson_r": 0.9999425411224365,
|
| 89 |
+
"n_genes": 5325,
|
| 90 |
+
"n_cells_sampled": 2344
|
| 91 |
+
},
|
| 92 |
+
{
|
| 93 |
+
"fraction": 0.8,
|
| 94 |
+
"repeat": 1,
|
| 95 |
+
"spearman_r": 0.9947911002635275,
|
| 96 |
+
"pearson_r": 0.9998971819877625,
|
| 97 |
+
"n_genes": 5325,
|
| 98 |
+
"n_cells_sampled": 2344
|
| 99 |
+
}
|
| 100 |
+
]
|
| 101 |
+
}
|
output/deep_benchmark/results/pancreas_benchmark.json
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"dataset": "pancreas",
|
| 3 |
+
"n_cells": 3696,
|
| 4 |
+
"n_genes": 11906,
|
| 5 |
+
"deepptr_epochs": 100,
|
| 6 |
+
"deepptr_time_s": 691.1493718624115,
|
| 7 |
+
"deepptr_final_val_loss": 581.9227905273438,
|
| 8 |
+
"halflife": {
|
| 9 |
+
"mouse_herzog": {
|
| 10 |
+
"analytical": {
|
| 11 |
+
"spearman_r": -0.34966929477006137,
|
| 12 |
+
"pearson_r": -0.23291966884468007,
|
| 13 |
+
"n_genes": 4611
|
| 14 |
+
},
|
| 15 |
+
"deepptr": {
|
| 16 |
+
"spearman_r": -0.19762101977653473,
|
| 17 |
+
"pearson_r": -0.13793936916451716,
|
| 18 |
+
"n_genes": 251
|
| 19 |
+
}
|
| 20 |
+
},
|
| 21 |
+
"human_schofield": {
|
| 22 |
+
"analytical": {
|
| 23 |
+
"spearman_r": -0.4020872107350974,
|
| 24 |
+
"pearson_r": -0.2849205668358859,
|
| 25 |
+
"n_genes": 4308
|
| 26 |
+
},
|
| 27 |
+
"deepptr": {
|
| 28 |
+
"spearman_r": -0.2767452667406712,
|
| 29 |
+
"pearson_r": -0.24032216943506846,
|
| 30 |
+
"n_genes": 236
|
| 31 |
+
}
|
| 32 |
+
}
|
| 33 |
+
},
|
| 34 |
+
"enrichment": {
|
| 35 |
+
"ARE": {
|
| 36 |
+
"analytical": {
|
| 37 |
+
"U_statistic": 44637.0,
|
| 38 |
+
"p_value": 0.9841054448243868,
|
| 39 |
+
"n_genes_in_set": 14
|
| 40 |
+
},
|
| 41 |
+
"deepptr": {
|
| 42 |
+
"U_statistic": NaN,
|
| 43 |
+
"p_value": NaN,
|
| 44 |
+
"n_genes_in_set": 0
|
| 45 |
+
}
|
| 46 |
+
},
|
| 47 |
+
"NMD": {
|
| 48 |
+
"analytical": {
|
| 49 |
+
"U_statistic": 141636.0,
|
| 50 |
+
"p_value": 0.31160184709114613,
|
| 51 |
+
"n_genes_in_set": 29
|
| 52 |
+
},
|
| 53 |
+
"deepptr": {
|
| 54 |
+
"U_statistic": 361.0,
|
| 55 |
+
"p_value": 0.705555655316379,
|
| 56 |
+
"n_genes_in_set": 3
|
| 57 |
+
}
|
| 58 |
+
}
|
| 59 |
+
},
|
| 60 |
+
"gamma_agreement": {
|
| 61 |
+
"spearman_r": 0.9796487428351981,
|
| 62 |
+
"spearman_p": 2.4770516189667095e-190,
|
| 63 |
+
"pearson_r": 0.8430279503146496,
|
| 64 |
+
"pearson_p": 1.1020222468626118e-74,
|
| 65 |
+
"n_genes": 272
|
| 66 |
+
},
|
| 67 |
+
"robustness_analytical": [
|
| 68 |
+
{
|
| 69 |
+
"fraction": 0.5,
|
| 70 |
+
"repeat": 0,
|
| 71 |
+
"spearman_r": 0.9935493190803674,
|
| 72 |
+
"pearson_r": 0.9995819926261902,
|
| 73 |
+
"n_genes": 11906,
|
| 74 |
+
"n_cells_sampled": 1848
|
| 75 |
+
},
|
| 76 |
+
{
|
| 77 |
+
"fraction": 0.5,
|
| 78 |
+
"repeat": 1,
|
| 79 |
+
"spearman_r": 0.9957264356741521,
|
| 80 |
+
"pearson_r": 0.9995998740196228,
|
| 81 |
+
"n_genes": 11906,
|
| 82 |
+
"n_cells_sampled": 1848
|
| 83 |
+
},
|
| 84 |
+
{
|
| 85 |
+
"fraction": 0.8,
|
| 86 |
+
"repeat": 0,
|
| 87 |
+
"spearman_r": 0.9975692623733601,
|
| 88 |
+
"pearson_r": 0.9999009370803833,
|
| 89 |
+
"n_genes": 11906,
|
| 90 |
+
"n_cells_sampled": 2956
|
| 91 |
+
},
|
| 92 |
+
{
|
| 93 |
+
"fraction": 0.8,
|
| 94 |
+
"repeat": 1,
|
| 95 |
+
"spearman_r": 0.9972764513977047,
|
| 96 |
+
"pearson_r": 0.9998981952667236,
|
| 97 |
+
"n_genes": 11906,
|
| 98 |
+
"n_cells_sampled": 2956
|
| 99 |
+
}
|
| 100 |
+
]
|
| 101 |
+
}
|
output/deep_benchmark/results/scifate_benchmark.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"dataset": "scifate",
|
| 3 |
+
"n_cells": 7404,
|
| 4 |
+
"n_genes_analytical": 7970,
|
| 5 |
+
"n_genes_deep": 500,
|
| 6 |
+
"ground_truth_corr": {
|
| 7 |
+
"analytical": {
|
| 8 |
+
"spearman_r": 0.9928182353002162,
|
| 9 |
+
"spearman_p": 0.0,
|
| 10 |
+
"n_genes": 7928
|
| 11 |
+
},
|
| 12 |
+
"deepptr": {
|
| 13 |
+
"spearman_r": 0.9977702950811802,
|
| 14 |
+
"spearman_p": 0.0,
|
| 15 |
+
"n_genes": 500
|
| 16 |
+
}
|
| 17 |
+
},
|
| 18 |
+
"halflife_human": {
|
| 19 |
+
"analytical": {
|
| 20 |
+
"spearman_r": -0.8122591276303375,
|
| 21 |
+
"n_genes": 6995
|
| 22 |
+
},
|
| 23 |
+
"deepptr": {
|
| 24 |
+
"spearman_r": -0.803948101301477,
|
| 25 |
+
"n_genes": 459
|
| 26 |
+
}
|
| 27 |
+
},
|
| 28 |
+
"gamma_agreement": {
|
| 29 |
+
"spearman_r": 0.9997681590726362,
|
| 30 |
+
"n_genes": 500
|
| 31 |
+
},
|
| 32 |
+
"deepptr_epochs": 100,
|
| 33 |
+
"deepptr_time_s": 1464.6209545135498
|
| 34 |
+
}
|
output/deep_benchmark/results/synthetic_recovery.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"gamma_recovery_per_gene": 0.8552942267330017,
|
| 3 |
+
"gamma_recovery_global": 0.853245950458172,
|
| 4 |
+
"ci_coverage_95": 0.26692,
|
| 5 |
+
"latent_recovery_T": 0.5561008036714264,
|
| 6 |
+
"latent_recovery_PT": 0.5348961912017587,
|
| 7 |
+
"n_epochs": 150,
|
| 8 |
+
"final_train_loss": 873.7797139485677,
|
| 9 |
+
"final_val_loss": 878.2410888671875,
|
| 10 |
+
"training_time_s": 24.290624618530273
|
| 11 |
+
}
|
output/deep_benchmark_v2/results/combined_v2.json
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"per_dataset": {
|
| 3 |
+
"pancreas": {
|
| 4 |
+
"dataset": "pancreas",
|
| 5 |
+
"n_epochs": 100,
|
| 6 |
+
"time_s": 1058.0878033638,
|
| 7 |
+
"enrichment": {
|
| 8 |
+
"ARE": {
|
| 9 |
+
"analytical": {
|
| 10 |
+
"p_value": 0.9841054448243868,
|
| 11 |
+
"n_genes_in_set": 14,
|
| 12 |
+
"median_gamma_in": 0.0,
|
| 13 |
+
"median_gamma_bg": 0.006473083980381489
|
| 14 |
+
},
|
| 15 |
+
"hybrid_deepptr": {
|
| 16 |
+
"p_value": 0.8696075799919176,
|
| 17 |
+
"n_genes_in_set": 4,
|
| 18 |
+
"median_gamma_in": 0.15123695135116577,
|
| 19 |
+
"median_gamma_bg": 0.5739089846611023
|
| 20 |
+
}
|
| 21 |
+
},
|
| 22 |
+
"NMD": {
|
| 23 |
+
"analytical": {
|
| 24 |
+
"p_value": 0.31160184709114613,
|
| 25 |
+
"n_genes_in_set": 29,
|
| 26 |
+
"median_gamma_in": 0.0036936448886990547,
|
| 27 |
+
"median_gamma_bg": 0.006401660852134228
|
| 28 |
+
},
|
| 29 |
+
"hybrid_deepptr": {
|
| 30 |
+
"p_value": 0.5626224403496046,
|
| 31 |
+
"n_genes_in_set": 16,
|
| 32 |
+
"median_gamma_in": 0.44641411304473877,
|
| 33 |
+
"median_gamma_bg": 0.5732103586196899
|
| 34 |
+
}
|
| 35 |
+
}
|
| 36 |
+
},
|
| 37 |
+
"celltype_agreement": [
|
| 38 |
+
{
|
| 39 |
+
"cell_type": "Alpha",
|
| 40 |
+
"n_cells_an": 481,
|
| 41 |
+
"n_cells_dp": 481,
|
| 42 |
+
"n_genes": 272,
|
| 43 |
+
"spearman_r": 0.9849840305125739
|
| 44 |
+
},
|
| 45 |
+
{
|
| 46 |
+
"cell_type": "Beta",
|
| 47 |
+
"n_cells_an": 591,
|
| 48 |
+
"n_cells_dp": 591,
|
| 49 |
+
"n_genes": 271,
|
| 50 |
+
"spearman_r": 0.9889437570846297
|
| 51 |
+
},
|
| 52 |
+
{
|
| 53 |
+
"cell_type": "Delta",
|
| 54 |
+
"n_cells_an": 70,
|
| 55 |
+
"n_cells_dp": 70,
|
| 56 |
+
"n_genes": 265,
|
| 57 |
+
"spearman_r": 0.9755626201008525
|
| 58 |
+
},
|
| 59 |
+
{
|
| 60 |
+
"cell_type": "Ductal",
|
| 61 |
+
"n_cells_an": 916,
|
| 62 |
+
"n_cells_dp": 916,
|
| 63 |
+
"n_genes": 243,
|
| 64 |
+
"spearman_r": 0.9851671207856005
|
| 65 |
+
},
|
| 66 |
+
{
|
| 67 |
+
"cell_type": "Epsilon",
|
| 68 |
+
"n_cells_an": 142,
|
| 69 |
+
"n_cells_dp": 142,
|
| 70 |
+
"n_genes": 269,
|
| 71 |
+
"spearman_r": 0.9825605553397818
|
| 72 |
+
},
|
| 73 |
+
{
|
| 74 |
+
"cell_type": "Ngn3 high EP",
|
| 75 |
+
"n_cells_an": 642,
|
| 76 |
+
"n_cells_dp": 642,
|
| 77 |
+
"n_genes": 257,
|
| 78 |
+
"spearman_r": 0.9788268595602208
|
| 79 |
+
},
|
| 80 |
+
{
|
| 81 |
+
"cell_type": "Ngn3 low EP",
|
| 82 |
+
"n_cells_an": 262,
|
| 83 |
+
"n_cells_dp": 262,
|
| 84 |
+
"n_genes": 243,
|
| 85 |
+
"spearman_r": 0.9875966152667593
|
| 86 |
+
},
|
| 87 |
+
{
|
| 88 |
+
"cell_type": "Pre-endocrine",
|
| 89 |
+
"n_cells_an": 592,
|
| 90 |
+
"n_cells_dp": 592,
|
| 91 |
+
"n_genes": 269,
|
| 92 |
+
"spearman_r": 0.9832497980974927
|
| 93 |
+
}
|
| 94 |
+
],
|
| 95 |
+
"uncertainty": {
|
| 96 |
+
"var_error_spearman_r": 0.9683824423893881,
|
| 97 |
+
"var_error_spearman_p": 7.848856686887698e-165,
|
| 98 |
+
"n_genes": 272,
|
| 99 |
+
"bin_median_var": [
|
| 100 |
+
2.6977049856213853e-06,
|
| 101 |
+
6.103933992562816e-05,
|
| 102 |
+
0.00129510962869972,
|
| 103 |
+
0.00978328101336956,
|
| 104 |
+
0.12321395426988602
|
| 105 |
+
],
|
| 106 |
+
"bin_median_error": [
|
| 107 |
+
1.4680728912353516,
|
| 108 |
+
8.11591625213623,
|
| 109 |
+
38.67870330810547,
|
| 110 |
+
107.40599060058594,
|
| 111 |
+
243.96734619140625
|
| 112 |
+
]
|
| 113 |
+
},
|
| 114 |
+
"latent_structure": {
|
| 115 |
+
"silhouette_z_T": 0.2205258160829544,
|
| 116 |
+
"silhouette_z_PT": -0.009808977134525776,
|
| 117 |
+
"silhouette_gamma": -0.11721337586641312
|
| 118 |
+
},
|
| 119 |
+
"gene_ranking": {
|
| 120 |
+
"n_shared_genes": 300,
|
| 121 |
+
"top_median_overlap": 20,
|
| 122 |
+
"top_median_overlap_frac": 0.4,
|
| 123 |
+
"top_var_overlap": 34,
|
| 124 |
+
"top_var_overlap_frac": 0.68,
|
| 125 |
+
"rank_correlation": 0.5066891652362127
|
| 126 |
+
}
|
| 127 |
+
},
|
| 128 |
+
"dentate_gyrus": {
|
| 129 |
+
"dataset": "dentate_gyrus",
|
| 130 |
+
"n_epochs": 100,
|
| 131 |
+
"time_s": 694.0770261287689,
|
| 132 |
+
"enrichment": {
|
| 133 |
+
"ARE": {
|
| 134 |
+
"analytical": {
|
| 135 |
+
"p_value": 0.9358900987906894,
|
| 136 |
+
"n_genes_in_set": 5,
|
| 137 |
+
"median_gamma_in": 0.0,
|
| 138 |
+
"median_gamma_bg": 0.0
|
| 139 |
+
},
|
| 140 |
+
"hybrid_deepptr": {
|
| 141 |
+
"p_value": NaN,
|
| 142 |
+
"n_genes_in_set": 0,
|
| 143 |
+
"median_gamma_in": NaN,
|
| 144 |
+
"median_gamma_bg": NaN
|
| 145 |
+
}
|
| 146 |
+
},
|
| 147 |
+
"NMD": {
|
| 148 |
+
"analytical": {
|
| 149 |
+
"p_value": 0.007296768008887519,
|
| 150 |
+
"n_genes_in_set": 11,
|
| 151 |
+
"median_gamma_in": 0.43911683559417725,
|
| 152 |
+
"median_gamma_bg": 0.0
|
| 153 |
+
},
|
| 154 |
+
"hybrid_deepptr": {
|
| 155 |
+
"p_value": 0.03924005081735492,
|
| 156 |
+
"n_genes_in_set": 7,
|
| 157 |
+
"median_gamma_in": 1.0233218669891357,
|
| 158 |
+
"median_gamma_bg": 0.4459204375743866
|
| 159 |
+
}
|
| 160 |
+
}
|
| 161 |
+
},
|
| 162 |
+
"celltype_agreement": [
|
| 163 |
+
{
|
| 164 |
+
"cell_type": "Astrocytes",
|
| 165 |
+
"n_cells_an": 120,
|
| 166 |
+
"n_cells_dp": 120,
|
| 167 |
+
"n_genes": 139,
|
| 168 |
+
"spearman_r": 0.9282586353277625
|
| 169 |
+
},
|
| 170 |
+
{
|
| 171 |
+
"cell_type": "Cajal Retzius",
|
| 172 |
+
"n_cells_an": 34,
|
| 173 |
+
"n_cells_dp": 34,
|
| 174 |
+
"n_genes": 194,
|
| 175 |
+
"spearman_r": 0.951966734189906
|
| 176 |
+
},
|
| 177 |
+
{
|
| 178 |
+
"cell_type": "Cck-Tox",
|
| 179 |
+
"n_cells_an": 27,
|
| 180 |
+
"n_cells_dp": 27,
|
| 181 |
+
"n_genes": 270,
|
| 182 |
+
"spearman_r": 0.9701510918454908
|
| 183 |
+
},
|
| 184 |
+
{
|
| 185 |
+
"cell_type": "Endothelial",
|
| 186 |
+
"n_cells_an": 87,
|
| 187 |
+
"n_cells_dp": 87,
|
| 188 |
+
"n_genes": 187,
|
| 189 |
+
"spearman_r": 0.9376658506417481
|
| 190 |
+
},
|
| 191 |
+
{
|
| 192 |
+
"cell_type": "GABA",
|
| 193 |
+
"n_cells_an": 61,
|
| 194 |
+
"n_cells_dp": 61,
|
| 195 |
+
"n_genes": 282,
|
| 196 |
+
"spearman_r": 0.9764516140252212
|
| 197 |
+
},
|
| 198 |
+
{
|
| 199 |
+
"cell_type": "Granule immature",
|
| 200 |
+
"n_cells_an": 785,
|
| 201 |
+
"n_cells_dp": 785,
|
| 202 |
+
"n_genes": 286,
|
| 203 |
+
"spearman_r": 0.991680719793685
|
| 204 |
+
},
|
| 205 |
+
{
|
| 206 |
+
"cell_type": "Granule mature",
|
| 207 |
+
"n_cells_an": 1070,
|
| 208 |
+
"n_cells_dp": 1070,
|
| 209 |
+
"n_genes": 282,
|
| 210 |
+
"spearman_r": 0.9910605281865464
|
| 211 |
+
},
|
| 212 |
+
{
|
| 213 |
+
"cell_type": "Microglia",
|
| 214 |
+
"n_cells_an": 81,
|
| 215 |
+
"n_cells_dp": 81,
|
| 216 |
+
"n_genes": 96,
|
| 217 |
+
"spearman_r": 0.9446418882257189
|
| 218 |
+
},
|
| 219 |
+
{
|
| 220 |
+
"cell_type": "Mossy",
|
| 221 |
+
"n_cells_an": 75,
|
| 222 |
+
"n_cells_dp": 75,
|
| 223 |
+
"n_genes": 282,
|
| 224 |
+
"spearman_r": 0.9759063415615609
|
| 225 |
+
},
|
| 226 |
+
{
|
| 227 |
+
"cell_type": "Neuroblast",
|
| 228 |
+
"n_cells_an": 417,
|
| 229 |
+
"n_cells_dp": 417,
|
| 230 |
+
"n_genes": 245,
|
| 231 |
+
"spearman_r": 0.9671217227394759
|
| 232 |
+
},
|
| 233 |
+
{
|
| 234 |
+
"cell_type": "OL",
|
| 235 |
+
"n_cells_an": 50,
|
| 236 |
+
"n_cells_dp": 50,
|
| 237 |
+
"n_genes": 140,
|
| 238 |
+
"spearman_r": 0.9271478865539788
|
| 239 |
+
},
|
| 240 |
+
{
|
| 241 |
+
"cell_type": "OPC",
|
| 242 |
+
"n_cells_an": 53,
|
| 243 |
+
"n_cells_dp": 53,
|
| 244 |
+
"n_genes": 170,
|
| 245 |
+
"spearman_r": 0.9368958421113697
|
| 246 |
+
},
|
| 247 |
+
{
|
| 248 |
+
"cell_type": "Radial Glia-like",
|
| 249 |
+
"n_cells_an": 51,
|
| 250 |
+
"n_cells_dp": 51,
|
| 251 |
+
"n_genes": 138,
|
| 252 |
+
"spearman_r": 0.9399528298655901
|
| 253 |
+
},
|
| 254 |
+
{
|
| 255 |
+
"cell_type": "nIPC",
|
| 256 |
+
"n_cells_an": 19,
|
| 257 |
+
"n_cells_dp": 19,
|
| 258 |
+
"n_genes": 183,
|
| 259 |
+
"spearman_r": 0.9286732511964161
|
| 260 |
+
}
|
| 261 |
+
],
|
| 262 |
+
"uncertainty": {
|
| 263 |
+
"var_error_spearman_r": 0.9442975955790626,
|
| 264 |
+
"var_error_spearman_p": 3.0595371213572223e-135,
|
| 265 |
+
"n_genes": 278,
|
| 266 |
+
"bin_median_var": [
|
| 267 |
+
3.0064077236602316e-06,
|
| 268 |
+
0.0005680265021510422,
|
| 269 |
+
0.005993897560983896,
|
| 270 |
+
0.028587721288204193,
|
| 271 |
+
0.0789710134267807
|
| 272 |
+
],
|
| 273 |
+
"bin_median_error": [
|
| 274 |
+
0.21611608564853668,
|
| 275 |
+
2.4018590450286865,
|
| 276 |
+
9.262259483337402,
|
| 277 |
+
21.739778518676758,
|
| 278 |
+
53.096153259277344
|
| 279 |
+
]
|
| 280 |
+
},
|
| 281 |
+
"latent_structure": {
|
| 282 |
+
"silhouette_z_T": 0.18228954076766968,
|
| 283 |
+
"silhouette_z_PT": 0.036660902202129364,
|
| 284 |
+
"silhouette_gamma": -0.08321909606456757
|
| 285 |
+
},
|
| 286 |
+
"gene_ranking": {
|
| 287 |
+
"n_shared_genes": 300,
|
| 288 |
+
"top_median_overlap": 30,
|
| 289 |
+
"top_median_overlap_frac": 0.6,
|
| 290 |
+
"top_var_overlap": 21,
|
| 291 |
+
"top_var_overlap_frac": 0.42,
|
| 292 |
+
"rank_correlation": 0.6159207216683028
|
| 293 |
+
}
|
| 294 |
+
}
|
| 295 |
+
},
|
| 296 |
+
"cross_dataset": {
|
| 297 |
+
"analytical": [
|
| 298 |
+
{
|
| 299 |
+
"dataset_a": "dentate_gyrus",
|
| 300 |
+
"dataset_b": "pancreas",
|
| 301 |
+
"n_shared_genes": 4915,
|
| 302 |
+
"spearman_r": 0.1915326245338796,
|
| 303 |
+
"pearson_r": 0.1037985123384687
|
| 304 |
+
}
|
| 305 |
+
],
|
| 306 |
+
"deepptr": [
|
| 307 |
+
{
|
| 308 |
+
"dataset_a": "dentate_gyrus",
|
| 309 |
+
"dataset_b": "pancreas",
|
| 310 |
+
"n_shared_genes": 53,
|
| 311 |
+
"spearman_r": 0.7214965328172875,
|
| 312 |
+
"pearson_r": 0.6869671266770031
|
| 313 |
+
}
|
| 314 |
+
]
|
| 315 |
+
},
|
| 316 |
+
"subsampling": {
|
| 317 |
+
"pancreas": [
|
| 318 |
+
{
|
| 319 |
+
"fraction": 0.5,
|
| 320 |
+
"n_cells": 1848,
|
| 321 |
+
"spearman_r": 0.9991751019455771
|
| 322 |
+
},
|
| 323 |
+
{
|
| 324 |
+
"fraction": 0.8,
|
| 325 |
+
"n_cells": 2956,
|
| 326 |
+
"spearman_r": 0.9997239969332992
|
| 327 |
+
}
|
| 328 |
+
],
|
| 329 |
+
"dentate_gyrus": [
|
| 330 |
+
{
|
| 331 |
+
"fraction": 0.5,
|
| 332 |
+
"n_cells": 1465,
|
| 333 |
+
"spearman_r": 0.997867531861465
|
| 334 |
+
},
|
| 335 |
+
{
|
| 336 |
+
"fraction": 0.8,
|
| 337 |
+
"n_cells": 2344,
|
| 338 |
+
"spearman_r": 0.9994866609628995
|
| 339 |
+
}
|
| 340 |
+
]
|
| 341 |
+
}
|
| 342 |
+
}
|
output/deep_benchmark_v2/results/dentate_gyrus_v2.json
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"dataset": "dentate_gyrus",
|
| 3 |
+
"n_epochs": 100,
|
| 4 |
+
"time_s": 694.0770261287689,
|
| 5 |
+
"enrichment": {
|
| 6 |
+
"ARE": {
|
| 7 |
+
"analytical": {
|
| 8 |
+
"p_value": 0.9358900987906894,
|
| 9 |
+
"n_genes_in_set": 5,
|
| 10 |
+
"median_gamma_in": 0.0,
|
| 11 |
+
"median_gamma_bg": 0.0
|
| 12 |
+
},
|
| 13 |
+
"hybrid_deepptr": {
|
| 14 |
+
"p_value": NaN,
|
| 15 |
+
"n_genes_in_set": 0,
|
| 16 |
+
"median_gamma_in": NaN,
|
| 17 |
+
"median_gamma_bg": NaN
|
| 18 |
+
}
|
| 19 |
+
},
|
| 20 |
+
"NMD": {
|
| 21 |
+
"analytical": {
|
| 22 |
+
"p_value": 0.007296768008887519,
|
| 23 |
+
"n_genes_in_set": 11,
|
| 24 |
+
"median_gamma_in": 0.43911683559417725,
|
| 25 |
+
"median_gamma_bg": 0.0
|
| 26 |
+
},
|
| 27 |
+
"hybrid_deepptr": {
|
| 28 |
+
"p_value": 0.03924005081735492,
|
| 29 |
+
"n_genes_in_set": 7,
|
| 30 |
+
"median_gamma_in": 1.0233218669891357,
|
| 31 |
+
"median_gamma_bg": 0.4459204375743866
|
| 32 |
+
}
|
| 33 |
+
}
|
| 34 |
+
},
|
| 35 |
+
"celltype_agreement": [
|
| 36 |
+
{
|
| 37 |
+
"cell_type": "Astrocytes",
|
| 38 |
+
"n_cells_an": 120,
|
| 39 |
+
"n_cells_dp": 120,
|
| 40 |
+
"n_genes": 139,
|
| 41 |
+
"spearman_r": 0.9282586353277625
|
| 42 |
+
},
|
| 43 |
+
{
|
| 44 |
+
"cell_type": "Cajal Retzius",
|
| 45 |
+
"n_cells_an": 34,
|
| 46 |
+
"n_cells_dp": 34,
|
| 47 |
+
"n_genes": 194,
|
| 48 |
+
"spearman_r": 0.951966734189906
|
| 49 |
+
},
|
| 50 |
+
{
|
| 51 |
+
"cell_type": "Cck-Tox",
|
| 52 |
+
"n_cells_an": 27,
|
| 53 |
+
"n_cells_dp": 27,
|
| 54 |
+
"n_genes": 270,
|
| 55 |
+
"spearman_r": 0.9701510918454908
|
| 56 |
+
},
|
| 57 |
+
{
|
| 58 |
+
"cell_type": "Endothelial",
|
| 59 |
+
"n_cells_an": 87,
|
| 60 |
+
"n_cells_dp": 87,
|
| 61 |
+
"n_genes": 187,
|
| 62 |
+
"spearman_r": 0.9376658506417481
|
| 63 |
+
},
|
| 64 |
+
{
|
| 65 |
+
"cell_type": "GABA",
|
| 66 |
+
"n_cells_an": 61,
|
| 67 |
+
"n_cells_dp": 61,
|
| 68 |
+
"n_genes": 282,
|
| 69 |
+
"spearman_r": 0.9764516140252212
|
| 70 |
+
},
|
| 71 |
+
{
|
| 72 |
+
"cell_type": "Granule immature",
|
| 73 |
+
"n_cells_an": 785,
|
| 74 |
+
"n_cells_dp": 785,
|
| 75 |
+
"n_genes": 286,
|
| 76 |
+
"spearman_r": 0.991680719793685
|
| 77 |
+
},
|
| 78 |
+
{
|
| 79 |
+
"cell_type": "Granule mature",
|
| 80 |
+
"n_cells_an": 1070,
|
| 81 |
+
"n_cells_dp": 1070,
|
| 82 |
+
"n_genes": 282,
|
| 83 |
+
"spearman_r": 0.9910605281865464
|
| 84 |
+
},
|
| 85 |
+
{
|
| 86 |
+
"cell_type": "Microglia",
|
| 87 |
+
"n_cells_an": 81,
|
| 88 |
+
"n_cells_dp": 81,
|
| 89 |
+
"n_genes": 96,
|
| 90 |
+
"spearman_r": 0.9446418882257189
|
| 91 |
+
},
|
| 92 |
+
{
|
| 93 |
+
"cell_type": "Mossy",
|
| 94 |
+
"n_cells_an": 75,
|
| 95 |
+
"n_cells_dp": 75,
|
| 96 |
+
"n_genes": 282,
|
| 97 |
+
"spearman_r": 0.9759063415615609
|
| 98 |
+
},
|
| 99 |
+
{
|
| 100 |
+
"cell_type": "Neuroblast",
|
| 101 |
+
"n_cells_an": 417,
|
| 102 |
+
"n_cells_dp": 417,
|
| 103 |
+
"n_genes": 245,
|
| 104 |
+
"spearman_r": 0.9671217227394759
|
| 105 |
+
},
|
| 106 |
+
{
|
| 107 |
+
"cell_type": "OL",
|
| 108 |
+
"n_cells_an": 50,
|
| 109 |
+
"n_cells_dp": 50,
|
| 110 |
+
"n_genes": 140,
|
| 111 |
+
"spearman_r": 0.9271478865539788
|
| 112 |
+
},
|
| 113 |
+
{
|
| 114 |
+
"cell_type": "OPC",
|
| 115 |
+
"n_cells_an": 53,
|
| 116 |
+
"n_cells_dp": 53,
|
| 117 |
+
"n_genes": 170,
|
| 118 |
+
"spearman_r": 0.9368958421113697
|
| 119 |
+
},
|
| 120 |
+
{
|
| 121 |
+
"cell_type": "Radial Glia-like",
|
| 122 |
+
"n_cells_an": 51,
|
| 123 |
+
"n_cells_dp": 51,
|
| 124 |
+
"n_genes": 138,
|
| 125 |
+
"spearman_r": 0.9399528298655901
|
| 126 |
+
},
|
| 127 |
+
{
|
| 128 |
+
"cell_type": "nIPC",
|
| 129 |
+
"n_cells_an": 19,
|
| 130 |
+
"n_cells_dp": 19,
|
| 131 |
+
"n_genes": 183,
|
| 132 |
+
"spearman_r": 0.9286732511964161
|
| 133 |
+
}
|
| 134 |
+
],
|
| 135 |
+
"uncertainty": {
|
| 136 |
+
"var_error_spearman_r": 0.9442975955790626,
|
| 137 |
+
"var_error_spearman_p": 3.0595371213572223e-135,
|
| 138 |
+
"n_genes": 278,
|
| 139 |
+
"bin_median_var": [
|
| 140 |
+
3.0064077236602316e-06,
|
| 141 |
+
0.0005680265021510422,
|
| 142 |
+
0.005993897560983896,
|
| 143 |
+
0.028587721288204193,
|
| 144 |
+
0.0789710134267807
|
| 145 |
+
],
|
| 146 |
+
"bin_median_error": [
|
| 147 |
+
0.21611608564853668,
|
| 148 |
+
2.4018590450286865,
|
| 149 |
+
9.262259483337402,
|
| 150 |
+
21.739778518676758,
|
| 151 |
+
53.096153259277344
|
| 152 |
+
]
|
| 153 |
+
},
|
| 154 |
+
"latent_structure": {
|
| 155 |
+
"silhouette_z_T": 0.18228954076766968,
|
| 156 |
+
"silhouette_z_PT": 0.036660902202129364,
|
| 157 |
+
"silhouette_gamma": -0.08321909606456757
|
| 158 |
+
},
|
| 159 |
+
"gene_ranking": {
|
| 160 |
+
"n_shared_genes": 300,
|
| 161 |
+
"top_median_overlap": 30,
|
| 162 |
+
"top_median_overlap_frac": 0.6,
|
| 163 |
+
"top_var_overlap": 21,
|
| 164 |
+
"top_var_overlap_frac": 0.42,
|
| 165 |
+
"rank_correlation": 0.6159207216683028
|
| 166 |
+
}
|
| 167 |
+
}
|
output/deep_benchmark_v2/results/pancreas_v2.json
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"dataset": "pancreas",
|
| 3 |
+
"n_epochs": 100,
|
| 4 |
+
"time_s": 1058.0878033638,
|
| 5 |
+
"enrichment": {
|
| 6 |
+
"ARE": {
|
| 7 |
+
"analytical": {
|
| 8 |
+
"p_value": 0.9841054448243868,
|
| 9 |
+
"n_genes_in_set": 14,
|
| 10 |
+
"median_gamma_in": 0.0,
|
| 11 |
+
"median_gamma_bg": 0.006473083980381489
|
| 12 |
+
},
|
| 13 |
+
"hybrid_deepptr": {
|
| 14 |
+
"p_value": 0.8696075799919176,
|
| 15 |
+
"n_genes_in_set": 4,
|
| 16 |
+
"median_gamma_in": 0.15123695135116577,
|
| 17 |
+
"median_gamma_bg": 0.5739089846611023
|
| 18 |
+
}
|
| 19 |
+
},
|
| 20 |
+
"NMD": {
|
| 21 |
+
"analytical": {
|
| 22 |
+
"p_value": 0.31160184709114613,
|
| 23 |
+
"n_genes_in_set": 29,
|
| 24 |
+
"median_gamma_in": 0.0036936448886990547,
|
| 25 |
+
"median_gamma_bg": 0.006401660852134228
|
| 26 |
+
},
|
| 27 |
+
"hybrid_deepptr": {
|
| 28 |
+
"p_value": 0.5626224403496046,
|
| 29 |
+
"n_genes_in_set": 16,
|
| 30 |
+
"median_gamma_in": 0.44641411304473877,
|
| 31 |
+
"median_gamma_bg": 0.5732103586196899
|
| 32 |
+
}
|
| 33 |
+
}
|
| 34 |
+
},
|
| 35 |
+
"celltype_agreement": [
|
| 36 |
+
{
|
| 37 |
+
"cell_type": "Alpha",
|
| 38 |
+
"n_cells_an": 481,
|
| 39 |
+
"n_cells_dp": 481,
|
| 40 |
+
"n_genes": 272,
|
| 41 |
+
"spearman_r": 0.9849840305125739
|
| 42 |
+
},
|
| 43 |
+
{
|
| 44 |
+
"cell_type": "Beta",
|
| 45 |
+
"n_cells_an": 591,
|
| 46 |
+
"n_cells_dp": 591,
|
| 47 |
+
"n_genes": 271,
|
| 48 |
+
"spearman_r": 0.9889437570846297
|
| 49 |
+
},
|
| 50 |
+
{
|
| 51 |
+
"cell_type": "Delta",
|
| 52 |
+
"n_cells_an": 70,
|
| 53 |
+
"n_cells_dp": 70,
|
| 54 |
+
"n_genes": 265,
|
| 55 |
+
"spearman_r": 0.9755626201008525
|
| 56 |
+
},
|
| 57 |
+
{
|
| 58 |
+
"cell_type": "Ductal",
|
| 59 |
+
"n_cells_an": 916,
|
| 60 |
+
"n_cells_dp": 916,
|
| 61 |
+
"n_genes": 243,
|
| 62 |
+
"spearman_r": 0.9851671207856005
|
| 63 |
+
},
|
| 64 |
+
{
|
| 65 |
+
"cell_type": "Epsilon",
|
| 66 |
+
"n_cells_an": 142,
|
| 67 |
+
"n_cells_dp": 142,
|
| 68 |
+
"n_genes": 269,
|
| 69 |
+
"spearman_r": 0.9825605553397818
|
| 70 |
+
},
|
| 71 |
+
{
|
| 72 |
+
"cell_type": "Ngn3 high EP",
|
| 73 |
+
"n_cells_an": 642,
|
| 74 |
+
"n_cells_dp": 642,
|
| 75 |
+
"n_genes": 257,
|
| 76 |
+
"spearman_r": 0.9788268595602208
|
| 77 |
+
},
|
| 78 |
+
{
|
| 79 |
+
"cell_type": "Ngn3 low EP",
|
| 80 |
+
"n_cells_an": 262,
|
| 81 |
+
"n_cells_dp": 262,
|
| 82 |
+
"n_genes": 243,
|
| 83 |
+
"spearman_r": 0.9875966152667593
|
| 84 |
+
},
|
| 85 |
+
{
|
| 86 |
+
"cell_type": "Pre-endocrine",
|
| 87 |
+
"n_cells_an": 592,
|
| 88 |
+
"n_cells_dp": 592,
|
| 89 |
+
"n_genes": 269,
|
| 90 |
+
"spearman_r": 0.9832497980974927
|
| 91 |
+
}
|
| 92 |
+
],
|
| 93 |
+
"uncertainty": {
|
| 94 |
+
"var_error_spearman_r": 0.9683824423893881,
|
| 95 |
+
"var_error_spearman_p": 7.848856686887698e-165,
|
| 96 |
+
"n_genes": 272,
|
| 97 |
+
"bin_median_var": [
|
| 98 |
+
2.6977049856213853e-06,
|
| 99 |
+
6.103933992562816e-05,
|
| 100 |
+
0.00129510962869972,
|
| 101 |
+
0.00978328101336956,
|
| 102 |
+
0.12321395426988602
|
| 103 |
+
],
|
| 104 |
+
"bin_median_error": [
|
| 105 |
+
1.4680728912353516,
|
| 106 |
+
8.11591625213623,
|
| 107 |
+
38.67870330810547,
|
| 108 |
+
107.40599060058594,
|
| 109 |
+
243.96734619140625
|
| 110 |
+
]
|
| 111 |
+
},
|
| 112 |
+
"latent_structure": {
|
| 113 |
+
"silhouette_z_T": 0.2205258160829544,
|
| 114 |
+
"silhouette_z_PT": -0.009808977134525776,
|
| 115 |
+
"silhouette_gamma": -0.11721337586641312
|
| 116 |
+
},
|
| 117 |
+
"gene_ranking": {
|
| 118 |
+
"n_shared_genes": 300,
|
| 119 |
+
"top_median_overlap": 20,
|
| 120 |
+
"top_median_overlap_frac": 0.4,
|
| 121 |
+
"top_var_overlap": 34,
|
| 122 |
+
"top_var_overlap_frac": 0.68,
|
| 123 |
+
"rank_correlation": 0.5066891652362127
|
| 124 |
+
}
|
| 125 |
+
}
|
output/deep_benchmark_v2/results/subsampling_robustness.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"pancreas": [
|
| 3 |
+
{
|
| 4 |
+
"fraction": 0.5,
|
| 5 |
+
"n_cells": 1848,
|
| 6 |
+
"spearman_r": 0.9991751019455771
|
| 7 |
+
},
|
| 8 |
+
{
|
| 9 |
+
"fraction": 0.8,
|
| 10 |
+
"n_cells": 2956,
|
| 11 |
+
"spearman_r": 0.9997239969332992
|
| 12 |
+
}
|
| 13 |
+
],
|
| 14 |
+
"dentate_gyrus": [
|
| 15 |
+
{
|
| 16 |
+
"fraction": 0.5,
|
| 17 |
+
"n_cells": 1465,
|
| 18 |
+
"spearman_r": 0.997867531861465
|
| 19 |
+
},
|
| 20 |
+
{
|
| 21 |
+
"fraction": 0.8,
|
| 22 |
+
"n_cells": 2344,
|
| 23 |
+
"spearman_r": 0.9994866609628995
|
| 24 |
+
}
|
| 25 |
+
]
|
| 26 |
+
}
|
output/deep_benchmarks/02_bootstrap_ci/figures/bootstrap_ci.png
ADDED
|
output/deep_benchmarks/20_uncertainty_advantage/results/uncertainty_advantage.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"pancreas": {
|
| 3 |
+
"analytical_300": {
|
| 4 |
+
"r": -0.22231279755152644,
|
| 5 |
+
"n": 219
|
| 6 |
+
},
|
| 7 |
+
"deepptr_all": {
|
| 8 |
+
"r": -0.2767452667406712,
|
| 9 |
+
"n": 236
|
| 10 |
+
},
|
| 11 |
+
"deepptr_filtered": [
|
| 12 |
+
{
|
| 13 |
+
"percentile": 75,
|
| 14 |
+
"r": -0.2935350818803388,
|
| 15 |
+
"n": 180,
|
| 16 |
+
"cv_cutoff": 0.11798103153705597
|
| 17 |
+
},
|
| 18 |
+
{
|
| 19 |
+
"percentile": 50,
|
| 20 |
+
"r": -0.2784230794623337,
|
| 21 |
+
"n": 120,
|
| 22 |
+
"cv_cutoff": 0.09954803436994553
|
| 23 |
+
},
|
| 24 |
+
{
|
| 25 |
+
"percentile": 25,
|
| 26 |
+
"r": -0.3779708509647762,
|
| 27 |
+
"n": 53,
|
| 28 |
+
"cv_cutoff": 0.08271166682243347
|
| 29 |
+
},
|
| 30 |
+
{
|
| 31 |
+
"percentile": 10,
|
| 32 |
+
"r": -0.2781954887218045,
|
| 33 |
+
"n": 20,
|
| 34 |
+
"cv_cutoff": 0.05375545844435692
|
| 35 |
+
}
|
| 36 |
+
],
|
| 37 |
+
"improvement": 0.15565805341324973
|
| 38 |
+
},
|
| 39 |
+
"dentate_gyrus": {
|
| 40 |
+
"analytical_300": {
|
| 41 |
+
"r": -0.3593371092867718,
|
| 42 |
+
"n": 199
|
| 43 |
+
},
|
| 44 |
+
"deepptr_all": {
|
| 45 |
+
"r": -0.35768791768630437,
|
| 46 |
+
"n": 211
|
| 47 |
+
},
|
| 48 |
+
"deepptr_filtered": [
|
| 49 |
+
{
|
| 50 |
+
"percentile": 75,
|
| 51 |
+
"r": -0.4037010444550779,
|
| 52 |
+
"n": 159,
|
| 53 |
+
"cv_cutoff": 0.13886773586273193
|
| 54 |
+
},
|
| 55 |
+
{
|
| 56 |
+
"percentile": 50,
|
| 57 |
+
"r": -0.3379248062565652,
|
| 58 |
+
"n": 100,
|
| 59 |
+
"cv_cutoff": 0.11725422739982605
|
| 60 |
+
},
|
| 61 |
+
{
|
| 62 |
+
"percentile": 25,
|
| 63 |
+
"r": -0.10556391748122433,
|
| 64 |
+
"n": 49,
|
| 65 |
+
"cv_cutoff": 0.0847647488117218
|
| 66 |
+
},
|
| 67 |
+
{
|
| 68 |
+
"percentile": 10,
|
| 69 |
+
"r": 0.004511278195488721,
|
| 70 |
+
"n": 20,
|
| 71 |
+
"cv_cutoff": 0.029071299359202385
|
| 72 |
+
}
|
| 73 |
+
],
|
| 74 |
+
"improvement": 0.04436393516830606
|
| 75 |
+
}
|
| 76 |
+
}
|
output/deep_benchmarks/24_fullgenome_gpu/results/fullgenome_gpu.json
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"pancreas": [
|
| 3 |
+
{
|
| 4 |
+
"n_genes": 300,
|
| 5 |
+
"label": "300 genes",
|
| 6 |
+
"device": "cpu",
|
| 7 |
+
"time_s": 126.28486108779907,
|
| 8 |
+
"n_epochs": 100,
|
| 9 |
+
"hl_mouse_r": -0.19762101977653473,
|
| 10 |
+
"hl_mouse_n": 251,
|
| 11 |
+
"hl_human_r": -0.2767452667406712,
|
| 12 |
+
"hl_human_n": 236
|
| 13 |
+
},
|
| 14 |
+
{
|
| 15 |
+
"n_genes": 500,
|
| 16 |
+
"label": "500 genes",
|
| 17 |
+
"device": "cpu",
|
| 18 |
+
"time_s": 195.14217829704285,
|
| 19 |
+
"n_epochs": 100,
|
| 20 |
+
"hl_mouse_r": -0.13388556817704766,
|
| 21 |
+
"hl_mouse_n": 425,
|
| 22 |
+
"hl_human_r": -0.23868475929802593,
|
| 23 |
+
"hl_human_n": 405
|
| 24 |
+
},
|
| 25 |
+
{
|
| 26 |
+
"n_genes": 1000,
|
| 27 |
+
"label": "1000 genes",
|
| 28 |
+
"device": "cpu",
|
| 29 |
+
"time_s": 250.388201713562,
|
| 30 |
+
"n_epochs": 100,
|
| 31 |
+
"hl_mouse_r": -0.1749767413418925,
|
| 32 |
+
"hl_mouse_n": 855,
|
| 33 |
+
"hl_human_r": -0.2701509996732806,
|
| 34 |
+
"hl_human_n": 828
|
| 35 |
+
},
|
| 36 |
+
{
|
| 37 |
+
"n_genes": 2000,
|
| 38 |
+
"label": "2000 genes",
|
| 39 |
+
"device": "cpu",
|
| 40 |
+
"time_s": 311.9621741771698,
|
| 41 |
+
"n_epochs": 100,
|
| 42 |
+
"hl_mouse_r": -0.2015269295525831,
|
| 43 |
+
"hl_mouse_n": 1732,
|
| 44 |
+
"hl_human_r": -0.29089533198701906,
|
| 45 |
+
"hl_human_n": 1653
|
| 46 |
+
},
|
| 47 |
+
{
|
| 48 |
+
"n_genes": 11906,
|
| 49 |
+
"label": "Analytical (all)",
|
| 50 |
+
"device": "cpu",
|
| 51 |
+
"hl_mouse_r": -0.34966929477006137,
|
| 52 |
+
"hl_human_r": -0.4020872107350974,
|
| 53 |
+
"hl_mouse_n": 4611,
|
| 54 |
+
"hl_human_n": 4308
|
| 55 |
+
}
|
| 56 |
+
],
|
| 57 |
+
"dentate_gyrus": [
|
| 58 |
+
{
|
| 59 |
+
"n_genes": 300,
|
| 60 |
+
"label": "300 genes",
|
| 61 |
+
"device": "cpu",
|
| 62 |
+
"time_s": 177.58326506614685,
|
| 63 |
+
"n_epochs": 100,
|
| 64 |
+
"hl_mouse_r": -0.2852053996865196,
|
| 65 |
+
"hl_mouse_n": 216,
|
| 66 |
+
"hl_human_r": -0.35768791768630437,
|
| 67 |
+
"hl_human_n": 211
|
| 68 |
+
},
|
| 69 |
+
{
|
| 70 |
+
"n_genes": 500,
|
| 71 |
+
"label": "500 genes",
|
| 72 |
+
"device": "cpu",
|
| 73 |
+
"time_s": 101.45515847206116,
|
| 74 |
+
"n_epochs": 100,
|
| 75 |
+
"hl_mouse_r": -0.32213690425879954,
|
| 76 |
+
"hl_mouse_n": 380,
|
| 77 |
+
"hl_human_r": -0.3782651711359226,
|
| 78 |
+
"hl_human_n": 361
|
| 79 |
+
},
|
| 80 |
+
{
|
| 81 |
+
"n_genes": 1000,
|
| 82 |
+
"label": "1000 genes",
|
| 83 |
+
"device": "cpu",
|
| 84 |
+
"time_s": 215.91462779045105,
|
| 85 |
+
"n_epochs": 100,
|
| 86 |
+
"hl_mouse_r": -0.3366768487779157,
|
| 87 |
+
"hl_mouse_n": 807,
|
| 88 |
+
"hl_human_r": -0.3801130440429436,
|
| 89 |
+
"hl_human_n": 752
|
| 90 |
+
},
|
| 91 |
+
{
|
| 92 |
+
"n_genes": 2000,
|
| 93 |
+
"label": "2000 genes",
|
| 94 |
+
"device": "cpu",
|
| 95 |
+
"time_s": 244.55135011672974,
|
| 96 |
+
"n_epochs": 100,
|
| 97 |
+
"hl_mouse_r": -0.3052852382518734,
|
| 98 |
+
"hl_mouse_n": 1681,
|
| 99 |
+
"hl_human_r": -0.3678329895976058,
|
| 100 |
+
"hl_human_n": 1542
|
| 101 |
+
},
|
| 102 |
+
{
|
| 103 |
+
"n_genes": 5325,
|
| 104 |
+
"label": "Analytical (all)",
|
| 105 |
+
"device": "cpu",
|
| 106 |
+
"hl_mouse_r": -0.3179587682344816,
|
| 107 |
+
"hl_human_r": -0.3812073018847205,
|
| 108 |
+
"hl_mouse_n": 1217,
|
| 109 |
+
"hl_human_n": 1126
|
| 110 |
+
}
|
| 111 |
+
]
|
| 112 |
+
}
|
output/figures/aim1/enrichment_barplot.png
ADDED
|
output/figures/aim1/subsampling_robustness.png
ADDED
|
output/figures/aim2/gamma_heatmap.png
ADDED
|
output/figures/aim2/tf_ptf_scatter.png
ADDED
|
output/gap_analysis/figures/network/network_dentate_gyrus.png
ADDED
|
output/gap_analysis/results/invisible_states/dentate_gyrus/invisible_states.csv
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
cluster,n_cells,n_subclusters,silhouette_gamma,silhouette_expr,invisibility_score,combined_p,top_diff_genes
|
| 2 |
+
Astrocytes,120,2,0.2716423273086548,0.22705544531345367,0.04458688199520111,0.0,"['Cnot6', 'Nlgn1', 'Pnpla7', 'Vmp1', 'Scfd1', 'Kcnd2', 'Hlcs', 'Lrp1b', 'Pcdh9', 'Luzp2']"
|
| 3 |
+
Endothelial,87,2,0.3709166646003723,0.6044033765792847,-0.23348671197891235,0.0,"['Pnpla7', 'Dapk1', 'Vmp1', 'Arl15', 'Cnot6', 'Hlcs', 'Prkg1', 'Igf1r', 'Pde7b', 'Plcl1']"
|
| 4 |
+
GABA,61,3,0.24294190108776093,0.20515747368335724,0.037784427404403687,0.0,"['Slc8a1', 'Dapk1', 'Nkain2', 'Kctd16', 'Cnot6', 'Xkr4', 'Nlgn1', 'Slit3', 'Sgcz', 'Fgf14']"
|
| 5 |
+
Granule immature,785,2,0.2173723578453064,0.0045087020844221115,0.21286365576088428,0.0,"['Pnpla7', 'Fgf14', 'Nkain2', 'Dlg2', 'Nlgn1', 'Slit3', 'Cnot6', 'Vmp1', 'Grm7', 'Kctd16']"
|
| 6 |
+
Granule mature,1070,3,0.14220979809761047,-0.040953319519758224,0.1831631176173687,0.0,"['Fgf14', 'Pnpla7', 'Kctd16', 'Dlg2', 'Nlgn1', 'Nkain2', 'Slit3', 'Cnot6', 'Vmp1', 'Fam155a']"
|
| 7 |
+
Microglia,81,2,0.3435395658016205,0.03644650802016258,0.3070930577814579,0.0,"['Pnpla7', 'Cnot6', 'Vmp1', 'Maml3', 'Rab39', 'Plcl1', 'Scfd1', 'Rmdn1', 'Nav3', 'Clic4']"
|
| 8 |
+
Mossy,75,3,0.277706503868103,0.21522463858127594,0.06248186528682709,0.0,"['Pnpla7', 'Hs6st3', 'Nkain2', 'Dlg2', 'Kctd16', 'Rgs7', 'Xkr4', 'Cacnb2', 'Slit3', 'Fgf14']"
|
| 9 |
+
Neuroblast,417,2,0.3091459274291992,0.32825586199760437,-0.01910993456840515,0.0,"['Pnpla7', 'Cnot6', 'Nlgn1', 'Dapk1', 'Nkain2', 'Slit3', 'Fgf14', 'Rgs7', 'Dlg2', 'Vmp1']"
|
| 10 |
+
OL,50,2,0.36671721935272217,0.14149659872055054,0.22522062063217163,0.0,"['Nkain2', 'Pnpla7', 'Plcl1', 'Ncam2', 'Spock1', 'Ptprk', 'Lrp1b', 'Chka', 'Dapk1', 'Cnot6']"
|
| 11 |
+
OPC,53,2,0.39298373460769653,0.1758575141429901,0.21712622046470642,0.0,"['Nlgn1', 'Pnpla7', 'Cnot6', 'Dapk1', 'Sgcz', 'Ncam2', 'Fam155a', 'Kcnd2', 'Mmp16', 'Luzp2']"
|
| 12 |
+
Radial Glia-like,51,2,0.30975162982940674,0.02847418375313282,0.2812774460762739,1.1102230246251565e-16,"['Nlgn1', 'Pnpla7', 'Cnot6', 'Scfd1', 'Vmp1', 'Lsamp', 'Cdyl2', 'Fgf14', 'Apba1', 'Erc2']"
|
output/gap_analysis/results/invisible_states/pancreas/invisible_states.csv
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
cluster,n_cells,n_subclusters,silhouette_gamma,silhouette_expr,invisibility_score,combined_p,top_diff_genes
|
| 2 |
+
Alpha,481,2,0.15257549285888672,0.18451890349388123,-0.03194341063499451,0.0,"['Ptprn2', 'Cacnb2', 'Pde4d', 'Hs6st3', 'Tmtc2', 'Park2', 'Airn', 'Rora', 'Wwox', 'Cacna1a']"
|
| 3 |
+
Beta,591,2,0.1581832617521286,0.2098182886838913,-0.051635026931762695,0.0,"['Cacnb2', 'Ptprn2', 'Grip1', 'Rora', 'Sntg1', 'Tmtc2', 'Fam155a', 'Airn', 'Phactr1', 'Rabgef1']"
|
| 4 |
+
Delta,70,2,0.27680444717407227,0.13608261942863464,0.14072182774543762,0.0,"['Hs6st3', 'Rora', 'Tmtc2', 'Ptprn2', 'Park2', 'Cacna1c', 'Fhit', 'Snd1', 'Psmd13', 'Dennd1a']"
|
| 5 |
+
Ductal,916,2,0.1913653016090393,0.30966684222221375,-0.11830154061317444,0.0,"['Kalrn', 'Naaladl2', 'Psmd13', 'Kcnh7', 'Traf5', 'Mecom', 'Pkhd1', 'Tmtc2', '9030622O22Rik', 'Wwox']"
|
| 6 |
+
Epsilon,142,3,0.19315549731254578,-0.05613249912858009,0.24928799644112587,0.0,"['Ptprn2', '9030622O22Rik', 'Nrg1', 'Cacna1c', 'Slc8a1', 'Cacnb2', 'Kalrn', 'Sorcs1', 'Nedd8', 'Nr6a1']"
|
| 7 |
+
Ngn3 high EP,642,2,0.2596045136451721,0.20566286146640778,0.05394165217876434,0.0,"['Adgrb3', 'Airn', 'Ptprn2', 'Kalrn', '9030622O22Rik', 'Nedd8', 'Cacna1a', 'Kcnb2', 'Kcnh7', 'Arl15']"
|
| 8 |
+
Ngn3 low EP,262,2,0.1923951506614685,0.28167375922203064,-0.08927860856056213,0.0,"['Kcnh7', 'Airn', 'Ptprj', 'Macrod2', 'Nedd8', 'Ttll3', 'Pkhd1', '9030622O22Rik', 'Psmd13', 'Tecpr2']"
|
| 9 |
+
Pre-endocrine,592,2,0.14428557455539703,0.06477079540491104,0.07951477915048599,0.0,"['Ptprn2', 'Adgrb3', 'Cacnb2', 'Cacna1a', 'Sdk1', 'Maml3', 'Pbx1', 'Wwox', 'Stxbp5l', 'Kcnb2']"
|
output/gap_analysis/results/network/dentate_gyrus/rbp_hub_counts.csv
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
rbp,0
|
| 2 |
+
Ybx1,550
|
| 3 |
+
Rbfox1,230
|
| 4 |
+
Celf2,214
|
| 5 |
+
Hnrnpa1,209
|
| 6 |
+
Rbfox3,197
|
| 7 |
+
Elavl3,194
|
| 8 |
+
Rbfox2,143
|
| 9 |
+
Matr3,134
|
| 10 |
+
Elavl1,129
|
| 11 |
+
Mbnl2,118
|
| 12 |
+
Hnrnpc,109
|
| 13 |
+
Celf1,102
|
| 14 |
+
Fto,80
|
| 15 |
+
Tia1,74
|
| 16 |
+
Elavl4,73
|
| 17 |
+
Ptbp2,64
|
| 18 |
+
Ago2,63
|
| 19 |
+
Pum1,58
|
| 20 |
+
Khdrbs1,55
|
| 21 |
+
Stau2,44
|
| 22 |
+
Msi1,35
|
| 23 |
+
Cirbp,34
|
| 24 |
+
Cnot1,33
|
| 25 |
+
Mbnl1,28
|
| 26 |
+
Ythdf2,23
|
| 27 |
+
Nova1,22
|
| 28 |
+
Igf2bp3,15
|
| 29 |
+
Dis3l2,11
|
| 30 |
+
Mettl3,8
|
| 31 |
+
Xrn1,1
|
output/gap_analysis/results/network/pancreas/network_edges.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
output/gap_analysis/results/network/pancreas/rbp_hub_counts.csv
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
rbp,0
|
| 2 |
+
Hnrnpa1,166
|
| 3 |
+
Ybx1,158
|
| 4 |
+
Srsf3,144
|
| 5 |
+
Rbfox3,70
|
| 6 |
+
Hnrnpd,48
|
| 7 |
+
Tra2b,47
|
| 8 |
+
Elavl1,43
|
| 9 |
+
Fus,32
|
| 10 |
+
Srsf1,30
|
| 11 |
+
Zfp36l1,30
|
| 12 |
+
Hnrnpc,29
|
| 13 |
+
Matr3,27
|
| 14 |
+
Ptbp1,27
|
| 15 |
+
Elavl4,24
|
| 16 |
+
Msi1,21
|
| 17 |
+
Mbnl1,16
|
| 18 |
+
Stau2,15
|
| 19 |
+
Hnrnpa2b1,12
|
| 20 |
+
Mettl3,11
|
| 21 |
+
Igf2bp1,11
|
| 22 |
+
Nova1,11
|
| 23 |
+
Tardbp,10
|
| 24 |
+
Upf1,9
|
| 25 |
+
Pum2,9
|
| 26 |
+
Mbnl2,8
|
| 27 |
+
Rbfox1,8
|
| 28 |
+
Celf2,8
|
| 29 |
+
Ptbp2,7
|
| 30 |
+
Cpeb1,6
|
| 31 |
+
Ago2,6
|