validmind 2.8.28__py3-none-any.whl → 2.9.1__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 (233) hide show
  1. validmind/__version__.py +1 -1
  2. validmind/ai/utils.py +4 -24
  3. validmind/api_client.py +6 -17
  4. validmind/logging.py +48 -0
  5. validmind/models/function.py +11 -3
  6. validmind/tests/__init__.py +2 -0
  7. validmind/tests/__types__.py +18 -0
  8. validmind/tests/data_validation/ACFandPACFPlot.py +3 -1
  9. validmind/tests/data_validation/ADF.py +3 -1
  10. validmind/tests/data_validation/AutoAR.py +3 -1
  11. validmind/tests/data_validation/AutoMA.py +5 -1
  12. validmind/tests/data_validation/AutoStationarity.py +5 -1
  13. validmind/tests/data_validation/BivariateScatterPlots.py +3 -1
  14. validmind/tests/data_validation/BoxPierce.py +4 -1
  15. validmind/tests/data_validation/ChiSquaredFeaturesTable.py +1 -1
  16. validmind/tests/data_validation/ClassImbalance.py +1 -1
  17. validmind/tests/data_validation/DatasetDescription.py +4 -1
  18. validmind/tests/data_validation/DatasetSplit.py +3 -2
  19. validmind/tests/data_validation/DescriptiveStatistics.py +3 -1
  20. validmind/tests/data_validation/DickeyFullerGLS.py +3 -1
  21. validmind/tests/data_validation/Duplicates.py +3 -1
  22. validmind/tests/data_validation/EngleGrangerCoint.py +6 -1
  23. validmind/tests/data_validation/FeatureTargetCorrelationPlot.py +3 -1
  24. validmind/tests/data_validation/HighCardinality.py +3 -1
  25. validmind/tests/data_validation/HighPearsonCorrelation.py +4 -1
  26. validmind/tests/data_validation/IQROutliersBarPlot.py +4 -1
  27. validmind/tests/data_validation/IQROutliersTable.py +6 -1
  28. validmind/tests/data_validation/IsolationForestOutliers.py +3 -1
  29. validmind/tests/data_validation/JarqueBera.py +3 -1
  30. validmind/tests/data_validation/KPSS.py +3 -1
  31. validmind/tests/data_validation/LJungBox.py +3 -1
  32. validmind/tests/data_validation/LaggedCorrelationHeatmap.py +6 -1
  33. validmind/tests/data_validation/MissingValues.py +5 -1
  34. validmind/tests/data_validation/MissingValuesBarPlot.py +3 -1
  35. validmind/tests/data_validation/MutualInformation.py +4 -1
  36. validmind/tests/data_validation/PearsonCorrelationMatrix.py +3 -1
  37. validmind/tests/data_validation/PhillipsPerronArch.py +3 -1
  38. validmind/tests/data_validation/ProtectedClassesCombination.py +5 -1
  39. validmind/tests/data_validation/ProtectedClassesDescription.py +5 -1
  40. validmind/tests/data_validation/ProtectedClassesDisparity.py +5 -3
  41. validmind/tests/data_validation/ProtectedClassesThresholdOptimizer.py +9 -2
  42. validmind/tests/data_validation/RollingStatsPlot.py +5 -1
  43. validmind/tests/data_validation/RunsTest.py +1 -1
  44. validmind/tests/data_validation/ScatterPlot.py +2 -1
  45. validmind/tests/data_validation/ScoreBandDefaultRates.py +3 -1
  46. validmind/tests/data_validation/SeasonalDecompose.py +6 -1
  47. validmind/tests/data_validation/ShapiroWilk.py +4 -1
  48. validmind/tests/data_validation/Skewness.py +3 -1
  49. validmind/tests/data_validation/SpreadPlot.py +3 -1
  50. validmind/tests/data_validation/TabularCategoricalBarPlots.py +4 -1
  51. validmind/tests/data_validation/TabularDateTimeHistograms.py +3 -1
  52. validmind/tests/data_validation/TabularDescriptionTables.py +4 -1
  53. validmind/tests/data_validation/TabularNumericalHistograms.py +3 -1
  54. validmind/tests/data_validation/TargetRateBarPlots.py +4 -1
  55. validmind/tests/data_validation/TimeSeriesDescription.py +1 -1
  56. validmind/tests/data_validation/TimeSeriesDescriptiveStatistics.py +1 -1
  57. validmind/tests/data_validation/TimeSeriesFrequency.py +5 -1
  58. validmind/tests/data_validation/TimeSeriesHistogram.py +4 -1
  59. validmind/tests/data_validation/TimeSeriesLinePlot.py +3 -1
  60. validmind/tests/data_validation/TimeSeriesMissingValues.py +6 -1
  61. validmind/tests/data_validation/TimeSeriesOutliers.py +5 -1
  62. validmind/tests/data_validation/TooManyZeroValues.py +6 -1
  63. validmind/tests/data_validation/UniqueRows.py +5 -1
  64. validmind/tests/data_validation/WOEBinPlots.py +4 -1
  65. validmind/tests/data_validation/WOEBinTable.py +5 -1
  66. validmind/tests/data_validation/ZivotAndrewsArch.py +3 -1
  67. validmind/tests/data_validation/nlp/CommonWords.py +2 -1
  68. validmind/tests/data_validation/nlp/Hashtags.py +2 -1
  69. validmind/tests/data_validation/nlp/LanguageDetection.py +4 -1
  70. validmind/tests/data_validation/nlp/Mentions.py +3 -1
  71. validmind/tests/data_validation/nlp/PolarityAndSubjectivity.py +6 -1
  72. validmind/tests/data_validation/nlp/Punctuations.py +2 -1
  73. validmind/tests/data_validation/nlp/Sentiment.py +3 -1
  74. validmind/tests/data_validation/nlp/StopWords.py +2 -1
  75. validmind/tests/data_validation/nlp/TextDescription.py +3 -1
  76. validmind/tests/data_validation/nlp/Toxicity.py +3 -1
  77. validmind/tests/load.py +91 -17
  78. validmind/tests/model_validation/BertScore.py +6 -3
  79. validmind/tests/model_validation/BleuScore.py +6 -1
  80. validmind/tests/model_validation/ClusterSizeDistribution.py +5 -1
  81. validmind/tests/model_validation/ContextualRecall.py +6 -1
  82. validmind/tests/model_validation/FeaturesAUC.py +5 -1
  83. validmind/tests/model_validation/MeteorScore.py +6 -1
  84. validmind/tests/model_validation/ModelMetadata.py +2 -1
  85. validmind/tests/model_validation/ModelPredictionResiduals.py +10 -2
  86. validmind/tests/model_validation/RegardScore.py +7 -1
  87. validmind/tests/model_validation/RegressionResidualsPlot.py +5 -1
  88. validmind/tests/model_validation/RougeScore.py +8 -1
  89. validmind/tests/model_validation/TimeSeriesPredictionWithCI.py +8 -1
  90. validmind/tests/model_validation/TimeSeriesPredictionsPlot.py +7 -1
  91. validmind/tests/model_validation/TimeSeriesR2SquareBySegments.py +6 -1
  92. validmind/tests/model_validation/TokenDisparity.py +6 -1
  93. validmind/tests/model_validation/ToxicityScore.py +6 -1
  94. validmind/tests/model_validation/embeddings/ClusterDistribution.py +6 -1
  95. validmind/tests/model_validation/embeddings/CosineSimilarityComparison.py +6 -1
  96. validmind/tests/model_validation/embeddings/CosineSimilarityDistribution.py +6 -1
  97. validmind/tests/model_validation/embeddings/CosineSimilarityHeatmap.py +7 -3
  98. validmind/tests/model_validation/embeddings/DescriptiveAnalytics.py +6 -1
  99. validmind/tests/model_validation/embeddings/EmbeddingsVisualization2D.py +4 -3
  100. validmind/tests/model_validation/embeddings/EuclideanDistanceComparison.py +6 -1
  101. validmind/tests/model_validation/embeddings/EuclideanDistanceHeatmap.py +7 -3
  102. validmind/tests/model_validation/embeddings/PCAComponentsPairwisePlots.py +6 -1
  103. validmind/tests/model_validation/embeddings/StabilityAnalysisKeyword.py +5 -2
  104. validmind/tests/model_validation/embeddings/StabilityAnalysisRandomNoise.py +5 -1
  105. validmind/tests/model_validation/embeddings/StabilityAnalysisSynonyms.py +4 -1
  106. validmind/tests/model_validation/embeddings/StabilityAnalysisTranslation.py +5 -1
  107. validmind/tests/model_validation/embeddings/TSNEComponentsPairwisePlots.py +9 -6
  108. validmind/tests/model_validation/ragas/AnswerCorrectness.py +8 -5
  109. validmind/tests/model_validation/ragas/AspectCritic.py +11 -8
  110. validmind/tests/model_validation/ragas/ContextEntityRecall.py +5 -2
  111. validmind/tests/model_validation/ragas/ContextPrecision.py +5 -2
  112. validmind/tests/model_validation/ragas/ContextPrecisionWithoutReference.py +5 -2
  113. validmind/tests/model_validation/ragas/ContextRecall.py +6 -2
  114. validmind/tests/model_validation/ragas/Faithfulness.py +9 -5
  115. validmind/tests/model_validation/ragas/NoiseSensitivity.py +10 -7
  116. validmind/tests/model_validation/ragas/ResponseRelevancy.py +9 -6
  117. validmind/tests/model_validation/ragas/SemanticSimilarity.py +7 -4
  118. validmind/tests/model_validation/sklearn/AdjustedMutualInformation.py +5 -1
  119. validmind/tests/model_validation/sklearn/AdjustedRandIndex.py +5 -1
  120. validmind/tests/model_validation/sklearn/CalibrationCurve.py +5 -1
  121. validmind/tests/model_validation/sklearn/ClassifierPerformance.py +5 -1
  122. validmind/tests/model_validation/sklearn/ClusterCosineSimilarity.py +5 -1
  123. validmind/tests/model_validation/sklearn/ClusterPerformanceMetrics.py +5 -1
  124. validmind/tests/model_validation/sklearn/CompletenessScore.py +5 -1
  125. validmind/tests/model_validation/sklearn/ConfusionMatrix.py +4 -1
  126. validmind/tests/model_validation/sklearn/FeatureImportance.py +5 -1
  127. validmind/tests/model_validation/sklearn/FowlkesMallowsScore.py +5 -1
  128. validmind/tests/model_validation/sklearn/HomogeneityScore.py +5 -1
  129. validmind/tests/model_validation/sklearn/HyperParametersTuning.py +2 -4
  130. validmind/tests/model_validation/sklearn/KMeansClustersOptimization.py +3 -3
  131. validmind/tests/model_validation/sklearn/MinimumAccuracy.py +5 -1
  132. validmind/tests/model_validation/sklearn/MinimumF1Score.py +5 -1
  133. validmind/tests/model_validation/sklearn/MinimumROCAUCScore.py +5 -1
  134. validmind/tests/model_validation/sklearn/ModelParameters.py +6 -1
  135. validmind/tests/model_validation/sklearn/ModelsPerformanceComparison.py +5 -1
  136. validmind/tests/model_validation/sklearn/OverfitDiagnosis.py +3 -2
  137. validmind/tests/model_validation/sklearn/PermutationFeatureImportance.py +4 -4
  138. validmind/tests/model_validation/sklearn/PopulationStabilityIndex.py +2 -2
  139. validmind/tests/model_validation/sklearn/PrecisionRecallCurve.py +5 -1
  140. validmind/tests/model_validation/sklearn/ROCCurve.py +3 -1
  141. validmind/tests/model_validation/sklearn/RegressionErrors.py +6 -1
  142. validmind/tests/model_validation/sklearn/RegressionErrorsComparison.py +6 -1
  143. validmind/tests/model_validation/sklearn/RegressionPerformance.py +5 -1
  144. validmind/tests/model_validation/sklearn/RegressionR2Square.py +6 -1
  145. validmind/tests/model_validation/sklearn/RegressionR2SquareComparison.py +6 -1
  146. validmind/tests/model_validation/sklearn/RobustnessDiagnosis.py +2 -2
  147. validmind/tests/model_validation/sklearn/ScoreProbabilityAlignment.py +3 -1
  148. validmind/tests/model_validation/sklearn/SilhouettePlot.py +6 -1
  149. validmind/tests/model_validation/sklearn/TrainingTestDegradation.py +2 -2
  150. validmind/tests/model_validation/sklearn/VMeasure.py +5 -1
  151. validmind/tests/model_validation/sklearn/WeakspotsDiagnosis.py +6 -5
  152. validmind/tests/model_validation/statsmodels/AutoARIMA.py +3 -1
  153. validmind/tests/model_validation/statsmodels/CumulativePredictionProbabilities.py +6 -1
  154. validmind/tests/model_validation/statsmodels/DurbinWatsonTest.py +6 -1
  155. validmind/tests/model_validation/statsmodels/GINITable.py +4 -1
  156. validmind/tests/model_validation/statsmodels/KolmogorovSmirnov.py +5 -1
  157. validmind/tests/model_validation/statsmodels/Lilliefors.py +3 -1
  158. validmind/tests/model_validation/statsmodels/PredictionProbabilitiesHistogram.py +6 -2
  159. validmind/tests/model_validation/statsmodels/RegressionCoeffs.py +4 -1
  160. validmind/tests/model_validation/statsmodels/RegressionFeatureSignificance.py +7 -2
  161. validmind/tests/model_validation/statsmodels/RegressionModelForecastPlot.py +5 -4
  162. validmind/tests/model_validation/statsmodels/RegressionModelForecastPlotLevels.py +4 -1
  163. validmind/tests/model_validation/statsmodels/RegressionModelSensitivityPlot.py +3 -2
  164. validmind/tests/model_validation/statsmodels/RegressionModelSummary.py +5 -1
  165. validmind/tests/model_validation/statsmodels/RegressionPermutationFeatureImportance.py +3 -1
  166. validmind/tests/model_validation/statsmodels/ScorecardHistogram.py +6 -1
  167. validmind/tests/ongoing_monitoring/CalibrationCurveDrift.py +2 -2
  168. validmind/tests/ongoing_monitoring/ClassDiscriminationDrift.py +2 -2
  169. validmind/tests/ongoing_monitoring/ClassImbalanceDrift.py +2 -2
  170. validmind/tests/ongoing_monitoring/ClassificationAccuracyDrift.py +2 -2
  171. validmind/tests/ongoing_monitoring/ConfusionMatrixDrift.py +2 -2
  172. validmind/tests/ongoing_monitoring/CumulativePredictionProbabilitiesDrift.py +2 -2
  173. validmind/tests/ongoing_monitoring/FeatureDrift.py +5 -2
  174. validmind/tests/ongoing_monitoring/PredictionAcrossEachFeature.py +6 -1
  175. validmind/tests/ongoing_monitoring/PredictionCorrelation.py +8 -1
  176. validmind/tests/ongoing_monitoring/PredictionProbabilitiesHistogramDrift.py +2 -2
  177. validmind/tests/ongoing_monitoring/PredictionQuantilesAcrossFeatures.py +6 -1
  178. validmind/tests/ongoing_monitoring/ROCCurveDrift.py +4 -2
  179. validmind/tests/ongoing_monitoring/ScoreBandsDrift.py +2 -2
  180. validmind/tests/ongoing_monitoring/ScorecardHistogramDrift.py +2 -2
  181. validmind/tests/ongoing_monitoring/TargetPredictionDistributionPlot.py +8 -1
  182. validmind/tests/output.py +9 -2
  183. validmind/tests/plots/BoxPlot.py +260 -0
  184. validmind/tests/plots/CorrelationHeatmap.py +235 -0
  185. validmind/tests/plots/HistogramPlot.py +233 -0
  186. validmind/tests/plots/ViolinPlot.py +125 -0
  187. validmind/tests/plots/__init__.py +0 -0
  188. validmind/tests/prompt_validation/Bias.py +5 -1
  189. validmind/tests/prompt_validation/Clarity.py +5 -1
  190. validmind/tests/prompt_validation/Conciseness.py +5 -1
  191. validmind/tests/prompt_validation/Delimitation.py +5 -1
  192. validmind/tests/prompt_validation/NegativeInstruction.py +5 -1
  193. validmind/tests/prompt_validation/Robustness.py +5 -1
  194. validmind/tests/prompt_validation/Specificity.py +5 -1
  195. validmind/tests/stats/CorrelationAnalysis.py +251 -0
  196. validmind/tests/stats/DescriptiveStats.py +197 -0
  197. validmind/tests/stats/NormalityTests.py +147 -0
  198. validmind/tests/stats/OutlierDetection.py +173 -0
  199. validmind/tests/stats/__init__.py +0 -0
  200. validmind/unit_metrics/classification/Accuracy.py +2 -1
  201. validmind/unit_metrics/classification/F1.py +2 -1
  202. validmind/unit_metrics/classification/Precision.py +2 -1
  203. validmind/unit_metrics/classification/ROC_AUC.py +2 -1
  204. validmind/unit_metrics/classification/Recall.py +2 -1
  205. validmind/unit_metrics/classification/individual/AbsoluteError.py +42 -0
  206. validmind/unit_metrics/classification/individual/BrierScore.py +56 -0
  207. validmind/unit_metrics/classification/individual/CalibrationError.py +77 -0
  208. validmind/unit_metrics/classification/individual/ClassBalance.py +65 -0
  209. validmind/unit_metrics/classification/individual/Confidence.py +52 -0
  210. validmind/unit_metrics/classification/individual/Correctness.py +41 -0
  211. validmind/unit_metrics/classification/individual/LogLoss.py +61 -0
  212. validmind/unit_metrics/classification/individual/OutlierScore.py +86 -0
  213. validmind/unit_metrics/classification/individual/ProbabilityError.py +54 -0
  214. validmind/unit_metrics/classification/individual/Uncertainty.py +60 -0
  215. validmind/unit_metrics/classification/individual/__init__.py +0 -0
  216. validmind/unit_metrics/regression/AdjustedRSquaredScore.py +2 -1
  217. validmind/unit_metrics/regression/GiniCoefficient.py +2 -1
  218. validmind/unit_metrics/regression/HuberLoss.py +2 -1
  219. validmind/unit_metrics/regression/KolmogorovSmirnovStatistic.py +2 -1
  220. validmind/unit_metrics/regression/MeanAbsoluteError.py +2 -1
  221. validmind/unit_metrics/regression/MeanAbsolutePercentageError.py +2 -1
  222. validmind/unit_metrics/regression/MeanBiasDeviation.py +2 -1
  223. validmind/unit_metrics/regression/MeanSquaredError.py +2 -1
  224. validmind/unit_metrics/regression/QuantileLoss.py +1 -1
  225. validmind/unit_metrics/regression/RSquaredScore.py +2 -1
  226. validmind/unit_metrics/regression/RootMeanSquaredError.py +2 -1
  227. validmind/vm_models/dataset/dataset.py +291 -38
  228. validmind/vm_models/result/result.py +26 -4
  229. {validmind-2.8.28.dist-info → validmind-2.9.1.dist-info}/METADATA +2 -2
  230. {validmind-2.8.28.dist-info → validmind-2.9.1.dist-info}/RECORD +233 -212
  231. {validmind-2.8.28.dist-info → validmind-2.9.1.dist-info}/LICENSE +0 -0
  232. {validmind-2.8.28.dist-info → validmind-2.9.1.dist-info}/WHEEL +0 -0
  233. {validmind-2.8.28.dist-info → validmind-2.9.1.dist-info}/entry_points.txt +0 -0
