smartml-assistant 2.0.0__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.
smartml/__init__.py ADDED
@@ -0,0 +1,576 @@
1
+ """
2
+ SmartML — Production-grade AutoML engine with a conversational Assistant.
3
+
4
+ Quick start
5
+ -----------
6
+ **One-line auto-pilot:**
7
+
8
+ import smartml as sml
9
+
10
+ result = sml.auto(df, target="Price")
11
+ result.predict(new_df)
12
+ result.report()
13
+ result.explain()
14
+
15
+ **Task-specific shortcuts:**
16
+
17
+ sml.regression(df, target="SalePrice")
18
+ sml.classification(df, target="Species")
19
+ sml.clustering(df)
20
+
21
+ **Data loading:**
22
+
23
+ df = sml.load("data/train.csv")
24
+ df = sml.load("report.parquet")
25
+
26
+ **Conversational assistant:**
27
+
28
+ assistant = sml.Assistant(df, target="Price")
29
+ assistant.ask("Clean my dataset")
30
+ assistant.ask("Train the best regression model")
31
+ assistant.ask("Why did you choose this algorithm?")
32
+ assistant.ask("Show feature importance")
33
+ assistant.ask("Generate a report")
34
+ assistant.ask("Predict on this CSV file", file_path="new_data.csv")
35
+ """
36
+
37
+ from __future__ import annotations
38
+
39
+ import logging
40
+ from typing import Any, Optional
41
+
42
+ import pandas as pd
43
+
44
+ from .assistant import Assistant, Session
45
+ from .core import (
46
+ analyzer,
47
+ preprocessor,
48
+ trainer as trainer_mod,
49
+ explainer,
50
+ visualizer,
51
+ reporter,
52
+ persistence,
53
+ feature_engineer,
54
+ feature_selector,
55
+ loaders,
56
+ clustering as clustering_mod,
57
+ anomaly,
58
+ semi_supervised as semi_supervised_mod,
59
+ reinforcement as reinforcement_mod,
60
+ self_supervised as self_supervised_mod,
61
+ forecaster as forecaster_mod,
62
+ reducer as reducer_mod,
63
+ stats as stats_mod,
64
+ wrangler as wrangler_mod,
65
+ )
66
+
67
+ __version__ = "2.0.0"
68
+ __author__ = "SmartML Contributors"
69
+ __license__ = "MIT"
70
+
71
+ # Set up a library-level logger that is silent by default (NullHandler).
72
+ # Callers can configure logging at the application level.
73
+ logging.getLogger("smartml").addHandler(logging.NullHandler())
74
+
75
+ __all__ = [
76
+ # High-level API for all ML Paradigms
77
+ "auto",
78
+ "regression",
79
+ "classification",
80
+ "clustering",
81
+ "clustering_pipeline",
82
+ "semi_supervised",
83
+ "reinforcement",
84
+ "self_supervised",
85
+ "forecast",
86
+ "load",
87
+ # Core classes
88
+ "Assistant",
89
+ "Session",
90
+ "SmartMLResult",
91
+ # Submodules
92
+ "analyzer",
93
+ "preprocessor",
94
+ "feature_engineer",
95
+ "feature_selector",
96
+ "trainer_mod",
97
+ "explainer",
98
+ "visualizer",
99
+ "reporter",
100
+ "persistence",
101
+ "loaders",
102
+ "clustering_mod",
103
+ "anomaly",
104
+ "semi_supervised_mod",
105
+ "reinforcement_mod",
106
+ "self_supervised_mod",
107
+ "forecaster_mod",
108
+ "reducer_mod",
109
+ "stats_mod",
110
+ "wrangler_mod",
111
+ ]
112
+
113
+
114
+ # =========================================================================== #
115
+ # SmartMLResult — unified result object returned by high-level functions
116
+ # =========================================================================== #
117
+
118
+ class SmartMLResult:
119
+ """Encapsulates a completed AutoML pipeline run.
120
+
121
+ Returned by :func:`auto`, :func:`regression`, :func:`classification`, and
122
+ :func:`clustering_pipeline`. Provides a consistent interface for
123
+ prediction, persistence, reporting, and explanation.
124
+
125
+ Attributes:
126
+ task: ML task type (``"classification"``, ``"regression"``,
127
+ ``"clustering"``, etc.).
128
+ training_result: :class:`~smartml.core.trainer.TrainingResult` (None
129
+ for clustering).
130
+ cleaning_result: :class:`~smartml.core.preprocessor.CleaningResult`.
131
+ dataset_report: :class:`~smartml.core.analyzer.DatasetReport`.
132
+ clustering_result: :class:`~smartml.core.clustering.ClusteringResult`
133
+ (None for supervised tasks).
134
+ output_dir: Directory where reports and plots are written.
135
+ target: Target column name (None for unsupervised tasks).
136
+ """
137
+
138
+ def __init__(
139
+ self,
140
+ task: str,
141
+ training_result: Optional[Any] = None,
142
+ cleaning_result: Optional[Any] = None,
143
+ dataset_report: Optional[Any] = None,
144
+ clustering_result: Optional[Any] = None,
145
+ output_dir: str = "smartml_output",
146
+ target: Optional[str] = None,
147
+ ):
148
+ self.task = task
149
+ self.training_result = training_result
150
+ self.cleaning_result = cleaning_result
151
+ self.dataset_report = dataset_report
152
+ self.clustering_result = clustering_result
153
+ self.output_dir = output_dir
154
+ self.target = target
155
+
156
+ # ------------------------------------------------------------------ #
157
+ # Prediction
158
+ # ------------------------------------------------------------------ #
159
+
160
+ def predict(self, data: pd.DataFrame) -> pd.Series:
161
+ """Generate predictions for new data.
162
+
163
+ The same preprocessing pipeline (imputation, encoding, scaling)
164
+ that was applied during training is applied here, ensuring
165
+ consistent predictions on unseen data.
166
+
167
+ Args:
168
+ data: New feature DataFrame (must contain the same columns as
169
+ the training data, minus the target column).
170
+
171
+ Returns:
172
+ Series of predictions indexed to match *data*.
173
+
174
+ Raises:
175
+ RuntimeError: If the result is from a clustering pipeline
176
+ (use ``clustering_result.labels`` instead).
177
+ """
178
+ if self.training_result is None:
179
+ raise RuntimeError(
180
+ "No trained model available. For clustering results, use "
181
+ ".clustering_result.labels instead."
182
+ )
183
+ assert self.cleaning_result is not None
184
+ X_new = preprocessor.Preprocessor.transform_new(data, self.cleaning_result)
185
+ preds = self.training_result.best_model.predict(X_new)
186
+ return pd.Series(preds, index=data.index, name="prediction")
187
+
188
+ # ------------------------------------------------------------------ #
189
+ # Save / Load
190
+ # ------------------------------------------------------------------ #
191
+
192
+ def save(self, path: str = "smartml_model") -> str:
193
+ """Save the trained model bundle to disk.
194
+
195
+ Args:
196
+ path: Destination path (without extension; ``.joblib`` is added).
197
+
198
+ Returns:
199
+ Full path of the saved file.
200
+
201
+ Raises:
202
+ RuntimeError: If no trained model is available.
203
+ """
204
+ if self.training_result is None:
205
+ raise RuntimeError("No trained model to save.")
206
+ tr = self.training_result
207
+ return persistence.save_model(
208
+ path=path,
209
+ model=tr.best_model,
210
+ feature_names=tr.feature_names,
211
+ target=self.target or "",
212
+ task=tr.task,
213
+ encoders=self.cleaning_result.encoders if self.cleaning_result else {},
214
+ scaler=self.cleaning_result.scaler if self.cleaning_result else None,
215
+ model_name=tr.best_name,
216
+ impute_values=self.cleaning_result.impute_values if self.cleaning_result else {},
217
+ )
218
+
219
+ # ------------------------------------------------------------------ #
220
+ # Explanation
221
+ # ------------------------------------------------------------------ #
222
+
223
+ def explain(self) -> str:
224
+ """Return a natural-language feature importance explanation.
225
+
226
+ Returns:
227
+ Explanation string from the explainer module.
228
+
229
+ Raises:
230
+ RuntimeError: If no trained model is available.
231
+ """
232
+ if self.training_result is None:
233
+ raise RuntimeError("No trained model to explain.")
234
+ tr = self.training_result
235
+ imp = explainer.explain_feature_importance(
236
+ model=tr.best_model,
237
+ X_test=tr.X_test,
238
+ y_test=tr.y_test,
239
+ feature_names=tr.feature_names,
240
+ task=tr.task,
241
+ )
242
+ return imp.narrative
243
+
244
+ # ------------------------------------------------------------------ #
245
+ # Reporting
246
+ # ------------------------------------------------------------------ #
247
+
248
+ def report(self, fmt: str = "html") -> str:
249
+ """Generate an HTML or Markdown report.
250
+
251
+ Args:
252
+ fmt: ``"html"`` or ``"markdown"`` (``"md"`` also accepted).
253
+
254
+ Returns:
255
+ Path to the generated report file.
256
+ """
257
+ import os
258
+
259
+ # Build a minimal session-like object the reporter understands
260
+ class _FakeSession:
261
+ def __init__(self, result: "SmartMLResult"):
262
+ self.dataset_report = result.dataset_report
263
+ self.cleaning_result = result.cleaning_result
264
+ self.training_result = result.training_result
265
+ self.target = result.target
266
+ self.visualizations: dict = {}
267
+ self.importance_result = None
268
+
269
+ session = _FakeSession(self)
270
+
271
+ os.makedirs(self.output_dir, exist_ok=True)
272
+ if fmt in ("markdown", "md"):
273
+ path = os.path.join(self.output_dir, "report.md")
274
+ return reporter.generate_markdown_report(session, path)
275
+ path = os.path.join(self.output_dir, "report.html")
276
+ return reporter.generate_html_report(session, path)
277
+
278
+ # ------------------------------------------------------------------ #
279
+ # String representation
280
+ # ------------------------------------------------------------------ #
281
+
282
+ @property
283
+ def summary(self) -> str:
284
+ """Executive summary and leaderboard of the completed run."""
285
+ if self.training_result:
286
+ tr = self.training_result
287
+ lines = [
288
+ f"SmartML {self.task.capitalize()} Result:",
289
+ f" Best Model: {tr.best_name}",
290
+ f" Summary: {tr.best_reason}",
291
+ f" Leaderboard:"
292
+ ]
293
+ for i, entry in enumerate(tr.leaderboard, 1):
294
+ marker = " <- best" if i == 1 else ""
295
+ metrics_str = ", ".join(f"{k}={v:.4f}" for k, v in list(entry.test_metrics.items())[:3])
296
+ lines.append(f" {i}. {entry.name}: CV={entry.cv_score:.4f}+/-{entry.cv_std:.4f} | {metrics_str}{marker}")
297
+ return "\n".join(lines)
298
+ if self.clustering_result:
299
+ cr = self.clustering_result
300
+ return f"SmartML Clustering Result:\n Algorithm: {cr.algorithm}\n Clusters: {cr.n_clusters}\n Silhouette: {cr.silhouette:.4f}"
301
+ return f"SmartML Result (task={self.task!r})"
302
+
303
+ def __repr__(self) -> str: # noqa: D105
304
+ if self.training_result:
305
+ return (
306
+ f"SmartMLResult(task={self.task!r}, "
307
+ f"best_model={self.training_result.best_name!r}, "
308
+ f"cv_score={self.training_result.leaderboard[0].cv_score:.4f})"
309
+ )
310
+ if self.clustering_result:
311
+ return (
312
+ f"SmartMLResult(task='clustering', "
313
+ f"algorithm={self.clustering_result.algorithm!r}, "
314
+ f"n_clusters={self.clustering_result.n_clusters})"
315
+ )
316
+ return f"SmartMLResult(task={self.task!r})"
317
+
318
+
319
+ # =========================================================================== #
320
+ # Top-level convenience functions
321
+ # =========================================================================== #
322
+
323
+ def auto(
324
+ data: pd.DataFrame,
325
+ target: str,
326
+ output_dir: str = "smartml_output",
327
+ task: Optional[str] = None,
328
+ ) -> SmartMLResult:
329
+ """Full AutoML pipeline: detect task → clean → train → return result.
330
+
331
+ Args:
332
+ data: Input DataFrame.
333
+ target: Name of the target column.
334
+ output_dir: Directory for reports and saved models.
335
+ task: Force a specific task (``"classification"`` or
336
+ ``"regression"``). Auto-detected if None.
337
+
338
+ Returns:
339
+ :class:`SmartMLResult` ready for ``.predict()``, ``.save()``,
340
+ ``.explain()``, and ``.report()``.
341
+
342
+ Example::
343
+
344
+ import smartml as sml
345
+ from sklearn.datasets import load_iris
346
+ import pandas as pd
347
+
348
+ df = load_iris(as_frame=True).frame
349
+ result = sml.auto(df, target="target")
350
+ print(result)
351
+ """
352
+ logger = logging.getLogger("smartml")
353
+ logger.info("SmartML.auto: analysing dataset (%d rows, %d cols)", *data.shape)
354
+
355
+ report = analyzer.analyze(data, target=target)
356
+ detected_task = task or report.task or "classification"
357
+
358
+ logger.info("Task: %s — %s", detected_task, report.task_reason)
359
+
360
+ pre = preprocessor.Preprocessor()
361
+ cleaning = pre.clean(data, target=target)
362
+ logger.info("Cleaning: %d step(s) applied.", len(cleaning.steps))
363
+
364
+ X = cleaning.df.drop(columns=[target], errors="ignore")
365
+ y = cleaning.df[target]
366
+
367
+ tr = trainer_mod.Trainer(task=detected_task)
368
+ training = tr.run(X, y)
369
+ logger.info(
370
+ "Training complete. Best model: %s (CV=%.4f)",
371
+ training.best_name,
372
+ training.leaderboard[0].cv_score,
373
+ )
374
+
375
+ return SmartMLResult(
376
+ task=detected_task,
377
+ training_result=training,
378
+ cleaning_result=cleaning,
379
+ dataset_report=report,
380
+ output_dir=output_dir,
381
+ target=target,
382
+ )
383
+
384
+
385
+ def regression(
386
+ data: pd.DataFrame,
387
+ target: str,
388
+ output_dir: str = "smartml_output",
389
+ ) -> SmartMLResult:
390
+ """Run a forced regression pipeline.
391
+
392
+ Equivalent to ``auto(data, target, task="regression")``.
393
+
394
+ Args:
395
+ data: Input DataFrame.
396
+ target: Name of the continuous target column.
397
+ output_dir: Directory for outputs.
398
+
399
+ Returns:
400
+ :class:`SmartMLResult`.
401
+ """
402
+ return auto(data, target=target, output_dir=output_dir, task="regression")
403
+
404
+
405
+ def classification(
406
+ data: pd.DataFrame,
407
+ target: str,
408
+ output_dir: str = "smartml_output",
409
+ ) -> SmartMLResult:
410
+ """Run a forced classification pipeline.
411
+
412
+ Equivalent to ``auto(data, target, task="classification")``.
413
+
414
+ Args:
415
+ data: Input DataFrame.
416
+ target: Name of the categorical/label target column.
417
+ output_dir: Directory for outputs.
418
+
419
+ Returns:
420
+ :class:`SmartMLResult`.
421
+ """
422
+ return auto(data, target=target, output_dir=output_dir, task="classification")
423
+
424
+
425
+ def clustering_pipeline(
426
+ data: pd.DataFrame,
427
+ algorithm: str = "auto",
428
+ n_clusters: Optional[int] = None,
429
+ output_dir: str = "smartml_output",
430
+ ) -> SmartMLResult:
431
+ """Run an unsupervised clustering pipeline.
432
+
433
+ Args:
434
+ data: Input DataFrame (all columns are used as features).
435
+ algorithm: Clustering algorithm name (``"auto"``, ``"kmeans"``,
436
+ ``"dbscan"``, ``"agglomerative"``, ``"gmm"``).
437
+ n_clusters: Number of clusters. Auto-selected if None.
438
+ output_dir: Directory for outputs.
439
+
440
+ Returns:
441
+ :class:`SmartMLResult` with a populated ``clustering_result``.
442
+ """
443
+ logger = logging.getLogger("smartml")
444
+ logger.info("SmartML.clustering: dataset (%d rows, %d cols)", *data.shape)
445
+
446
+ ct = clustering_mod.ClusteringTrainer(
447
+ algorithm=algorithm, n_clusters=n_clusters
448
+ )
449
+ result = ct.run(data)
450
+ logger.info(
451
+ "Clustering complete: %s found %d clusters (silhouette=%.3f).",
452
+ result.algorithm, result.n_clusters, result.silhouette,
453
+ )
454
+
455
+ return SmartMLResult(
456
+ task="clustering",
457
+ clustering_result=result,
458
+ output_dir=output_dir,
459
+ )
460
+
461
+
462
+ def load(path: str, **kwargs: Any) -> pd.DataFrame:
463
+ """Load a dataset from a file (CSV, Excel, JSON, Parquet, Feather, …).
464
+
465
+ This is a thin wrapper around :func:`smartml.core.loaders.load` exposed
466
+ at the package level for convenience.
467
+
468
+ Args:
469
+ path: Path to the data file.
470
+ **kwargs: Forwarded to the underlying pandas reader.
471
+
472
+ Returns:
473
+ Loaded DataFrame.
474
+
475
+ Example::
476
+
477
+ import smartml as sml
478
+
479
+ df = sml.load("data/titanic.csv")
480
+ df = sml.load("sales_2024.parquet")
481
+ """
482
+ return loaders.load(path, **kwargs)
483
+
484
+
485
+ def clustering(
486
+ data: pd.DataFrame,
487
+ algorithm: str = "auto",
488
+ n_clusters: Optional[int] = None,
489
+ output_dir: str = "smartml_output",
490
+ ) -> SmartMLResult:
491
+ """Run an unsupervised clustering pipeline (alias of clustering_pipeline)."""
492
+ return clustering_pipeline(data, algorithm=algorithm, n_clusters=n_clusters, output_dir=output_dir)
493
+
494
+
495
+ def semi_supervised(
496
+ data: pd.DataFrame,
497
+ target: str,
498
+ algorithm: str = "auto",
499
+ unlabeled_identifier: int = -1,
500
+ random_state: int = 42,
501
+ ) -> semi_supervised_mod.SemiSupervisedResult:
502
+ """Run semi-supervised learning on partially labeled tabular data.
503
+
504
+ Args:
505
+ data: Input DataFrame.
506
+ target: Target column containing labeled entries and unlabeled marks (-1 or NaN).
507
+ algorithm: 'self_training', 'label_spreading', 'label_propagation', or 'auto'.
508
+ unlabeled_identifier: Marker for unlabeled rows (default -1).
509
+ random_state: Seed for reproducibility.
510
+ """
511
+ if target not in data.columns:
512
+ raise KeyError(f"Target column '{target}' not found.")
513
+ X = data.drop(columns=[target], errors="ignore")
514
+ y = data[target]
515
+ trainer = semi_supervised_mod.SemiSupervisedTrainer(
516
+ algorithm=algorithm, unlabeled_identifier=unlabeled_identifier, random_state=random_state
517
+ )
518
+ return trainer.fit(X, y)
519
+
520
+
521
+ def reinforcement(
522
+ data: pd.DataFrame,
523
+ action: str,
524
+ reward: str,
525
+ context_cols: Optional[list[str]] = None,
526
+ strategy: str = "auto",
527
+ **kwargs: Any,
528
+ ) -> reinforcement_mod.BanditResult:
529
+ """Run offline tabular contextual bandit / reinforcement decision optimization.
530
+
531
+ Args:
532
+ data: Logged interaction dataset.
533
+ action: Column indicating action/arm taken.
534
+ reward: Column indicating observed outcome/reward.
535
+ context_cols: Optional list of state/feature columns for contextual bandits (LinUCB).
536
+ strategy: 'linucb', 'thompson', 'epsilon_greedy', or 'auto'.
537
+ """
538
+ opt = reinforcement_mod.TabularBanditOptimizer(strategy=strategy, **kwargs)
539
+ return opt.optimize(data, action_col=action, reward_col=reward, context_cols=context_cols)
540
+
541
+
542
+ def self_supervised(
543
+ data: pd.DataFrame,
544
+ target: Optional[str] = None,
545
+ embedding_dim: Optional[int] = None,
546
+ **kwargs: Any,
547
+ ) -> self_supervised_mod.SelfSupervisedResult:
548
+ """Extract dense tabular latent representations using Masked/Denoising Autoencoders.
549
+
550
+ Args:
551
+ data: Tabular dataset.
552
+ target: Optional target column to isolate and exclude from representations.
553
+ embedding_dim: Latent representation size (default: half of feature count, min 2).
554
+ """
555
+ learner = self_supervised_mod.TabularSelfSupervisedLearner(embedding_dim=embedding_dim, **kwargs)
556
+ return learner.fit_transform(data, target_col=target)
557
+
558
+
559
+ def forecast(
560
+ data: pd.DataFrame,
561
+ target: str,
562
+ date_col: Optional[str] = None,
563
+ horizon: int = 5,
564
+ **kwargs: Any,
565
+ ) -> forecaster_mod.ForecastResult:
566
+ """Run multi-step recursive time series forecasting with automatic lag features.
567
+
568
+ Args:
569
+ data: Time series or sequential DataFrame.
570
+ target: Numeric column to forecast.
571
+ date_col: Optional date/timestamp column. Auto-detected if omitted.
572
+ horizon: Number of future periods/steps to forecast.
573
+ """
574
+ fc = forecaster_mod.TabularForecaster(**kwargs)
575
+ return fc.forecast(data, target_col=target, date_col=date_col, horizon=horizon)
576
+