maten 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.
- maten/__init__.py +57 -0
- maten/core/creation.py +68 -0
- maten/core/linalg.py +78 -0
- maten/core/math_ops.py +56 -0
- maten/core/tensor.py +250 -0
- maten/frame/__init__.py +15 -0
- maten/frame/dataframe.py +287 -0
- maten/frame/groupby.py +80 -0
- maten/frame/io.py +50 -0
- maten/frame/series.py +178 -0
- maten/neural/__init__.py +32 -0
- maten/neural/autograd.py +96 -0
- maten/neural/dataloader.py +78 -0
- maten/neural/layers.py +132 -0
- maten/neural/losses.py +60 -0
- maten/neural/metrics.py +47 -0
- maten/neural/optimizers.py +114 -0
- maten/neural/sequential.py +63 -0
- maten/neural/tensor.py +157 -0
- maten/plot/__init__.py +19 -0
- maten/plot/charts.py +98 -0
- maten/plot/colors.py +21 -0
- maten/plot/figure.py +342 -0
- maten/plot/utils.py +39 -0
- maten-0.1.0.dist-info/METADATA +123 -0
- maten-0.1.0.dist-info/RECORD +29 -0
- maten-0.1.0.dist-info/WHEEL +5 -0
- maten-0.1.0.dist-info/licenses/LICENSE +21 -0
- maten-0.1.0.dist-info/top_level.txt +1 -0
maten/__init__.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""
|
|
2
|
+
maten — Pure Python scientific computing library
|
|
3
|
+
=================================================
|
|
4
|
+
Single import replaces NumPy + Pandas + Matplotlib + TensorFlow.
|
|
5
|
+
|
|
6
|
+
import maten as mt
|
|
7
|
+
|
|
8
|
+
mt.array(...) # arrays
|
|
9
|
+
mt.DataFrame(...) # dataframes
|
|
10
|
+
mt.subplots() # plots
|
|
11
|
+
mt.Sequential(...) # neural networks
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
__version__ = "0.1.0"
|
|
15
|
+
__author__ = "maten contributors"
|
|
16
|
+
|
|
17
|
+
# ── core (NumPy replacement) ──────────────────────────────────
|
|
18
|
+
from maten.core.tensor import Tensor, array, matmul
|
|
19
|
+
from maten.core.creation import (zeros, ones, full, eye, arange, linspace,
|
|
20
|
+
rand, randn, randint, concatenate, stack)
|
|
21
|
+
from maten.core.math_ops import (sqrt, exp, log, log2, log10,
|
|
22
|
+
sin, cos, tan, tanh,
|
|
23
|
+
abs_, ceil, floor,
|
|
24
|
+
sigmoid, relu, leaky_relu,
|
|
25
|
+
clip, softmax, where, unique, sort)
|
|
26
|
+
from maten.core.linalg import linalg
|
|
27
|
+
|
|
28
|
+
# ── frame (Pandas replacement) ────────────────────────────────
|
|
29
|
+
from maten.frame.series import Series
|
|
30
|
+
from maten.frame.dataframe import DataFrame
|
|
31
|
+
from maten.frame.groupby import GroupBy
|
|
32
|
+
from maten.frame.io import read_csv, read_json, concat as concat_frames
|
|
33
|
+
|
|
34
|
+
# ── plot (Matplotlib replacement) ─────────────────────────────
|
|
35
|
+
from maten.plot.figure import (Figure, Axes,
|
|
36
|
+
figure, subplots, show, savefig,
|
|
37
|
+
plot, scatter, bar, barh, hist,
|
|
38
|
+
fill_between, axhline, axvline,
|
|
39
|
+
xlabel, ylabel, title, legend, xlim, ylim)
|
|
40
|
+
from maten.plot.charts import pie, heatmap
|
|
41
|
+
|
|
42
|
+
# ── neural (TensorFlow replacement) ───────────────────────────
|
|
43
|
+
from maten.neural.autograd import Value
|
|
44
|
+
from maten.neural.sequential import Module, Sequential
|
|
45
|
+
from maten.neural.layers import (Parameter, Linear,
|
|
46
|
+
ReLU, Sigmoid, Tanh, Softmax,
|
|
47
|
+
LeakyReLU, Dropout, BatchNorm1d, Flatten)
|
|
48
|
+
from maten.neural.losses import (MSELoss, MAELoss, BCELoss,
|
|
49
|
+
CrossEntropyLoss, HuberLoss)
|
|
50
|
+
from maten.neural.optimizers import SGD, Adam, AdamW, RMSProp, Adagrad
|
|
51
|
+
from maten.neural.metrics import (accuracy, mean_squared_error,
|
|
52
|
+
mean_absolute_error, r2_score, rmse,
|
|
53
|
+
precision, recall, f1_score, confusion_matrix)
|
|
54
|
+
from maten.neural.dataloader import DataLoader, Trainer
|
|
55
|
+
|
|
56
|
+
# ── neural tensor (separate namespace to avoid clash with core)
|
|
57
|
+
from maten.neural import tensor as nn_tensor
|
maten/core/creation.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""
|
|
2
|
+
maten.core.creation — array creation functions
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import random
|
|
6
|
+
from functools import reduce
|
|
7
|
+
import operator
|
|
8
|
+
from maten.core.tensor import Tensor
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def zeros(shape, dtype=float):
|
|
12
|
+
if isinstance(shape, int): shape = (shape,)
|
|
13
|
+
size = reduce(operator.mul, shape, 1)
|
|
14
|
+
return Tensor._make([dtype(0)] * size, shape, dtype)
|
|
15
|
+
|
|
16
|
+
def ones(shape, dtype=float):
|
|
17
|
+
if isinstance(shape, int): shape = (shape,)
|
|
18
|
+
size = reduce(operator.mul, shape, 1)
|
|
19
|
+
return Tensor._make([dtype(1)] * size, shape, dtype)
|
|
20
|
+
|
|
21
|
+
def full(shape, fill_value, dtype=float):
|
|
22
|
+
if isinstance(shape, int): shape = (shape,)
|
|
23
|
+
size = reduce(operator.mul, shape, 1)
|
|
24
|
+
return Tensor._make([dtype(fill_value)] * size, shape, dtype)
|
|
25
|
+
|
|
26
|
+
def eye(n, dtype=float):
|
|
27
|
+
t = zeros((n, n), dtype=dtype)
|
|
28
|
+
for i in range(n):
|
|
29
|
+
t._data[i * n + i] = dtype(1)
|
|
30
|
+
return t
|
|
31
|
+
|
|
32
|
+
def arange(start, stop=None, step=1, dtype=float):
|
|
33
|
+
if stop is None: start, stop = 0, start
|
|
34
|
+
data, v = [], start
|
|
35
|
+
while (step > 0 and v < stop) or (step < 0 and v > stop):
|
|
36
|
+
data.append(dtype(v)); v += step
|
|
37
|
+
return Tensor._make(data, (len(data),), dtype)
|
|
38
|
+
|
|
39
|
+
def linspace(start, stop, num=50, dtype=float):
|
|
40
|
+
if num == 1:
|
|
41
|
+
return Tensor._make([dtype(start)], (1,), dtype)
|
|
42
|
+
step = (stop - start) / (num - 1)
|
|
43
|
+
data = [dtype(start + i * step) for i in range(num)]
|
|
44
|
+
return Tensor._make(data, (num,), dtype)
|
|
45
|
+
|
|
46
|
+
def rand(*shape):
|
|
47
|
+
size = reduce(operator.mul, shape, 1)
|
|
48
|
+
return Tensor._make([random.random() for _ in range(size)], shape, float)
|
|
49
|
+
|
|
50
|
+
def randn(*shape):
|
|
51
|
+
size = reduce(operator.mul, shape, 1)
|
|
52
|
+
return Tensor._make([random.gauss(0, 1) for _ in range(size)], shape, float)
|
|
53
|
+
|
|
54
|
+
def randint(low, high, shape):
|
|
55
|
+
if isinstance(shape, int): shape = (shape,)
|
|
56
|
+
size = reduce(operator.mul, shape, 1)
|
|
57
|
+
return Tensor._make([float(random.randint(low, high - 1)) for _ in range(size)], shape, float)
|
|
58
|
+
|
|
59
|
+
def concatenate(tensors, axis=0):
|
|
60
|
+
if axis == 0:
|
|
61
|
+
data = []
|
|
62
|
+
for t in tensors: data.extend(t._data)
|
|
63
|
+
new_shape = (sum(t._shape[0] for t in tensors),) + tensors[0]._shape[1:]
|
|
64
|
+
return Tensor._make(data, new_shape, tensors[0].dtype)
|
|
65
|
+
raise NotImplementedError("concatenate on axis != 0 coming soon")
|
|
66
|
+
|
|
67
|
+
def stack(tensors, axis=0):
|
|
68
|
+
return concatenate([t.expand_dims(axis) for t in tensors], axis=axis)
|
maten/core/linalg.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""
|
|
2
|
+
maten.core.linalg — linear algebra operations
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
import operator
|
|
7
|
+
from functools import reduce
|
|
8
|
+
from maten.core.tensor import Tensor, matmul
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class linalg:
|
|
12
|
+
|
|
13
|
+
@staticmethod
|
|
14
|
+
def norm(t, ord=2):
|
|
15
|
+
if ord == 2: return Tensor._make([math.sqrt(sum(x**2 for x in t._data))], (1,), float)
|
|
16
|
+
elif ord == 1: return Tensor._make([sum(abs(x) for x in t._data)], (1,), float)
|
|
17
|
+
elif ord == float('inf'): return Tensor._make([max(abs(x) for x in t._data)], (1,), float)
|
|
18
|
+
raise ValueError(f"Unsupported norm order: {ord}")
|
|
19
|
+
|
|
20
|
+
@staticmethod
|
|
21
|
+
def dot(a, b):
|
|
22
|
+
return a.dot(b)
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def matmul(a, b):
|
|
26
|
+
return matmul(a, b)
|
|
27
|
+
|
|
28
|
+
@staticmethod
|
|
29
|
+
def transpose(t):
|
|
30
|
+
return t.T
|
|
31
|
+
|
|
32
|
+
@staticmethod
|
|
33
|
+
def trace(t):
|
|
34
|
+
assert t.ndim == 2 and t._shape[0] == t._shape[1]
|
|
35
|
+
n = t._shape[0]
|
|
36
|
+
return Tensor._make([sum(t._data[i * n + i] for i in range(n))], (1,), float)
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def det(t):
|
|
40
|
+
assert t.ndim == 2 and t._shape[0] == t._shape[1]
|
|
41
|
+
n = t._shape[0]
|
|
42
|
+
from maten.core.tensor import _make_nested
|
|
43
|
+
mat = [row[:] for row in _make_nested(t._data, t._shape)]
|
|
44
|
+
det = 1.0
|
|
45
|
+
for col in range(n):
|
|
46
|
+
pivot = next((r for r in range(col, n) if abs(mat[r][col]) > 1e-12), None)
|
|
47
|
+
if pivot is None: return Tensor._make([0.0], (1,), float)
|
|
48
|
+
if pivot != col:
|
|
49
|
+
mat[col], mat[pivot] = mat[pivot], mat[col]; det *= -1
|
|
50
|
+
det *= mat[col][col]
|
|
51
|
+
for row in range(col + 1, n):
|
|
52
|
+
f = mat[row][col] / mat[col][col]
|
|
53
|
+
for k in range(col, n):
|
|
54
|
+
mat[row][k] -= f * mat[col][k]
|
|
55
|
+
return Tensor._make([det], (1,), float)
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
def inv(t):
|
|
59
|
+
assert t.ndim == 2 and t._shape[0] == t._shape[1]
|
|
60
|
+
n = t._shape[0]
|
|
61
|
+
from maten.core.tensor import _make_nested
|
|
62
|
+
mat = [row[:] for row in _make_nested(t._data, t._shape)]
|
|
63
|
+
inv = [[float(i == j) for j in range(n)] for i in range(n)]
|
|
64
|
+
for col in range(n):
|
|
65
|
+
pivot = next((r for r in range(col, n) if abs(mat[r][col]) > 1e-12), None)
|
|
66
|
+
if pivot is None: raise ValueError("Matrix is singular")
|
|
67
|
+
mat[col], mat[pivot] = mat[pivot], mat[col]
|
|
68
|
+
inv[col], inv[pivot] = inv[pivot], inv[col]
|
|
69
|
+
scale = mat[col][col]
|
|
70
|
+
mat[col] = [v / scale for v in mat[col]]
|
|
71
|
+
inv[col] = [v / scale for v in inv[col]]
|
|
72
|
+
for row in range(n):
|
|
73
|
+
if row != col:
|
|
74
|
+
f = mat[row][col]
|
|
75
|
+
mat[row] = [mat[row][k] - f * mat[col][k] for k in range(n)]
|
|
76
|
+
inv[row] = [inv[row][k] - f * inv[col][k] for k in range(n)]
|
|
77
|
+
flat = [v for row in inv for v in row]
|
|
78
|
+
return Tensor._make(flat, (n, n), float)
|
maten/core/math_ops.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""
|
|
2
|
+
maten.core.math_ops — mathematical functions
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
from maten.core.tensor import Tensor
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _apply(t, func):
|
|
10
|
+
if isinstance(t, (int, float)): return func(t)
|
|
11
|
+
return t.apply(func)
|
|
12
|
+
|
|
13
|
+
def sqrt(t): return _apply(t, math.sqrt)
|
|
14
|
+
def exp(t): return _apply(t, math.exp)
|
|
15
|
+
def log(t): return _apply(t, math.log)
|
|
16
|
+
def log2(t): return _apply(t, math.log2)
|
|
17
|
+
def log10(t): return _apply(t, math.log10)
|
|
18
|
+
def sin(t): return _apply(t, math.sin)
|
|
19
|
+
def cos(t): return _apply(t, math.cos)
|
|
20
|
+
def tan(t): return _apply(t, math.tan)
|
|
21
|
+
def tanh(t): return _apply(t, math.tanh)
|
|
22
|
+
def abs_(t): return _apply(t, abs)
|
|
23
|
+
def ceil(t): return _apply(t, math.ceil)
|
|
24
|
+
def floor(t): return _apply(t, math.floor)
|
|
25
|
+
|
|
26
|
+
def sigmoid(t):
|
|
27
|
+
return _apply(t, lambda x: 1 / (1 + math.exp(-x)))
|
|
28
|
+
|
|
29
|
+
def relu(t):
|
|
30
|
+
return _apply(t, lambda x: max(0.0, x))
|
|
31
|
+
|
|
32
|
+
def leaky_relu(t, alpha=0.01):
|
|
33
|
+
return _apply(t, lambda x: x if x > 0 else alpha * x)
|
|
34
|
+
|
|
35
|
+
def clip(t, min_val, max_val):
|
|
36
|
+
return _apply(t, lambda x: max(min_val, min(max_val, x)))
|
|
37
|
+
|
|
38
|
+
def softmax(t):
|
|
39
|
+
m = max(t._data)
|
|
40
|
+
exps = [math.exp(x - m) for x in t._data]
|
|
41
|
+
total = sum(exps)
|
|
42
|
+
return Tensor._make([e / total for e in exps], t._shape, float)
|
|
43
|
+
|
|
44
|
+
def where(condition, x, y):
|
|
45
|
+
data = [a if c else b for c, a, b in zip(condition._data, x._data, y._data)]
|
|
46
|
+
return Tensor._make(data, condition._shape, x.dtype)
|
|
47
|
+
|
|
48
|
+
def unique(t):
|
|
49
|
+
return sorted(set(t._data))
|
|
50
|
+
|
|
51
|
+
def sort(t):
|
|
52
|
+
return Tensor._make(sorted(t._data), t._shape, t.dtype)
|
|
53
|
+
|
|
54
|
+
def matmul(a, b):
|
|
55
|
+
from maten.core.tensor import matmul as _matmul
|
|
56
|
+
return _matmul(a, b)
|
maten/core/tensor.py
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"""
|
|
2
|
+
maten.core.tensor — N-dimensional Tensor class
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
import operator
|
|
7
|
+
from functools import reduce
|
|
8
|
+
from copy import deepcopy
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _flat(nested):
|
|
12
|
+
if isinstance(nested, (int, float, bool)):
|
|
13
|
+
return [float(nested)]
|
|
14
|
+
out = []
|
|
15
|
+
for item in nested:
|
|
16
|
+
out.extend(_flat(item))
|
|
17
|
+
return out
|
|
18
|
+
|
|
19
|
+
def _shape_of(nested):
|
|
20
|
+
if not isinstance(nested, list):
|
|
21
|
+
return ()
|
|
22
|
+
if not nested:
|
|
23
|
+
return (0,)
|
|
24
|
+
return (len(nested),) + _shape_of(nested[0])
|
|
25
|
+
|
|
26
|
+
def _make_nested(flat, shape):
|
|
27
|
+
if len(shape) == 1:
|
|
28
|
+
return list(flat[:shape[0]])
|
|
29
|
+
size = reduce(operator.mul, shape[1:], 1)
|
|
30
|
+
return [_make_nested(flat[i*size:(i+1)*size], shape[1:]) for i in range(shape[0])]
|
|
31
|
+
|
|
32
|
+
def _broadcast_shapes(a, b):
|
|
33
|
+
ra, rb = list(reversed(a)), list(reversed(b))
|
|
34
|
+
out = []
|
|
35
|
+
for i in range(max(len(ra), len(rb))):
|
|
36
|
+
da = ra[i] if i < len(ra) else 1
|
|
37
|
+
db = rb[i] if i < len(rb) else 1
|
|
38
|
+
if da == db: out.append(da)
|
|
39
|
+
elif da == 1: out.append(db)
|
|
40
|
+
elif db == 1: out.append(da)
|
|
41
|
+
else: raise ValueError(f"Cannot broadcast shapes {a} and {b}")
|
|
42
|
+
return tuple(reversed(out))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Tensor:
|
|
46
|
+
"""N-dimensional array — core data structure of maten."""
|
|
47
|
+
|
|
48
|
+
def __init__(self, data, dtype=float, shape=None):
|
|
49
|
+
if isinstance(data, Tensor):
|
|
50
|
+
self._data = data._data[:]
|
|
51
|
+
self._shape = data._shape
|
|
52
|
+
elif isinstance(data, list):
|
|
53
|
+
self._data = [dtype(x) for x in _flat(data)]
|
|
54
|
+
self._shape = shape if shape else _shape_of(data)
|
|
55
|
+
elif isinstance(data, (int, float)):
|
|
56
|
+
self._data = [dtype(data)]
|
|
57
|
+
self._shape = ()
|
|
58
|
+
else:
|
|
59
|
+
raise TypeError(f"Unsupported type: {type(data)}")
|
|
60
|
+
self.dtype = dtype
|
|
61
|
+
|
|
62
|
+
# ── internal factory ──────────────────────────────────────
|
|
63
|
+
@classmethod
|
|
64
|
+
def _make(cls, data, shape, dtype=float):
|
|
65
|
+
t = cls.__new__(cls)
|
|
66
|
+
t._data = data
|
|
67
|
+
t._shape = shape
|
|
68
|
+
t.dtype = dtype
|
|
69
|
+
return t
|
|
70
|
+
|
|
71
|
+
# ── properties ────────────────────────────────────────────
|
|
72
|
+
@property
|
|
73
|
+
def shape(self): return self._shape
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def ndim(self): return len(self._shape)
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def size(self): return len(self._data)
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def T(self):
|
|
83
|
+
if self.ndim != 2:
|
|
84
|
+
raise ValueError("Transpose only supports 2D tensors.")
|
|
85
|
+
r, c = self._shape
|
|
86
|
+
data = [self._data[j * c + i] for i in range(c) for j in range(r)]
|
|
87
|
+
return Tensor._make(data, (c, r), self.dtype)
|
|
88
|
+
|
|
89
|
+
# ── indexing ──────────────────────────────────────────────
|
|
90
|
+
def _flat_index(self, idx):
|
|
91
|
+
strides, s = [], 1
|
|
92
|
+
for d in reversed(self._shape):
|
|
93
|
+
strides.insert(0, s); s *= d
|
|
94
|
+
return sum(i * st for i, st in zip(idx, strides))
|
|
95
|
+
|
|
96
|
+
def __getitem__(self, idx):
|
|
97
|
+
if isinstance(idx, tuple):
|
|
98
|
+
return self._data[self._flat_index(idx)]
|
|
99
|
+
if isinstance(idx, int):
|
|
100
|
+
if self.ndim == 1:
|
|
101
|
+
return self._data[idx]
|
|
102
|
+
size = self.size // self._shape[0]
|
|
103
|
+
sub = self._data[idx * size:(idx + 1) * size]
|
|
104
|
+
return Tensor._make(sub, self._shape[1:], self.dtype)
|
|
105
|
+
raise TypeError(f"Invalid index: {idx}")
|
|
106
|
+
|
|
107
|
+
def __setitem__(self, idx, value):
|
|
108
|
+
if isinstance(idx, int) and self.ndim == 1:
|
|
109
|
+
self._data[idx] = self.dtype(value)
|
|
110
|
+
elif isinstance(idx, tuple):
|
|
111
|
+
self._data[self._flat_index(idx)] = self.dtype(value)
|
|
112
|
+
|
|
113
|
+
# ── repr ──────────────────────────────────────────────────
|
|
114
|
+
def __repr__(self):
|
|
115
|
+
nested = _make_nested(self._data, self._shape) if self._shape else self._data[0]
|
|
116
|
+
return f"Tensor({nested}, shape={self._shape}, dtype={self.dtype.__name__})"
|
|
117
|
+
|
|
118
|
+
def tolist(self):
|
|
119
|
+
return _make_nested(self._data, self._shape) if self._shape else self._data[0]
|
|
120
|
+
|
|
121
|
+
# ── broadcast ─────────────────────────────────────────────
|
|
122
|
+
def broadcast_to(self, shape):
|
|
123
|
+
if self._shape == shape:
|
|
124
|
+
return self
|
|
125
|
+
padded = (1,) * (len(shape) - len(self._shape)) + self._shape
|
|
126
|
+
repeats = [s // p for s, p in zip(shape, padded)]
|
|
127
|
+
data = self._data[:]
|
|
128
|
+
cur = list(padded)
|
|
129
|
+
for axis, rep in enumerate(repeats):
|
|
130
|
+
if rep > 1:
|
|
131
|
+
sb = reduce(operator.mul, cur[:axis], 1)
|
|
132
|
+
sa = reduce(operator.mul, cur[axis:], 1) // cur[axis]
|
|
133
|
+
nd = []
|
|
134
|
+
for i in range(sb):
|
|
135
|
+
chunk = data[i * sa * cur[axis]:(i + 1) * sa * cur[axis]]
|
|
136
|
+
for _ in range(rep):
|
|
137
|
+
nd.extend(chunk[:sa])
|
|
138
|
+
data = nd
|
|
139
|
+
cur[axis] *= rep
|
|
140
|
+
return Tensor._make(data, shape, self.dtype)
|
|
141
|
+
|
|
142
|
+
# ── element-wise ops ──────────────────────────────────────
|
|
143
|
+
def _ew(self, other, op):
|
|
144
|
+
if isinstance(other, (int, float)):
|
|
145
|
+
return Tensor._make([op(x, other) for x in self._data], self._shape, self.dtype)
|
|
146
|
+
if isinstance(other, Tensor):
|
|
147
|
+
s = _broadcast_shapes(self._shape, other._shape)
|
|
148
|
+
a = self.broadcast_to(s); b = other.broadcast_to(s)
|
|
149
|
+
return Tensor._make([op(x, y) for x, y in zip(a._data, b._data)], s, self.dtype)
|
|
150
|
+
raise TypeError(f"Unsupported: {type(other)}")
|
|
151
|
+
|
|
152
|
+
def __add__(self, o): return self._ew(o, operator.add)
|
|
153
|
+
def __radd__(self, o): return self._ew(o, operator.add)
|
|
154
|
+
def __sub__(self, o): return self._ew(o, operator.sub)
|
|
155
|
+
def __rsub__(self, o): return self._ew(o, lambda a, b: b - a)
|
|
156
|
+
def __mul__(self, o): return self._ew(o, operator.mul)
|
|
157
|
+
def __rmul__(self, o): return self._ew(o, operator.mul)
|
|
158
|
+
def __truediv__(self, o): return self._ew(o, operator.truediv)
|
|
159
|
+
def __rtruediv__(self, o): return self._ew(o, lambda a, b: b / a)
|
|
160
|
+
def __pow__(self, e): return self._ew(e, operator.pow)
|
|
161
|
+
def __neg__(self): return Tensor._make([-x for x in self._data], self._shape, self.dtype)
|
|
162
|
+
def __abs__(self): return Tensor._make([abs(x) for x in self._data], self._shape, self.dtype)
|
|
163
|
+
|
|
164
|
+
def __eq__(self, o): return self._ew(o, lambda a, b: float(a == b))
|
|
165
|
+
def __lt__(self, o): return self._ew(o, lambda a, b: float(a < b))
|
|
166
|
+
def __le__(self, o): return self._ew(o, lambda a, b: float(a <= b))
|
|
167
|
+
def __gt__(self, o): return self._ew(o, lambda a, b: float(a > b))
|
|
168
|
+
def __ge__(self, o): return self._ew(o, lambda a, b: float(a >= b))
|
|
169
|
+
|
|
170
|
+
# ── reductions ────────────────────────────────────────────
|
|
171
|
+
def sum(self, axis=None):
|
|
172
|
+
if axis is None:
|
|
173
|
+
return Tensor._make([sum(self._data)], (1,), self.dtype)
|
|
174
|
+
os = self._shape[:axis] + self._shape[axis+1:]
|
|
175
|
+
osz = reduce(operator.mul, os, 1) if os else 1
|
|
176
|
+
res = [0.0] * osz
|
|
177
|
+
sa = reduce(operator.mul, self._shape[axis+1:], 1)
|
|
178
|
+
sb = reduce(operator.mul, self._shape[:axis], 1)
|
|
179
|
+
for i in range(sb):
|
|
180
|
+
for k in range(self._shape[axis]):
|
|
181
|
+
for j in range(sa):
|
|
182
|
+
res[i * sa + j] += self._data[i * self._shape[axis] * sa + k * sa + j]
|
|
183
|
+
return Tensor._make(res, os if os else (1,), self.dtype)
|
|
184
|
+
|
|
185
|
+
def mean(self, axis=None):
|
|
186
|
+
if axis is None:
|
|
187
|
+
return Tensor._make([sum(self._data) / len(self._data)], (1,), self.dtype)
|
|
188
|
+
return self.sum(axis=axis) / self._shape[axis]
|
|
189
|
+
|
|
190
|
+
def std(self, axis=None):
|
|
191
|
+
m = self.mean(axis=axis)
|
|
192
|
+
diff = self - m
|
|
193
|
+
return ((diff * diff).mean(axis=axis)) ** 0.5
|
|
194
|
+
|
|
195
|
+
def max(self, axis=None):
|
|
196
|
+
if axis is None:
|
|
197
|
+
return Tensor._make([max(self._data)], (1,), self.dtype)
|
|
198
|
+
raise NotImplementedError("axis max coming soon")
|
|
199
|
+
|
|
200
|
+
def min(self, axis=None):
|
|
201
|
+
if axis is None:
|
|
202
|
+
return Tensor._make([min(self._data)], (1,), self.dtype)
|
|
203
|
+
raise NotImplementedError("axis min coming soon")
|
|
204
|
+
|
|
205
|
+
def argmax(self): return self._data.index(max(self._data))
|
|
206
|
+
def argmin(self): return self._data.index(min(self._data))
|
|
207
|
+
|
|
208
|
+
# ── shape ops ─────────────────────────────────────────────
|
|
209
|
+
def reshape(self, *shape):
|
|
210
|
+
s = shape[0] if len(shape) == 1 and isinstance(shape[0], tuple) else shape
|
|
211
|
+
if -1 in s:
|
|
212
|
+
known = reduce(operator.mul, [x for x in s if x != -1], 1)
|
|
213
|
+
s = tuple(self.size // known if x == -1 else x for x in s)
|
|
214
|
+
assert reduce(operator.mul, s, 1) == self.size, "Shape mismatch"
|
|
215
|
+
return Tensor._make(self._data[:], s, self.dtype)
|
|
216
|
+
|
|
217
|
+
def flatten(self): return self.reshape(self.size)
|
|
218
|
+
def squeeze(self): return self.reshape(tuple(s for s in self._shape if s != 1) or (1,))
|
|
219
|
+
def expand_dims(self, axis):
|
|
220
|
+
s = list(self._shape); s.insert(axis, 1)
|
|
221
|
+
return self.reshape(tuple(s))
|
|
222
|
+
|
|
223
|
+
# ── matrix ops ────────────────────────────────────────────
|
|
224
|
+
def dot(self, other):
|
|
225
|
+
if self.ndim == 1 and other.ndim == 1:
|
|
226
|
+
return Tensor._make([sum(a * b for a, b in zip(self._data, other._data))], (1,), self.dtype)
|
|
227
|
+
return matmul(self, other)
|
|
228
|
+
|
|
229
|
+
def __matmul__(self, other): return matmul(self, other)
|
|
230
|
+
|
|
231
|
+
def apply(self, func):
|
|
232
|
+
return Tensor._make([func(x) for x in self._data], self._shape, self.dtype)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def matmul(a, b):
|
|
236
|
+
assert a.ndim == 2 and b.ndim == 2
|
|
237
|
+
m, k = a._shape; k2, n = b._shape
|
|
238
|
+
assert k == k2, f"Shape mismatch: {k} != {k2}"
|
|
239
|
+
res = [0.0] * (m * n)
|
|
240
|
+
for i in range(m):
|
|
241
|
+
for j in range(n):
|
|
242
|
+
s = 0.0
|
|
243
|
+
for p in range(k):
|
|
244
|
+
s += a._data[i * k + p] * b._data[p * n + j]
|
|
245
|
+
res[i * n + j] = s
|
|
246
|
+
return Tensor._make(res, (m, n), float)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def array(data, dtype=float):
|
|
250
|
+
return Tensor(data, dtype=dtype)
|
maten/frame/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Pandas-like Series and DataFrame utilities."""
|
|
2
|
+
|
|
3
|
+
from maten.frame.dataframe import DataFrame
|
|
4
|
+
from maten.frame.groupby import GroupBy
|
|
5
|
+
from maten.frame.io import concat, read_csv, read_json
|
|
6
|
+
from maten.frame.series import Series
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"Series",
|
|
10
|
+
"DataFrame",
|
|
11
|
+
"GroupBy",
|
|
12
|
+
"read_csv",
|
|
13
|
+
"read_json",
|
|
14
|
+
"concat",
|
|
15
|
+
]
|