mostlyai-engine 2.0.1__tar.gz → 2.1.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 (53) hide show
  1. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/PKG-INFO +1 -1
  2. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/__init__.py +1 -1
  3. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/_tabular/interface.py +129 -29
  4. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/pyproject.toml +1 -1
  5. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/.gitignore +0 -0
  6. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/LICENSE +0 -0
  7. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/README.md +0 -0
  8. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/_common.py +0 -0
  9. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/_dtypes.py +0 -0
  10. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/_encoding_types/__init__.py +0 -0
  11. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/_encoding_types/language/__init__.py +0 -0
  12. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/_encoding_types/language/categorical.py +0 -0
  13. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/_encoding_types/language/datetime.py +0 -0
  14. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/_encoding_types/language/numeric.py +0 -0
  15. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/_encoding_types/language/text.py +0 -0
  16. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/_encoding_types/tabular/__init__.py +0 -0
  17. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/_encoding_types/tabular/categorical.py +0 -0
  18. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/_encoding_types/tabular/character.py +0 -0
  19. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/_encoding_types/tabular/datetime.py +0 -0
  20. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/_encoding_types/tabular/itt.py +0 -0
  21. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/_encoding_types/tabular/lat_long.py +0 -0
  22. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/_encoding_types/tabular/numeric.py +0 -0
  23. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/_language/__init__.py +0 -0
  24. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/_language/common.py +0 -0
  25. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/_language/encoding.py +0 -0
  26. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/_language/engine/__init__.py +0 -0
  27. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/_language/engine/base.py +0 -0
  28. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/_language/engine/hf_engine.py +0 -0
  29. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/_language/engine/vllm_engine.py +0 -0
  30. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/_language/generation.py +0 -0
  31. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/_language/interface.py +0 -0
  32. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/_language/lstm.py +0 -0
  33. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/_language/tokenizer_utils.py +0 -0
  34. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/_language/training.py +0 -0
  35. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/_language/xgrammar_utils.py +0 -0
  36. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/_memory.py +0 -0
  37. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/_tabular/__init__.py +0 -0
  38. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/_tabular/argn.py +0 -0
  39. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/_tabular/common.py +0 -0
  40. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/_tabular/encoding.py +0 -0
  41. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/_tabular/fairness.py +0 -0
  42. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/_tabular/generation.py +0 -0
  43. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/_tabular/training.py +0 -0
  44. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/_training_utils.py +0 -0
  45. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/_workspace.py +0 -0
  46. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/analysis.py +0 -0
  47. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/domain.py +0 -0
  48. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/encoding.py +0 -0
  49. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/generation.py +0 -0
  50. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/logging.py +0 -0
  51. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/random_state.py +0 -0
  52. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/splitting.py +0 -0
  53. {mostlyai_engine-2.0.1 → mostlyai_engine-2.1.0}/mostlyai/engine/training.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mostlyai-engine
3
- Version: 2.0.1
3
+ Version: 2.1.0
4
4
  Summary: Synthetic Data Engine
5
5
  Project-URL: homepage, https://github.com/mostly-ai/mostlyai-engine
6
6
  Project-URL: repository, https://github.com/mostly-ai/mostlyai-engine
@@ -34,7 +34,7 @@ __all__ = [
34
34
  "TabularARGN",
35
35
  "LanguageModel",
36
36
  ]
37
- __version__ = "2.0.1"
37
+ __version__ = "2.1.0"
38
38
 
39
39
  # suppress specific warning related to os.fork() in multi-threaded processes
40
40
  warnings.filterwarnings("ignore", category=DeprecationWarning, message=".*multi-threaded.*fork.*")
@@ -30,13 +30,30 @@ import pandas as pd
30
30
  import torch
31
31
  from sklearn.base import BaseEstimator
32
32
 
33
- from mostlyai.engine._common import ensure_dataframe, list_fn, load_generated_data, mean_fn, median_fn, mode_fn
33
+ from mostlyai.engine._common import (
34
+ ensure_dataframe,
35
+ list_fn,
36
+ load_generated_data,
37
+ mean_fn,
38
+ median_fn,
39
+ mode_fn,
40
+ )
41
+ from mostlyai.engine._encoding_types.tabular.categorical import (
42
+ encode_categorical,
43
+ )
44
+ from mostlyai.engine._encoding_types.tabular.numeric import (
45
+ NUMERIC_BINNED_MAX_TOKEN,
46
+ NUMERIC_BINNED_MIN_TOKEN,
47
+ NUMERIC_BINNED_UNKNOWN_TOKEN,
48
+ encode_numeric,
49
+ )
34
50
  from mostlyai.engine._workspace import Workspace
35
51
  from mostlyai.engine.analysis import analyze