@@ -8,7 +8,7 @@ Dataset class wrapper
8
8
 
9
9
  import warnings
10
10
  from copy import deepcopy
11
- from typing import Any, Dict, List, Optional
11
+ from typing import Any, Dict, List, Optional, Union
12
12
 
13
13
  import numpy as np
14
14
  import pandas as pd
@@ -258,69 +258,91 @@ class VMDataset(VMInput):
258
258
  f"Options {kwargs} are not supported for this input"
259
259
  )
260
260
 
261
- def assign_predictions(
262
- self,
263
- model: VMModel,
264
- prediction_column: Optional[str] = None,
265
- prediction_values: Optional[List[Any]] = None,
266
- probability_column: Optional[str] = None,
267
- probability_values: Optional[List[float]] = None,
268
- prediction_probabilities: Optional[
269
- List[float]
270
- ] = None, # DEPRECATED: use probability_values
271
- **kwargs: Dict[str, Any],
272
- ) -> None:
273
- """Assign predictions and probabilities to the dataset.
274
-
275
- Args:
276
- model (VMModel): The model used to generate the predictions.
277
- prediction_column (Optional[str]): The name of the column containing the predictions.
278
- prediction_values (Optional[List[Any]]): The values of the predictions.
279
- probability_column (Optional[str]): The name of the column containing the probabilities.
280
- probability_values (Optional[List[float]]): The values of the probabilities.
281
- prediction_probabilities (Optional[List[float]]): DEPRECATED: The values of the probabilities.
282
- **kwargs: Additional keyword arguments that will get passed through to the model's `predict` method.
283
- """
261
+ def _handle_deprecated_parameters(
262
+ self, prediction_probabilities, probability_values
263
+ ):
264
+ """Handle deprecated parameters and return the correct probability values."""
284
265
  if prediction_probabilities is not None:
285
266
  warnings.warn(
286
267
  "The `prediction_probabilities` argument is deprecated. Use `probability_values` instead.",
287
268
  DeprecationWarning,
288
269
  )
289
- probability_values = prediction_probabilities
290
-
291
- self._validate_assign_predictions(
292
- model,
293
- prediction_column,
294
- prediction_values,
295
- probability_column,
296
- probability_values,
297
- )
270
+ return prediction_probabilities
271
+ return probability_values
298
272
 
273
+ def _check_existing_predictions(self, model):
274
+ """Check for existing predictions and probabilities, warn if overwriting."""
299
275
  if self.prediction_column(model):
300
276
  logger.warning("Model predictions already assigned... Overwriting.")
301
277
 
302
278
  if self.probability_column(model):
303
279
  logger.warning("Model probabilities already assigned... Overwriting.")
304
280
 
305
- # if the user passes a column name, we assume it has precomputed predictions
281
+ def _get_precomputed_values(self, prediction_column, probability_column):
282
+ """Get precomputed prediction and probability values from existing columns."""
283
+ prediction_values = None
284
+ probability_values = None
285
+
306
286
  if prediction_column:
307
287
  prediction_values = self._df[prediction_column].values
308
288
 
309
289
  if probability_column:
310
290
  probability_values = self._df[probability_column].values
