validmind 2.7.12__py3-none-any.whl → 2.8.12__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. validmind/__init__.py +58 -10
  2. validmind/__version__.py +1 -1
  3. validmind/ai/test_descriptions.py +17 -73
  4. validmind/api_client.py +18 -1
  5. validmind/models/r_model.py +5 -1
  6. validmind/tests/comparison.py +28 -2
  7. validmind/tests/data_validation/ACFandPACFPlot.py +4 -1
  8. validmind/tests/data_validation/AutoMA.py +1 -1
  9. validmind/tests/data_validation/BivariateScatterPlots.py +5 -1
  10. validmind/tests/data_validation/BoxPierce.py +3 -1
  11. validmind/tests/data_validation/ClassImbalance.py +1 -1
  12. validmind/tests/data_validation/DatasetDescription.py +1 -1
  13. validmind/tests/data_validation/DickeyFullerGLS.py +1 -1
  14. validmind/tests/data_validation/FeatureTargetCorrelationPlot.py +5 -10
  15. validmind/tests/data_validation/HighCardinality.py +5 -1
  16. validmind/tests/data_validation/HighPearsonCorrelation.py +1 -1
  17. validmind/tests/data_validation/IQROutliersBarPlot.py +5 -3
  18. validmind/tests/data_validation/IQROutliersTable.py +5 -2
  19. validmind/tests/data_validation/IsolationForestOutliers.py +5 -4
  20. validmind/tests/data_validation/JarqueBera.py +2 -2
  21. validmind/tests/data_validation/LJungBox.py +2 -2
  22. validmind/tests/data_validation/LaggedCorrelationHeatmap.py +1 -1
  23. validmind/tests/data_validation/MissingValues.py +14 -10
  24. validmind/tests/data_validation/MissingValuesBarPlot.py +3 -1
  25. validmind/tests/data_validation/MutualInformation.py +2 -1
  26. validmind/tests/data_validation/PearsonCorrelationMatrix.py +1 -1
  27. validmind/tests/data_validation/ProtectedClassesCombination.py +2 -0
  28. validmind/tests/data_validation/ProtectedClassesDescription.py +2 -2
  29. validmind/tests/data_validation/ProtectedClassesDisparity.py +9 -5
  30. validmind/tests/data_validation/ProtectedClassesThresholdOptimizer.py +10 -2
  31. validmind/tests/data_validation/RollingStatsPlot.py +2 -1
  32. validmind/tests/data_validation/ScoreBandDefaultRates.py +4 -2
  33. validmind/tests/data_validation/SeasonalDecompose.py +1 -1
  34. validmind/tests/data_validation/ShapiroWilk.py +2 -2
  35. validmind/tests/data_validation/SpreadPlot.py +1 -1
  36. validmind/tests/data_validation/TabularCategoricalBarPlots.py +1 -1
  37. validmind/tests/data_validation/TabularDateTimeHistograms.py +1 -1
  38. validmind/tests/data_validation/TargetRateBarPlots.py +4 -1
  39. validmind/tests/data_validation/TimeSeriesFrequency.py +1 -1
  40. validmind/tests/data_validation/TimeSeriesOutliers.py +7 -2
  41. validmind/tests/data_validation/WOEBinPlots.py +1 -1
  42. validmind/tests/data_validation/WOEBinTable.py +1 -1
  43. validmind/tests/data_validation/ZivotAndrewsArch.py +5 -2
  44. validmind/tests/data_validation/nlp/CommonWords.py +1 -1
  45. validmind/tests/data_validation/nlp/Hashtags.py +1 -1
  46. validmind/tests/data_validation/nlp/LanguageDetection.py +1 -1
  47. validmind/tests/data_validation/nlp/Mentions.py +1 -1
  48. validmind/tests/data_validation/nlp/PolarityAndSubjectivity.py +5 -1
  49. validmind/tests/data_validation/nlp/Punctuations.py +1 -1
  50. validmind/tests/data_validation/nlp/Sentiment.py +3 -1
  51. validmind/tests/data_validation/nlp/TextDescription.py +1 -1
  52. validmind/tests/data_validation/nlp/Toxicity.py +1 -1
  53. validmind/tests/model_validation/BertScore.py +7 -1
  54. validmind/tests/model_validation/BleuScore.py +7 -1
  55. validmind/tests/model_validation/ClusterSizeDistribution.py +3 -1
  56. validmind/tests/model_validation/ContextualRecall.py +9 -1
  57. validmind/tests/model_validation/FeaturesAUC.py +1 -1
  58. validmind/tests/model_validation/MeteorScore.py +7 -1
  59. validmind/tests/model_validation/ModelPredictionResiduals.py +5 -1
  60. validmind/tests/model_validation/RegardScore.py +6 -1
  61. validmind/tests/model_validation/RegressionResidualsPlot.py +10 -1
  62. validmind/tests/model_validation/RougeScore.py +3 -1
  63. validmind/tests/model_validation/TimeSeriesPredictionWithCI.py +2 -0
  64. validmind/tests/model_validation/TimeSeriesPredictionsPlot.py +10 -2
  65. validmind/tests/model_validation/TimeSeriesR2SquareBySegments.py +6 -2
  66. validmind/tests/model_validation/TokenDisparity.py +5 -1
  67. validmind/tests/model_validation/ToxicityScore.py +3 -1
  68. validmind/tests/model_validation/embeddings/ClusterDistribution.py +1 -1
  69. validmind/tests/model_validation/embeddings/CosineSimilarityComparison.py +5 -1
  70. validmind/tests/model_validation/embeddings/CosineSimilarityDistribution.py +5 -1
  71. validmind/tests/model_validation/embeddings/CosineSimilarityHeatmap.py +5 -1
  72. validmind/tests/model_validation/embeddings/DescriptiveAnalytics.py +2 -0
  73. validmind/tests/model_validation/embeddings/EmbeddingsVisualization2D.py +5 -1
  74. validmind/tests/model_validation/embeddings/EuclideanDistanceComparison.py +6 -2
  75. validmind/tests/model_validation/embeddings/EuclideanDistanceHeatmap.py +3 -1
  76. validmind/tests/model_validation/embeddings/PCAComponentsPairwisePlots.py +4 -1
  77. validmind/tests/model_validation/embeddings/StabilityAnalysisKeyword.py +5 -1
  78. validmind/tests/model_validation/embeddings/StabilityAnalysisRandomNoise.py +5 -1
  79. validmind/tests/model_validation/embeddings/StabilityAnalysisSynonyms.py +5 -1
  80. validmind/tests/model_validation/embeddings/StabilityAnalysisTranslation.py +5 -1
  81. validmind/tests/model_validation/embeddings/TSNEComponentsPairwisePlots.py +6 -1
  82. validmind/tests/model_validation/embeddings/utils.py +6 -9
  83. validmind/tests/model_validation/ragas/AnswerCorrectness.py +1 -1
  84. validmind/tests/model_validation/ragas/AspectCritic.py +4 -1
  85. validmind/tests/model_validation/ragas/ContextEntityRecall.py +1 -1
  86. validmind/tests/model_validation/ragas/ContextPrecision.py +1 -1
  87. validmind/tests/model_validation/ragas/ContextPrecisionWithoutReference.py +1 -1
  88. validmind/tests/model_validation/ragas/ContextRecall.py +1 -1
  89. validmind/tests/model_validation/ragas/Faithfulness.py +1 -1
  90. validmind/tests/model_validation/ragas/NoiseSensitivity.py +1 -1
  91. validmind/tests/model_validation/ragas/ResponseRelevancy.py +1 -1
  92. validmind/tests/model_validation/ragas/SemanticSimilarity.py +1 -1
  93. validmind/tests/model_validation/ragas/utils.py +8 -7
  94. validmind/tests/model_validation/sklearn/AdjustedMutualInformation.py +9 -9
  95. validmind/tests/model_validation/sklearn/AdjustedRandIndex.py +9 -9
  96. validmind/tests/model_validation/sklearn/CalibrationCurve.py +5 -2
  97. validmind/tests/model_validation/sklearn/ClassifierThresholdOptimization.py +15 -2
  98. validmind/tests/model_validation/sklearn/ClusterCosineSimilarity.py +5 -1
  99. validmind/tests/model_validation/sklearn/ClusterPerformanceMetrics.py +24 -14
  100. validmind/tests/model_validation/sklearn/CompletenessScore.py +8 -9
  101. validmind/tests/model_validation/sklearn/ConfusionMatrix.py +22 -3
  102. validmind/tests/model_validation/sklearn/FeatureImportance.py +6 -2
  103. validmind/tests/model_validation/sklearn/FowlkesMallowsScore.py +12 -9
  104. validmind/tests/model_validation/sklearn/HomogeneityScore.py +14 -9
  105. validmind/tests/model_validation/sklearn/HyperParametersTuning.py +4 -2
  106. validmind/tests/model_validation/sklearn/KMeansClustersOptimization.py +6 -1
  107. validmind/tests/model_validation/sklearn/MinimumAccuracy.py +12 -7
  108. validmind/tests/model_validation/sklearn/MinimumF1Score.py +12 -7
  109. validmind/tests/model_validation/sklearn/MinimumROCAUCScore.py +18 -7
  110. validmind/tests/model_validation/sklearn/OverfitDiagnosis.py +8 -2
  111. validmind/tests/model_validation/sklearn/PermutationFeatureImportance.py +5 -1
  112. validmind/tests/model_validation/sklearn/PopulationStabilityIndex.py +5 -1
  113. validmind/tests/model_validation/sklearn/PrecisionRecallCurve.py +6 -1
  114. validmind/tests/model_validation/sklearn/ROCCurve.py +3 -1
  115. validmind/tests/model_validation/sklearn/RegressionErrors.py +6 -2
  116. validmind/tests/model_validation/sklearn/RegressionPerformance.py +13 -8
  117. validmind/tests/model_validation/sklearn/RegressionR2Square.py +8 -5
  118. validmind/tests/model_validation/sklearn/RobustnessDiagnosis.py +5 -1
  119. validmind/tests/model_validation/sklearn/SHAPGlobalImportance.py +6 -1
  120. validmind/tests/model_validation/sklearn/ScoreProbabilityAlignment.py +10 -2
  121. validmind/tests/model_validation/sklearn/SilhouettePlot.py +5 -1
  122. validmind/tests/model_validation/sklearn/VMeasure.py +12 -9
  123. validmind/tests/model_validation/statsmodels/CumulativePredictionProbabilities.py +5 -1
  124. validmind/tests/model_validation/statsmodels/DurbinWatsonTest.py +6 -1
  125. validmind/tests/model_validation/statsmodels/GINITable.py +8 -1
  126. validmind/tests/model_validation/statsmodels/KolmogorovSmirnov.py +2 -2
  127. validmind/tests/model_validation/statsmodels/PredictionProbabilitiesHistogram.py +6 -2
  128. validmind/tests/model_validation/statsmodels/RegressionCoeffs.py +8 -2
  129. validmind/tests/model_validation/statsmodels/RegressionFeatureSignificance.py +3 -1
  130. validmind/tests/model_validation/statsmodels/RegressionModelForecastPlot.py +7 -2
  131. validmind/tests/model_validation/statsmodels/RegressionModelForecastPlotLevels.py +2 -0
  132. validmind/tests/model_validation/statsmodels/RegressionModelSensitivityPlot.py +2 -0
  133. validmind/tests/model_validation/statsmodels/RegressionModelSummary.py +11 -9
  134. validmind/tests/model_validation/statsmodels/RegressionPermutationFeatureImportance.py +3 -1
  135. validmind/tests/ongoing_monitoring/CalibrationCurveDrift.py +11 -1
  136. validmind/tests/ongoing_monitoring/ClassificationAccuracyDrift.py +10 -2
  137. validmind/tests/ongoing_monitoring/ConfusionMatrixDrift.py +8 -1
  138. validmind/tests/ongoing_monitoring/CumulativePredictionProbabilitiesDrift.py +18 -2
  139. validmind/tests/ongoing_monitoring/FeatureDrift.py +9 -2
  140. validmind/tests/ongoing_monitoring/PredictionAcrossEachFeature.py +8 -2
  141. validmind/tests/ongoing_monitoring/PredictionCorrelation.py +13 -2
  142. validmind/tests/ongoing_monitoring/PredictionProbabilitiesHistogramDrift.py +13 -2
  143. validmind/tests/ongoing_monitoring/ROCCurveDrift.py +16 -2
  144. validmind/tests/ongoing_monitoring/ScoreBandsDrift.py +11 -2
  145. validmind/tests/ongoing_monitoring/TargetPredictionDistributionPlot.py +13 -2
  146. validmind/tests/prompt_validation/Clarity.py +1 -1
  147. validmind/tests/prompt_validation/NegativeInstruction.py +1 -1
  148. validmind/tests/prompt_validation/Robustness.py +6 -1
  149. validmind/tests/prompt_validation/Specificity.py +1 -1
  150. validmind/tests/prompt_validation/ai_powered_test.py +5 -4
  151. validmind/tests/run.py +5 -1
  152. validmind/utils.py +13 -0
  153. validmind/vm_models/result/result.py +43 -2
  154. {validmind-2.7.12.dist-info → validmind-2.8.12.dist-info}/METADATA +3 -2
  155. {validmind-2.7.12.dist-info → validmind-2.8.12.dist-info}/RECORD +158 -163
  156. validmind/ai/test_result_description/config.yaml +0 -29
  157. validmind/ai/test_result_description/context.py +0 -73
  158. validmind/ai/test_result_description/image_processing.py +0 -124
  159. validmind/ai/test_result_description/system.jinja +0 -39
  160. validmind/ai/test_result_description/user.jinja +0 -30
  161. {validmind-2.7.12.dist-info → validmind-2.8.12.dist-info}/LICENSE +0 -0
  162. {validmind-2.7.12.dist-info → validmind-2.8.12.dist-info}/WHEEL +0 -0
  163. {validmind-2.7.12.dist-info → validmind-2.8.12.dist-info}/entry_points.txt +0 -0
