plotlibs 0.3.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.
plotlibs/ml.py ADDED
@@ -0,0 +1,205 @@
1
+ """plotlibs.ml — رسوم تعلم الآلة والتعلم العميق (تُشهر المكتبة عند ML/DL).
2
+
3
+ تعمل بـ numpy فقط — لا تحتاج sklearn/keras للرسم، لكن تقبل مخرجاتها.
4
+
5
+ import plotlibs as pl
6
+ pl.plot_history({"loss": [...], "val_loss": [...]})
7
+ pl.plot_confusion_matrix(y_true, y_pred)
8
+ pl.plot_roc(y_true, y_score)
9
+ pl.plot_feature_importance(names, values)
10
+ pl.plot_images(images) # للتعلم العميق / رؤية حاسوبية
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import numpy as np
15
+
16
+
17
+ def _plt():
18
+ from . import pyplot as plt
19
+ return plt
20
+
21
+
22
+ def plot_history(history, metrics=("loss",), savefig=None, title="training history", smooth: int = 1):
23
+ """ارسم history من Keras (dict) أو DataFrame أو dict of lists.
24
+
25
+ history = {"loss": [...], "val_loss": [...], "accuracy": [...]}
26
+ """
27
+ plt = _plt()
28
+ if hasattr(history, "history"): # keras History object
29
+ history = history.history
30
+ if hasattr(history, "to_dict"):
31
+ try:
32
+ history = history.to_dict(orient="list")
33
+ except Exception:
34
+ pass
35
+ keys = [k for k in history.keys() if not k.startswith("val_")] if isinstance(history, dict) else []
36
+ if not keys and isinstance(history, dict):
37
+ keys = list(history.keys())
38
+ if metrics == ("loss",) and keys:
39
+ # تلقائياً: كل المقاييس الموجودة
40
+ metrics = tuple(keys)
41
+ fig_axes = []
42
+ for m in metrics:
43
+ train = np.asarray(history[m], dtype=float).ravel() if m in history else None
44
+ val = np.asarray(history.get(f"val_{m}", []), dtype=float).ravel() if isinstance(history, dict) else None
45
+ if train is None:
46
+ continue
47
+ if smooth > 1 and train.size >= smooth:
48
+ train = np.convolve(train, np.ones(smooth) / smooth, mode="same")
49
+ plt.figure()
50
+ plt.plot(train, label=f"train {m}")
51
+ if val is not None and val.size:
52
+ plt.plot(val, label=f"val {m}")
53
+ plt.xlabel("epoch")
54
+ plt.ylabel(m)
55
+ plt.title(title if len(metrics) == 1 else f"{title} — {m}")
56
+ plt.legend()
57
+ plt.grid(True)
58
+ fig_axes.append(plt.gcf())
59
+ if savefig and len(metrics) == 1:
60
+ plt.savefig(savefig)
61
+ return fig_axes[0] if len(fig_axes) == 1 else fig_axes
62
+
63
+
64
+ def confusion_matrix_data(y_true, y_pred, labels=None):
65
+ y_true = np.asarray(y_true).ravel()
66
+ y_pred = np.asarray(y_pred).ravel()
67
+ n = min(y_true.size, y_pred.size)
68
+ y_true, y_pred = y_true[:n], y_pred[:n]
69
+ if labels is None:
70
+ labels = sorted(map(str, np.unique(np.concatenate([y_true, y_pred]))))
71
+ lab_to_i = {str(l): i for i, l in enumerate(labels)}
72
+ cm = np.zeros((len(labels), len(labels)), dtype=float)
73
+ for t, p in zip(y_true, y_pred):
74
+ i = lab_to_i.get(str(t))
75
+ j = lab_to_i.get(str(p))
76
+ if i is not None and j is not None:
77
+ cm[i, j] += 1
78
+ return cm, labels
79
+
80
+
81
+ def plot_confusion_matrix(y_true=None, y_pred=None, cm=None, labels=None,
82
+ normalize: bool = False, title="confusion matrix", **kw):
83
+ """مصفوفة الالتباس — تقبل (y_true, y_pred) أو مصفوفة جاهزة cm."""
84
+ plt = _plt()
85
+ if cm is None:
86
+ if y_true is None or y_pred is None:
87
+ raise ValueError("pass (y_true, y_pred) or cm=")
88
+ cm, labels = confusion_matrix_data(y_true, y_pred, labels)
89
+ else:
90
+ cm = np.asarray(cm, dtype=float)
91
+ labels = labels or [str(i) for i in range(cm.shape[0])]
92
+ disp = cm / cm.sum(axis=1, keepdims=True).clip(min=1) if normalize else cm
93
+ plt.gca().heatmap(disp, xticks=labels, yticks=labels, annot=True, cmap="viridis")
94
+ plt.title(title + (" (normalized)" if normalize else ""))
95
+ plt.xlabel("predicted")
96
+ plt.ylabel("true")
97
+ return disp
98
+
99
+
100
+ def plot_roc(y_true, y_score, label="", **kw):
101
+ """منحنى ROC + AUC (numpy فقط)."""
102
+ from .fast import roc_from_scores
103
+ plt = _plt()
104
+ fpr, tpr, auc = roc_from_scores(y_true, y_score)
105
+ plt.plot(fpr, tpr, label=f"{label} AUC={auc:.3f}" if label else f"AUC={auc:.3f}")
106
+ plt.plot([0, 1], [0, 1], linestyle="--", color="gray")
107
+ plt.xlabel("FPR")
108
+ plt.ylabel("TPR")
109
+ plt.title("ROC curve")
110
+ plt.legend()
111
+ plt.grid(True)
112
+ return fpr, tpr, auc
113
+
114
+
115
+ def plot_pr(y_true, y_score, label="", **kw):
116
+ """منحنى Precision-Recall + AP."""
117
+ from .fast import pr_from_scores
118
+ plt = _plt()
119
+ rec, prec, ap = pr_from_scores(y_true, y_score)
120
+ plt.plot(rec, prec, label=f"{label} AP={ap:.3f}" if label else f"AP={ap:.3f}")
121
+ plt.xlabel("recall")
122
+ plt.ylabel("precision")
123
+ plt.title("precision-recall curve")
124
+ plt.legend()
125
+ plt.grid(True)
126
+ return rec, prec, ap
127
+
128
+
129
+ def plot_feature_importance(names, values, top: int = 20, title="feature importance", **kw):
130
+ """أهمية الميزات (XGBoost/sklearn/RF) كأشرطة أفقية مرتبة."""
131
+ plt = _plt()
132
+ names = np.asarray(names).ravel()
133
+ values = np.asarray(values, dtype=float).ravel()
134
+ n = min(names.size, values.size)
135
+ names, values = names[:n], values[:n]
136
+ order = np.argsort(values, kind="stable")[-top:]
137
+ plt.barh([str(names[i]) for i in order], values[order])
138
+ plt.title(title)
139
+ plt.grid(True)
140
+ return order
141
+
142
+
143
+ def plot_residuals(y_true, y_pred, title="residuals", **kw):
144
+ """بقايا الانحدار: predicted مقابل residual + هستوغرام."""
145
+ plt = _plt()
146
+ y_true = np.asarray(y_true, dtype=float).ravel()
147
+ y_pred = np.asarray(y_pred, dtype=float).ravel()
148
+ n = min(y_true.size, y_pred.size)
149
+ res = y_true[:n] - y_pred[:n]
150
+ fig, axs = plt.subplots(1, 2, figsize=(10, 4))
151
+ axs[0].scatter(y_pred[:n], res)
152
+ axs[0].axhline(0)
153
+ axs[0].set_xlabel("predicted")
154
+ axs[0].set_ylabel("residual")
155
+ axs[0].set_title(title)
156
+ axs[1].hist(res, bins=30)
157
+ axs[1].set_title("residual dist")
158
+ fig.suptitle(title)
159
+ return res
160
+
161
+
162
+ def plot_elbow(k_list, inertia, title="elbow method", **kw):
163
+ plt = _plt()
164
+ plt.plot(np.asarray(k_list), np.asarray(inertia, dtype=float), marker="o")
165
+ plt.xlabel("k")
166
+ plt.ylabel("inertia")
167
+ plt.title(title)
168
+ plt.grid(True)
169
+
170
+
171
+ def plot_images(images, ncols: int = 8, max_images: int = 64, title="", cmap="gray", **kw):
172
+ """شبكة صور للتعلم العميق (MNIST/CIFAR) — numpy فقط."""
173
+ plt = _plt()
174
+ arr = np.asarray(images)
175
+ if arr.ndim == 3:
176
+ arr = arr[:max_images]
177
+ elif arr.ndim == 4:
178
+ arr = arr[:max_images]
179
+ else:
180
+ raise ValueError("images must be (N,H,W) or (N,H,W,C)")
181
+ n = arr.shape[0]
182
+ ncols = min(ncols, n)
183
+ nrows = int(np.ceil(n / ncols))
184
+ fig, axs = plt.subplots(nrows, ncols, figsize=(ncols * 1.4, nrows * 1.4))
185
+ axs = np.asarray(axs, dtype=object).ravel()
186
+ for i in range(len(axs)):
187
+ if i < n:
188
+ axs[i].imshow(arr[i], cmap=cmap)
189
+ axs[i].set_title("")
190
+ if title:
191
+ fig.suptitle(title)
192
+ return fig
193
+
194
+
195
+ def plot_clusters_2d(X, labels, title="clusters", **kw):
196
+ """عناقيد 2D ملونة (KMeans/DBSCAN) — سريعة حتى 50k نقطة."""
197
+ plt = _plt()
198
+ X = np.asarray(X, dtype=float)
199
+ labels = np.asarray(labels).ravel()
200
+ for lab in sorted(np.unique(labels)):
201
+ m = labels == lab
202
+ plt.scatter(X[m, 0][:10000], X[m, 1][:10000], label=f"c{lab}")
203
+ plt.title(title)
204
+ plt.legend()
205
+ return plt.gca()
plotlibs/pyplot.py ADDED
@@ -0,0 +1,283 @@
1
+ """plotlibs.pyplot — matplotlib.pyplot-compatible state machine, faster."""
2
+ from __future__ import annotations
3
+
4
+ from . import style
5
+ from .figure import Figure, subplots as _subplots
6
+
7
+ _figures: dict[int, Figure] = {}
8
+ _current: Figure | None = None
9
+ _counter = [0]
10
+
11
+
12
+ def _cur() -> Figure:
13
+ global _current
14
+ if _current is None:
15
+ _current = figure()
16
+ return _current
17
+
18
+
19
+ def figure(figsize=None, dpi=None, facecolor=None, num=None):
20
+ global _current
21
+ _counter[0] += 1
22
+ n = num if num is not None else _counter[0]
23
+ fig = Figure(figsize=figsize, dpi=dpi, facecolor=facecolor, num=n)
24
+ _figures[n] = fig
25
+ _current = fig
26
+ return fig
27
+
28
+
29
+ def gcf():
30
+ return _cur()
31
+
32
+
33
+ def gca():
34
+ fig = _cur()
35
+ if not fig.axes:
36
+ return fig.add_subplot(1, 1, 1)
37
+ return fig.axes[-1]
38
+
39
+
40
+ def subplots(nrows=1, ncols=1, figsize=None, dpi=None, **kw):
41
+ global _current
42
+ fig = Figure(figsize=figsize, dpi=dpi)
43
+ axs = fig.subplots(nrows, ncols)
44
+ _counter[0] += 1
45
+ _figures[_counter[0]] = fig
46
+ _current = fig
47
+ return fig, axs
48
+
49
+
50
+ def subplot(nrows, ncols, index, **kw):
51
+ return _cur().add_subplot(nrows, ncols, index)
52
+
53
+
54
+ def plot(*args, **kw):
55
+ return gca().plot(*args, **kw)
56
+
57
+
58
+ def scatter(*args, **kw):
59
+ return gca().scatter(*args, **kw)
60
+
61
+
62
+ def bar(*args, **kw):
63
+ return gca().bar(*args, **kw)
64
+
65
+
66
+ def barh(*args, **kw):
67
+ return gca().barh(*args, **kw)
68
+
69
+
70
+ def hist(*args, **kw):
71
+ return gca().hist(*args, **kw)
72
+
73
+
74
+ def imshow(*args, **kw):
75
+ return gca().imshow(*args, **kw)
76
+
77
+
78
+ def pie(*args, **kw):
79
+ return gca().pie(*args, **kw)
80
+
81
+
82
+ def fill_between(*args, **kw):
83
+ return gca().fill_between(*args, **kw)
84
+
85
+
86
+ def step(*args, **kw):
87
+ return gca().step(*args, **kw)
88
+
89
+
90
+ def errorbar(*args, **kw):
91
+ return gca().errorbar(*args, **kw)
92
+
93
+
94
+ def axhline(*args, **kw):
95
+ return gca().axhline(*args, **kw)
96
+
97
+
98
+ def axvline(*args, **kw):
99
+ return gca().axvline(*args, **kw)
100
+
101
+
102
+ def text(x, y, s, **kw):
103
+ return gca().text(x, y, s, **kw)
104
+
105
+
106
+ def boxplot(*args, **kw):
107
+ return gca().boxplot(*args, **kw)
108
+
109
+
110
+ def violinplot(*args, **kw):
111
+ return gca().violinplot(*args, **kw)
112
+
113
+
114
+ def kde(*args, **kw):
115
+ return gca().kde(*args, **kw)
116
+
117
+
118
+ density = kde
119
+
120
+
121
+ def heatmap(*args, **kw):
122
+ return gca().heatmap(*args, **kw)
123
+
124
+
125
+ def corr(*args, **kw):
126
+ return gca().corr(*args, **kw)
127
+
128
+
129
+ def corrcoef(*args, **kw):
130
+ return gca().corr(*args, **kw)
131
+
132
+
133
+ def countplot(*args, **kw):
134
+ return gca().countplot(*args, **kw)
135
+
136
+
137
+ count = countplot
138
+
139
+
140
+ def area(*args, **kw):
141
+ return gca().area(*args, **kw)
142
+
143
+
144
+ stackplot = area
145
+
146
+
147
+ def hist2d(*args, **kw):
148
+ return gca().hist2d(*args, **kw)
149
+
150
+
151
+ def stem(*args, **kw):
152
+ return gca().stem(*args, **kw)
153
+
154
+
155
+ def xlim(*args, **kw):
156
+ ax = gca()
157
+ if not args and not kw:
158
+ return ax.get_xlim()
159
+ if len(args) == 2:
160
+ return ax.set_xlim(args[0], args[1])
161
+ if len(args) == 1:
162
+ return ax.set_xlim(args[0][0], args[0][1])
163
+ left = kw.pop("left", None)
164
+ right = kw.pop("right", None)
165
+ return ax.set_xlim(left, right)
166
+
167
+
168
+ def ylim(*args, **kw):
169
+ ax = gca()
170
+ if not args and not kw:
171
+ return ax.get_ylim()
172
+ if len(args) == 2:
173
+ return ax.set_ylim(args[0], args[1])
174
+ if len(args) == 1:
175
+ return ax.set_ylim(args[0][0], args[0][1])
176
+ bottom = kw.pop("bottom", None)
177
+ top = kw.pop("top", None)
178
+ return ax.set_ylim(bottom, top)
179
+
180
+
181
+ def xlabel(s, **kw):
182
+ gca().set_xlabel(s)
183
+
184
+
185
+ def ylabel(s, **kw):
186
+ gca().set_ylabel(s)
187
+
188
+
189
+ def title(s, **kw):
190
+ gca().set_title(s)
191
+
192
+
193
+ def suptitle(s, **kw):
194
+ _cur().suptitle(s)
195
+
196
+
197
+ def legend(*args, **kw):
198
+ return gca().legend(*args, **kw)
199
+
200
+
201
+ def grid(visible=True, **kw):
202
+ gca().grid(visible, **kw)
203
+
204
+
205
+ def xticks(ticks=None, labels=None, **kw):
206
+ ax = gca()
207
+ if ticks is None:
208
+ from .fast import nice_ticks
209
+ xl = ax.get_xlim()
210
+ return nice_ticks(xl[0], xl[1]), None
211
+ ax.set_xticks(ticks)
212
+ return ticks, labels
213
+
214
+
215
+ def yticks(ticks=None, labels=None, **kw):
216
+ ax = gca()
217
+ if ticks is None:
218
+ from .fast import nice_ticks
219
+ yl = ax.get_ylim()
220
+ return nice_ticks(yl[0], yl[1]), None
221
+ ax.set_yticks(ticks)
222
+ return ticks, labels
223
+
224
+
225
+ def xscale(s, **kw):
226
+ gca().set_xscale(s)
227
+
228
+
229
+ def yscale(s, **kw):
230
+ gca().set_yscale(s)
231
+
232
+
233
+ def semilogx(*a, **k):
234
+ r = plot(*a, **k)
235
+ gca().set_xscale("log")
236
+ return r
237
+
238
+
239
+ def semilogy(*a, **k):
240
+ r = plot(*a, **k)
241
+ gca().set_yscale("log")
242
+ return r
243
+
244
+
245
+ def loglog(*a, **k):
246
+ r = plot(*a, **k)
247
+ gca().set_xscale("log")
248
+ gca().set_yscale("log")
249
+ return r
250
+
251
+
252
+ def tight_layout(**kw):
253
+ _cur().tight_layout(**kw)
254
+
255
+
256
+ def savefig(fname, dpi=None, **kw):
257
+ _cur().savefig(fname, dpi=dpi, **kw)
258
+
259
+
260
+ def show(**kw):
261
+ _cur().show()
262
+
263
+
264
+ def close(*args, **kw):
265
+ global _current
266
+ _figures.clear()
267
+ _current = None
268
+
269
+
270
+ def clf():
271
+ _cur().clf()
272
+
273
+
274
+ def cla():
275
+ gca().cla()
276
+
277
+
278
+ def rcParams_update(d):
279
+ style.rcParams.update(d)
280
+
281
+
282
+ rcParams = style.rcParams
283
+ style_use = style.use
plotlibs/style.py ADDED
@@ -0,0 +1,96 @@
1
+ """plotlibs.style — rcParams + styles, matplotlib-compatible subset."""
2
+ from __future__ import annotations
3
+
4
+ rcParams: dict = {
5
+ "figure.figsize": (6.4, 4.8),
6
+ "figure.dpi": 100,
7
+ "figure.facecolor": "white",
8
+ "axes.facecolor": "white",
9
+ "axes.edgecolor": "black",
10
+ "axes.grid": False,
11
+ "axes.titlesize": 13,
12
+ "axes.labelsize": 11,
13
+ "xtick.labelsize": 9,
14
+ "ytick.labelsize": 9,
15
+ "font.size": 10,
16
+ "lines.linewidth": 1.8,
17
+ "lines.markersize": 6,
18
+ "savefig.dpi": 100,
19
+ "image.cmap": "viridis",
20
+ }
21
+
22
+ BRAND = {
23
+ "primary": "#4F46E5",
24
+ "accent": "#06B6D4",
25
+ "ink": "#0F172A",
26
+ "paper": "#F8FAFC",
27
+ "lime": "#A3E635",
28
+ "amber": "#FACC15",
29
+ }
30
+
31
+ _STYLES = {
32
+ "default": {},
33
+ "plotlibs": {"axes.facecolor": "white", "axes.grid": True,
34
+ "figure.facecolor": "white", "axes.edgecolor": "#0F172A",
35
+ "lines.linewidth": 2.2, "font.size": 11,
36
+ "axes.labelsize": 11, "axes.titlesize": 13,
37
+ "image.cmap": "viridis"},
38
+ "plotlibs-dark": {"figure.facecolor": "#0F172A", "axes.facecolor": "#1E293B",
39
+ "axes.edgecolor": "white", "axes.grid": True,
40
+ "lines.linewidth": 2.2, "font.size": 11},
41
+ "fast": {"axes.grid": True, "figure.dpi": 80},
42
+ "dark": {"figure.facecolor": "#1e1e1e", "axes.facecolor": "#1e1e1e",
43
+ "axes.edgecolor": "white", "axes.grid": True},
44
+ "ggplot": {"axes.facecolor": "#E5E5E5", "axes.grid": True,
45
+ "figure.facecolor": "#F5F5F5"},
46
+ "grayscale": {},
47
+ # extra gallery styles
48
+ "seaborn": {"axes.facecolor": "white", "axes.grid": True,
49
+ "figure.facecolor": "white", "lines.linewidth": 2.0,
50
+ "font.size": 11, "axes.labelsize": 11},
51
+ "seaborn-darkgrid": {"axes.facecolor": "#EAEAF2", "axes.grid": True,
52
+ "figure.facecolor": "white", "axes.edgecolor": "white",
53
+ "lines.linewidth": 2.0},
54
+ "plotly": {"axes.facecolor": "white", "axes.grid": True,
55
+ "figure.facecolor": "white", "lines.linewidth": 2.2,
56
+ "font.size": 12},
57
+ "publication": {"axes.facecolor": "white", "axes.grid": False,
58
+ "figure.dpi": 150, "lines.linewidth": 1.5,
59
+ "font.size": 10},
60
+ "darkgrid": {"figure.facecolor": "#212121", "axes.facecolor": "#2E2E2E",
61
+ "axes.edgecolor": "white", "axes.grid": True},
62
+ }
63
+
64
+ _available_ = sorted(_STYLES)
65
+
66
+
67
+ def use(name: str):
68
+ if name not in _STYLES:
69
+ raise ValueError(f"Unknown style {name!r}. Available: {_available_}")
70
+ base_face = rcParams.get("figure.facecolor", "white")
71
+ rcParams.update(_STYLES[name])
72
+ return base_face
73
+
74
+
75
+ def available():
76
+ return list(_available_)
77
+
78
+
79
+ class _StyleCtx:
80
+ def __init__(self, name):
81
+ self.name = name
82
+ self._saved = None
83
+
84
+ def __enter__(self):
85
+ self._saved = dict(rcParams)
86
+ use(self.name)
87
+ return self
88
+
89
+ def __exit__(self, *a):
90
+ rcParams.clear()
91
+ rcParams.update(self._saved)
92
+ return False
93
+
94
+
95
+ def context(name: str):
96
+ return _StyleCtx(name)