calm-data-generator 2.0.1__tar.gz → 2.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. {calm_data_generator-2.0.1/calm_data_generator.egg-info → calm_data_generator-2.2.0}/PKG-INFO +42 -1
  2. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/README.md +39 -0
  3. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/__init__.py +1 -1
  4. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/cli.py +14 -5
  5. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/docs/PRESETS_REFERENCE.md +21 -0
  6. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/docs/PRESETS_REFERENCE_ES.md +21 -0
  7. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/docs/REAL_GENERATOR_REFERENCE.md +62 -6
  8. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/docs/REAL_GENERATOR_REFERENCE_ES.md +62 -1
  9. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/docs/REPORTS_REFERENCE.md +45 -0
  10. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/docs/REPORTS_REFERENCE_ES.md +45 -0
  11. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/generators/clinical/Clinic.py +21 -9
  12. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/generators/configs.py +2 -0
  13. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/generators/drift/DriftInjector.py +14 -15
  14. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/generators/dynamics/ScenarioInjector.py +23 -3
  15. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/generators/persistence_models.py +23 -14
  16. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/generators/tabular/RealGenerator.py +399 -289
  17. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/reports/LocalIndexGenerator.py +28 -0
  18. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/reports/QualityReporter.py +145 -8
  19. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/reports/Visualizer.py +4 -2
  20. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/tutorials/advanced_drifts.py +2 -1
  21. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/tutorials/clinical_block_generator.py +0 -17
  22. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0/calm_data_generator.egg-info}/PKG-INFO +42 -1
  23. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator.egg-info/SOURCES.txt +1 -0
  24. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator.egg-info/requires.txt +2 -0
  25. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/pyproject.toml +17 -4
  26. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/requirements.txt +4 -0
  27. calm_data_generator-2.2.0/tests/test_scgft_reporter.py +127 -0
  28. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/LICENSE +0 -0
  29. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/MANIFEST.in +0 -0
  30. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/docs/API.md +0 -0
  31. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/docs/API_ES.md +0 -0
  32. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/docs/CAUSAL_ENGINE_REFERENCE.md +0 -0
  33. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/docs/CAUSAL_ENGINE_REFERENCE_ES.md +0 -0
  34. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/docs/CLINICAL_BLOCK_GENERATOR_REFERENCE.md +0 -0
  35. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/docs/CLINICAL_BLOCK_GENERATOR_REFERENCE_ES.md +0 -0
  36. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/docs/CLINICAL_GENERATOR_REFERENCE.md +0 -0
  37. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/docs/CLINICAL_GENERATOR_REFERENCE_ES.md +0 -0
  38. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/docs/COMPLEX_GENERATOR_REFERENCE.md +0 -0
  39. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/docs/COMPLEX_GENERATOR_REFERENCE_ES.md +0 -0
  40. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/docs/DOCUMENTATION.md +0 -0
  41. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/docs/DOCUMENTATION_ES.md +0 -0
  42. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/docs/DRIFT_INJECTOR_REFERENCE.md +0 -0
  43. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/docs/DRIFT_INJECTOR_REFERENCE_ES.md +0 -0
  44. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/docs/REAL_BLOCK_GENERATOR_REFERENCE.md +0 -0
  45. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/docs/REAL_BLOCK_GENERATOR_REFERENCE_ES.md +0 -0
  46. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/docs/SCENARIO_INJECTOR_REFERENCE.md +0 -0
  47. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/docs/SCENARIO_INJECTOR_REFERENCE_ES.md +0 -0
  48. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/docs/STREAM_BLOCK_GENERATOR_REFERENCE.md +0 -0
  49. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/docs/STREAM_BLOCK_GENERATOR_REFERENCE_ES.md +0 -0
  50. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/docs/STREAM_GENERATOR_REFERENCE.md +0 -0
  51. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/docs/STREAM_GENERATOR_REFERENCE_ES.md +0 -0
  52. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/generators/__init__.py +0 -0
  53. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/generators/base.py +0 -0
  54. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/generators/clinical/ClinicGeneratorBlock.py +0 -0
  55. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/generators/clinical/ClinicReporter.py +0 -0
  56. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/generators/clinical/__init__.py +0 -0
  57. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/generators/complex/ComplexGenerator.py +0 -0
  58. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/generators/complex/__init__.py +0 -0
  59. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/generators/drift/__init__.py +0 -0
  60. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/generators/dynamics/CausalEngine.py +0 -0
  61. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/generators/dynamics/__init__.py +0 -0
  62. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/generators/stream/GeneratorFactory.py +0 -0
  63. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/generators/stream/StreamBlockGenerator.py +0 -0
  64. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/generators/stream/StreamGenerator.py +0 -0
  65. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/generators/stream/StreamReporter.py +0 -0
  66. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/generators/stream/__init__.py +0 -0
  67. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/generators/tabular/CustomPluginAdapter.py +0 -0
  68. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/generators/tabular/QualityReporter.py +0 -0
  69. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/generators/tabular/RealBlockGenerator.py +0 -0
  70. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/generators/tabular/__init__.py +0 -0
  71. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/generators/utils/__init__.py +0 -0
  72. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/generators/utils/propagation.py +0 -0
  73. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/logger.py +0 -0
  74. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/presets/BalancePreset.py +0 -0
  75. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/presets/ConceptDriftPreset.py +0 -0
  76. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/presets/CopulaPreset.py +0 -0
  77. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/presets/DataQualityAuditPreset.py +0 -0
  78. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/presets/DiffusionPreset.py +0 -0
  79. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/presets/DriftScenarioPreset.py +0 -0
  80. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/presets/FastPreset.py +0 -0
  81. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/presets/FastPrototypePreset.py +0 -0
  82. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/presets/GradualDriftPreset.py +0 -0
  83. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/presets/HighFidelityPreset.py +0 -0
  84. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/presets/ImbalancePreset.py +0 -0
  85. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/presets/LongitudinalHealthPreset.py +0 -0
  86. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/presets/OmicsIntegrationPreset.py +0 -0
  87. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/presets/RareDiseasePreset.py +0 -0
  88. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/presets/ScenarioInjectorPreset.py +0 -0
  89. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/presets/SeasonalTimeSeriesPreset.py +0 -0
  90. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/presets/SingleCellQualityPreset.py +0 -0
  91. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/presets/TimeSeriesPreset.py +0 -0
  92. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/presets/__init__.py +0 -0
  93. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/presets/base.py +0 -0
  94. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/reports/DiscriminatorReporter.py +0 -0
  95. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/reports/ExternalReporter.py +0 -0
  96. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/reports/__init__.py +0 -0
  97. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/reports/base.py +0 -0
  98. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/tutorials/clinic_generator.py +0 -0
  99. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/tutorials/clinical_generator.py +0 -0
  100. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/tutorials/correlation_drift.py +0 -0
  101. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/tutorials/drift_injector.py +0 -0
  102. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/tutorials/real_block_generator.py +0 -0
  103. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/tutorials/real_generator.py +0 -0
  104. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/tutorials/reports_deep_dive.py +0 -0
  105. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/tutorials/scenario_injector.py +0 -0
  106. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/tutorials/stream_block_generator.py +0 -0
  107. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/tutorials/stream_generator.py +0 -0
  108. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator/tutorials/tutorial_advanced_methods.py +0 -0
  109. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator.egg-info/dependency_links.txt +0 -0
  110. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator.egg-info/entry_points.txt +0 -0
  111. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/calm_data_generator.egg-info/top_level.txt +0 -0
  112. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/setup.cfg +0 -0
  113. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/tests/test_accessors.py +0 -0
  114. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/tests/test_anndata_support.py +0 -0
  115. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/tests/test_block_generators_extended.py +0 -0
  116. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/tests/test_causal_engine.py +0 -0
  117. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/tests/test_clinic_block_generator.py +0 -0
  118. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/tests/test_clinical_advanced.py +0 -0
  119. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/tests/test_clinical_regression.py +0 -0
  120. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/tests/test_complex_generator.py +0 -0
  121. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/tests/test_comprehensive.py +0 -0
  122. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/tests/test_comprehensive_reporting.py +0 -0
  123. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/tests/test_correlation_propagation.py +0 -0
  124. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/tests/test_custom_plugin_adapter.py +0 -0
  125. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/tests/test_differentiation_factor.py +0 -0
  126. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/tests/test_discriminator_reporter.py +0 -0
  127. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/tests/test_disease_effects_fix.py +0 -0
  128. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/tests/test_drift_correlations.py +0 -0
  129. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/tests/test_drift_injector_advanced.py +0 -0
  130. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/tests/test_drift_injector_math.py +0 -0
  131. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/tests/test_functional_drift.py +0 -0
  132. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/tests/test_imbalance.py +0 -0
  133. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/tests/test_migration.py +0 -0
  134. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/tests/test_presets.py +0 -0
  135. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/tests/test_quality_metrics.py +0 -0
  136. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/tests/test_real_block_generator.py +0 -0
  137. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/tests/test_real_generator_persistence.py +0 -0
  138. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/tests/test_reporters_extended.py +0 -0
  139. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/tests/test_reporting_fix.py +0 -0
  140. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/tests/test_river_integration.py +0 -0
  141. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/tests/test_scenario_extended.py +0 -0
  142. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/tests/test_scvi_quality_regression.py +0 -0
  143. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/tests/test_single_call.py +0 -0
  144. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/tests/test_stream_block_generator.py +0 -0
  145. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/tests/test_tabular_extended.py +0 -0
  146. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/tests/test_timeseries_extended.py +0 -0
  147. {calm_data_generator-2.0.1 → calm_data_generator-2.2.0}/tests/test_timeseries_real.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: calm-data-generator
