plotlp 0.1.17__tar.gz → 0.1.19__tar.gz

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.
Files changed (85) hide show
  1. {plotlp-0.1.17 → plotlp-0.1.19}/PKG-INFO +1 -1
  2. {plotlp-0.1.17 → plotlp-0.1.19}/pyproject.toml +1 -1
  3. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/__init__.py +1 -0
  4. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/StyledAxes_LP/StyledAxes.py +24 -1
  5. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/StyledFigure_LP/StyledFigure.py +32 -0
  6. plotlp-0.1.19/src/plotlp/modules/compare_distributions_LP/compare_distributions.py +326 -0
  7. plotlp-0.1.19/src/plotlp/modules/compare_distributions_LP/test_compare_distributions.py +52 -0
  8. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/figure_LP/test_figure.py +6 -4
  9. plotlp-0.1.19/src/plotlp/modules/subplots_LP/_doctype.txt +1 -0
  10. plotlp-0.1.19/src/plotlp/modules/subplots_LP/_functions/__template__.py +19 -0
  11. plotlp-0.1.19/src/plotlp/py.typed +0 -0
  12. plotlp-0.1.19/src/plotlp/routines/__init__.py +0 -0
  13. {plotlp-0.1.17 → plotlp-0.1.19}/README.md +0 -0
  14. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/.docignore +0 -0
  15. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/StyledAxes_LP/__init__.py +0 -0
  16. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/StyledAxes_LP/_doctype.txt +0 -0
  17. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/StyledAxes_LP/_functions/__init__.py +0 -0
  18. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/StyledAxes_LP/_functions/__template__.py +0 -0
  19. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/StyledAxes_LP/_functions/scalebar.py +0 -0
  20. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/StyledAxes_LP/test_StyledAxes.py +0 -0
  21. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/StyledFigure_LP/__init__.py +0 -0
  22. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/StyledFigure_LP/_doctype.txt +0 -0
  23. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/StyledFigure_LP/_functions/__init__.py +0 -0
  24. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/StyledFigure_LP/_functions/__template__.py +0 -0
  25. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/StyledFigure_LP/test_StyledFigure.py +0 -0
  26. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/__init__.py +0 -0
  27. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/cmap_LP/__init__.py +0 -0
  28. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/cmap_LP/_doctype.txt +0 -0
  29. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/cmap_LP/_functions/__init__.py +0 -0
  30. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/cmap_LP/_functions/__template__.py +0 -0
  31. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/cmap_LP/cmap.py +0 -0
  32. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/cmap_LP/test_cmap.py +0 -0
  33. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/color_LP/__init__.py +0 -0
  34. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/color_LP/_doctype.txt +0 -0
  35. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/color_LP/_functions/__init__.py +0 -0
  36. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/color_LP/_functions/__template__.py +0 -0
  37. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/color_LP/color.py +0 -0
  38. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/color_LP/test_color.py +0 -0
  39. {plotlp-0.1.17/src/plotlp/modules/figure_LP → plotlp-0.1.19/src/plotlp/modules/compare_distributions_LP}/__init__.py +0 -0
  40. {plotlp-0.1.17/src/plotlp/modules/figure_LP → plotlp-0.1.19/src/plotlp/modules/compare_distributions_LP}/_doctype.txt +0 -0
  41. {plotlp-0.1.17/src/plotlp/modules/figure_LP → plotlp-0.1.19/src/plotlp/modules/compare_distributions_LP}/_functions/__init__.py +0 -0
  42. {plotlp-0.1.17/src/plotlp/modules/figure_LP → plotlp-0.1.19/src/plotlp/modules/compare_distributions_LP}/_functions/__template__.py +0 -0
  43. {plotlp-0.1.17/src/plotlp/modules/gridsubplots_LP → plotlp-0.1.19/src/plotlp/modules/figure_LP}/__init__.py +0 -0
  44. {plotlp-0.1.17/src/plotlp/modules/gridsubplots_LP → plotlp-0.1.19/src/plotlp/modules/figure_LP}/_doctype.txt +0 -0
  45. {plotlp-0.1.17/src/plotlp/modules/gridsubplots_LP → plotlp-0.1.19/src/plotlp/modules/figure_LP}/_functions/__init__.py +0 -0
  46. {plotlp-0.1.17/src/plotlp/modules/gridsubplots_LP → plotlp-0.1.19/src/plotlp/modules/figure_LP}/_functions/__template__.py +0 -0
  47. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/figure_LP/figure.py +0 -0
  48. {plotlp-0.1.17/src/plotlp/modules/imgfigure_LP → plotlp-0.1.19/src/plotlp/modules/gridsubplots_LP}/__init__.py +0 -0
  49. {plotlp-0.1.17/src/plotlp/modules/imgfigure_LP → plotlp-0.1.19/src/plotlp/modules/gridsubplots_LP}/_doctype.txt +0 -0
  50. {plotlp-0.1.17/src/plotlp/modules/imgfigure_LP → plotlp-0.1.19/src/plotlp/modules/gridsubplots_LP}/_functions/__init__.py +0 -0
  51. {plotlp-0.1.17/src/plotlp/modules/imgfigure_LP → plotlp-0.1.19/src/plotlp/modules/gridsubplots_LP}/_functions/__template__.py +0 -0
  52. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/gridsubplots_LP/gridsubplots.py +0 -0
  53. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/gridsubplots_LP/test_gridsubplots.py +0 -0
  54. {plotlp-0.1.17/src/plotlp/modules/make_animation_LP → plotlp-0.1.19/src/plotlp/modules/imgfigure_LP}/__init__.py +0 -0
  55. {plotlp-0.1.17/src/plotlp/modules/make_animation_LP → plotlp-0.1.19/src/plotlp/modules/imgfigure_LP}/_doctype.txt +0 -0
  56. {plotlp-0.1.17/src/plotlp/modules/make_animation_LP → plotlp-0.1.19/src/plotlp/modules/imgfigure_LP}/_functions/__init__.py +0 -0
  57. {plotlp-0.1.17/src/plotlp/modules/make_animation_LP → plotlp-0.1.19/src/plotlp/modules/imgfigure_LP}/_functions/__template__.py +0 -0
  58. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/imgfigure_LP/imgfigure.py +0 -0
  59. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/imgfigure_LP/test_imgfigure.py +0 -0
  60. {plotlp-0.1.17/src/plotlp/modules/plt_LP → plotlp-0.1.19/src/plotlp/modules/make_animation_LP}/__init__.py +0 -0
  61. {plotlp-0.1.17/src/plotlp/modules/plt_LP → plotlp-0.1.19/src/plotlp/modules/make_animation_LP}/_doctype.txt +0 -0
  62. {plotlp-0.1.17/src/plotlp/modules/plt_LP → plotlp-0.1.19/src/plotlp/modules/make_animation_LP}/_functions/__init__.py +0 -0
  63. {plotlp-0.1.17/src/plotlp/modules/plt_LP → plotlp-0.1.19/src/plotlp/modules/make_animation_LP}/_functions/__template__.py +0 -0
  64. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/make_animation_LP/make_animation.py +0 -0
  65. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/make_animation_LP/test_make_animation.py +0 -0
  66. {plotlp-0.1.17/src/plotlp/modules/style_LP → plotlp-0.1.19/src/plotlp/modules/plt_LP}/__init__.py +0 -0
  67. {plotlp-0.1.17/src/plotlp/modules/style_LP → plotlp-0.1.19/src/plotlp/modules/plt_LP}/_doctype.txt +0 -0
  68. {plotlp-0.1.17/src/plotlp/modules/style_LP → plotlp-0.1.19/src/plotlp/modules/plt_LP}/_functions/__init__.py +0 -0
  69. {plotlp-0.1.17/src/plotlp/modules/style_LP → plotlp-0.1.19/src/plotlp/modules/plt_LP}/_functions/__template__.py +0 -0
  70. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/plt_LP/plt.py +0 -0
  71. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/plt_LP/test_plt.py +0 -0
  72. {plotlp-0.1.17/src/plotlp/modules/subplots_LP → plotlp-0.1.19/src/plotlp/modules/style_LP}/__init__.py +0 -0
  73. {plotlp-0.1.17/src/plotlp/modules/subplots_LP → plotlp-0.1.19/src/plotlp/modules/style_LP}/_doctype.txt +0 -0
  74. {plotlp-0.1.17/src/plotlp/modules/subplots_LP → plotlp-0.1.19/src/plotlp/modules/style_LP}/_functions/__init__.py +0 -0
  75. {plotlp-0.1.17/src/plotlp/modules/subplots_LP → plotlp-0.1.19/src/plotlp/modules/style_LP}/_functions/__template__.py +0 -0
  76. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/style_LP/_functions/_darkLP.py +0 -0
  77. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/style_LP/_functions/_default.py +0 -0
  78. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/style_LP/_functions/_lightLP.py +0 -0
  79. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/style_LP/_functions/_styleLP.py +0 -0
  80. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/style_LP/style.py +0 -0
  81. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/style_LP/test_style.py +0 -0
  82. {plotlp-0.1.17/src/plotlp/routines → plotlp-0.1.19/src/plotlp/modules/subplots_LP}/__init__.py +0 -0
  83. /plotlp-0.1.17/src/plotlp/py.typed → /plotlp-0.1.19/src/plotlp/modules/subplots_LP/_functions/__init__.py +0 -0
  84. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/subplots_LP/subplots.py +0 -0
  85. {plotlp-0.1.17 → plotlp-0.1.19}/src/plotlp/modules/subplots_LP/test_subplots.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: plotlp