validmind/__init__.py CHANGED
@@ -30,8 +30,12 @@ vm.init(
30
30
 
31
31
  After you have pasted the code snippet into your development source code and executed the code, the Python Library API will register with ValidMind. You can now use the ValidMind Library to document and test your models, and to upload to the ValidMind Platform.
32
32
  """
33
+ import threading
33
34
  import warnings
34
35
 
36
+ import pkg_resources
37
+ from IPython.display import HTML, display
38
+
35
39
  # Ignore Numba warnings. We are not requiring this package directly
36
40
  from numba.core.errors import NumbaDeprecationWarning, NumbaPendingDeprecationWarning
37
41
 
@@ -51,30 +55,74 @@ from .client import ( # noqa: E402
51
55
  )
52
56
  from .tests.decorator import tags, tasks, test
53
57
  from .tests.run import print_env
58
+ from .utils import is_notebook, parse_version
54
59
  from .vm_models.result import RawData
55
60
 
61
+ __shown = False
62
+
63
+
64
+ def show_warning(installed, running):
65
+ global __shown
66
+
67
+ if __shown:
68
+ return
69
+ __shown = True
70
+
71
+ message = (
72
+ f"⚠️ This kernel is running an older version of validmind ({running}) "
73
+ f"than the latest version installed on your system ({installed}).\n\n"
74
+ "You may need to restart the kernel if you are experiencing issues."
75
+ )
76
+ display(HTML(f"<div style='color: red;'>{message}</div>"))
77
+
78
+
79
+ def check_version():
80
+ # get the installed vs running version of validmind
81
+ # to make sure we are using the latest installed version
82
+ # in case user has updated the package but forgot to restart the kernel
83
+ installed = pkg_resources.get_distribution("validmind").version
84
+ running = __version__
85
+
86
+ if parse_version(installed) > parse_version(running):
87
+ show_warning(installed, running)
88
+
89
+ # Schedule the next check for 5 minutes from now
90
+ timer = threading.Timer(300, check_version)
91
+ timer.daemon = True
92
+ timer.start()
93
+
94
+
95
+ if is_notebook():
96
+ check_version()
97
+
56
98
  __all__ = [ # noqa
57
99
  "__version__",
58
- # Python Library API
59
- "datasets",
60
- "errors",
61
- "get_test_suite",
100
+ # main library API
62
101
  "init",
102
+ "reload",
63
103
  "init_dataset",
64
104
  "init_model",
65
105
  "init_r_model",
66
106
  "preview_template",
67
- "print_env",
68
- "RawData",
69
- "reload",
70
107
  "run_documentation_tests",
108
+ # log metric function (for direct/bulk/retroactive logging of metrics)
109
+ "log_metric",
110
+ # test suite functions (less common)
111
+ "get_test_suite",
71
112
  "run_test_suite",
113
+ # helper functions (for troubleshooting)
114
+ "print_env",
115
+ # decorators (for building tests
72
116
  "tags",
73
117
  "tasks",
74
118
  "test",
75
- "tests",
76
- "test_suites",
119
+ # raw data (for post-processing test results and building tests)
120
+ "RawData",
121
+ # submodules
122
+ "datasets",
123
+ "errors",
77
124
  "vm_models",
125
+ "tests",
78
126
  "unit_metrics",
79
- "log_metric",
127
+ "test_suites",
80
128
  ]
validmind/__version__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "2.7.12"
1
+ __version__ = "2.8.12"
@@ -4,70 +4,24 @@
4
4
 
5
5
  import json
6
6
  import os
7
- import re
8
7
  from concurrent.futures import ThreadPoolExecutor
9
8
  from typing import List, Optional, Union
10
9
 
11
10
  import tiktoken
12
- from jinja2 import Template
13
11
 
14
12
  from ..client_config import client_config
15
13
  from ..logging import get_logger
16
14
  from ..utils import NumpyEncoder, md_to_html, test_id_to_name
17
15
  from ..vm_models.figure import Figure
18
16
  from ..vm_models.result import ResultTable
19
- from .utils import DescriptionFuture, get_client_and_model
17
+ from .utils import DescriptionFuture
20
18
 
21
19
  __executor = ThreadPoolExecutor()
22
- __prompt = None
23
20
 
24
21
  logger = get_logger(__name__)
25
22
 
26
23
 
27
- def _load_prompt():
28
- global __prompt
29
-
30
- if not __prompt:
31
- folder_path = os.path.join(os.path.dirname(__file__), "test_result_description")
32
- with open(os.path.join(folder_path, "system.jinja"), "r") as f:
33
- system_prompt = f.read()
34
- with open(os.path.join(folder_path, "user.jinja"), "r") as f:
35
- user_prompt = f.read()
36
-
37
- __prompt = (Template(system_prompt), Template(user_prompt))
38
-
39
- return __prompt
40
-
41
-
42
- def prompt_to_message(role, prompt):
43
- if "[[IMAGE:" not in prompt:
44
- return {"role": role, "content": prompt}
45
-
46
- content = []
47
-
48
- # Regex pattern to find [[IMAGE:<b64-data>]] markers
49
- pattern = re.compile(r"\[\[IMAGE:(.*?)\]\]", re.DOTALL)
50
-
51
- last_index = 0
52
- for match in pattern.finditer(prompt):
53
- # Text before the image marker
54
- start, end = match.span()
55
- if start > last_index:
56
- content.append({"type": "text", "text": prompt[last_index:start]})
57
-
58
- content.append({"type": "image_url", "image_url": {"url": match.group(1)}})
59
-
60
- last_index = end
61
-
62
- # Text after the last image
63
- if last_index < len(prompt):
64
- content.append({"type": "text", "text": prompt[last_index:]})
65
-
66
- return {"role": role, "content": content}
67
-
68
-
69
24
  def _get_llm_global_context():
70
-
71
25
  # Get the context from the environment variable
72
26
  context = os.getenv("VALIDMIND_LLM_DESCRIPTIONS_CONTEXT", "")
73
27
 
@@ -117,13 +71,13 @@ def generate_description(
117
71
  title: Optional[str] = None,
118
72
  ):
119
73
  """Generate the description for the test results"""
74
+ from validmind.api_client import generate_test_result_description
75
+
120
76
  if not tables and not figures and not metric:
121
77
  raise ValueError(
122
78
  "No tables, unit metric or figures provided - cannot generate description"
123
79
  )
124
80
 
125
- client, model = get_client_and_model()
126
-
127
81
  # get last part of test id
128
82
  test_name = title or test_id.split(".")[-1]
129
83
 
@@ -147,29 +101,18 @@ def generate_description(
147
101
  else:
148
102
  summary = None
149
103
 
150
- context = _get_llm_global_context()
151
-
152
- input_data = {
153
- "test_name": test_name,
154
- "test_description": test_description,
155
- "title": title,
156
- "summary": _truncate_summary(summary, test_id),
157
- "figures": [figure._get_b64_url() for figure in ([] if tables else figures)],
158
- "context": context,
159
- }
160
- system, user = _load_prompt()
161
-
162
- messages = [
163
- prompt_to_message("system", system.render(input_data)),
164
- prompt_to_message("user", user.render(input_data)),
165
- ]
166
- response = client.chat.completions.create(
167
- model=model,
168
- temperature=0.0,
169
- messages=messages,
170
- )
171
-
172
- return response.choices[0].message.content
104
+ return generate_test_result_description(
105
+ {
106
+ "test_name": test_name,
107
+ "test_description": test_description,
108
+ "title": title,
109
+ "summary": _truncate_summary(summary, test_id),
110
+ "figures": [
111
+ figure._get_b64_url() for figure in ([] if tables else figures)
112
+ ],
113
+ "context": _get_llm_global_context(),
114
+ }
115
+ )["content"]
173
116
 
174
117
 
175
118
  def background_generate_description(
@@ -240,7 +183,8 @@ def get_result_description(
240
183
  # Check the feature flag first, then the environment variable
241
184
  llm_descriptions_enabled = (
242
185
  client_config.can_generate_llm_test_descriptions()
243
- and os.getenv("VALIDMIND_LLM_DESCRIPTIONS_ENABLED", "1") not in ["0", "false"]
186
+ and os.getenv("VALIDMIND_LLM_DESCRIPTIONS_ENABLED", "1").lower()
187
+ not in ["0", "false"]
244
188
  )
245
189
 
246
190
  # TODO: fix circular import
validmind/api_client.py CHANGED
@@ -194,6 +194,7 @@ def init(
194
194
  api_host: Optional[str] = None,
195
195
  model: Optional[str] = None,
196
196
  monitoring: bool = False,
197
+ generate_descriptions: Optional[bool] = None,
197
198
  ):
198
199
  """
199
200
  Initializes the API client instances and calls the /ping endpoint to ensure
@@ -209,7 +210,7 @@ def init(
209
210
  api_secret (str, optional): The API secret. Defaults to None.
210
211
  api_host (str, optional): The API host. Defaults to None.
211
212
  monitoring (bool): The ongoing monitoring flag. Defaults to False.
212
-
213
+ generate_descriptions (bool): Whether to use GenAI to generate test result descriptions. Defaults to True.
213
214
  Raises:
214
215
  ValueError: If the API key and secret are not provided
215
216
  """
@@ -235,6 +236,9 @@ def init(
235
236
 
236
237
  _monitoring = monitoring
237
238
 
239
+ if generate_descriptions is not None:
240
+ os.environ["VALIDMIND_LLM_DESCRIPTIONS_ENABLED"] = str(generate_descriptions)
241
+
238
242
  reload()
239
243
 
240
244
 
@@ -487,3 +491,16 @@ def get_ai_key() -> Dict[str, Any]:
487
491
  raise_api_error(r.text)
488
492
 
489
493
  return r.json()
494
+
495
+
496
+ def generate_test_result_description(test_result_data: Dict[str, Any]) -> str:
497
+ r = requests.post(
498
+ url=_get_url("ai/generate/test_result_description"),
499
+ headers=_get_api_headers(),
500
+ json=test_result_data,
501
+ )
502
+
503
+ if r.status_code != 200:
504
+ raise_api_error(r.text)
505
+
506
+ return r.json()
@@ -5,6 +5,7 @@
5
5
  import numpy as np
6
6
  import pandas as pd
7
7
 
8
+ from validmind.errors import MissingRExtrasError
8
9
  from validmind.logging import get_logger
9
10
  from validmind.vm_models.model import VMModel
10
11
 
@@ -125,7 +126,10 @@ class RModel(VMModel):
125
126
  """
126
127
  Converts the predicted probabilities to classes
127
128
  """
128
- from rpy2.robjects import pandas2ri
129
+ try:
130
+ from rpy2.robjects import pandas2ri
131
+ except ImportError:
132
+ raise MissingRExtrasError()
129
133
 
130
134
  # Activate the pandas conversion for rpy2
131
135
  pandas2ri.activate()
@@ -15,7 +15,7 @@ from validmind.vm_models.figure import (
15
15
  is_png_image,
16
16
  )
17
17
  from validmind.vm_models.input import VMInput
18
- from validmind.vm_models.result import ResultTable, TestResult
18
+ from validmind.vm_models.result import RawData, ResultTable, TestResult
19
19
 
20
20
  logger = get_logger(__name__)
21
21
 
@@ -312,6 +312,25 @@ def get_comparison_test_configs(
312
312
  return test_configs
313
313
 
314
314
 
315
+ def _combine_raw_data(results: List[TestResult]) -> RawData:
316
+ """Combine RawData objects"""
317
+ attribute_names = results[0].raw_data.__dict__.keys()
318
+
319
+ # check that all the raw data objects have the same attributes
320
+ for result in results:
321
+ if not isinstance(result.raw_data, RawData):
322
+ raise ValueError("All raw data objects must be of type RawData")
323
+ if result.raw_data.__dict__.keys() != attribute_names:
324
+ raise ValueError("RawData objects must have the same attributes")
325
+
326
+ return RawData(
327
+ **{
328
+ key: [getattr(result.raw_data, key) for result in results]
329
+ for key in attribute_names
330
+ }
331
+ )
332
+
333
+
315
334
  def combine_results(
316
335
  results: List[TestResult],
317
336
  ) -> Tuple[List[Any], Dict[str, List[Any]], Dict[str, List[Any]]]:
@@ -338,6 +357,9 @@ def combine_results(
338
357
  # handle threshold tests (i.e. tests that have pass/fail bool status)
339
358
  if results[0].passed is not None:
340
359
  combined_outputs.append(all(result.passed for result in results))
360
+ # handle raw data (if any)
361
+ if results[0].raw_data:
362
+ combined_outputs.append(_combine_raw_data(results))
341
363
 
342
364
  # combine inputs and params
343
365
  combined_inputs = {}
@@ -359,4 +381,8 @@ def combine_results(
359
381
  combined_inputs = _combine_dict_values(combined_inputs)
360
382
  combined_params = _combine_dict_values(combined_params)
361
383
 
362
- return combined_outputs, combined_inputs, combined_params
384
+ return (
385
+ tuple(combined_outputs),
386
+ combined_inputs,
387
+ combined_params,
388
+ )
@@ -94,4 +94,7 @@ def ACFandPACFPlot(dataset: VMDataset):
94
94
  figures.append(pacf_fig)
95
95
  pacf_store[col] = pacf_values
96
96
 
97
- return (*figures, RawData(acf_values=acf_store, pacf_values=pacf_store))
97
+ return (
98
+ *figures,
99
+ RawData(acf_values=acf_store, pacf_values=pacf_store, dataset=dataset.input_id),
100
+ )
@@ -116,4 +116,4 @@ def AutoMA(dataset: VMDataset, max_ma_order: int = 3):
116
116
  return {
117
117
  "Auto MA Analysis Results": summary_ma_analysis,
118
118
  "Best MA Order Results": best_ma_order,
119
- }, RawData(raw_series_data=df)
119
+ }, RawData(raw_series_data=df, dataset=dataset.input_id)
@@ -80,5 +80,9 @@ def BivariateScatterPlots(dataset):
80
80
  figures.append(fig)
81
81
 
82
82
  return tuple(figures) + (
83
- RawData(selected_numerical_df=df, feature_pairs=features_pairs),
83
+ RawData(
84
+ selected_numerical_df=df,
85
+ feature_pairs=features_pairs,
86
+ dataset=dataset.input_id,
87
+ ),
84
88
  )
@@ -68,4 +68,6 @@ def BoxPierce(dataset):
68
68
  box_pierce_df.reset_index(inplace=True)
69
69
  box_pierce_df.columns = ["column", "stat", "pvalue"]
70
70
 
71
- return box_pierce_df, RawData(box_pierce_values=box_pierce_values)
71
+ return box_pierce_df, RawData(
72
+ box_pierce_values=box_pierce_values, dataset=dataset.input_id
73
+ )
@@ -104,5 +104,5 @@ def ClassImbalance(
104
104
  },
105
105
  go.Figure(data=[trace], layout=layout),
106
106
  all(row["Pass/Fail"] == "Pass" for row in imbalanced_classes),
107
- RawData(imbalance_percentages=imbalance_percentages),
107
+ RawData(imbalance_percentages=imbalance_percentages, dataset=dataset.input_id),
108
108
  )
@@ -242,4 +242,4 @@ def DatasetDescription(dataset: VMDataset):
242
242
  }
243
243
  for column in results
244
244
  ]
245
- }, RawData(raw_data=raw_data)
245
+ }, RawData(raw_data=raw_data, dataset=dataset.input_id)
@@ -97,4 +97,4 @@ def DickeyFullerGLS(dataset: VMDataset):
97
97
 
98
98
  return {
99
99
  "DFGLS Test Results": dfgls_values,
100
- }, RawData(df=df)
100
+ }, RawData(df=df, dataset=dataset.input_id)
@@ -52,19 +52,13 @@ def FeatureTargetCorrelationPlot(dataset, fig_height=600):
52
52
  - Not apt for models that employ complex feature interactions, like Decision Trees or Neural Networks, as the test
53
53
  may not accurately reflect their importance.
54
54
  """
55
-
56
- # Filter DataFrame based on features and target_column
57
55
  df = dataset.df[dataset.feature_columns + [dataset.target_column]]
58
56
 
59
- fig = _visualize_feature_target_correlation(df, dataset.target_column, fig_height)
60
-
61
- correlations = (
62
- df.corr(numeric_only=True)[dataset.target_column]
63
- .drop(dataset.target_column)
64
- .to_frame()
57
+ fig, correlations = _visualize_feature_target_correlation(
58
+ df, dataset.target_column, fig_height
65
59
  )
66
60
 
67
- return fig, RawData(correlation_data=correlations)
61
+ return fig, RawData(correlation_data=correlations, dataset=dataset.input_id)
68
62
 
69
63
 
70
64
  def _visualize_feature_target_correlation(df, target_column, fig_height):
@@ -100,4 +94,5 @@ def _visualize_feature_target_correlation(df, target_column, fig_height):
100
94
  yaxis_title="",
101
95
  height=fig_height, # Adjust the height value as needed
102
96
  )
103
- return fig
97
+
98
+ return fig, correlations
@@ -83,4 +83,8 @@ def HighCardinality(
83
83
  if not passed:
84
84
  all_passed = False
85
85
 
86
- return table, all_passed, RawData(raw_cardinality_details=raw_data)
86
+ return (
87
+ table,
88
+ all_passed,
89
+ RawData(raw_cardinality_details=raw_data, dataset=dataset.input_id),
90
+ )
@@ -84,5 +84,5 @@ def HighPearsonCorrelation(
84
84
  return (
85
85
  pairs,
86
86
  all(p["Pass/Fail"] == "Pass" for p in pairs),
87
- RawData(correlation_matrix=corr),
87
+ RawData(correlation_matrix=corr, dataset=dataset.input_id),
88
88
  )
@@ -118,11 +118,13 @@ def IQROutliersBarPlot(
118
118
  )
119
119
  figures.append(fig)
120
120
 
121
+ outliers_by_feature = df[dataset.feature_columns_numeric].apply(
122
+ lambda col: compute_outliers(col, threshold)
123
+ )
124
+
121
125
  return (
122
126
  *figures,
123
127
  RawData(
124
- outlier_counts_by_feature=df[dataset.feature_columns_numeric].apply(
125
- lambda col: compute_outliers(col, threshold)
126
- )
128
+ outlier_counts_by_feature=outliers_by_feature, dataset=dataset.input_id
127
129
  ),
128
130
  )
@@ -2,7 +2,7 @@
2
2
  # See the LICENSE file in the root of this repository for details.
3
3
  # SPDX-License-Identifier: AGPL-3.0 AND ValidMind Commercial
4
4
 
5
- from validmind import tags, tasks
5
+ from validmind import RawData, tags, tasks
6
6
  from validmind.vm_models import VMDataset
7
7
 
8
8
 
@@ -64,6 +64,7 @@ def IQROutliersTable(dataset: VMDataset, threshold: float = 1.5):
64
64
  df = dataset.df
65
65
 
66
66
  outliers_table = []
67
+ all_outliers = {}
67
68
 
68
69
  for col in dataset.feature_columns_numeric:
69
70
  # Skip binary features
@@ -71,6 +72,8 @@ def IQROutliersTable(dataset: VMDataset, threshold: float = 1.5):
71
72
  continue
72
73
 
73
74
  outliers = compute_outliers(df[col], threshold)
75
+ all_outliers[col] = outliers
76
+
74
77
  if outliers.empty:
75
78
  continue
76
79
 
@@ -89,4 +92,4 @@ def IQROutliersTable(dataset: VMDataset, threshold: float = 1.5):
89
92
 
90
93
  return {
91
94
  "Summary of Outliers Detected by IQR Method": outliers_table,
92
- }
95
+ }, RawData(all_outliers=all_outliers, dataset=dataset.input_id)
@@ -8,7 +8,7 @@ import matplotlib.pyplot as plt
8
8
  import seaborn as sns
9
9
  from sklearn.ensemble import IsolationForest
10
10
 
11
- from validmind import tags, tasks
11
+ from validmind import RawData, tags, tasks
12
12
  from validmind.vm_models import VMDataset
13
13
 
14
14
 
@@ -91,6 +91,7 @@ def IsolationForestOutliers(
91
91
 
92
92
  figures.append(fig)
93
93
 
94
- plt.close()
95
-
96
- return tuple(figures)
94
+ return (
95
+ *figures,
96
+ RawData(predictions=y_pred, dataset=dataset.input_id),
97
+ )
@@ -5,7 +5,7 @@
5
5
  import pandas as pd
6
6
  from statsmodels.stats.stattools import jarque_bera
7
7
 
8
- from validmind import tags, tasks
8
+ from validmind import RawData, tags, tasks
9
9
 
10
10
 
11
11
  @tasks("classification", "regression")
@@ -67,4 +67,4 @@ def JarqueBera(dataset):
67
67
  jb_df.reset_index(inplace=True)
68
68
  jb_df.columns = ["column", "stat", "pvalue", "skew", "kurtosis"]
69
69
 
70
- return jb_df
70
+ return jb_df, RawData(jb_values=jb_values, dataset=dataset.input_id)
@@ -5,7 +5,7 @@
5
5
  import pandas as pd
6
6
  from statsmodels.stats.diagnostic import acorr_ljungbox
7
7
 
8
- from validmind import tags, tasks
8
+ from validmind import RawData, tags, tasks
9
9
 
10
10
 
11
11
  @tasks("regression")
@@ -63,4 +63,4 @@ def LJungBox(dataset):
63
63
  ljung_box_df.reset_index(inplace=True)
64
64
  ljung_box_df.columns = ["column", "stat", "pvalue"]
65
65
 
66
- return ljung_box_df
66
+ return ljung_box_df, RawData(ljung_box_df=ljung_box_df, dataset=dataset.input_id)
@@ -101,4 +101,4 @@ def LaggedCorrelationHeatmap(dataset: VMDataset, num_lags: int = 10):
101
101
  xaxis_title="Lags",
102
102
  )
103
103
 
104
- return fig, RawData(correlation_matrix=correlation_df)
104
+ return fig, RawData(correlation_matrix=correlation_df, dataset=dataset.input_id)
@@ -2,7 +2,7 @@
2
2
  # See the LICENSE file in the root of this repository for details.
3
3
  # SPDX-License-Identifier: AGPL-3.0 AND ValidMind Commercial
4
4
 
5
- from validmind import tags, tasks
5
+ from validmind import RawData, tags, tasks
6
6
  from validmind.vm_models import VMDataset
7
7
 
8
8
 
@@ -49,12 +49,16 @@ def MissingValues(dataset: VMDataset, min_threshold: int = 1):
49
49
  df = dataset.df
50
50
  missing = df.isna().sum()
51
51
 
52
- return [
53
- {
54
- "Column": col,
55
- "Number of Missing Values": missing[col],
56
- "Percentage of Missing Values (%)": missing[col] / df.shape[0] * 100,
57
- "Pass/Fail": "Pass" if missing[col] < min_threshold else "Fail",
58
- }
59
- for col in missing.index
60
- ], all(missing[col] < min_threshold for col in missing.index)
52
+ return (
53
+ [
54
+ {
55
+ "Column": col,
56
+ "Number of Missing Values": missing[col],
57
+ "Percentage of Missing Values (%)": missing[col] / df.shape[0] * 100,
58
+ "Pass/Fail": "Pass" if missing[col] < min_threshold else "Fail",
59
+ }
60
+ for col in missing.index
61
+ ],
62
+ all(missing[col] < min_threshold for col in missing.index),
63
+ RawData(missing_values=missing, dataset=dataset.input_id),
64
+ )
@@ -117,5 +117,7 @@ def MissingValuesBarPlot(
117
117
  height=fig_height,
118
118
  ),
119
119
  ),
120
- RawData(missing_percentages=missing_percentages_sorted),
120
+ RawData(
121
+ missing_percentages=missing_percentages_sorted, dataset=dataset.input_id
122
+ ),
121
123
  )
@@ -123,5 +123,6 @@ def MutualInformation(
123
123
  return fig, RawData(
124
124
  mutual_information_scores={
125
125
  feature: score for feature, score in zip(sorted_features, sorted_scores)
126
- }
126
+ },
127
+ dataset=dataset.input_id,
127
128
  )
@@ -88,4 +88,4 @@ def PearsonCorrelationMatrix(dataset):
88
88
 
89
89
  fig = go.Figure(data=[heatmap], layout=layout)
90
90
 
91
- return fig, RawData(correlation_matrix=corr_matrix)
91
+ return fig, RawData(correlation_matrix=corr_matrix, dataset=dataset.input_id)
@@ -206,5 +206,7 @@ def ProtectedClassesCombination(dataset, model, protected_classes=None):
206
206
  metrics_frame=mf,
207
207
  demographic_parity_ratios=m_dpr,
208
208
  equalized_odds_ratios=m_eqo,
209
+ model=model.input_id,
210
+ dataset=dataset.input_id,
209
211
  ),
210
212
  )
@@ -6,7 +6,7 @@
6
6
  import pandas as pd
7
7
  import plotly.graph_objects as go
8
8
 
9
- from validmind import tags, tasks
9
+ from validmind import RawData, tags, tasks
10
10
  from validmind.logging import get_logger
11
11
 
12
12
  logger = get_logger(__name__)
@@ -127,4 +127,4 @@ def ProtectedClassesDescription(dataset, protected_classes=None):
127
127
  ["Protected Class", "Count"], ascending=[True, False]
128
128
  )
129
129
 
130
- return (stats_df, *figures)
130
+ return (stats_df, *figures, RawData(dataset=dataset.input_id))