3
- Version: 2.0.1
3
+ Version: 2.2.0
4
4
  Summary: CALM-Data-Generator: A Python library for synthetic data generation with support for drift injection and clinical data.
5
5
  Author-email: Alejandro Belda Fernandez <alejandrobeldafernandez@gmail.com>
6
6
  License: MIT
@@ -40,6 +40,7 @@ Requires-Dist: imbalanced-learn<0.15.0,>=0.14.0
40
40
  Requires-Dist: copulae<0.8.0,>=0.7.9
41
41
  Requires-Dist: anndata<0.12.0,>=0.11.0
42
42
  Requires-Dist: scvi-tools<1.4.0,>=1.3.0
43
+ Requires-Dist: scanpy<1.11.0,>=1.10.0
43
44
  Requires-Dist: lightning<2.4.0,>=2.3.0
44
45
  Requires-Dist: torchmetrics<1.9.0,>=1.8.0
45
46
  Requires-Dist: plotly<6.6.0,>=6.5.0
@@ -52,6 +53,7 @@ Requires-Dist: hmmlearn<0.4.0,>=0.3.0
52
53
  Requires-Dist: pyarrow<15.0.0,>=14.0.0
53
54
  Requires-Dist: setuptools<70.0.0,>=59.0.0
54
55
  Requires-Dist: fsspec<2026.0.0,>=2025.12.0