3
- Version: 0.1.17
3
+ Version: 0.1.19
4
4
  Summary: A library wrapper around matplotlib for custom plots.
5
5
  Requires-Dist: corelp
6
6
  Requires-Dist: cycler
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "plotlp"
3
- version = "0.1.17"
3
+ version = "0.1.19"
4
4
  description = "A library wrapper around matplotlib for custom plots."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -17,6 +17,7 @@ sources = {
17
17
  'StyledFigure': 'plotlp.modules.StyledFigure_LP.StyledFigure',
18
18
  'cmap': 'plotlp.modules.cmap_LP.cmap',
19
19
  'color': 'plotlp.modules.color_LP.color',
20
+ 'compare_distributions': 'plotlp.modules.compare_distributions_LP.compare_distributions',
20
21
  'figure': 'plotlp.modules.figure_LP.figure',
21
22
  'gridsubplots': 'plotlp.modules.gridsubplots_LP.gridsubplots',
22
23
  'imgfigure': 'plotlp.modules.imgfigure_LP.imgfigure',
@@ -133,6 +133,7 @@ class StyledAxes(Axes) :
133
133
  if self.polish_imscale : self.imscale()
134
134
  if self.polish_noborders : self.noborders()
135
135
  if self.polish_equiscale : self.equiscale()
136
+ if self.polish_figscale : self.figscale()
136
137
 
137
138
  # grids
138
139
  @prop()
@@ -179,6 +180,28 @@ class StyledAxes(Axes) :
179
180
  with plt.style.context(self.style) :
180
181
  self.set_aspect(aspect='equal', adjustable='box')
181
182
 
183
+ # figscale
184
+ @prop()
185
+ def polish_figscale(self) :
186
+ return self.figure.naxes == 1 and not self.polish_imscale and self.polish_equiscale
187
+ def figscale(self) :
188
+ with plt.style.context(self.style) :
189
+ ratio = self.figure.figsize_ratio
190
+ xmin, xmax = self.get_xlim()
191
+ ymin, ymax = self.get_ylim()
192
+ dx, dy = xmax - xmin, ymax - ymin
193
+ x, y = (xmax + xmin) / 2, (ymax + ymin) / 2
194
+ if dx / dy != ratio :
195
+ _dx, _dy = ratio * dy, dx / ratio
196
+ dx = _dx if _dx > dx else dx
197
+ dy = _dy if _dy > dy else dy
198
+ xmin, xmax = x - dx/2, x + dx/2
199
+ ymin, ymax = y - dy/2, y + dy/2
200
+ if self.get_autoscalex_on() :
201
+ self.set_xlim(xmin, xmax)
202
+ if self.get_autoscaley_on() :
203
+ self.set_ylim(ymin, ymax)
204
+
182
205
 
183
206
 
184
207
  ### --- Regenerate parent class methods in the given style ---
@@ -189,7 +212,7 @@ def is_plottable(method_name, method_obj):
189
212
  """
190
213
  Returns True if this method should be wrapped automatically.
191
214
  """
192
- if hasattr(StyledAxes, method_name): # Already overriden
215
+ if method_name in StyledAxes.__dict__ : # Already overriden
193
216
  return False
194
217
  if method_name.startswith("_"): # private
195
218
  return False # handled separately
@@ -156,10 +156,15 @@ class StyledFigure(Figure) :
156
156
 
157
157
 
158
158
 
159
+ # Merging and splitting
159
160
  @property
160
161
  def merge(self):
161
162
  return _MergeIndexer(self)
162
163
 
164
+ @property
165
+ def split(self):
166
+ return _SplitIndexer(self)
167
+
163
168
  def _normalize_indices(self, idx, size):
164
169
  if isinstance(idx, slice):
165
170
  return list(range(*idx.indices(size)))
@@ -198,6 +203,17 @@ class StyledFigure(Figure) :
198
203
 
199
204
  return new_ax
200
205
 
206
+ def _split_axis(self, row, col, height, width):
207
+
208
+ if not hasattr(self, "_axes_grid"):
209
+ raise RuntimeError("merge only works with fig.subplots()")
210
+
211
+ grid = self._axes_grid
212
+ axes_to_merge = [grid[i, j] for i in row for j in col]
213
+
214
+ # TODO
215
+
216
+ return None
201
217
 
202
218
 
203
219
  # Paper index
@@ -351,6 +367,22 @@ class _MergeIndexer:
351
367
 
352
368
 
353
369
 
370
+ class _SplitIndexer:
371
+
372
+ def __init__(self, fig):
373
+ self.fig = fig
374
+
375
+ def __getitem__(self, key):
376
+
377
+ if not isinstance(key, tuple):
378
+ raise KeyError(f'Splitting figure axis should only be done with two coordinates, not {key}')
379
+
380
+ r, c, h, w = key
381
+
382
+ return self.fig._split_axis(r, c, h, w)
383
+
384
+
385
+
354
386
  # %% Test function run
355
387
  if __name__ == "__main__":
356
388
  from corelp import test
@@ -0,0 +1,326 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # Date : 2026-05-28
4
+ # Author : Lancelot PINCET
5
+ # GitHub : https://github.com/LancelotPincet
6
+ # Library : plotLP
7
+ # Module : compare_distributions
8
+
9
+ """
10
+ This function create a figure which compares distributions via histograms and scatterplots.
11
+ """
12
+
13
+
14
+
15
+ # %% Libraries
16
+ import numpy as np
17
+ from plotlp import subplots
18
+
19
+
20
+
21
+ # %% Function
22
+ def compare_distributions(
23
+ *vectors,
24
+ labels=None,
25
+ hue=None,
26
+ npoints=1000,
27
+ bins=64,
28
+ percentile=99,
29
+ bissectrice=False,
30
+ title=None,
31
+ ):
32
+ """Compare distributions with pairwise scatterplots and diagonal histograms."""
33
+ if len(vectors) < 2:
34
+ raise ValueError("compare_distributions requires at least two vectors")
35
+
36
+ vectors = [np.asarray(vector, dtype=np.float32).ravel() for vector in vectors]
37
+ n = len(vectors)
38
+
39
+ if labels is None:
40
+ labels = [f"Vector {index + 1}" for index in range(n)]
41
+ labels = list(labels)
42
+ if len(labels) != n:
43
+ raise ValueError("labels length must match the number of vectors")
44
+
45
+ if hue is not None:
46
+ hue = np.asarray(hue).ravel()
47
+ expected_size = vectors[0].size
48
+ if any(vector.size != expected_size for vector in vectors):
49
+ raise ValueError("all vectors must have the same size when hue is provided")
50
+ if hue.size != expected_size:
51
+ raise ValueError("hue length must match vector length")
52
+ categories = list(dict.fromkeys(hue.tolist()))
53
+ else:
54
+ categories = [None]
55
+
56
+ npoints = int(npoints)
57
+ if npoints < 1:
58
+ raise ValueError("npoints must be >= 1")
59
+
60
+ percentile = float(percentile)
61
+ if percentile <= 0 or percentile > 100:
62
+ raise ValueError("percentile must be in (0, 100]")
63
+ q_low = (100 - percentile) / 2
64
+ q_high = 100 - q_low
65
+
66
+ global_scatter_limits = None
67
+ if bissectrice:
68
+ finite_all = [values[np.isfinite(values)] for values in vectors]
69
+ finite_all = [values for values in finite_all if values.size]
70
+ if finite_all:
71
+ limits_values = np.concatenate(finite_all)
72
+ low = float(np.nanpercentile(limits_values, q_low))
73
+ high = float(np.nanpercentile(limits_values, q_high))
74
+ if high <= low:
75
+ high = low + 1.0
76
+ global_scatter_limits = (low, high)
77
+
78
+ color_map = {category: f"C{index % 10}" for index, category in enumerate(categories)}
79
+
80
+ if n == 2:
81
+ figure, axes = subplots(
82
+ 2,
83
+ 2,
84
+ figsize_ratio=1,
85
+ dpi=100,
86
+ sharex="col",
87
+ gridspec_kw={"width_ratios": [3, 1], "height_ratios": [1, 3]},
88
+ )
89
+ ax_hist_x = axes[0, 0]
90
+ ax_scatter = axes[1, 0]
91
+ ax_hist_y = axes[1, 1]
92
+ axes[0, 1].axis("off")
93
+
94
+ x_values, y_values = vectors
95
+ pair_mask = np.isfinite(x_values) & np.isfinite(y_values)
96
+ if hue is None:
97
+ category_masks = [(None, pair_mask)]
98
+ else:
99
+ category_masks = [
100
+ (category, pair_mask & (hue == category)) for category in categories
101
+ ]
102
+
103
+ x_finite = x_values[np.isfinite(x_values)]
104
+ y_finite = y_values[np.isfinite(y_values)]
105
+ if x_finite.size:
106
+ if bissectrice and global_scatter_limits is not None:
107
+ _low, _high = global_scatter_limits
108
+ x_bins = np.linspace(_low, _high, int(bins) + 1, dtype=np.float32)
109
+ else:
110
+ x_bins = np.histogram_bin_edges(x_finite, bins=bins)
111
+ for category, mask in category_masks:
112
+ x_mask = mask if hue is None else np.isfinite(x_values) & (hue == category)
113
+ x_cat = x_values[x_mask]
114
+ x_cat = x_cat[np.isfinite(x_cat)]
115
+ if x_cat.size:
116
+ ax_hist_x.hist(
117
+ x_cat,
118
+ bins=x_bins,
119
+ alpha=0.45,
120
+ edgecolor=color_map[category],
121
+ linewidth=0.8,
122
+ color=color_map[category],
123
+ )
124
+
125
+ if y_finite.size:
126
+ if bissectrice and global_scatter_limits is not None:
127
+ _low, _high = global_scatter_limits
128
+ y_bins = np.linspace(_low, _high, int(bins) + 1, dtype=np.float32)
129
+ else:
130
+ y_bins = np.histogram_bin_edges(y_finite, bins=bins)
131
+ for category, mask in category_masks:
132
+ y_mask = mask if hue is None else np.isfinite(y_values) & (hue == category)
133
+ y_cat = y_values[y_mask]
134
+ y_cat = y_cat[np.isfinite(y_cat)]
135
+ if y_cat.size:
136
+ ax_hist_y.hist(
137
+ y_cat,
138
+ bins=y_bins,
139
+ alpha=0.45,
140
+ edgecolor=color_map[category],
141
+ linewidth=0.8,
142
+ color=color_map[category],
143
+ orientation="horizontal",
144
+ )
145
+
146
+ rng = np.random.default_rng(0)
147
+ points_drawn = 0
148
+ for category, mask in category_masks:
149
+ indices = np.flatnonzero(mask)
150
+ if indices.size == 0:
151
+ continue
152
+ sample_size = min(npoints, indices.size)
153
+ sample_indices = rng.choice(indices, size=sample_size, replace=False)
154
+ points_drawn += sample_size
155
+ ax_scatter.scatter(
156
+ x_values[sample_indices],
157
+ y_values[sample_indices],
158
+ s=6,
159
+ alpha=0.35,
160
+ color=color_map[category],
161
+ )
162
+
163
+ if points_drawn == 0:
164
+ ax_scatter.text(0.5, 0.5, "No finite paired values", ha="center", va="center")
165
+
166
+ ax_scatter.set_xlabel(labels[0])
167
+ ax_scatter.set_ylabel(labels[1])
168
+ ax_scatter.polish_equiscale = True
169
+ ax_hist_x.set_ylabel("Counts")
170
+ ax_hist_y.set_xlabel("Counts")
171
+ ax_hist_x.tick_params(axis="x", labelbottom=False)
172
+ ax_hist_y.tick_params(axis="y", labelleft=False)
173
+ ax_hist_x.polish_grids = True
174
+ ax_hist_y.polish_grids = True
175
+
176
+ if bissectrice and np.any(pair_mask):
177
+ low, high = global_scatter_limits
178
+ ax_scatter.plot([low, high], [low, high], "k--", linewidth=1.2)
179
+ ax_scatter.set_xlim(low, high)
180
+ ax_scatter.set_ylim(low, high)
181
+ ax_hist_x.set_xlim(low, high)
182
+ ax_hist_y.set_ylim(low, high)
183
+ elif np.any(pair_mask):
184
+ x_pair = x_values[pair_mask]
185
+ y_pair = y_values[pair_mask]
186
+ x_low = float(np.nanpercentile(x_pair, q_low))
187
+ x_high = float(np.nanpercentile(x_pair, q_high))
188
+ y_low = float(np.nanpercentile(y_pair, q_low))
189
+ y_high = float(np.nanpercentile(y_pair, q_high))
190
+ if x_high <= x_low:
191
+ x_high = x_low + 1.0
192
+ if y_high <= y_low:
193
+ y_high = y_low + 1.0
194
+ ax_scatter.set_xlim(x_low, x_high)
195
+ ax_scatter.set_ylim(y_low, y_high)
196
+
197
+ if title is not None:
198
+ figure.title = title
199
+ return figure
200
+
201
+ figure, axes = subplots(
202
+ n,
203
+ n,
204
+ figsize_fact=(n / 2, n / 2),
205
+ figsize_ratio=1,
206
+ dpi=100,
207
+ sharex="col",
208
+ )
209
+ rng = np.random.default_rng(0)
210
+
211
+ for i in range(n):
212
+ for j in range(n):
213
+ axis = axes[i, j]
214
+ if i < j:
215
+ axis.axis("off")
216
+ continue
217
+
218
+ if i == j:
219
+ values = vectors[i]
220
+ finite = values[np.isfinite(values)]
221
+ axis.set_box_aspect(1)
222
+ if finite.size == 0:
223
+ axis.text(0.5, 0.5, "No finite values", ha="center", va="center")
224
+ else:
225
+ if bissectrice and global_scatter_limits is not None:
226
+ _low, _high = global_scatter_limits
227
+ edges = np.linspace(_low, _high, int(bins) + 1, dtype=np.float32)
228
+ else:
229
+ edges = np.histogram_bin_edges(finite, bins=bins)
230
+ for category in categories:
231
+ if hue is None:
232
+ category_values = finite
233
+ else:
234
+ category_mask = np.isfinite(values) & (hue == category)
235
+ category_values = values[category_mask]
236
+ if category_values.size:
237
+ axis.hist(
238
+ category_values,
239
+ bins=edges,
240
+ alpha=0.45,
241
+ edgecolor=color_map[category],
242
+ linewidth=0.8,
243
+ color=color_map[category],
244
+ )
245
+ axis.set_ylabel("Counts")
246
+ axis.polish_grids = True
247
+ if bissectrice and global_scatter_limits is not None:
248
+ _low, _high = global_scatter_limits
249
+ axis.set_xlim(_low, _high)
250
+ if i == n - 1:
251
+ axis.set_xlabel(labels[j])
252
+ else:
253
+ axis.set_xlabel("")
254
+ axis.tick_params(axis="x", labelbottom=False)
255
+ continue
256
+
257
+ x_values = vectors[j]
258
+ y_values = vectors[i]
259
+ pair_mask = np.isfinite(x_values) & np.isfinite(y_values)
260
+ if hue is None:
261
+ category_masks = [(None, pair_mask)]
262
+ else:
263
+ category_masks = [
264
+ (category, pair_mask & (hue == category)) for category in categories
265
+ ]
266
+
267
+ points_drawn = 0
268
+ for category, mask in category_masks:
269
+ indices = np.flatnonzero(mask)
270
+ if indices.size == 0:
271
+ continue
272
+ sample_size = min(npoints, indices.size)
273
+ sample_indices = rng.choice(indices, size=sample_size, replace=False)
274
+ points_drawn += sample_size
275
+ axis.scatter(
276
+ x_values[sample_indices],
277
+ y_values[sample_indices],
278
+ s=5,
279
+ alpha=0.35,
280
+ color=color_map[category],
281
+ )
282
+
283
+ if points_drawn == 0:
284
+ axis.text(0.5, 0.5, "No finite paired values", ha="center", va="center")
285
+
286
+ axis.polish_equiscale = True
287
+ if i == n - 1:
288
+ axis.set_xlabel(labels[j])
289
+ else:
290
+ axis.set_xlabel("")
291
+ axis.tick_params(axis="x", labelbottom=False)
292
+ if j == 0:
293
+ axis.set_ylabel(labels[i])
294
+ else:
295
+ axis.set_ylabel("")
296
+ axis.tick_params(axis="y", labelleft=False)
297
+
298
+ if bissectrice and np.any(pair_mask):
299
+ low, high = global_scatter_limits
300
+ axis.plot([low, high], [low, high], "k--", linewidth=1.0)
301
+ axis.set_xlim(low, high)
302
+ axis.set_ylim(low, high)
303
+ elif np.any(pair_mask):
304
+ x_pair = x_values[pair_mask]
305
+ y_pair = y_values[pair_mask]
306
+ x_low = float(np.nanpercentile(x_pair, q_low))
307
+ x_high = float(np.nanpercentile(x_pair, q_high))
308
+ y_low = float(np.nanpercentile(y_pair, q_low))
309
+ y_high = float(np.nanpercentile(y_pair, q_high))
310
+ if x_high <= x_low:
311
+ x_high = x_low + 1.0
312
+ if y_high <= y_low:
313
+ y_high = y_low + 1.0
314
+ axis.set_xlim(x_low, x_high)
315
+ axis.set_ylim(y_low, y_high)
316
+
317
+ if title is not None:
318
+ figure.title = title
319
+ return figure
320
+
321
+
322
+
323
+ # %% Test function run
324
+ if __name__ == "__main__":
325
+ from corelp import test
326
+ test(__file__)
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # Date : 2026-05-28
4
+ # Author : Lancelot PINCET
5
+ # GitHub : https://github.com/LancelotPincet
6
+ # Library : plotLP
7
+ # Module : compare_distributions
8
+
9
+ """
10
+ This file allows to test compare_distributions
11
+
12
+ compare_distributions : This function create a figure which compares distributions via histograms and scatterplots.
13
+ """
14
+
15
+
16
+
17
+ # %% Libraries
18
+ import numpy as np
19
+ import pytest
20
+ from plotlp import compare_distributions
21
+
22
+
23
+
24
+ # %% Function test
25
+ def test_two_vectors_returns_figure():
26
+ """Two vectors should return a figure."""
27
+ x = np.arange(20, dtype=np.float32)
28
+ y = x + 1
29
+ figure = compare_distributions(x, y)
30
+ assert figure is not None
31
+
32
+
33
+ def test_multiple_vectors_returns_figure():
34
+ """Three vectors should return a pairwise-plot figure."""
35
+ x = np.arange(30, dtype=np.float32)
36
+ y = x * 2
37
+ z = x * 3
38
+ figure = compare_distributions(x, y, z, bissectrice=True)
39
+ assert figure is not None
40
+
41
+
42
+ def test_requires_at_least_two_vectors():
43
+ """One vector should raise an error."""
44
+ with pytest.raises(ValueError, match="at least two vectors"):
45
+ compare_distributions(np.arange(10))
46
+
47
+
48
+
49
+ # %% Test function run
50
+ if __name__ == "__main__":
51
+ from corelp import test
52
+ test(__file__)
@@ -31,12 +31,14 @@ def test_plot() :
31
31
  y = x**2
32
32
  fig = figure(figsize_fact=(0.5,0.5), darkmode=True)
33
33
  fig.axis.plot(x, -y)
34
+ fig.axis.scatter(x, -y)
34
35
  plt.plot(x, y)
36
+ plt.scatter(x, y)
35
37
  plt.xlabel('x [ua]')
36
38
  fig.axis.set_ylabel('y [ua]')
37
39
  fig.title = 'test title'
38
- plt.savefig(path_png = debug_folder / 'plot_png', close=False)
39
- plt.savefig(path_pdf = debug_folder / 'plot_pdf', close=False)
40
+ fig.savefig(path_png = debug_folder / 'plot_png', close=False)
41
+ fig.savefig(path_pdf = debug_folder / 'plot_pdf', close=False)
40
42
  plt.savefig(debug_folder / 'plot_all')
41
43
 
42
44
  def test_imshow() :
@@ -50,8 +52,8 @@ def test_imshow() :
50
52
  fig = figure(figsize_ratio=4/3)
51
53
  fig.axis.imshow(img, barname='intensity')
52
54
  fig.title = 'test title'
53
- plt.savefig(path_png = debug_folder / 'imshow_png', close=False)
54
- plt.savefig(path_pdf = debug_folder / 'imshow_pdf', close=False)
55
+ fig.savefig(path_png = debug_folder / 'imshow_png', close=False)
56
+ fig.savefig(path_pdf = debug_folder / 'imshow_pdf', close=False)
55
57
  plt.savefig(debug_folder / 'imshow_all')
56
58
 
57
59
 
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # Author : Lancelot PINCET
4
+ # GitHub : https://github.com/LancelotPincet
5
+
6
+
7
+
8
+ # %% Libraries
9
+
10
+
11
+
12
+
13
+ # %% Function
14
+ def temp(**kwargs) :
15
+ '''
16
+ TODO
17
+ '''
18
+
19
+ print('Hello world!')
File without changes
File without changes
File without changes
File without changes