pythios 0.1.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.
pythios/__init__.py ADDED
@@ -0,0 +1,78 @@
1
+ """
2
+ Pythios — The oracle for your data.
3
+
4
+ Low-code machine learning for everyone.
5
+ pip install pythios | pythios.xyz
6
+
7
+ Classes:
8
+ Data — load, inspect, split
9
+ Preprocess — scale, encode, impute, select
10
+ Visualize — distributions, correlations, missing
11
+ Models — compare, train, tune, save
12
+ Evaluate — metrics, SHAP, ROC, cross-validate
13
+ Unsupervised — cluster, reduce dimensions
14
+ Embeddings — text and image vectors, similarity search
15
+ Tools — utilities, profiling, balancing, outliers
16
+ """
17
+
18
+ from .data import Data
19
+ from .tools import Tools
20
+
21
+ # These are imported lazily so missing files don't break the base install.
22
+ # As you build each class, they will automatically become available.
23
+
24
+ try:
25
+ from .preprocess import Preprocess
26
+ except ImportError:
27
+ pass
28
+
29
+ try:
30
+ from .visualize import Visualize
31
+ except ImportError:
32
+ pass
33
+
34
+ try:
35
+ from .models import Models
36
+ except ImportError:
37
+ pass
38
+
39
+ try:
40
+ from .evaluate import Evaluate
41
+ except ImportError:
42
+ pass
43
+
44
+ try:
45
+ from .unsupervised import Unsupervised
46
+ except ImportError:
47
+ pass
48
+
49
+ try:
50
+ from .embeddings import Embeddings
51
+ except ImportError:
52
+ pass
53
+
54
+
55
+ __version__ = "0.1.0"
56
+ __author__ = "Advaith"
57
+ __all__ = [
58
+ "Data",
59
+ "Preprocess",
60
+ "Visualize",
61
+ "Models",
62
+ "Evaluate",
63
+ "Unsupervised",
64
+ "Embeddings",
65
+ "Tools",
66
+ ]
67
+
68
+
69
+ def __getattr__(name):
70
+ """Friendly error when accessing a class that hasn't been built yet."""
71
+ available = ["Data", "Tools"]
72
+ if name in __all__ and name not in available:
73
+ raise ImportError(
74
+ f"'{name}' hasn't been built yet.\n"
75
+ f"Currently available: {available}\n"
76
+ f"Check the build order in your Obsidian vault."
77
+ )
78
+ raise AttributeError(f"module 'pythios' has no attribute '{name}'")
pythios/_utils.py ADDED
@@ -0,0 +1,155 @@
1
+ """Shared utilities for Pythios library."""
2
+
3
+ import importlib
4
+
5
+
6
+ def lazy_import(module_name: str, install_name: str = None):
7
+ """
8
+ Import a module only when needed, with helpful error if missing.
9
+
10
+ Args:
11
+ module_name: Name of module to import (e.g. "torch", "xgboost")
12
+ install_name: Package name for pip install. Defaults to module_name.
13
+
14
+ Returns:
15
+ The imported module object.
16
+
17
+ Raises:
18
+ ImportError: If module is not installed, with clear install instructions.
19
+ """
20
+ try:
21
+ return importlib.import_module(module_name)
22
+ except ImportError:
23
+ pkg = install_name or module_name
24
+ raise ImportError(
25
+ f"'{module_name}' is required for this feature.\n"
26
+ f"Install it with: pip install {pkg}\n"
27
+ f"Or install everything: pip install pythios[full]"
28
+ ) from None
29
+
30
+
31
+ def detect_task_type(target_series) -> str:
32
+ """
33
+ Automatically detect whether the task is binary classification,
34
+ multiclass classification, or regression.
35
+
36
+ Logic:
37
+ - String/object target → classification
38
+ - 2 unique values → binary
39
+ - 3+ unique values → multiclass
40
+ - Integer target with few unique values → classification
41
+ - 2 unique values → binary (e.g. 0/1 encoded labels)
42
+ - 3-20 unique values → multiclass
43
+ - Float target OR integer with many unique values → regression
44
+ """
45
+ if target_series is None or len(target_series) == 0:
46
+ return None
47
+
48
+ n_unique = target_series.nunique()
49
+ is_categorical = (
50
+ target_series.dtype == "object"
51
+ or target_series.dtype.name == "category"
52
+ )
53
+ is_float = target_series.dtype.kind == "f"
54
+
55
+ if is_categorical:
56
+ return "binary" if n_unique == 2 else "multiclass"
57
+
58
+ elif is_float:
59
+ return "regression"
60
+
61
+ else:
62
+ unique_vals = sorted(target_series.dropna().unique())
63
+ if not unique_vals:
64
+ return None
65
+ min_val, max_val = unique_vals[0], unique_vals[-1]
66
+ is_contiguous = max_val == min_val + n_unique - 1
67
+ is_small = n_unique <= 20 and max_val <= 100
68
+ if is_contiguous and is_small:
69
+ return "binary" if n_unique == 2 else "multiclass"
70
+ return "regression"
71
+
72
+ def format_table(headers: list, rows: list, title: str = None):
73
+ """
74
+ Pretty-print a formatted table to the console.
75
+
76
+ Args:
77
+ headers: List of column header strings
78
+ rows: List of rows, where each row is a list of values
79
+ title: Optional title printed above the table
80
+ """
81
+ if not rows:
82
+ print("(empty table)")
83
+ return
84
+
85
+ # Calculate column widths from headers and row values
86
+ widths = [len(str(h)) for h in headers]
87
+ for row in rows:
88
+ for i, val in enumerate(row):
89
+ if i < len(widths):
90
+ widths[i] = max(widths[i], len(str(val)))
91
+
92
+ divider = " ".join("─" * w for w in widths)
93
+
94
+ if title:
95
+ print(f"\n{title}")
96
+
97
+ print(divider)
98
+ print(" ".join(f"{str(h):<{w}}" for h, w in zip(headers, widths)))
99
+ print(divider)
100
+
101
+ for row in rows:
102
+ print(" ".join(f"{str(v):<{w}}" for v, w in zip(row, widths)))
103
+
104
+ print()
105
+
106
+
107
+ METRICS = {
108
+ "binary": ["accuracy", "f1", "recall", "precision", "auc_roc"],
109
+ "multiclass": ["accuracy", "f1", "recall", "precision"],
110
+ "regression": ["r2", "rmse", "mae"],
111
+ }
112
+
113
+ DEFAULT_METRIC = {
114
+ "binary": "accuracy",
115
+ "multiclass": "accuracy",
116
+ "regression": "r2",
117
+ }
118
+
119
+
120
+ def validate_metric(metric: str, task_type: str) -> str:
121
+ valid = METRICS.get(task_type, [])
122
+ if metric not in valid:
123
+ raise ValueError(
124
+ f"Metric '{metric}' is not valid for task '{task_type}'.\n"
125
+ f"Valid metrics for {task_type}: {valid}"
126
+ )
127
+ return metric
128
+
129
+
130
+ def compute_metric(metric: str, model, X, y_true, task_type: str) -> float:
131
+ import numpy as np
132
+ from sklearn import metrics as skmetrics
133
+ y_pred = model.predict(X)
134
+ if metric == "accuracy":
135
+ return skmetrics.accuracy_score(y_true, y_pred)
136
+ if metric in {"f1", "recall", "precision"}:
137
+ average = "binary" if task_type == "binary" and set(np.unique(y_true)).issubset({0, 1}) else "macro"
138
+ fn = getattr(skmetrics, f"{metric}_score")
139
+ return fn(y_true, y_pred, average=average, zero_division=0)
140
+ if metric == "auc_roc":
141
+ if hasattr(model, "predict_proba"):
142
+ y_prob = model.predict_proba(X)
143
+ if task_type == "binary":
144
+ return skmetrics.roc_auc_score(y_true, y_prob[:, 1])
145
+ return skmetrics.roc_auc_score(y_true, y_prob, multi_class="ovr", average="macro")
146
+ if hasattr(model, "decision_function"):
147
+ return skmetrics.roc_auc_score(y_true, model.decision_function(X))
148
+ return skmetrics.accuracy_score(y_true, y_pred)
149
+ if metric == "r2":
150
+ return skmetrics.r2_score(y_true, y_pred)
151
+ if metric == "rmse":
152
+ return -np.sqrt(skmetrics.mean_squared_error(y_true, y_pred))
153
+ if metric == "mae":
154
+ return -skmetrics.mean_absolute_error(y_true, y_pred)
155
+ raise ValueError(f"Unknown metric: '{metric}'")