311
291
 
292
+ return prediction_values, probability_values
293
+
294
+ def _compute_predictions_if_needed(self, model, prediction_values, **kwargs):
295
+ """Compute predictions if not provided."""
312
296
  if prediction_values is None:
313
297
  X = self.df if isinstance(model, (FunctionModel, PipelineModel)) else self.x
314
- probability_values, prediction_values = compute_predictions(
315
- model, X, **kwargs
298
+ return compute_predictions(model, X, **kwargs)
299
+ return None, prediction_values
300
+
301
+ def _handle_dictionary_predictions(self, model, prediction_values):
302
+ """Handle dictionary predictions by converting to separate columns."""
303
+ if (
304
+ prediction_values is not None
305
+ and len(prediction_values) > 0
306
+ and isinstance(prediction_values[0], dict)
307
+ ):
308
+ df_prediction_values = pd.DataFrame.from_dict(
309
+ prediction_values, orient="columns"
316
310
  )
317
311
 
318
- prediction_column = prediction_column or f"{model.input_id}_prediction"
312
+ for column_name in df_prediction_values.columns.tolist():
313
+ values = df_prediction_values[column_name].values
314
+
315
+ if column_name == "prediction":
316
+ prediction_column = f"{model.input_id}_prediction"
317
+ self._add_column(prediction_column, values)
318
+ self.prediction_column(model, prediction_column)
319
+ else:
320
+ self._add_column(f"{model.input_id}_{column_name}", values)
321
+
322
+ return (
323
+ True,
324
+ None,
325
+ ) # Return True to indicate dictionary handled, None for prediction_column
326
+ return False, None
327
+
328
+ def _add_prediction_columns(
329
+ self,
330
+ model,
331
+ prediction_column,
332
+ prediction_values,
333
+ probability_column,
334
+ probability_values,
335
+ ):
336
+ """Add prediction and probability columns to the dataset."""
337
+ if prediction_column is None:
338
+ prediction_column = f"{model.input_id}_prediction"
339
+
319
340
  self._add_column(prediction_column, prediction_values)
320
341
  self.prediction_column(model, prediction_column)
321
342
 
322
343
  if probability_values is not None:
323
- probability_column = probability_column or f"{model.input_id}_probabilities"
344
+ if probability_column is None:
345
+ probability_column = f"{model.input_id}_probabilities"
324
346
  self._add_column(probability_column, probability_values)
325
347
  self.probability_column(model, probability_column)
326
348
  else:
@@ -329,6 +351,91 @@ class VMDataset(VMInput):
329
351
  "Not adding probability column to the dataset."
330
352
  )
