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 +78 -0
- pythios/_utils.py +155 -0
- pythios/data.py +572 -0
- pythios/embeddings.py +29 -0
- pythios/evaluate.py +76 -0
- pythios/models.py +142 -0
- pythios/preprocess.py +96 -0
- pythios/tools.py +656 -0
- pythios/unsupervised.py +58 -0
- pythios/visualize.py +48 -0
- pythios-0.1.0.dist-info/METADATA +2408 -0
- pythios-0.1.0.dist-info/RECORD +15 -0
- pythios-0.1.0.dist-info/WHEEL +5 -0
- pythios-0.1.0.dist-info/licenses/LICENCE +0 -0
- pythios-0.1.0.dist-info/top_level.txt +1 -0
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}'")
|