36
52
  from mostlyai.engine.domain import (
37
53
  DifferentialPrivacyConfig,
38
54
  FairnessConfig,
39
55
  ImputationConfig,
56
+ ModelEncodingType,
40
57
  ModelType,
41
58
  RareCategoryReplacementMethod,
42
59
  RebalancingConfig,
@@ -446,6 +463,13 @@ class TabularARGN(BaseEstimator):
446
463
 
447
464
  return X_imputed
448
465
 
466
+ def _get_column_stats(self) -> dict:
467
+ """Get column statistics from workspace."""
468
+ workspace_dir = self._get_workspace_dir()
469
+ workspace = Workspace(workspace_dir)
470
+ stats = workspace.tgt_stats.read()
471
+ return stats.get("columns", {})
472
+
449
473
  def _get_target_encoding_type(self, target_column: str | None = None) -> str | None:
450
474
  """Get the encoding type of the target column from workspace stats."""
451
475
  if not self._fitted:
@@ -455,10 +479,7 @@ class TabularARGN(BaseEstimator):
455
479
  if target_col is None:
456
480
  return None
457
481
 
458
- workspace_dir = self._get_workspace_dir()
459
- workspace = Workspace(workspace_dir)
460
- stats = workspace.tgt_stats.read()
461
- columns = stats.get("columns", {})
482
+ columns = self._get_column_stats()
462
483
  if target_col in columns:
463
484
  return columns[target_col].get("encoding_type")
464
485
  return None
@@ -597,13 +618,18 @@ class TabularARGN(BaseEstimator):
597
618
  ctx_data: pd.DataFrame | None = None,
598
619
  n_draws: int = 1,
599
620
  **kwargs,
600
- ) -> np.ndarray:
621
+ ) -> pd.DataFrame:
601
622
  """
602
623
  Predict class probabilities for samples in X.
603
624
 
604
625
  This method generates synthetic samples conditioned on the input features and computes
605
626
  the probability of each class based on the frequency across multiple draws.
606
627
 
628
+ Supported encoding types:
629
+ - tabular_categorical: Multi-class categorical variables
630
+ - tabular_numeric_discrete: Discrete numeric variables
631
+ - tabular_numeric_binned: Binned numeric variables
632
+
607
633
  Args:
608
634
  X: Input samples. Can be array-like or pd.DataFrame of shape (n_samples, n_features).
609
635
  target: Name of the target column to predict. If None, uses the target column from fit().
@@ -612,8 +638,15 @@ class TabularARGN(BaseEstimator):
612
638
  **kwargs: Additional arguments passed to sample() method.
613
639
 
614
640
  Returns:
615
- Predicted class probabilities as np.ndarray of shape (n_samples, n_classes).
616
- Each row sums to 1.0.
641
+ pd.DataFrame with shape (n_samples, n_classes) where columns are named by class labels.
642
+ Column names are derived from encoding stats:
643
+ - Binned: Special tokens (e.g., "<<NULL>>"), min value, bin ranges (e.g., "<10000", ">=50000"), and max value (e.g., "0", ..., "99999")
644
+ - Categorical: All category names including special tokens (e.g., "_RARE_", "<<NULL>>", "male", "female")
645
+ - Discrete: All numeric values including special tokens (e.g., "_RARE_", "<<NULL>>", "1", "2", "3")
646
+ Each row contains probability distribution that sums to 1.0.
647
+
648
+ Raises:
649
+ ValueError: If target column has unsupported encoding type.
617
650
  """
618
651
  if not self._fitted:
619
652
  raise ValueError("Model must be fitted before prediction. Call fit() first.")
@@ -625,33 +658,100 @@ class TabularARGN(BaseEstimator):
625
658
  "Target column must be specified for prediction. Provide 'target' parameter or fit with y."
626
659
  )
627
660
 
661
+ # Get target column stats and validate encoding type
662
+ columns = self._get_column_stats()
663
+ if target_column not in columns:
664
+ raise ValueError(f"Target column '{target_column}' not found in model statistics")
665
+
666
+ target_stats = columns[target_column]
667
+ encoding_type = target_stats.get("encoding_type")
668
+ if not encoding_type:
669
+ raise ValueError(f"Target column '{target_column}' has no encoding type")
670
+
628
671
  X_df = ensure_dataframe(X, columns=self._feature_names)
629
672
 
630
673
  # Exclude target column from seed if present
631
674
  if target_column in X_df.columns:
632
675
  X_df = X_df.drop(columns=[target_column])
633
676
 