331
353
 
354
+ def assign_predictions(
355
+ self,
356
+ model: VMModel,
357
+ prediction_column: Optional[str] = None,
358
+ prediction_values: Optional[Any] = None,
359
+ probability_column: Optional[str] = None,
360
+ probability_values: Optional[Any] = None,
361
+ prediction_probabilities: Optional[
362
+ Any
363
+ ] = None, # DEPRECATED: use probability_values
364
+ **kwargs: Dict[str, Any],
365
+ ) -> None:
366
+ """Assign predictions and probabilities to the dataset.
367
+
368
+ Args:
369
+ model (VMModel): The model used to generate the predictions.
370
+ prediction_column (Optional[str]): The name of the column containing the predictions.
371
+ prediction_values (Optional[Any]): The values of the predictions. Can be array-like (list, numpy array, pandas Series, etc.).
372
+ probability_column (Optional[str]): The name of the column containing the probabilities.
373
+ probability_values (Optional[Any]): The values of the probabilities. Can be array-like (list, numpy array, pandas Series, etc.).
374
+ prediction_probabilities (Optional[Any]): DEPRECATED: The values of the probabilities. Use probability_values instead.
375
+ **kwargs: Additional keyword arguments that will get passed through to the model's `predict` method.
376
+ """
377
+ # Handle deprecated parameters
378
+ probability_values = self._handle_deprecated_parameters(
379
+ prediction_probabilities, probability_values
380
+ )
381
+
382
+ # Convert pandas Series to numpy array for prediction_values
383
+ if (
384
+ hasattr(prediction_values, "values")
385
+ and hasattr(prediction_values, "index")
386
+ and hasattr(prediction_values, "dtype")
387
+ ):
388
+ prediction_values = prediction_values.values
389
+
390
+ # Convert pandas Series to numpy array for probability_values
391
+ if (
392
+ hasattr(probability_values, "values")
393
+ and hasattr(probability_values, "index")
394
+ and hasattr(probability_values, "dtype")
395
+ ):
396
+ probability_values = probability_values.values
397
+
398
+ # Validate input parameters
399
+ self._validate_assign_predictions(
400
+ model,
401
+ prediction_column,
402
+ prediction_values,
403
+ probability_column,
404
+ probability_values,
405
+ )
406
+
407
+ # Check for existing predictions and warn if overwriting
408
+ self._check_existing_predictions(model)
409
+
410
+ # Get precomputed values if column names are provided
411
+ if prediction_column or probability_column:
412
+ prediction_values, prob_values_from_column = self._get_precomputed_values(
413
+ prediction_column, probability_column
414
+ )
415
+ if prob_values_from_column is not None:
416
+ probability_values = prob_values_from_column
417
+
418
+ # Compute predictions if not provided
419
+ if prediction_values is None:
420
+ probability_values, prediction_values = self._compute_predictions_if_needed(
421
+ model, prediction_values, **kwargs
422
+ )
423
+
424
+ # Handle dictionary predictions
425
+ is_dict_handled, _ = self._handle_dictionary_predictions(
426
+ model, prediction_values
427
+ )
428
+
429
+ # Add prediction and probability columns (skip if dictionary was handled)
430
+ if not is_dict_handled:
431
+ self._add_prediction_columns(
432
+ model,
433
+ prediction_column,
434
+ prediction_values,
435
+ probability_column,
436
+ probability_values,
437
+ )
438
+
332
439
  def prediction_column(self, model: VMModel, column_name: str = None) -> str:
333
440
  """Get or set the prediction column for a model."""
334
441
  if column_name and column_name not in self.columns:
@@ -351,6 +458,152 @@ class VMDataset(VMInput):
351
458
 
352
459
  return self.extra_columns.probability_column(model, column_name)
353
460
 
461
+ def assign_scores(
462
+ self,
463
+ model: VMModel,
464
+ metrics: Union[str, List[str]],
465
+ **kwargs: Dict[str, Any],
466
+ ) -> None:
467
+ """Assign computed unit metric scores to the dataset as new columns.
468
+
469
+ This method computes unit metrics for the given model and dataset, then adds
470
+ the computed scores as new columns to the dataset using the naming convention:
471
+ {model.input_id}_{metric_name}
472
+
473
+ Args:
474
+ model (VMModel): The model used to compute the scores.
475
+ metrics (Union[str, List[str]]): Single metric ID or list of metric IDs.
476
+ Can be either:
477
+ - Short name (e.g., "F1", "Precision")
478
+ - Full metric ID (e.g., "validmind.unit_metrics.classification.F1")
479
+ **kwargs: Additional parameters passed to the unit metrics.
480
+
481
+ Examples:
482
+ # Single metric
483
+ dataset.assign_scores(model, "F1")
484
+
485
+ # Multiple metrics
486
+ dataset.assign_scores(model, ["F1", "Precision", "Recall"])
487
+
488
+ # With parameters
489
+ dataset.assign_scores(model, "ROC_AUC", average="weighted")
490
+
491
+ Raises:
492
+ ValueError: If the model input_id is None or if metric computation fails.
493
+ ImportError: If unit_metrics module cannot be imported.
494
+ """
495
+ if model.input_id is None:
496
+ raise ValueError("Model input_id must be set to use assign_scores")
497
+
498
+ # Import unit_metrics module
499
+ try:
500
+ from validmind.unit_metrics import run_metric
501
+ except ImportError as e:
502
+ raise ImportError(
503
+ f"Failed to import unit_metrics module: {e}. "
504
+ "Make sure validmind.unit_metrics is available."
505
+ ) from e
506
+
507
+ # Normalize metrics to a list
508
+ if isinstance(metrics, str):
509
+ metrics = [metrics]
510
+
511
+ # Process each metric
512
+ for metric in metrics:
513
+ # Normalize metric ID
514
+ metric_id = self._normalize_metric_id(metric)
515
+
516
+ # Extract metric name for column naming
517
+ metric_name = self._extract_metric_name(metric_id)
518
+
519
+ # Generate column name
520
+ column_name = f"{model.input_id}_{metric_name}"
521
+
522
+ try:
523
+ # Run the unit metric
524
+ result = run_metric(
525
+ metric_id,
526
+ inputs={
527
+ "model": model,
528
+ "dataset": self,
529
+ },
530
+ params=kwargs,
531
+ show=False, # Don't show widget output
532
+ )
533
+
534
+ # Extract the metric value
535
+ metric_value = result.metric
536
+
537
+ # Create column values (repeat the scalar value for all rows)
538
+ if np.isscalar(metric_value):
539
+ column_values = np.full(len(self._df), metric_value)
540
+ else:
541
+ if len(metric_value) != len(self._df):
542
+ raise ValueError(
543
+ f"Metric value length {len(metric_value)} does not match dataset length {len(self._df)}"
544
+ )
545
+ column_values = metric_value
546
+
547
+ # Add the column to the dataset
548
+ self.add_extra_column(column_name, column_values)
549
+
550
+ logger.info(f"Added metric column '{column_name}'")
551
+ except Exception as e:
552
+ logger.error(f"Failed to compute metric {metric_id}: {e}")
553
+ raise ValueError(f"Failed to compute metric {metric_id}: {e}") from e
554
+
555
+ def _normalize_metric_id(self, metric: str) -> str:
556
+ """Normalize metric identifier to full validmind unit metric ID.
557
+
558
+ Args:
559
+ metric (str): Metric identifier (short name or full ID)
560
+
561
+ Returns:
562
+ str: Full metric ID
563
+ """
564
+ # If already a full ID, return as-is
565
+ if metric.startswith("validmind.unit_metrics."):
566
+ return metric
567
+
568
+ # Try to find the metric by short name
569
+ try:
570
+ from validmind.unit_metrics import list_metrics
571
+
572
+ available_metrics = list_metrics()
573
+
574
+ # Look for exact match with short name
575
+ for metric_id in available_metrics:
576
+ if metric_id.endswith(f".{metric}"):
577
+ return metric_id
578
+
579
+ # If no exact match found, raise error with suggestions
580
+ suggestions = [m for m in available_metrics if metric.lower() in m.lower()]
581
+ if suggestions:
582
+ raise ValueError(
583
+ f"Metric '{metric}' not found. Did you mean one of: {suggestions[:5]}"
584
+ )
585
+ else:
586
+ raise ValueError(
587
+ f"Metric '{metric}' not found. Available metrics: {available_metrics[:10]}..."
588
+ )
589
+
590
+ except ImportError as e:
591
+ raise ImportError(
592
+ f"Failed to import unit_metrics for metric lookup: {e}"
593
+ ) from e
594
+
595
+ def _extract_metric_name(self, metric_id: str) -> str:
596
+ """Extract the metric name from a full metric ID.
597
+
598
+ Args:
599
+ metric_id (str): Full metric ID
600
+
601
+ Returns:
602
+ str: Metric name
603
+ """
604
+ # Extract the last part after the final dot
605
+ return metric_id.split(".")[-1]
606
+
354
607
  def add_extra_column(self, column_name, column_values=None):