56
+ Requires-Dist: scgft-evaluator<0.2.0,>=0.1.0
55
57
  Provides-Extra: stream
56
58
  Requires-Dist: river>=0.21.0; extra == "stream"
57
59
  Provides-Extra: full
@@ -587,6 +589,26 @@ synthetic = gen.generate(
587
589
  |--------|----------|
588
590
  | `scvi` | Generate new cells from learned distribution |
589
591
 
592
+ Once you have synthetic single-cell data, validate its quality using [scGFT Evaluator](https://github.com/nasim23ea/scgft-evaluator) via `QualityReporter`:
593
+
594
+ ```python
595
+ from calm_data_generator.reports.QualityReporter import QualityReporter
596
+ from calm_data_generator.generators.configs import ReportConfig
597
+
598
+ reporter = QualityReporter(verbose=True)
599
+ reporter.generate_comprehensive_report(
600
+ real_df=real_df,
601
+ synthetic_df=synthetic_df,
602
+ generator_name="SingleCell_Example",
603
+ report_config=ReportConfig(
604
+ output_dir="./sc_report",
605
+ use_scgft=True,
606
+ target_column="cell_type",
607
+ ),
608
+ )
609
+ ```
610
+
611
+ > See [REPORTS_REFERENCE.md](calm_data_generator/docs/REPORTS_REFERENCE.md#single-cell-evaluation-scgft) for full details.
590
612
 
591
613
  ### Stream Data Generation
592
614
 
@@ -624,6 +646,24 @@ reporter.generate_report(
624
646
  # Results JSON (including compared_data_files) saved to ./quality_report/report_results.json
625
647
  ```
626
648
 
649
+ For **single-cell data**, enable [scGFT](https://github.com/nasim23ea/scgft-evaluator) evaluation (Graph Fourier Transform-based manifold preservation metrics):
650
+
651
+ ```python
652
+ from calm_data_generator.generators.configs import ReportConfig
653
+
654
+ reporter.generate_comprehensive_report(
655
+ real_df=real_df,
656
+ synthetic_df=synthetic_df,
657
+ generator_name="MyGen",
658
+ report_config=ReportConfig(
659
+ output_dir="./sc_report",
660
+ use_scgft=True,
661
+ target_column="cell_type",
662
+ ),
663
+ )
664
+ # Generates scgft_report.html with ARI, MMD, Jaccard, Kendall Tau metrics
665
+ ```
666
+
627
667
  ---
628
668
 
629
669
  ## Modules
@@ -759,3 +799,4 @@ We stand on the shoulders of giants. This library is possible thanks to these am
759
799
  - **[Hugging Face Hub](https://github.com/huggingface/huggingface_hub)** (Apache-2.0) - Facilitating model sharing and versioning.
760
800
  - **[Plotly](https://github.com/plotly/plotly.py)** (MIT) - Enabling interactive data visualizations.
761
801
  - **[hmmlearn](https://github.com/hmmlearn/hmmlearn)** (BSD-3-Clause) - Powering the `hmm` method for drift-aware generation via Hidden Markov Models.
802
+ - **[scgft-evaluator](https://github.com/nasim23ea/scgft-evaluator)** - Providing Graph Fourier Transform-based evaluation for single-cell synthetic data quality assessment.
@@ -527,6 +527,26 @@ synthetic = gen.generate(
527
527
  |--------|----------|
528
528
  | `scvi` | Generate new cells from learned distribution |
529
529
 
530
+ Once you have synthetic single-cell data, validate its quality using [scGFT Evaluator](https://github.com/nasim23ea/scgft-evaluator) via `QualityReporter`:
531
+
532
+ ```python
533
+ from calm_data_generator.reports.QualityReporter import QualityReporter
534
+ from calm_data_generator.generators.configs import ReportConfig
535
+
536
+ reporter = QualityReporter(verbose=True)
537
+ reporter.generate_comprehensive_report(
538
+ real_df=real_df,
539
+ synthetic_df=synthetic_df,
540
+ generator_name="SingleCell_Example",
541
+ report_config=ReportConfig(
542
+ output_dir="./sc_report",
543
+ use_scgft=True,
544
+ target_column="cell_type",
545
+ ),
546
+ )
547
+ ```
548
+
549
+ > See [REPORTS_REFERENCE.md](calm_data_generator/docs/REPORTS_REFERENCE.md#single-cell-evaluation-scgft) for full details.
530
550
 
531
551
  ### Stream Data Generation
532
552
 
@@ -564,6 +584,24 @@ reporter.generate_report(
564
584
  # Results JSON (including compared_data_files) saved to ./quality_report/report_results.json
565
585
  ```
566
586
 
587
+ For **single-cell data**, enable [scGFT](https://github.com/nasim23ea/scgft-evaluator) evaluation (Graph Fourier Transform-based manifold preservation metrics):
588
+
589
+ ```python
590
+ from calm_data_generator.generators.configs import ReportConfig
591
+
592
+ reporter.generate_comprehensive_report(
593
+ real_df=real_df,
594
+ synthetic_df=synthetic_df,
595
+ generator_name="MyGen",
596
+ report_config=ReportConfig(
597
+ output_dir="./sc_report",
598
+ use_scgft=True,
599
+ target_column="cell_type",
600
+ ),
601
+ )
602
+ # Generates scgft_report.html with ARI, MMD, Jaccard, Kendall Tau metrics
603
+ ```
604
+
567
605
  ---
568
606
 
569
607
  ## Modules
@@ -699,3 +737,4 @@ We stand on the shoulders of giants. This library is possible thanks to these am
699
737
  - **[Hugging Face Hub](https://github.com/huggingface/huggingface_hub)** (Apache-2.0) - Facilitating model sharing and versioning.
700
738
  - **[Plotly](https://github.com/plotly/plotly.py)** (MIT) - Enabling interactive data visualizations.
701
739
  - **[hmmlearn](https://github.com/hmmlearn/hmmlearn)** (BSD-3-Clause) - Powering the `hmm` method for drift-aware generation via Hidden Markov Models.
740
+ - **[scgft-evaluator](https://github.com/nasim23ea/scgft-evaluator)** - Providing Graph Fourier Transform-based evaluation for single-cell synthetic data quality assessment.
@@ -13,7 +13,7 @@ try:
13
13
  except ImportError:
14
14
  StreamGenerator = None
15
15
 
16
- __version__ = "2.0.1"
16
+ __version__ = "2.1.0"
17
17
 
18
18
  __all__ = [
19
19
  # Generators
@@ -103,6 +103,9 @@ def show_tutorial(number):
103
103
 
104
104
  def run_tutorial(number):
105
105
  """Run a specific tutorial."""
106
+ import subprocess
107
+ import sys
108
+
106
109
  tutorials_dir = get_tutorials_dir()
107
110
  tutorials = sorted(tutorials_dir.glob("*.py"))
108
111
 
@@ -114,15 +117,21 @@ def run_tutorial(number):
114
117
  idx = int(number) - 1
115
118
  if 0 <= idx < len(tutorials):
116
119
  tutorial = tutorials[idx]
117
- print(f"\n🚀 Running: {tutorial.name}")
120
+ print(f"\n Running: {tutorial.name}")
118
121
  print("=" * 60)
119
- exec(open(tutorial).read())
122
+ # Run in a subprocess to keep the tutorial isolated from this process
123
+ result = subprocess.run(
124
+ [sys.executable, str(tutorial)],
125
+ check=False,
126
+ )
127
+ if result.returncode != 0:
128
+ print(f"Tutorial exited with code {result.returncode}")
120
129
  else:
121
- print(f"Invalid tutorial number. Choose 1-{len(tutorials)}")
130
+ print(f"Invalid tutorial number. Choose 1-{len(tutorials)}")
122
131
  except ValueError:
123
- print("Please provide a tutorial number (e.g., 1, 2, 3)")
132
+ print("Please provide a tutorial number (e.g., 1, 2, 3)")
124
133
  except Exception as e:
125
- print(f"Error running tutorial: {e}")
134
+ print(f"Error running tutorial: {e}")
126
135
 
127
136
 
128
137
  def show_path():
@@ -521,6 +521,27 @@ preset = SingleCellQualityPreset(random_state=42)
521
521
  synthetic_cells = preset.generate(data=adata_df, n_samples=500)
522
522
  ```
523
523
 
524
+ To validate the generated data quality, use `QualityReporter` with scGFT enabled:
525
+
526
+ ```python
527
+ from calm_data_generator.reports.QualityReporter import QualityReporter
528
+ from calm_data_generator.generators.configs import ReportConfig
529
+
530
+ reporter = QualityReporter(verbose=True)
531
+ reporter.generate_comprehensive_report(
532
+ real_df=adata_df,
533
+ synthetic_df=synthetic_cells,
534
+ generator_name="SingleCellQualityPreset",
535
+ report_config=ReportConfig(
536
+ output_dir="./sc_report",
537
+ use_scgft=True,
538
+ target_column="cell_type",
539
+ ),
540
+ )
541
+ ```
542
+
543
+ > See [REPORTS_REFERENCE.md](REPORTS_REFERENCE.md#single-cell-evaluation-scgft) for full scGFT documentation.
544
+
524
545
  ---
525
546
 
526
547
  ## Import Summary
@@ -521,6 +521,27 @@ preset = SingleCellQualityPreset(random_state=42)
521
521
  synthetic_cells = preset.generate(data=adata_df, n_samples=500)
522
522
  ```
523
523
 
524
+ Para validar la calidad de los datos generados, usa `QualityReporter` con scGFT activado:
525
+
526
+ ```python
527
+ from calm_data_generator.reports.QualityReporter import QualityReporter
528
+ from calm_data_generator.generators.configs import ReportConfig
529
+
530
+ reporter = QualityReporter(verbose=True)
531
+ reporter.generate_comprehensive_report(
532
+ real_df=adata_df,
533
+ synthetic_df=synthetic_cells,
534
+ generator_name="SingleCellQualityPreset",
535
+ report_config=ReportConfig(
536
+ output_dir="./sc_report",
537
+ use_scgft=True,
538
+ target_column="cell_type",
539
+ ),
540
+ )
541
+ ```
542
+
543
+ > Ver [REPORTS_REFERENCE_ES.md](REPORTS_REFERENCE_ES.md#evaluación-single-cell-scgft) para documentación completa de scGFT.
544
+
524
545
  ---
525
546
 
526
547
  ## Resumen de Imports
@@ -560,14 +560,70 @@ synthetic = gen.generate(
560
560
  )
561
561
  ```
562
562
 
563
- | Parameter | Default | Description |
564
- |-----------|---------|-------------|
565
- | `target_col` | **required** | Column name with cell type labels |
566
- | `epochs` | 200 | Training epochs for the base scVI model |
567
- | `n_latent` | 30 | Latent space dimensionality |
568
- | `n_layers` | 1 | Number of encoder/decoder layers |
569
563
  | `custom_distributions` | `None` | Per-class proportions for generation |
570
564
 
565
+ #### Single-Cell Workflow Utilities
566
+
567
+ For users working with single-cell transcriptomics, `RealGenerator` provides a utility to convert the generated synthetic DataFrames back into `AnnData` objects, which are the standard format for analysis libraries like `scanpy` or `squidpy`.
568
+
569
+ **`to_anndata(df, target_col=None, obs_cols=None)`** (Static Method)
570
+
571
+ Converts a synthetic DataFrame (the output of `generate()`) into an `AnnData` object.
572
+
573
+ | Parameter | Type | Default | Description |
574
+ |-----------|------|---------|-------------|
575
+ | `df` | `pd.DataFrame` | **required** | The synthetic DataFrame generated by `calm_data_generator`. |
576
+ | `target_col` | `str` | `None` | The column to be used as `cell_type` in `adata.obs`. |
577
+ | `obs_cols` | `List[str]` | `None` | List of additional columns to move from the feature matrix (`X`) to the metadata (`obs`). |
578
+
579
+ **Example:**
580
+ ```python
581
+ from calm_data_generator.generators.tabular import RealGenerator
582
+
583
+ # 1. Generate synthetic data (e.g., using scVI)
584
+ synthetic_df = gen.generate(
585
+ data=real_adata,
586
+ n_samples=2000,
587
+ method="scvi",
588
+ target_col="cell_type"
589
+ )
590
+
591
+ # 2. Convert back to AnnData for scanpy analysis
592
+ synthetic_adata = RealGenerator.to_anndata(
593
+ synthetic_df,
594
+ target_col="cell_type"
595
+ )
596
+
597
+ # 3. Standard scanpy analysis
598
+ import scanpy as sc
599
+ sc.pp.pca(synthetic_adata)
600
+ sc.pl.pca(synthetic_adata, color="cell_type")
601
+ ```
602
+
603
+ **Validating single-cell quality with scGFT:**
604
+
605
+ After generating synthetic single-cell data, validate its fidelity using [scgft-evaluator](https://github.com/nasim23ea/scgft-evaluator):
606
+
607
+ ```python
608
+ from calm_data_generator.reports.QualityReporter import QualityReporter
609
+ from calm_data_generator.generators.configs import ReportConfig
610
+
611
+ reporter = QualityReporter(verbose=True)
612
+ reporter.generate_comprehensive_report(
613
+ real_df=real_df,
614
+ synthetic_df=synthetic_df,
615
+ generator_name="scVI_SingleCell",
616
+ report_config=ReportConfig(
617
+ output_dir="./sc_report",
618
+ use_scgft=True,
619
+ target_column="cell_type",
620
+ ),
621
+ )
622
+ # Generates scgft_report.html with ARI, MMD, Jaccard, Kendall Tau metrics
623
+ ```
624
+
625
+ > See [REPORTS_REFERENCE.md](REPORTS_REFERENCE.md#single-cell-evaluation-scgft) for full details.
626
+
571
627
  ---
572
628
 
573
629
  ---
@@ -195,8 +195,69 @@ synthetic = gen.generate(
195
195
 
196
196
 
197
197
 
198
- ---
198
+ | `custom_distributions` | `None` | Proporciones por clase para la generación |
199
+
200
+ #### Utilidades de Flujo de Trabajo para Single-Cell
201
+
202
+ Para los usuarios que trabajan con transcriptómica de célula única (single-cell), `RealGenerator` proporciona una utilidad para convertir los DataFrames sintéticos generados de vuelta a objetos `AnnData`, que es el formato estándar para librerías de análisis como `scanpy` o `squidpy`.
203
+
204
+ **`to_anndata(df, target_col=None, obs_cols=None)`** (Método Estático)
205
+
206
+ Convierte un DataFrame sintético (la salida de `generate()`) en un objeto `AnnData`.
207
+
208
+ | Parámetro | Tipo | Defecto | Descripción |
209
+ |-----------|------|---------|-------------|
210
+ | `df` | `pd.DataFrame` | **requerido** | El DataFrame sintético generado por `calm_data_generator`. |
211
+ | `target_col` | `str` | `None` | La columna que se usará como `cell_type` en `adata.obs`. |
212
+ | `obs_cols` | `List[str]` | `None` | Lista de columnas adicionales que se moverán de la matriz de características (`X`) a los metadatos (`obs`). |
213
+
214
+ **Ejemplo:**
215
+ ```python
216
+ from calm_data_generator.generators.tabular import RealGenerator
217
+
218
+ # 1. Generar datos sintéticos (ej. usando scANVI)
219
+ synthetic_df = gen.generate(
220
+ data=real_adata,
221
+ n_samples=2000,
222
+ method="scanvi",
223
+ target_col="cell_type"
224
+ )
225
+
226
+ # 2. Convertir de vuelta a AnnData para análisis con scanpy
227
+ synthetic_adata = RealGenerator.to_anndata(
228
+ synthetic_df,
229
+ target_col="cell_type"
230
+ )
231
+
232
+ # 3. Análisis estándar con scanpy
233
+ import scanpy as sc
234
+ sc.pp.pca(synthetic_adata)
235
+ sc.pl.pca(synthetic_adata, color="cell_type")
236
+ ```
237
+
238
+ **Validación de calidad single-cell con scGFT:**
239
+
240
+ Tras generar datos sintéticos de célula única, valida su fidelidad usando [scgft-evaluator](https://github.com/nasim23ea/scgft-evaluator):
241
+
242
+ ```python
243
+ from calm_data_generator.reports.QualityReporter import QualityReporter
244
+ from calm_data_generator.generators.configs import ReportConfig
245
+
246
+ reporter = QualityReporter(verbose=True)
247
+ reporter.generate_comprehensive_report(
248
+ real_df=real_df,
249
+ synthetic_df=synthetic_df,
250
+ generator_name="scVI_SingleCell",
251
+ report_config=ReportConfig(
252
+ output_dir="./sc_report",
253
+ use_scgft=True,
254
+ target_column="cell_type",
255
+ ),
256
+ )
257
+ # Genera scgft_report.html con métricas ARI, MMD, Jaccard, Kendall Tau
258
+ ```
199
259
 
260
+ > Ver [REPORTS_REFERENCE_ES.md](REPORTS_REFERENCE_ES.md#evaluación-single-cell-scgft) para documentación completa.
200
261
 
201
262
  ---
202
263
 
@@ -27,6 +27,7 @@ The `calm_data_generator` library includes a suite of reporting tools designed t
27
27
  | `constraints_stats` | Dict[str, int] | `None` | Constraint violation statistics |
28
28
  | `sequence_config` | Dict | `None` | Configuration for sequence-based analysis |
29
29
  | `per_block_external_reports` | bool | `False` | Generate separate reports per block |
30
+ | `use_scgft` | bool | `False` | Enable specialized scGFT single-cell evaluation |
30
31
 
31
32
  ### Usage Examples
32
33
 
@@ -173,6 +174,50 @@ reporter.generate_report(
173
174
  ```
174
175
 
175
176
 
177
+ ## Single-Cell Evaluation (scGFT)
178
+ **Module:** `calm_data_generator.reports.QualityReporter`
179
+
180
+ The library integrates [`scgft-evaluator`](https://github.com/nasim23ea/scgft-evaluator) to provide specialized validation for single-cell RNA sequencing (scRNA-seq) data. This method uses Graph Fourier Transforms (GFT) to assess if the synthetic data preserves the underlying manifold and biological structure of the original cells.
181
+
182
+ ### Installation
183
+
184
+ ```bash
185
+ pip install scgft-evaluator @ git+https://github.com/nasim23ea/scgft-evaluator.git
186
+ ```
187
+
188
+ Or via `requirements.txt` (already included in calm-data-generator):
189
+
190
+ ```
191
+ scgft-evaluator @ git+https://github.com/nasim23ea/scgft-evaluator.git
192
+ ```
193
+
194
+ ### Key Features
195
+ - **Manifold Preservation**: Evaluates if the cell-to-cell relationships are maintained.
196
+ - **Cluster/Population Integrity**: Metrics on how well synthetic cells represent real populations (ARI, MMD, Jaccard, Kendall Tau).
197
+ - **Limma-based DE comparison**: Differential expression concordance between real and synthetic via `limma`.
198
+ - **Dashboard Integration**: Generates a dedicated `scgft_report.html` tab in the HTML dashboard with a results table.
199
+
200
+ ### Usage
201
+ Set `use_scgft=True` in your `ReportConfig` and specify the cell-type column:
202
+
203
+ ```python
204
+ from calm_data_generator.generators.configs import ReportConfig
205
+
206
+ reporter.generate_comprehensive_report(
207
+ ...,
208
+ report_config=ReportConfig(
209
+ output_dir="./sc_report",
210
+ use_scgft=True,
211
+ target_column="cell_type" # column with cell type labels
212
+ )
213
+ )
214
+ ```
215
+
216
+ The evaluator runs `ScGFT_Evaluator.run_all()` comparing the two most prevalent cell populations and prints a metrics table when `verbose=True`.
217
+
218
+ > [!IMPORTANT]
219
+ > **Data Format**: This method is specifically designed for single-cell data where columns represent genes and rows represent cells. It is **not recommended** for standard bulk or tabular data.
220
+
176
221
  ## Clinic Reporter (`Clinical`)
177
222
  **Module:** `calm_data_generator.generators.clinical.ClinicReporter`
178
223
 
@@ -27,6 +27,7 @@ La biblioteca `calm_data_generator` incluye un conjunto de herramientas de repor
27
27
  | `constraints_stats` | Dict[str, int] | `None` | Estadísticas de violación de restricciones |
28
28
  | `sequence_config` | Dict | `None` | Configuración para análisis basado en secuencias |
29
29
  | `per_block_external_reports` | bool | `False` | Generar reportes separados por bloque |
30
+ | `use_scgft` | bool | `False` | Habilitar evaluación especializada scGFT para single-cell |
30
31
 
31
32
  ### Ejemplos de Uso
32
33
 
@@ -173,6 +174,50 @@ reporter.generate_report(
173
174
  ```
174
175
 
175
176
 
177
+ ## Evaluación Single-Cell (scGFT)
178
+ **Módulo:** `calm_data_generator.reports.QualityReporter`
179
+
180
+ La biblioteca integra [`scgft-evaluator`](https://github.com/nasim23ea/scgft-evaluator) para proporcionar validación especializada para datos de secuenciación de ARN de célula única (scRNA-seq). Este método utiliza Transformadas de Fourier en Grafos (GFT) para evaluar si los datos sintéticos preservan el manifold subyacente y la estructura biológica de las células originales.
181
+
182
+ ### Instalación
183
+
184
+ ```bash
185
+ pip install scgft-evaluator @ git+https://github.com/nasim23ea/scgft-evaluator.git
186
+ ```
187
+
188
+ O mediante `requirements.txt` (ya incluido en calm-data-generator):
189
+
190
+ ```
191
+ scgft-evaluator @ git+https://github.com/nasim23ea/scgft-evaluator.git
192
+ ```
193
+
194
+ ### Características Clave
195
+ - **Preservación del Manifold**: Evalúa si se mantienen las relaciones célula a célula.
196
+ - **Integridad de Clusters/Poblaciones**: Métricas sobre qué tan bien las células sintéticas representan las poblaciones reales (ARI, MMD, Jaccard, Tau de Kendall).
197
+ - **Comparación DE basada en limma**: Concordancia de expresión diferencial entre real y sintético vía `limma`.
198
+ - **Integración en el Dashboard**: Genera una pestaña dedicada `scgft_report.html` en el panel HTML con tabla de resultados.
199
+
200
+ ### Uso
201
+ Establezca `use_scgft=True` en su `ReportConfig` e indique la columna de tipo celular:
202
+
203
+ ```python
204
+ from calm_data_generator.generators.configs import ReportConfig
205
+
206
+ reporter.generate_comprehensive_report(
207
+ ...,
208
+ report_config=ReportConfig(
209
+ output_dir="./sc_report",
210
+ use_scgft=True,
211
+ target_column="cell_type" # columna con las etiquetas de tipo celular
212
+ )
213
+ )
214
+ ```
215
+
216
+ El evaluador ejecuta `ScGFT_Evaluator.run_all()` comparando las dos poblaciones celulares más frecuentes e imprime una tabla de métricas cuando `verbose=True`.
217
+
218
+ > [!IMPORTANT]
219
+ > **Formato de Datos**: Este método está diseñado específicamente para datos de célula única donde las columnas representan genes y las filas representan células. **No se recomienda** para datos estándar de bulk o tabulares.
220
+
176
221
  ## Reporter Clínico (`Clinical`)
177
222
  **Módulo:** `calm_data_generator.generators.clinical.ClinicReporter`
178
223
 
@@ -451,6 +451,18 @@ class ClinicalDataGenerator(ComplexGenerator):
451
451
 
452
452
  return df_temp, raw_demographic_data
453
453
 
454
+ def _apply_constraints(self, df: pd.DataFrame, constraints: List[Dict]) -> pd.DataFrame:
455
+ mask = pd.Series(True, index=df.index)
456
+ for c in constraints:
457
+ col, op, val = c.get("col"), c.get("op"), c.get("val")
458
+ if col not in df.columns: continue
459
+ if op == ">=": mask &= df[col] >= val
460
+ elif op == "<=": mask &= df[col] <= val
461
+ elif op == "==": mask &= df[col] == val
462
+ elif op == ">": mask &= df[col] > val
463
+ elif op == "<": mask &= df[col] < val
464
+ return df[mask].copy()
465
+
454
466
  def _prepare_demographic_context(
455
467
  self,
456
468
  demographic_df,
@@ -1293,15 +1305,15 @@ class ClinicalDataGenerator(ComplexGenerator):
1293
1305
  if not patient_ids_to_modify:
1294
1306
  return updated_demographic_df, updated_omics_data_df
1295
1307
 
1296
- # Update demographic data
1297
- for patient_id in patient_ids_to_modify:
1298
- current_group = updated_demographic_df.loc[patient_id, "Group"]
1299
- new_group = "Disease" if current_group == "Control" else "Control"
1300
- updated_demographic_df.loc[patient_id, "Group"] = new_group
1301
- if "Binary_Group" in updated_demographic_df.columns:
1302
- updated_demographic_df.loc[patient_id, "Binary_Group"] = (
1303
- 1 if new_group == "Disease" else 0
1304
- )
1308
+ # Update demographic data (vectorized)
1309
+ modify_idx = pd.Index(patient_ids_to_modify)
1310
+ mask_control = updated_demographic_df.loc[modify_idx, "Group"] == "Control"
1311
+ mask_disease = ~mask_control
1312
+ updated_demographic_df.loc[modify_idx[mask_control], "Group"] = "Disease"
1313
+ updated_demographic_df.loc[modify_idx[mask_disease], "Group"] = "Control"
1314
+ if "Binary_Group" in updated_demographic_df.columns:
1315
+ updated_demographic_df.loc[modify_idx[mask_control], "Binary_Group"] = 1
1316
+ updated_demographic_df.loc[modify_idx[mask_disease], "Binary_Group"] = 0
1305
1317
 
1306
1318
  # Regenerate omics data for all transitioned patients
1307
1319
  if patient_ids_to_modify:
@@ -108,3 +108,5 @@ class ReportConfig(BaseModel):
108
108
  constraints_stats: Optional[Dict[str, int]] = None
109
109
  sequence_config: Optional[Dict] = None
110
110
  per_block_external_reports: bool = False
111
+ use_scgft: bool = False
112
+
@@ -552,16 +552,17 @@ class DriftInjector:
552
552
  if len(idxs) == 0:
553
553
  return s
554
554
 
555
- # for each row to change, choose a different category
556
- current = s.loc[idxs].to_numpy()
557
- new_vals = []
558
- for cur in current:
559
- choices = [u for u in uniques if u != cur]
555
+ # for each row to change, choose a different category (vectorized by value group)
556
+ current = s.loc[idxs]
557
+ result_vals = current.copy()
558
+ for uval in uniques:
559
+ sub_idx = current.index[current == uval]
560
+ if len(sub_idx) == 0:
561
+ continue
562
+ choices = [u for u in uniques if u != uval]
560
563
  if choices:
561
- new_vals.append(rng.choice(choices))
562
- else:
563
- new_vals.append(cur)
564
- s.loc[idxs] = new_vals
564
+ result_vals.loc[sub_idx] = rng.choice(choices, size=len(sub_idx))
565
+ s.loc[idxs] = result_vals
565
566
  return s
566
567
 
567
568
  def _validate_feature_op(self, drift_type: str, drift_magnitude: float):
@@ -1755,7 +1756,7 @@ class DriftInjector:
1755
1756
  try:
1756
1757
  df_drift = method(**params)
1757
1758
  except Exception as e:
1758
- print(f"Failed to apply {method_name}: {e}")
1759
+ warnings.warn(f"Failed to apply {method_name}: {e}")
1759
1760
  else:
1760
1761
  print(f"Method {method_name} not found")
1761
1762
 
@@ -2024,7 +2025,7 @@ class DriftInjector:
2024
2025
  Returns:
2025
2026
  pd.DataFrame: The drifted dataframe.
2026
2027
  """
2027
- current_df = df.copy()
2028
+ current_df = df
2028
2029
 
2029
2030
  for i, config in enumerate(schedule):
2030
2031
  method_name = "inject_feature_drift"
@@ -2208,8 +2209,7 @@ class DriftInjector:
2208
2209
  time_col=time_col,
2209
2210
  )
2210
2211
  except Exception as e:
2211
- import warnings as _w
2212
- _w.warn(f"inject_functional_drift: report failed: {e}")
2212
+ warnings.warn(f"inject_functional_drift: report failed: {e}")
2213
2213
 
2214
2214
  return df_result
2215
2215
 
@@ -2320,8 +2320,7 @@ class DriftInjector:
2320
2320
  time_col=time_col,
2321
2321
  )
2322
2322
  except Exception as e:
2323
- import warnings as _w
2324
- _w.warn(f"inject_causal_cascade: report failed: {e}")
2323
+ warnings.warn(f"inject_causal_cascade: report failed: {e}")
2325
2324
 
2326
2325
  return df_result
2327
2326
 
@@ -283,10 +283,30 @@ class ScenarioInjector:
283
283
 
284
284
  # 1. Calculate Raw Score
285
285
  if isinstance(formula, str):
286
+ # Validate the formula before calling eval().
287
+ # Only allow identifiers that are actual column names plus arithmetic
288
+ # operators and numeric literals — this prevents arbitrary code execution.
289
+ import re as _re
290
+ _allowed_pattern = _re.compile(
291
+ r"^[\w\s\.\+\-\*\/\(\)\,\d\.\>\<\=\!]+$"
292
+ )
293
+ if not _allowed_pattern.match(formula):
294
+ raise ValueError(
295
+ f"Formula '{formula}' contains disallowed characters. "
296
+ "Only column names, numeric literals, and arithmetic operators "
297
+ "(+, -, *, /, parentheses, inequalities) are permitted."
298
+ )
299
+ # Additionally verify that every identifier in the formula is an
300
+ # existing column name or a numeric token, not an arbitrary name.
301
+ _tokens = _re.findall(r"[A-Za-z_]\w*", formula)
302
+ _unknown = [t for t in _tokens if t not in df_target.columns]
303
+ if _unknown:
304
+ raise ValueError(
305
+ f"Formula references unknown identifiers: {_unknown}. "
306
+ f"Available columns: {list(df_target.columns)}"
307
+ )
286
308
  try:
287
- # Use pandas eval for string formulas
288
- # We perform eval in the context of the dataframe
289
- raw_score = df_target.eval(formula)
309
+ raw_score = df_target.eval(formula, engine="python")
290
310
  except Exception as e:
291
311
  raise ValueError(f"Error evaluating formula '{formula}': {e}")
292
312
  elif callable(formula):