plot-misc 2.1.0__tar.gz → 2.2.1__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 (46) hide show
  1. {plot_misc-2.1.0/plot_misc.egg-info → plot_misc-2.2.1}/PKG-INFO +75 -19
  2. {plot_misc-2.1.0 → plot_misc-2.2.1}/README.md +63 -17
  3. plot_misc-2.2.1/plot_misc/_version.py +1 -0
  4. {plot_misc-2.1.0 → plot_misc-2.2.1}/plot_misc/barchart.py +72 -30
  5. {plot_misc-2.1.0 → plot_misc-2.2.1}/plot_misc/constants.py +4 -4
  6. {plot_misc-2.1.0 → plot_misc-2.2.1}/plot_misc/errors.py +6 -6
  7. {plot_misc-2.1.0 → plot_misc-2.2.1}/plot_misc/example_data/examples.py +233 -1
  8. {plot_misc-2.1.0 → plot_misc-2.2.1}/plot_misc/forest.py +55 -55
  9. {plot_misc-2.1.0 → plot_misc-2.2.1}/plot_misc/heatmap.py +18 -141
  10. {plot_misc-2.1.0 → plot_misc-2.2.1}/plot_misc/incidencematrix.py +38 -28
  11. {plot_misc-2.1.0 → plot_misc-2.2.1}/plot_misc/machine_learning.py +35 -35
  12. {plot_misc-2.1.0 → plot_misc-2.2.1}/plot_misc/piechart.py +10 -10
  13. {plot_misc-2.1.0 → plot_misc-2.2.1}/plot_misc/survival.py +10 -11
  14. {plot_misc-2.1.0 → plot_misc-2.2.1}/plot_misc/utils/colour.py +1 -17
  15. {plot_misc-2.1.0 → plot_misc-2.2.1}/plot_misc/utils/formatting.py +10 -13
  16. {plot_misc-2.1.0 → plot_misc-2.2.1}/plot_misc/utils/utils.py +29 -30
  17. {plot_misc-2.1.0 → plot_misc-2.2.1}/plot_misc/volcano.py +38 -24
  18. {plot_misc-2.1.0 → plot_misc-2.2.1/plot_misc.egg-info}/PKG-INFO +75 -19
  19. {plot_misc-2.1.0 → plot_misc-2.2.1}/plot_misc.egg-info/SOURCES.txt +0 -1
  20. {plot_misc-2.1.0 → plot_misc-2.2.1}/plot_misc.egg-info/requires.txt +11 -1
  21. {plot_misc-2.1.0 → plot_misc-2.2.1}/pyproject.toml +1 -1
  22. plot_misc-2.2.1/requirements-dev.txt +20 -0
  23. {plot_misc-2.1.0 → plot_misc-2.2.1}/requirements.txt +0 -1
  24. plot_misc-2.1.0/plot_misc/_version.py +0 -1
  25. plot_misc-2.1.0/plot_misc/example_data/example_datasets/string_data.txt +0 -1
  26. plot_misc-2.1.0/requirements-dev.txt +0 -9
  27. {plot_misc-2.1.0 → plot_misc-2.2.1}/LICENSE +0 -0
  28. {plot_misc-2.1.0 → plot_misc-2.2.1}/MANIFEST.in +0 -0
  29. {plot_misc-2.1.0 → plot_misc-2.2.1}/plot_misc/__init__.py +0 -0
  30. {plot_misc-2.1.0 → plot_misc-2.2.1}/plot_misc/example_data/__init__.py +0 -0
  31. {plot_misc-2.1.0 → plot_misc-2.2.1}/plot_misc/example_data/example_datasets/bar_points.tsv.gz +0 -0
  32. {plot_misc-2.1.0 → plot_misc-2.2.1}/plot_misc/example_data/example_datasets/barchart.tsv.gz +0 -0
  33. {plot_misc-2.1.0 → plot_misc-2.2.1}/plot_misc/example_data/example_datasets/calibration_bins.tsv.gz +0 -0
  34. {plot_misc-2.1.0 → plot_misc-2.2.1}/plot_misc/example_data/example_datasets/calibration_data.tsv.gz +0 -0
  35. {plot_misc-2.1.0 → plot_misc-2.2.1}/plot_misc/example_data/example_datasets/forest_data.tsv.gz +0 -0
  36. {plot_misc-2.1.0 → plot_misc-2.2.1}/plot_misc/example_data/example_datasets/group_bar.tsv.gz +0 -0
  37. {plot_misc-2.1.0 → plot_misc-2.2.1}/plot_misc/example_data/example_datasets/heatmap_data.tsv.gz +0 -0
  38. {plot_misc-2.1.0 → plot_misc-2.2.1}/plot_misc/example_data/example_datasets/incidence_matrix_data.tsv.gz +0 -0
  39. {plot_misc-2.1.0 → plot_misc-2.2.1}/plot_misc/example_data/example_datasets/lollipop_data.tsv.gz +0 -0
  40. {plot_misc-2.1.0 → plot_misc-2.2.1}/plot_misc/example_data/example_datasets/mace_associations.tsv.gz +0 -0
  41. {plot_misc-2.1.0 → plot_misc-2.2.1}/plot_misc/example_data/example_datasets/net_benefit.tsv.gz +0 -0
  42. {plot_misc-2.1.0 → plot_misc-2.2.1}/plot_misc/example_data/example_datasets/volcano.tsv.gz +0 -0
  43. {plot_misc-2.1.0 → plot_misc-2.2.1}/plot_misc/utils/__init__.py +0 -0
  44. {plot_misc-2.1.0 → plot_misc-2.2.1}/plot_misc.egg-info/dependency_links.txt +0 -0
  45. {plot_misc-2.1.0 → plot_misc-2.2.1}/plot_misc.egg-info/top_level.txt +0 -0
  46. {plot_misc-2.1.0 → plot_misc-2.2.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plot-misc
3
- Version: 2.1.0
3
+ Version: 2.2.1
4
4
  Summary: Various plotting templates built on top of matplotlib
5
5
  Author-email: A Floriaan Schmidt <floriaanschmidt@gmail.com>
6
6
  License-Expression: GPL-3.0-or-later
@@ -18,7 +18,6 @@ License-File: LICENSE
18
18
  Requires-Dist: pandas>=1.3
19
19
  Requires-Dist: numpy>=1.21
20
20
  Requires-Dist: matplotlib>=3.5
21
- Requires-Dist: seaborn>=0.11
22
21
  Requires-Dist: scipy>=1.5
23
22
  Requires-Dist: statsmodels>=0.1
24
23
  Requires-Dist: scikit-learn>=1.4
@@ -32,13 +31,24 @@ Requires-Dist: pytest>=6; extra == "dev"
32
31
  Requires-Dist: pytest-mock>=3; extra == "dev"
33
32
  Requires-Dist: pytest-dependency>=0.5; extra == "dev"
34
33
  Requires-Dist: bump2version>=1; extra == "dev"
34
+ Requires-Dist: codespell; extra == "dev"
35
35
  Requires-Dist: jupyter; extra == "dev"
36
+ Requires-Dist: sphinx_rtd_theme; extra == "dev"
37
+ Requires-Dist: sphinx-bootstrap-theme; extra == "dev"
38
+ Requires-Dist: furo; extra == "dev"
39
+ Requires-Dist: myst-parser; extra == "dev"
40
+ Requires-Dist: notebook; extra == "dev"
41
+ Requires-Dist: nbsphinx; extra == "dev"
42
+ Requires-Dist: nbsphinx-link; extra == "dev"
43
+ Requires-Dist: sphinx-argparse; extra == "dev"
44
+ Requires-Dist: numpydoc; extra == "dev"
45
+ Requires-Dist: docutils<0.21; extra == "dev"
36
46
  Dynamic: license-file
37
47
 
38
48
  <img src="https://schmidtaf.gitlab.io/plot-misc/_images/icon.png" alt="plot-misc icon" width="250"/>
39
49
 
40
50
  # A collection of plotting functions
41
- __version__: `2.1.0`
51
+ __version__: `2.2.1`
42
52
 
43
53
  This repository collects plotting modules written on top of `matplotlib`.
44
54
  The functions are intended to set up light-touch, basic illustrations that
@@ -48,7 +58,8 @@ covering forest plots, volcano plots, incidence matrices/bubble charts,
48
58
  illustrations to evaluate prediction models (e.g. feature importance, net benefit, calibration plots),
49
59
  and more.
50
60
 
51
- The documentation for plot-misc can be found [here](https://SchmidtAF.gitlab.io/plot-misc/).
61
+ Please consult the **[documentation](https://SchmidtAF.gitlab.io/plot-misc/)**
62
+ for plot-misc.
52
63
 
53
64
  ## Installation
54
65
  The package is available on PyPI, and conda, with the latest source code
@@ -58,7 +69,7 @@ available on gitlab.
58
69
 
59
70
  To install the package from PyPI, run:
60
71
 
61
- ```sh
72
+ ```bash
62
73
  pip install plot-misc
63
74
  ```
64
75
 
@@ -69,50 +80,94 @@ This installs the latest stable release along with its dependencies.
69
80
  A Conda package is maintained in my personal Conda channel.
70
81
  To install from this channel, run:
71
82
 
72
-
73
- ```sh
83
+ ```bash
74
84
  conda install afschmidt::plot-misc
75
85
  ```
76
86
 
77
87
  ### Installation using gitlab
78
88
 
79
- If you require the latest updates, potentially not yet formally released, you can install the package directly from GitLab.
89
+ If you require the latest updates, potentially not yet formally released,
90
+ you can install the package directly from GitLab.
80
91
 
81
92
  First, clone the repository and move into its root directory:
82
93
 
83
- ```sh
94
+ ```bash
84
95
  git clone git@gitlab.com:SchmidtAF/plot-misc.git
85
96
  cd plot-misc
86
97
  ```
87
98
 
88
99
  Install the dependencies:
89
100
 
90
- ```sh
101
+ ```bash
91
102
  # From the root of the repository
92
103
  conda env create --file ./resources/conda/envs/conda_create.yaml
93
104
  ```
94
105
 
95
106
  To add to an existing environment use:
96
107
 
97
- ```sh
108
+ ```bash
98
109
  # From the root of the repository
99
110
  conda env update --file ./resources/conda/envs/conda_update.yaml
100
111
  ```
101
112
 
102
113
  Next the package can be installed:
103
114
 
104
- ```sh
105
- python -m pip install .
115
+ ```bash
116
+ make install
106
117
  ```
107
118
 
108
- Or for an editable (developer) install run the command below from the
109
- root of the repository.
110
- The difference with this is that you can just run `git pull` to
111
- update repository, or switch branches without re-installing:
119
+ #### Development
120
+ For development work, install the package in editable mode with Git commit
121
+ hooks configured:
122
+
123
+ ```bash
124
+ make install-dev
125
+ ```
126
+ This command installs the package in editable mode and configures Git commit
127
+ hooks, allowing you to run `git pull` to update the repository or switch
128
+ branches without reinstalling.
112
129
 
113
- ```sh
130
+ Alternatively, you can install manually:
131
+ ```bash
114
132
  python -m pip install -e .
133
+ python .setup_git_hooks.py
134
+ ```
135
+
136
+ #### Git Hooks Configuration
137
+
115
138
 
139
+ When setting up a development environment, the `setup-hooks` command
140
+ configures Git hooks to enforce conventional commit message formatting and
141
+ spell check using `codespell`.
142
+
143
+ To view the commit message format requirements, run:
144
+
145
+ ```bash
146
+ ./.githooks/commit-msg -help
147
+ ```
148
+
149
+ For frequent use, add this function to your shell configuration (`~/.bashrc`
150
+ or `~/.zshrc`):
151
+
152
+ ```bash
153
+ commit-format-help() {
154
+ local git_root
155
+ git_root=$(git rev-parse --show-toplevel 2>/dev/null)
156
+
157
+ if [ -z "$git_root" ]; then
158
+ echo "Error: Not inside a git repository"
159
+ return 1
160
+ fi
161
+
162
+ local hook_path="$git_root/.githooks/commit-msg"
163
+
164
+ if [ ! -f "$hook_path" ]; then
165
+ echo "Error: commit-msg hook not found"
166
+ return 1
167
+ fi
168
+
169
+ "$hook_path" --help
170
+ }
116
171
  ```
117
172
 
118
173
  #### Validating the package
@@ -120,7 +175,7 @@ python -m pip install -e .
120
175
  After installing the package from GitLab, you may wish to run the test
121
176
  suite to confirm everything is working as expected:
122
177
 
123
- ```sh
178
+ ```bash
124
179
  # From the root of the repository
125
180
  pytest tests
126
181
  ```
@@ -131,3 +186,4 @@ Please have a look at the examples in
131
186
  [resources](https://gitlab.com/SchmidtAF/plot-misc/-/tree/master/resources/examples)
132
187
  for some possible recipes.
133
188
 
189
+
@@ -1,7 +1,7 @@
1
1
  <img src="https://schmidtaf.gitlab.io/plot-misc/_images/icon.png" alt="plot-misc icon" width="250"/>
2
2
 
3
3
  # A collection of plotting functions
4
- __version__: `2.1.0`
4
+ __version__: `2.2.1`
5
5
 
6
6
  This repository collects plotting modules written on top of `matplotlib`.
7
7
  The functions are intended to set up light-touch, basic illustrations that
@@ -11,7 +11,8 @@ covering forest plots, volcano plots, incidence matrices/bubble charts,
11
11
  illustrations to evaluate prediction models (e.g. feature importance, net benefit, calibration plots),
12
12
  and more.
13
13
 
14
- The documentation for plot-misc can be found [here](https://SchmidtAF.gitlab.io/plot-misc/).
14
+ Please consult the **[documentation](https://SchmidtAF.gitlab.io/plot-misc/)**
15
+ for plot-misc.
15
16
 
16
17
  ## Installation
17
18
  The package is available on PyPI, and conda, with the latest source code
@@ -21,7 +22,7 @@ available on gitlab.
21
22
 
22
23
  To install the package from PyPI, run:
23
24
 
24
- ```sh
25
+ ```bash
25
26
  pip install plot-misc
26
27
  ```
27
28
 
@@ -32,50 +33,94 @@ This installs the latest stable release along with its dependencies.
32
33
  A Conda package is maintained in my personal Conda channel.
33
34
  To install from this channel, run:
34
35
 
35
-
36
- ```sh
36
+ ```bash
37
37
  conda install afschmidt::plot-misc
38
38
  ```
39
39
 
40
40
  ### Installation using gitlab
41
41
 
42
- If you require the latest updates, potentially not yet formally released, you can install the package directly from GitLab.
42
+ If you require the latest updates, potentially not yet formally released,
43
+ you can install the package directly from GitLab.
43
44
 
44
45
  First, clone the repository and move into its root directory:
45
46
 
46
- ```sh
47
+ ```bash
47
48
  git clone git@gitlab.com:SchmidtAF/plot-misc.git
48
49
  cd plot-misc
49
50
  ```
50
51
 
51
52
  Install the dependencies:
52
53
 
53
- ```sh
54
+ ```bash
54
55
  # From the root of the repository
55
56
  conda env create --file ./resources/conda/envs/conda_create.yaml
56
57
  ```
57
58
 
58
59
  To add to an existing environment use:
59
60
 
60
- ```sh
61
+ ```bash
61
62
  # From the root of the repository
62
63
  conda env update --file ./resources/conda/envs/conda_update.yaml
63
64
  ```
64
65
 
65
66
  Next the package can be installed:
66
67
 
67
- ```sh
68
- python -m pip install .
68
+ ```bash
69
+ make install
69
70
  ```
70
71
 
71
- Or for an editable (developer) install run the command below from the
72
- root of the repository.
73
- The difference with this is that you can just run `git pull` to
74
- update repository, or switch branches without re-installing:
72
+ #### Development
73
+ For development work, install the package in editable mode with Git commit
74
+ hooks configured:
75
+
76
+ ```bash
77
+ make install-dev
78
+ ```
79
+ This command installs the package in editable mode and configures Git commit
80
+ hooks, allowing you to run `git pull` to update the repository or switch
81
+ branches without reinstalling.
75
82
 
76
- ```sh
83
+ Alternatively, you can install manually:
84
+ ```bash
77
85
  python -m pip install -e .
86
+ python .setup_git_hooks.py
87
+ ```
88
+
89
+ #### Git Hooks Configuration
90
+
78
91
 
92
+ When setting up a development environment, the `setup-hooks` command
93
+ configures Git hooks to enforce conventional commit message formatting and
94
+ spell check using `codespell`.
95
+
96
+ To view the commit message format requirements, run:
97
+
98
+ ```bash
99
+ ./.githooks/commit-msg -help
100
+ ```
101
+
102
+ For frequent use, add this function to your shell configuration (`~/.bashrc`
103
+ or `~/.zshrc`):
104
+
105
+ ```bash
106
+ commit-format-help() {
107
+ local git_root
108
+ git_root=$(git rev-parse --show-toplevel 2>/dev/null)
109
+
110
+ if [ -z "$git_root" ]; then
111
+ echo "Error: Not inside a git repository"
112
+ return 1
113
+ fi
114
+
115
+ local hook_path="$git_root/.githooks/commit-msg"
116
+
117
+ if [ ! -f "$hook_path" ]; then
118
+ echo "Error: commit-msg hook not found"
119
+ return 1
120
+ fi
121
+
122
+ "$hook_path" --help
123
+ }
79
124
  ```
80
125
 
81
126
  #### Validating the package
@@ -83,7 +128,7 @@ python -m pip install -e .
83
128
  After installing the package from GitLab, you may wish to run the test
84
129
  suite to confirm everything is working as expected:
85
130
 
86
- ```sh
131
+ ```bash
87
132
  # From the root of the repository
88
133
  pytest tests
89
134
  ```
@@ -94,3 +139,4 @@ Please have a look at the examples in
94
139
  [resources](https://gitlab.com/SchmidtAF/plot-misc/-/tree/master/resources/examples)
95
140
  for some possible recipes.
96
141
 
142
+
@@ -0,0 +1 @@
1
+ __version__ = '2.2.1'
@@ -30,11 +30,12 @@ from plot_misc.errors import (
30
30
  is_df,
31
31
  Error_MSG,
32
32
  )
33
- from typing import Any, Optional
33
+ from typing import Any
34
34
  from plot_misc.constants import Real
35
35
 
36
36
  # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
37
37
  def bar(data:pd.DataFrame, label:str, column:str,
38
+ positions:np.ndarray | list[Real] | None = None,
38
39
  error_max:str | None = None, error_min:str | None = None,
39
40
  colours:list[str]=['tab:blue', 'tab:pink'], transparency:float=0.7,
40
41
  wd:Real=1.0, edgecolour:str='black',
@@ -54,9 +55,13 @@ def bar(data:pd.DataFrame, label:str, column:str,
54
55
  The column name for the axes labels.
55
56
  column : `str`
56
57
  The column name for the bar height values.
57
- error_max : `str`, default `NoneType`
58
+ positions : `np.ndarray` or `list` or `None`, default `None`
59
+ Numeric positions for the bars along the category axis. If None,
60
+ bars are placed at integer positions 0, 1, 2, ... with tick
61
+ labels taken from the `label` column.
62
+ error_max : `str`, default `None`
58
63
  column name for the upper value of the error line segment.
59
- error_min : `str`, default `NoneType`
64
+ error_min : `str`, default `None`
60
65
  column name for the lower value of the error line segment.
61
66
  colours : `list` [`str`], default ['tab:blue', 'tab:pink']
62
67
  Colours for the bars; recycled if shorter than the number of bars.
@@ -68,7 +73,7 @@ def bar(data:pd.DataFrame, label:str, column:str,
68
73
  The bar edgecolour.
69
74
  horizontal : `bool`, default `False`
70
75
  Whether plot a horizontal bar chart.
71
- ax : `plt.ax`, default `NoneType`
76
+ ax : `plt.ax`, default `None`
72
77
  The pyplot.axes object.
73
78
  figsize : `tuple` [`float`, `float`], default (2, 2),
74
79
  The figure size in inches, when ax is set to None.
@@ -96,6 +101,7 @@ def bar(data:pd.DataFrame, label:str, column:str,
96
101
  is_df(data)
97
102
  is_type(label, str)
98
103
  is_type(column, str)
104
+ is_type(positions, (type(None), np.ndarray, list))
99
105
  is_type(colours, list)
100
106
  is_type(transparency, float)
101
107
  is_type(wd, (float, int))
@@ -117,10 +123,19 @@ def bar(data:pd.DataFrame, label:str, column:str,
117
123
  # ### check input
118
124
  if any(data.isna().any()):
119
125
  raise ValueError(Error_MSG.MISSING_DF.format('data'))
120
- # ### get labels
126
+ # ### get labels and positions
121
127
  labels = data[label]
128
+ if positions is None:
129
+ pos = np.arange(len(labels))
130
+ else:
131
+ pos = np.asarray(positions)
132
+ if len(pos) != len(labels):
133
+ raise ValueError(
134
+ f'Length of positions ({len(pos)}) does not match '
135
+ f'number of rows in data ({len(labels)}).'
136
+ )
122
137
  # ### plotting
123
- if horizontal == False:
138
+ if not horizontal:
124
139
  # plotting vertical bar chart
125
140
  new_kwargs = _update_kwargs(update_dict=kwargs_bar,
126
141
  edgecolor=edgecolour,
@@ -128,8 +143,10 @@ def bar(data:pd.DataFrame, label:str, column:str,
128
143
  alpha=transparency,
129
144
  zorder=2,
130
145
  )
131
- bars = ax.bar(labels, height=data[column], **new_kwargs,
146
+ bars = ax.bar(pos, height=data[column], **new_kwargs,
132
147
  )
148
+ ax.set_xticks(pos)
149
+ ax.set_xticklabels(labels)
133
150
  else:
134
151
  # plotting horizontal bar chart
135
152
  new_kwargs = _update_kwargs(update_dict=kwargs_bar,
@@ -138,13 +155,15 @@ def bar(data:pd.DataFrame, label:str, column:str,
138
155
  alpha=transparency,
139
156
  zorder=2,
140
157
  )
141
- bars = ax.barh(labels, width=data[column], **new_kwargs,
158
+ bars = ax.barh(pos, width=data[column], **new_kwargs,
142
159
  )
160
+ ax.set_yticks(pos)
161
+ ax.set_yticklabels(labels)
143
162
  # do we need to plot error bars
144
163
  if error_min is not None or error_max is not None:
145
164
  # finding the mid points of the bars and
146
165
  # initialising the bounds, allowing for one-sided limits.
147
- if horizontal == False:
166
+ if not horizontal:
148
167
  min_l = [b.get_y() + b.get_height() for b in bars]
149
168
  max_l = min_l.copy()
150
169
  else:
@@ -164,7 +183,7 @@ def bar(data:pd.DataFrame, label:str, column:str,
164
183
  color='black',
165
184
  zorder=1,
166
185
  )
167
- if horizontal == False:
186
+ if not horizontal:
168
187
  mids = [b.get_x() + b.get_width() / 2 for b in bars]
169
188
  ax.vlines(mids, min_l, max_l, **new_kwargs_error,)
170
189
  else:
@@ -178,10 +197,11 @@ def bar(data:pd.DataFrame, label:str, column:str,
178
197
 
179
198
  # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
180
199
  def stack_bar(data:pd.DataFrame, label:str, columns:list[str],
200
+ positions:np.ndarray | list[Real] | None = None,
181
201
  colours:list[str]=['tab:blue', 'tab:pink'],
182
202
  transparency:float=0.7, wd:Real=1.0, edgecolour:str='black',
183
203
  horizontal:bool = False, figsize:tuple[Real,Real] = (2,2),
184
- ax:plt.Axes | None = None, **kwargs:Optional[Any],
204
+ ax:plt.Axes | None = None, **kwargs:Any,
185
205
  ) -> tuple[plt.Figure, plt.Axes]:
186
206
  """
187
207
  Plot a stacked bar chart with each bar divided into segments.
@@ -194,6 +214,9 @@ def stack_bar(data:pd.DataFrame, label:str, columns:list[str],
194
214
  Column name used for bar labels.
195
215
  columns : `list` [`str`]
196
216
  Column names representing bar segments to stack.
217
+ positions : `np.ndarray` or `list` or `None`, default `None`
218
+ Numeric positions for the bars along the category axis. If None,
219
+ bars are placed at integer positions 0, 1, 2, ...
197
220
  colours : `list` [`str`]
198
221
  List of colours for each stack segment.
199
222
  transparency : `float`, default 0.7
@@ -204,7 +227,7 @@ def stack_bar(data:pd.DataFrame, label:str, columns:list[str],
204
227
  Colour for bar borders.
205
228
  horizontal : `bool`, default `False`
206
229
  Whether plot a horizontal barchart.
207
- ax : `plt.ax`, default `NoneType`
230
+ ax : `plt.ax`, default `None`
208
231
  The pyplot.axes object.
209
232
  figsize : `tuple` [`float`, `float`], default (2, 2),
210
233
  The figure size in inches, when ax is set to None.
@@ -222,6 +245,7 @@ def stack_bar(data:pd.DataFrame, label:str, columns:list[str],
222
245
  is_df(data)
223
246
  is_type(label, str)
224
247
  is_type(columns, list)
248
+ is_type(positions, (type(None), np.ndarray, list))
225
249
  is_type(colours, list)
226
250
  is_type(transparency, float)
227
251
  is_type(wd, (float, int))
@@ -233,7 +257,7 @@ def stack_bar(data:pd.DataFrame, label:str, columns:list[str],
233
257
  f, ax = plt.subplots(figsize=figsize)
234
258
  else:
235
259
  f = ax.figure
236
- # ### should not be any missings
260
+ # ### should not be any missing
237
261
  # NOTE consider making this into a function
238
262
  if any(data.isna().any()):
239
263
  raise ValueError(Error_MSG.MISSING_DF.format('data'))
@@ -254,17 +278,17 @@ def stack_bar(data:pd.DataFrame, label:str, columns:list[str],
254
278
  color=colours[idx],
255
279
  alpha=transparency,
256
280
  )
257
- if horizontal == False:
281
+ if not horizontal:
258
282
  new_kwargs = _update_kwargs(new_kwargs, bottom=left,
259
283
  )
260
284
  else:
261
285
  new_kwargs = _update_kwargs(new_kwargs, left=left,
262
286
  )
263
287
  # The actual plotting
264
- # NOTE adding wd here because it bar assigns it to either width or
288
+ # NOTE adding wd here because bar assigns it to either width or
265
289
  # height depending on horizontal.
266
- _, ax = bar(data=data, label=label, column=name, horizontal=horizontal,
267
- wd=wd, ax=ax, kwargs_bar=new_kwargs,
290
+ _, ax = bar(data=data, label=label, column=name, positions=positions,
291
+ horizontal=horizontal, wd=wd, ax=ax, kwargs_bar=new_kwargs,
268
292
  )
269
293
  # updating the coordinate where the last bar stops
270
294
  left = left + data[name]
@@ -277,6 +301,7 @@ def stack_bar(data:pd.DataFrame, label:str, columns:list[str],
277
301
  # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
278
302
  def subtotal_bar(data:pd.DataFrame, label:str, total_col:str,
279
303
  subtotal_col: str | None = None,
304
+ positions:np.ndarray | list[Real] | None = None,
280
305
  colours:tuple[str,str]=('grey','tab:blue'),
281
306
  transparency:tuple[float,float]=(0.7,0.9),
282
307
  wd:tuple[float,float]=(1,0.6),
@@ -299,8 +324,11 @@ def subtotal_bar(data:pd.DataFrame, label:str, total_col:str,
299
324
  Column name for axis labels.
300
325
  total_col : `str`
301
326
  Column containing values for the base (total) bars.
302
- subtotal_col : `str` or `None`, default `NoneType`
327
+ subtotal_col : `str` or `None`, default `None`
303
328
  Column containing values for (smaller) overlaid subtotal bars.
329
+ positions : `np.ndarray` or `list` or `None`, default `None`
330
+ Numeric positions for the bars along the category axis. If None,
331
+ bars are placed at integer positions 0, 1, 2, ...
304
332
  colours : `tuple` [`str`,`str`], default ("grey", "tab:blue")
305
333
  Colours for the total and subtotal bars.
306
334
  transparency : `tuple` [`float`,`float`], default (0.7, 0.9)
@@ -339,6 +367,7 @@ def subtotal_bar(data:pd.DataFrame, label:str, total_col:str,
339
367
  is_type(ax, (type(None), plt.Axes))
340
368
  is_type(total_col, str)
341
369
  is_type(subtotal_col, (str, type(None)))
370
+ is_type(positions, (type(None), np.ndarray, list))
342
371
  is_type(zorder, tuple)
343
372
  is_type(colours, tuple)
344
373
  is_type(transparency, tuple)
@@ -370,6 +399,7 @@ def subtotal_bar(data:pd.DataFrame, label:str, total_col:str,
370
399
  ax=ax,
371
400
  label=label,
372
401
  column=total_col,
402
+ positions=positions,
373
403
  colours=[colours[0]],
374
404
  transparency=transparency[0],
375
405
  wd=wd[0],
@@ -378,7 +408,7 @@ def subtotal_bar(data:pd.DataFrame, label:str, total_col:str,
378
408
  kwargs_bar=new_total_kwargs_bar,
379
409
  )
380
410
  # plot subtotal
381
- if not subtotal_col is None:
411
+ if subtotal_col is not None:
382
412
  subtotal = data[subtotal_col]
383
413
  # updating kwargs
384
414
  new_subtotal_kwargs_bar = _update_kwargs(
@@ -390,6 +420,7 @@ def subtotal_bar(data:pd.DataFrame, label:str, total_col:str,
390
420
  ax=ax,
391
421
  label=label,
392
422
  column=subtotal_col,
423
+ positions=positions,
393
424
  colours=[colours[1]],
394
425
  transparency=transparency[1],
395
426
  wd=wd[1],
@@ -405,6 +436,7 @@ def subtotal_bar(data:pd.DataFrame, label:str, total_col:str,
405
436
 
406
437
  # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
407
438
  def group_bar(data:pd.DataFrame, label:str, columns:list[str],
439
+ group_positions:np.ndarray | list[Real] | None = None,
408
440
  errors_max:list[str] | None = None,
409
441
  errors_min:list[str] | None = None,
410
442
  colours:list[str]=['tab:blue', 'tab:pink'],
@@ -433,10 +465,14 @@ def group_bar(data:pd.DataFrame, label:str, columns:list[str],
433
465
  Column name for group labels.
434
466
  column : `list` [`str`]
435
467
  Value columns to plot as grouped bars.
436
- errors_max : `list` [`str`] or `None`, default `NoneType`
468
+ group_positions : `np.ndarray` or `list` or `None`, default `None`
469
+ Numeric positions for the group centres along the category axis.
470
+ If None, groups are placed at positions determined by
471
+ `group_spacing` (0, group_spacing, 2*group_spacing, ...).
472
+ errors_max : `list` [`str`] or `None`, default `None`
437
473
  Column names in `data` containing the upper values of the error bars.
438
474
  Should be structured similarly to `columns` if used.
439
- errors_min : `list` [`str`] or `None` default `NoneType`
475
+ errors_min : `list` [`str`] or `None` default `None`
440
476
  Column names in `data` containing the lower values of the error bars.
441
477
  colours : `list` [`str`], default ['tab:blue', 'tab:pink']
442
478
  Colours for the bars. Recycled if fewer colours than `columns`.
@@ -448,7 +484,7 @@ def group_bar(data:pd.DataFrame, label:str, columns:list[str],
448
484
  The bar edge colours.
449
485
  horizontal : `bool`, default `False`
450
486
  Whether plot a horizontal barchart.
451
- ax : `plt.ax`, default `NoneType`
487
+ ax : `plt.ax`, default `None`
452
488
  The pyplot.axes object.
453
489
  figsize : `tuple` [`float`, `float`], default (2, 2),
454
490
  The figure size in inches, when ax is set to None.
@@ -464,11 +500,10 @@ def group_bar(data:pd.DataFrame, label:str, columns:list[str],
464
500
  ax : plt.Axes
465
501
  The matplotlib Axes object with the plot.
466
502
  """
467
- # constants
468
- OFFSET_COL = "__offset__"
469
503
  # check input - most will be done by bar, just keeping the minimum
470
504
  is_df(data)
471
505
  is_type(columns, list)
506
+ is_type(group_positions, (type(None), np.ndarray, list))
472
507
  is_type(errors_max, (type(None),list))
473
508
  is_type(errors_min, (type(None),list))
474
509
  is_type(horizontal, bool)
@@ -483,8 +518,16 @@ def group_bar(data:pd.DataFrame, label:str, columns:list[str],
483
518
  # ### prepare the loop
484
519
  # the number of bars for each group
485
520
  n_bars = len(columns)
486
- # the number of groups
487
- base = np.arange(data.shape[0]) * group_spacing
521
+ # the base position of each group
522
+ if group_positions is None:
523
+ base = np.arange(data.shape[0]) * group_spacing
524
+ else:
525
+ base = np.asarray(group_positions)
526
+ if len(base) != data.shape[0]:
527
+ raise ValueError(
528
+ f'Length of group_positions ({len(base)}) does not '
529
+ f'match number of rows in data ({data.shape[0]}).'
530
+ )
488
531
  # the total width of all the bars in a single group
489
532
  spacing_per_bar = bar_spacing * wd
490
533
  total_spacing = spacing_per_bar * (n_bars - 1)
@@ -494,20 +537,19 @@ def group_bar(data:pd.DataFrame, label:str, columns:list[str],
494
537
  group_width = wd * n_bars + total_spacing
495
538
  tick_pos = base + (group_width - wd) / 2
496
539
  # looping
497
- df_offset = data.copy()
498
540
  for i, column in enumerate(columns):
499
541
  # the location of the bar
500
542
  offset = base + i * (wd + spacing_per_bar)
501
- df_offset[OFFSET_COL] = offset
502
543
  # cycling the colours
503
544
  col = colours[i % len(colours)]
504
545
  # the limits
505
546
  err_max = errors_max[i] if errors_max else None
506
547
  err_min = errors_min[i] if errors_min else None
507
548
  _ = bar(
508
- data=df_offset,
509
- label=OFFSET_COL,
549
+ data=data,
550
+ label=label,
510
551
  column=column,
552
+ positions=offset,
511
553
  error_max=err_max,
512
554
  error_min=err_min,
513
555
  colours=[col],
@@ -42,9 +42,9 @@ class ForestNames(object):
42
42
  PVALUE = 'p-value'
43
43
  CI = 'confidence_interval'
44
44
  data_table = 'data_table'
45
- EmpericalSupport_Coverage = 'coverage'
46
- EmpericalSupport_Compatability = 'compatibility'
47
- EmpericalSupportResults = 'results_'
45
+ EmpiricalSupport_Coverage = 'coverage'
46
+ EmpiricalSupport_Compatibility = 'compatibility'
47
+ EmpiricalSupportResults = 'results_'
48
48
 
49
49
  # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
50
50
  # Utils Names
@@ -103,7 +103,7 @@ class NamesIncidenceMatrix(object):
103
103
  GRID_POS_O = 'outline'
104
104
 
105
105
  # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
106
- class NamesMachineLearnig(object):
106
+ class NamesMachineLearning(object):
107
107
  '''
108
108
  Names used by machinelearning.py
109
109
  '''