355
608
  """Adds an extra column to the dataset without modifying the dataset `features` and `target` columns.
356
609
 
@@ -7,6 +7,7 @@ Result objects for test results
7
7
  """
8
8
  import asyncio
9
9
  import json
10
+ import os
10
11
  from abc import abstractmethod
11
12
  from dataclasses import dataclass
12
13
  from typing import Any, Dict, List, Optional, Union
@@ -20,7 +21,7 @@ from ipywidgets import HTML, VBox
20
21
  from ... import api_client
21
22
  from ...ai.utils import DescriptionFuture
22
23
  from ...errors import InvalidParameterError
23
- from ...logging import get_logger
24
+ from ...logging import get_logger, log_api_operation
24
25
  from ...utils import (
25
26
  HumanReadableEncoder,
26
27
  NumpyEncoder,
@@ -177,7 +178,7 @@ class TestResult(Result):
177
178
  title: Optional[str] = None
178
179
  doc: Optional[str] = None
179
180
  description: Optional[Union[str, DescriptionFuture]] = None
180
- metric: Optional[Union[int, float]] = None
181
+ metric: Optional[Union[int, float, List[Union[int, float]]]] = None
181
182
  tables: Optional[List[ResultTable]] = None
182
183
  raw_data: Optional[RawData] = None
183
184
  figures: Optional[List[Figure]] = None
@@ -476,9 +477,30 @@ class TestResult(Result):
476
477
  )