634
- # Generate predictions across multiple draws
635
- all_predictions = []
636
- for _ in range(n_draws):
637
- samples = self.sample(seed_data=X_df, ctx_data=ctx_data, **kwargs)
638
- if target_column in samples.columns:
639
- all_predictions.append(samples[target_column].values)
640
- else:
641
- raise ValueError(f"Target column '{target_column}' not found in generated samples")
642
-
643
- # Stack predictions
644
- predictions_array = np.column_stack(all_predictions)
677
+ # Repeat X_df n_draws times for batch sampling
678
+ n_samples = len(X_df)
679
+ X_df_repeated = pd.concat([X_df] * n_draws, ignore_index=True)
680
+
681
+ # Generate all samples in a single batch
682
+ samples = self.sample(seed_data=X_df_repeated, ctx_data=ctx_data, **kwargs)
683
+ if target_column not in samples.columns:
684
+ raise ValueError(f"Target column '{target_column}' not found in generated samples")
685
+
686
+ # Re-encode the sampled values using the same encoding logic as the generator
687
+ sampled_values = samples[target_column]
688
+
689
+ if encoding_type == ModelEncodingType.tabular_numeric_binned:
690
+ encoded_df = encode_numeric(sampled_values, target_stats)
691
+ codes = encoded_df["bin"].values
692
+ elif encoding_type == ModelEncodingType.tabular_numeric_discrete:
693
+ encoded_df = encode_numeric(sampled_values, target_stats)
694
+ codes = encoded_df["cat"].values
695
+ elif encoding_type == ModelEncodingType.tabular_categorical:
696
+ encoded_df = encode_categorical(sampled_values, target_stats)
697
+ codes = encoded_df["cat"].values
698
+ else:
699
+ raise ValueError(
700
+ f"Target column '{target_column}' has unsupported encoding type '{encoding_type}'. "
701
+ f"Only categorical, discrete numeric, and binned numeric columns are supported."
702
+ )
645
703
 
646
- # Get unique classes and compute probabilities
647
- classes = np.unique(predictions_array)
648
- n_samples = predictions_array.shape[0]
649
- n_classes = len(classes)
704
+ # Reshape codes to (n_samples, n_draws)
705
+ codes_array = codes.reshape(n_samples, n_draws)
706
+
707
+ # Build class labels with their corresponding code values
708
+ codes = target_stats["codes"]
709
+ class_labels = []
710
+ class_code_values = []
711
+
712
+ # For binned: replace <<MIN>>/<<MAX>> tokens with actual bin values and reorder
713
+ if encoding_type == ModelEncodingType.tabular_numeric_binned:
714
+ bins = target_stats["bins"]
715
+ min_code_value = None
716
+ max_code_value = None
717
+
718
+ # First add special tokens (except <<UNK>>, <<MIN>>, <<MAX>>)
719
+ for code_name, code_value in codes.items():
720
+ if code_name == NUMERIC_BINNED_UNKNOWN_TOKEN:
721
+ continue # Skip <<UNK>>
722
+ elif code_name == NUMERIC_BINNED_MIN_TOKEN:
723
+ min_code_value = code_value # Save for later
724
+ elif code_name == NUMERIC_BINNED_MAX_TOKEN:
725
+ max_code_value = code_value # Save for later
726
+ else:
727
+ class_labels.append(code_name)
728
+ class_code_values.append(code_value)
729
+
730
+ # Add MIN value if present
731
+ if min_code_value is not None:
732
+ class_labels.append(str(bins[0]))
733
+ class_code_values.append(min_code_value)
734
+
735
+ # Add bin range labels
736
+ codes_bin_offset = len(codes)
737
+ for i in range(len(bins) - 1):
738
+ lower_bound = bins[i]
739
+ upper_bound = bins[i + 1]
740
+ label = f">={lower_bound}" if i == len(bins) - 2 else f"<{upper_bound}"
741
+ class_labels.append(label)
742
+ class_code_values.append(i + codes_bin_offset)
743
+
744
+ # Add MAX value at the end if present
745
+ if max_code_value is not None:
746
+ class_labels.append(str(bins[-1]))
747
+ class_code_values.append(max_code_value)
748
+ else:
749
+ # For discrete and categorical: use code names as-is
750
+ class_labels = list(codes.keys())
751
+ class_code_values = list(codes.values())
650
752
 
651
- # Compute probability for each class
652
- proba = np.zeros((n_samples, n_classes))
653
- for i, cls in enumerate(classes):
654
- proba[:, i] = np.mean(predictions_array == cls, axis=1)
753
+ # Calculate probabilities for all classes at once
754
+ proba_list = [np.mean(codes_array == code_value, axis=1) for code_value in class_code_values]
755
+ proba = np.column_stack(proba_list)
655
756
 
656
- self.classes_ = classes
657
- return proba
757
+ return pd.DataFrame(proba, columns=class_labels)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mostlyai-engine"
3
- version = "2.0.1"
3
+ version = "2.1.0"
4
4
  description = "Synthetic Data Engine"
5
5
  authors = [{ name = "MOSTLY AI", email = "dev@mostly.ai" }]
6
6
  requires-python = ">=3.10"
File without changes