477
478
 
478
479
  if self.figures:
479
- tasks.extend(
480
- [api_client.alog_figure(figure) for figure in (self.figures or [])]
480
+ batch_size = min(
481
+ len(self.figures), int(os.getenv("VM_FIGURE_MAX_BATCH_SIZE", 20))
481
482
  )
483
+ figure_batches = [
484
+ self.figures[i : i + batch_size]
485
+ for i in range(0, len(self.figures), batch_size)
486
+ ]
487
+
488
+ async def upload_figures_in_batches():
489
+ for batch in figure_batches:
490
+
491
+ @log_api_operation(
492
+ operation_name=f"Uploading batch of {len(batch)} figures"
493
+ )
494
+ async def process_batch():
495
+ batch_tasks = [
496
+ api_client.alog_figure(figure) for figure in batch
497
+ ]
498
+ return await asyncio.gather(*batch_tasks)
499
+
500
+ await process_batch()
501
+
502
+ tasks.append(upload_figures_in_batches())
503
+
482
504
  if self.description:
483
505
  revision_name = (
484
506
  AI_REVISION_NAME
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: validmind
3
- Version: 2.8.28
3
+ Version: 2.9.1
4
4
  Summary: ValidMind Library
5
5
  License: Commercial License
6
6
  Author: Andres Rodriguez
@@ -24,7 +24,7 @@ Requires-Dist: datasets (>=2.10.0,<3.0.0)
24
24
  Requires-Dist: evaluate
25
25
  Requires-Dist: h11 (>=0.16.0)
26
26
  Requires-Dist: ipywidgets
27
- Requires-Dist: kaleido (>=0.2.1,!=0.2.1.post1)
27
+ Requires-Dist: kaleido (>=0.2.1,!=0.2.1.post1,<1.0.0)
28
28
  Requires-Dist: langchain-openai (>=0.1.8) ; extra == "all" or extra == "llm"
29
29
  Requires-Dist: langdetect
30
30
  Requires-Dist: llvmlite ; python_version >= "3.8" and python_full_version <= "3.11.0"