data-visualiser-package 0.1.0__tar.gz → 0.2.0__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 (30) hide show
  1. {data_visualiser_package-0.1.0 → data_visualiser_package-0.2.0}/.github/workflows/python-package.yml +14 -14
  2. {data_visualiser_package-0.1.0 → data_visualiser_package-0.2.0}/.gitignore +5 -0
  3. data_visualiser_package-0.2.0/CHANGELOG.md +60 -0
  4. data_visualiser_package-0.2.0/PKG-INFO +243 -0
  5. {data_visualiser_package-0.1.0 → data_visualiser_package-0.2.0}/PYPI_PUBLISH.md +0 -1
  6. data_visualiser_package-0.2.0/README.md +208 -0
  7. data_visualiser_package-0.2.0/examples/demo.py +108 -0
  8. {data_visualiser_package-0.1.0 → data_visualiser_package-0.2.0}/pyproject.toml +40 -8
  9. data_visualiser_package-0.2.0/src/data_visualiser_package/__init__.py +30 -0
  10. data_visualiser_package-0.2.0/src/data_visualiser_package/data_visualiser.py +801 -0
  11. data_visualiser_package-0.2.0/src/data_visualiser_package/export_latex_tables.py +215 -0
  12. data_visualiser_package-0.2.0/tests/test_data_visualiser.py +389 -0
  13. data_visualiser_package-0.2.0/uv.lock +2809 -0
  14. data_visualiser_package-0.1.0/CHANGELOG.md +0 -16
  15. data_visualiser_package-0.1.0/PKG-INFO +0 -181
  16. data_visualiser_package-0.1.0/README.md +0 -154
  17. data_visualiser_package-0.1.0/examples/demo.py +0 -95
  18. data_visualiser_package-0.1.0/setup.py +0 -45
  19. data_visualiser_package-0.1.0/src/data_visualiser_package/__init__.py +0 -12
  20. data_visualiser_package-0.1.0/src/data_visualiser_package/data_visualiser.py +0 -643
  21. data_visualiser_package-0.1.0/src/data_visualiser_package/export_latex_tables.py +0 -102
  22. data_visualiser_package-0.1.0/tests/test_data_visualiser.py +0 -100
  23. data_visualiser_package-0.1.0/uv.lock +0 -298
  24. {data_visualiser_package-0.1.0 → data_visualiser_package-0.2.0}/.python-version +0 -0
  25. {data_visualiser_package-0.1.0 → data_visualiser_package-0.2.0}/LICENSE +0 -0
  26. {data_visualiser_package-0.1.0 → data_visualiser_package-0.2.0}/MANIFEST.in +0 -0
  27. {data_visualiser_package-0.1.0 → data_visualiser_package-0.2.0}/pytest.ini +0 -0
  28. {data_visualiser_package-0.1.0 → data_visualiser_package-0.2.0}/requirements-dev.txt +0 -0
  29. {data_visualiser_package-0.1.0 → data_visualiser_package-0.2.0}/requirements.txt +0 -0
  30. {data_visualiser_package-0.1.0 → data_visualiser_package-0.2.0}/src/data_visualiser_package/py.typed +0 -0
@@ -2,38 +2,38 @@ name: Python Package
2
2
 
3
3
  on:
4
4
  push:
5
- branches: [ main ]
5
+ branches: [ master, main ]
6
6
  tags:
7
7
  - 'v*'
8
8
  pull_request:
9
- branches: [ main ]
9
+ branches: [ master, main ]
10
10
 
11
11
  jobs:
12
12
  test:
13
13
  runs-on: ubuntu-latest
14
14
  strategy:
15
15
  matrix:
16
- python-version: ['3.7', '3.8', '3.9', '3.10', '3.11']
16
+ python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
17
17
 
18
18
  steps:
19
- - uses: actions/checkout@v3
19
+ - uses: actions/checkout@v4
20
20
  - name: Set up Python ${{ matrix.python-version }}
21
- uses: actions/setup-python@v4
21
+ uses: actions/setup-python@v5
22
22
  with:
23
23
  python-version: ${{ matrix.python-version }}
24
24
  - name: Install dependencies
25
25
  run: |
26
26
  python -m pip install --upgrade pip
27
- python -m pip install flake8 pytest
28
- if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
29
- python -m pip install -e .
27
+ python -m pip install -e ".[dev]"
30
28
  - name: Lint with flake8
31
29
  run: |
32
30
  # stop the build if there are Python syntax errors or undefined names
33
- flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
31
+ flake8 src tests examples --count --select=E9,F63,F7,F82 --show-source --statistics
34
32
  # exit-zero treats all errors as warnings
35
- flake8 . --count --exit-zero --max-complexity=10 --max-line-length=120 --statistics
33
+ flake8 src tests examples --count --exit-zero --max-complexity=10 --max-line-length=120 --statistics
36
34
  - name: Test with pytest
35
+ env:
36
+ MPLBACKEND: Agg
37
37
  run: |
38
38
  pytest
39
39
 
@@ -41,13 +41,13 @@ jobs:
41
41
  needs: test
42
42
  runs-on: ubuntu-latest
43
43
  if: startsWith(github.ref, 'refs/tags/')
44
-
44
+
45
45
  steps:
46
- - uses: actions/checkout@v3
46
+ - uses: actions/checkout@v4
47
47
  - name: Set up Python
48
- uses: actions/setup-python@v4
48
+ uses: actions/setup-python@v5
49
49
  with:
50
- python-version: '3.9'
50
+ python-version: '3.12'
51
51
  - name: Install dependencies
52
52
  run: |
53
53
  python -m pip install --upgrade pip
@@ -49,3 +49,8 @@ demo_output/
49
49
  .DS_Store
50
50
  Thumbs.db
51
51
  desktop.ini
52
+
53
+ # Editor/tool caches
54
+ tags
55
+ .mypy_cache/
56
+ .pytest_cache/
@@ -0,0 +1,60 @@
1
+ # Changelog
2
+
3
+ All notable changes to the Data Visualiser Package will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.2.0] - 2026-09-11
9
+
10
+ ### Fixed
11
+ - Missing values are now handled consistently: `None`, `NaN` and `pd.NA` all become a single
12
+ `"NaN"` category placed last, `show_nan=False` really excludes them, and numeric categories
13
+ are ordered numerically (`2 < 5 < 10`) instead of alphabetically. Previously values were
14
+ converted to strings before being filled, which produced `"None"`/`"nan"` labels and made
15
+ `show_nan` a no-op.
16
+ - Every column title of saved LaTeX tables is now bold, including titles containing `_` or `%`
17
+ (`n_nan`, `nan_perc`, `25%`, ...). Count tables now use the same bold-header writer.
18
+ - LaTeX export no longer depends on pandas' `to_latex`, which required the undeclared
19
+ optional dependency `jinja2` on pandas >= 2.0 and escaped special characters differently
20
+ across versions. Special characters (`#`, `%`, `_`, `&`, ...) are always escaped.
21
+ - Saving a distribution table whose standard deviation is NaN (single value / all missing)
22
+ no longer crashes.
23
+ - `plot_kwargs` dictionaries passed by the caller (and the defaults) are no longer mutated.
24
+ - `create_dirs=True` now creates each configured directory independently.
25
+ - Figures are saved and closed by object rather than through the "current figure".
26
+ - CI runs on the `master` branch and on Python 3.9-3.13.
27
+
28
+ ### Changed (breaking)
29
+ - `col` / `hue` are now required arguments of the stratified and hued methods (the previous
30
+ default `"sexe_desc"` was dataset-specific).
31
+ - `save_fig=True` / `save_table=True` raise a `ValueError` when the corresponding directory
32
+ is not configured, instead of silently doing nothing.
33
+ - `get_dist_stats_df` / `get_dist_stratified_stats_df` / `get_box_stratified_plot` raise a
34
+ `TypeError` for non-numeric variables.
35
+ - `categorise_variable_in_df` raises `ValueError` (instead of `AssertionError`) when
36
+ `forced_order` is incomplete.
37
+ - Minimum supported Python is 3.9; minimum pandas is 1.5.
38
+ - The unused low-level helpers of `export_latex_tables` (`format_table`, `boldify_line`, ...)
39
+ were removed. `get_latex_table_bold_col_header` and `get_n_decimals_to_include` remain.
40
+ - `setup.py` was removed; `pyproject.toml` (hatchling) is the single build configuration.
41
+
42
+ ### Added
43
+ - `DataVisualiser(nan_label=..., fig_format=..., dpi=..., table_format=...)` to customise the
44
+ missing-value label, the figure file format/resolution and the default table format.
45
+ - Tables can be saved as LaTeX, CSV, Markdown or HTML (`table_format` on the constructor or
46
+ per call).
47
+ - `get_box_stratified_plot(var, df, col, kind="box"|"violin")`.
48
+ - `get_count_stratified_stats_df(..., wide=True)` returns a crosstab with strata as columns.
49
+ - `round_n_digits` on the distribution table methods (auto-derived from the spread by default).
50
+ - Public helpers `prepare_categorical`, `dataframe_to_latex`, `dataframe_to_markdown`.
51
+
52
+ ## [0.1.0] - 2025-05-14
53
+
54
+ ### Added
55
+ - Initial release of the DataVisualiser package
56
+ - Core functionality for data visualization and statistical analysis
57
+ - Support for count plots and distribution plots
58
+ - Support for stratified visualizations
59
+ - LaTeX table export functionality
60
+ - Comprehensive documentation and examples
@@ -0,0 +1,243 @@
1
+ Metadata-Version: 2.5
2
+ Name: data-visualiser-package
3
+ Version: 0.2.0
4
+ Summary: A utility package for data visualization and statistical analysis with Matplotlib and Seaborn
5
+ Project-URL: Homepage, https://github.com/jonathan-doenz/data-visualiser-package
6
+ Project-URL: Bug Tracker, https://github.com/jonathan-doenz/data-visualiser-package/issues
7
+ Author-email: jonathan-doenz <jonathan.doenz@gmail.com>
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
20
+ Classifier: Topic :: Scientific/Engineering :: Visualization
21
+ Requires-Python: >=3.9
22
+ Requires-Dist: matplotlib>=3.5.0
23
+ Requires-Dist: numpy>=1.21.0
24
+ Requires-Dist: pandas>=1.5.0
25
+ Requires-Dist: seaborn>=0.11.2
26
+ Provides-Extra: dev
27
+ Requires-Dist: black>=23.0.0; extra == 'dev'
28
+ Requires-Dist: build>=0.10.0; extra == 'dev'
29
+ Requires-Dist: flake8>=5.0.0; extra == 'dev'
30
+ Requires-Dist: isort>=5.10.0; extra == 'dev'
31
+ Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
32
+ Requires-Dist: pytest>=7.0.0; extra == 'dev'
33
+ Requires-Dist: twine>=4.0.0; extra == 'dev'
34
+ Description-Content-Type: text/markdown
35
+
36
+ # Data Visualiser Package
37
+
38
+ A Python utility package for data visualization and statistical analysis with Matplotlib and Seaborn. This package provides a simple interface to create common visualizations and statistical tables for exploratory data analysis and reporting.
39
+
40
+ ## Installation
41
+
42
+ ```bash
43
+ pip install data-visualiser-package
44
+ ```
45
+
46
+ Requires Python 3.9+ (pandas >= 1.5, seaborn >= 0.11.2, matplotlib >= 3.5).
47
+
48
+ ## Features
49
+
50
+ - Count plots and distribution plots, plain or stratified by a categorical variable
51
+ - Box / violin plots of a numerical variable per category
52
+ - Count and distribution statistics tables, including stratified (long or wide) versions
53
+ - Save figures as PNG, PDF, SVG, ... and tables as LaTeX (bold headers, escaped special characters), CSV, Markdown or HTML
54
+ - Consistent handling of missing values: `None`, `NaN` and `pd.NA` become one `"NaN"` category placed last, or are excluded with `show_nan=False`
55
+ - Categories are ordered on their native type (`2 < 5 < 10`), or by an explicit `forced_order`
56
+
57
+ ## Quick Start
58
+
59
+ ```python
60
+ import pandas as pd
61
+ from data_visualiser_package import DataVisualiser
62
+
63
+ # Create a DataVisualiser instance
64
+ dv = DataVisualiser(
65
+ record_unit_name="patient", # What a single record represents
66
+ figures_dirpath="./figures", # Where to save figures
67
+ tables_dirpath="./tables", # Where to save tables
68
+ create_dirs=True, # Create directories if they don't exist
69
+ fig_format="png", # Any format matplotlib supports (png, pdf, svg, ...)
70
+ dpi=150, # Resolution of saved figures (None = matplotlib default)
71
+ table_format="latex", # Default table format: latex, csv, markdown or html
72
+ )
73
+
74
+ # Load your data
75
+ df = pd.read_csv("your_data.csv")
76
+
77
+ # Create a count plot
78
+ fig, ax = dv.get_count_plot(
79
+ var="diagnosis", # Categorical variable to plot
80
+ df=df, # DataFrame containing the data
81
+ show_nan=True, # Show NaN values as a separate (last) category
82
+ save_fig=True, # Save the figure to disk
83
+ )
84
+
85
+ # Create a distribution plot
86
+ fig, ax = dv.get_dist_plot(var="age", df=df, save_fig=True)
87
+
88
+ # Generate statistics tables
89
+ stats_df = dv.get_count_stats_df(var="diagnosis", df=df, save_table=True)
90
+
91
+ # Create stratified visualizations
92
+ fg = dv.get_dist_stratified_plot(var="age", df=df, col="gender", save_fig=True)
93
+
94
+ # Box plot of a numerical variable per category
95
+ fig, ax = dv.get_box_stratified_plot(var="age", df=df, col="diagnosis", save_fig=True)
96
+
97
+ # Wide crosstab (rows: diagnosis categories, columns: one block per gender), saved as CSV
98
+ wide = dv.get_count_stratified_stats_df(
99
+ "diagnosis", df, col="gender", wide=True, save_table=True, table_format="csv"
100
+ )
101
+ ```
102
+
103
+ ## Missing values and category order
104
+
105
+ All categorical inputs go through the same preprocessing (`prepare_categorical`):
106
+
107
+ - `None`, `np.nan` and `pd.NA` are treated as missing. With `show_nan=True` (default) they form
108
+ a single category labelled `"NaN"` (configurable through `DataVisualiser(nan_label=...)`) that is
109
+ always placed last. With `show_nan=False` the records are excluded from that plot/table, and
110
+ percentages are computed over the remaining records.
111
+ - Categories are sorted on their native values, so numeric codes are ordered numerically.
112
+ Integral floats are labelled without decimals (`10.0` -> `"10"`).
113
+ - `forced_order` (and `forced_order_col` for the stratification variable) imposes an explicit
114
+ order and may list categories absent from the data (they appear with a count of 0). It must
115
+ cover every value present, otherwise a `ValueError` is raised.
116
+
117
+ Distribution tables report `n_nan` and `nan_perc` next to the usual `describe()` statistics.
118
+
119
+ ## Example
120
+
121
+ Here's a complete example of how to use the DataVisualiser class:
122
+
123
+ ```python
124
+ import pandas as pd
125
+ import numpy as np
126
+ from data_visualiser_package import DataVisualiser
127
+
128
+ # Create a sample dataset
129
+ np.random.seed(42)
130
+ n = 1000
131
+
132
+ data = {
133
+ 'age': np.random.normal(50, 15, n),
134
+ 'gender': np.random.choice(['Male', 'Female'], n),
135
+ 'diagnosis': np.random.choice(['Healthy', 'Condition A', 'Condition B', None], n, p=[0.6, 0.2, 0.15, 0.05]),
136
+ 'heart_rate': np.random.normal(80, 10, n),
137
+ 'blood_pressure': np.random.normal(120, 15, n)
138
+ }
139
+ df = pd.DataFrame(data)
140
+
141
+ dv = DataVisualiser(
142
+ record_unit_name="patient",
143
+ figures_dirpath="./output/figures",
144
+ tables_dirpath="./output/tables",
145
+ create_dirs=True
146
+ )
147
+
148
+ # Plots
149
+ dv.get_count_plot('gender', df, save_fig=True)
150
+ dv.get_count_plot('diagnosis', df, save_fig=True)
151
+ dv.get_count_stratified_plot('diagnosis', df, col='gender', save_fig=True)
152
+ dv.get_dist_plot('age', df, save_fig=True)
153
+ dv.get_dist_stratified_plot('age', df, col='gender', save_fig=True)
154
+ dv.get_dist_hued_plot('age', df, hue='gender', save_fig=True)
155
+ dv.get_box_stratified_plot('heart_rate', df, col='diagnosis', kind='violin', save_fig=True)
156
+
157
+ # Statistics tables
158
+ dv.get_count_stats_df('diagnosis', df, save_table=True, add_total=True)
159
+ dv.get_count_stratified_stats_df('diagnosis', df, col='gender', wide=True, save_table=True)
160
+ dv.get_dist_stats_df('age', df, save_table=True)
161
+ dv.get_dist_stratified_stats_df('age', df, col='gender', save_table=True, table_format="markdown")
162
+
163
+ print("All visualizations and tables have been generated successfully!")
164
+ ```
165
+
166
+ See `examples/demo.py` for a runnable version.
167
+
168
+ ## API Reference
169
+
170
+ ### DataVisualiser Class
171
+
172
+ ```python
173
+ class DataVisualiser(
174
+ record_unit_name="patient",
175
+ figures_dirpath=None,
176
+ tables_dirpath=None,
177
+ create_dirs=False,
178
+ nan_label="NaN",
179
+ fig_format="png",
180
+ dpi=None,
181
+ table_format="latex",
182
+ )
183
+ ```
184
+
185
+ `save_fig=True` / `save_table=True` require the corresponding directory to be configured and raise a `ValueError` otherwise. Saved figures are closed after writing.
186
+
187
+ #### Count Visualizations
188
+
189
+ - `get_count_plot(var, df, show_nan=True, save_fig=False, plot_kwargs=None, xlabels_rotation=None, forced_order=None)` -> `(fig, ax)`
190
+ - `get_count_stratified_plot(var, df, col, show_nan=True, show_nan_col=True, save_fig=False, col_wrap=3, plot_kwargs=None, xlabels_rotation=None, forced_order=None, forced_order_col=None)` -> `FacetGrid`
191
+ - `get_count_stats_df(var, df, show_nan=True, save_table=False, percentage=True, add_total=False, round_n_digits=1, forced_order=None, table_format=None)` -> `DataFrame`
192
+ - `get_count_stratified_stats_df(var, df, col, show_nan=True, show_nan_col=True, save_table=False, percentage=True, round_n_digits=1, forced_order=None, forced_order_col=None, wide=False, table_format=None)` -> `DataFrame` (percentages are within each stratum; `wide=True` returns strata as columns)
193
+
194
+ #### Distribution Visualizations
195
+
196
+ - `get_dist_plot(var, df, save_fig=False, plot_kwargs=None, xlabels_rotation=None)` -> `(fig, ax)` (`plot_kwargs` defaults to `{"kde": True}`)
197
+ - `get_dist_stratified_plot(var, df, col, show_nan_col=True, save_fig=False, col_wrap=3, plot_kwargs=None, xlabels_rotation=None, forced_order_col=None)` -> `FacetGrid`
198
+ - `get_dist_hued_plot(var, df, hue, show_nan_col=True, save_fig=False, plot_kwargs=None, xlabels_rotation=None, forced_order_col=None)` -> `FacetGrid`
199
+ - `get_box_stratified_plot(var, df, col, kind="box", show_nan_col=True, save_fig=False, plot_kwargs=None, xlabels_rotation=None, forced_order_col=None)` -> `(fig, ax)` (`kind` is `"box"` or `"violin"`)
200
+ - `get_dist_stats_df(var, df, save_table=False, round_n_digits=None, table_format=None)` -> `DataFrame`
201
+ - `get_dist_stratified_stats_df(var, df, col, show_nan_col=True, save_table=False, forced_order_col=None, round_n_digits=None, table_format=None)` -> `DataFrame`
202
+
203
+ The distribution methods require a numeric `var` (a `TypeError` is raised otherwise). When `round_n_digits` is `None`, the number of decimals written to the table is derived from the standard deviation of the data.
204
+
205
+ #### Output files
206
+
207
+ | Method | File name (in `figures_dirpath` / `tables_dirpath`) |
208
+ | --- | --- |
209
+ | `get_count_plot` | `count_plot_{var}.{fig_format}` |
210
+ | `get_count_stratified_plot` | `count_plot_{var}_for_each_{col}.{fig_format}` |
211
+ | `get_dist_plot` | `dist_plot_{var}.{fig_format}` |
212
+ | `get_dist_stratified_plot` | `dist_plot_{var}_for_each_{col}.{fig_format}` |
213
+ | `get_dist_hued_plot` | `dist_plot_{var}_for_hue_{hue}.{fig_format}` |
214
+ | `get_box_stratified_plot` | `{kind}_plot_{var}_for_each_{col}.{fig_format}` |
215
+ | `get_count_stats_df` | `count_stats_table_{var}.{tex,csv,md,html}` |
216
+ | `get_count_stratified_stats_df` | `count_stats_table_{var}_for_each_{col}.{tex,csv,md,html}` |
217
+ | `get_dist_stats_df` | `dist_stats_table_{var}.{tex,csv,md,html}` |
218
+ | `get_dist_stratified_stats_df` | `dist_stats_table_{var}_for_each_{col}.{tex,csv,md,html}` |
219
+
220
+ LaTeX tables use `booktabs` rules (`\toprule`, `\midrule`, `\bottomrule`), bold column titles and escaped special characters (`#`, `%`, `_`, `&`, ...).
221
+
222
+ ### Helper functions
223
+
224
+ - `prepare_categorical(series, show_nan=True, forced_order=None, nan_label="NaN")` -> ordered categorical `Series` of string labels
225
+ - `categorise_variable_in_df(var, df, forced_order=None, nan_replacement_str="NaN")` -> categorises a column in place
226
+ - `dataframe_to_latex(df, bold_header=True, round_n_digits=None, na_rep="", column_format=None)` -> `str`
227
+ - `dataframe_to_markdown(df, round_n_digits=None, na_rep="")` -> `str`
228
+
229
+ ## Development
230
+
231
+ ```bash
232
+ uv sync --group dev # or: pip install -e ".[dev]"
233
+ uv run pytest
234
+ uv run flake8 src tests examples --max-line-length=120
235
+ ```
236
+
237
+ ## License
238
+
239
+ MIT
240
+
241
+ ## Contributing
242
+
243
+ Contributions are welcome! Please feel free to submit a Pull Request.
@@ -56,7 +56,6 @@ When releasing a new version:
56
56
 
57
57
  1. Update the version number in:
58
58
  - `pyproject.toml`
59
- - `setup.py`
60
59
  - `src/data_visualiser_package/__init__.py`
61
60
 
62
61
  2. Add release notes to the README.md or CHANGELOG.md file.
@@ -0,0 +1,208 @@
1
+ # Data Visualiser Package
2
+
3
+ A Python utility package for data visualization and statistical analysis with Matplotlib and Seaborn. This package provides a simple interface to create common visualizations and statistical tables for exploratory data analysis and reporting.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install data-visualiser-package
9
+ ```
10
+
11
+ Requires Python 3.9+ (pandas >= 1.5, seaborn >= 0.11.2, matplotlib >= 3.5).
12
+
13
+ ## Features
14
+
15
+ - Count plots and distribution plots, plain or stratified by a categorical variable
16
+ - Box / violin plots of a numerical variable per category
17
+ - Count and distribution statistics tables, including stratified (long or wide) versions
18
+ - Save figures as PNG, PDF, SVG, ... and tables as LaTeX (bold headers, escaped special characters), CSV, Markdown or HTML
19
+ - Consistent handling of missing values: `None`, `NaN` and `pd.NA` become one `"NaN"` category placed last, or are excluded with `show_nan=False`
20
+ - Categories are ordered on their native type (`2 < 5 < 10`), or by an explicit `forced_order`
21
+
22
+ ## Quick Start
23
+
24
+ ```python
25
+ import pandas as pd
26
+ from data_visualiser_package import DataVisualiser
27
+
28
+ # Create a DataVisualiser instance
29
+ dv = DataVisualiser(
30
+ record_unit_name="patient", # What a single record represents
31
+ figures_dirpath="./figures", # Where to save figures
32
+ tables_dirpath="./tables", # Where to save tables
33
+ create_dirs=True, # Create directories if they don't exist
34
+ fig_format="png", # Any format matplotlib supports (png, pdf, svg, ...)
35
+ dpi=150, # Resolution of saved figures (None = matplotlib default)
36
+ table_format="latex", # Default table format: latex, csv, markdown or html
37
+ )
38
+
39
+ # Load your data
40
+ df = pd.read_csv("your_data.csv")
41
+
42
+ # Create a count plot
43
+ fig, ax = dv.get_count_plot(
44
+ var="diagnosis", # Categorical variable to plot
45
+ df=df, # DataFrame containing the data
46
+ show_nan=True, # Show NaN values as a separate (last) category
47
+ save_fig=True, # Save the figure to disk
48
+ )
49
+
50
+ # Create a distribution plot
51
+ fig, ax = dv.get_dist_plot(var="age", df=df, save_fig=True)
52
+
53
+ # Generate statistics tables
54
+ stats_df = dv.get_count_stats_df(var="diagnosis", df=df, save_table=True)
55
+
56
+ # Create stratified visualizations
57
+ fg = dv.get_dist_stratified_plot(var="age", df=df, col="gender", save_fig=True)
58
+
59
+ # Box plot of a numerical variable per category
60
+ fig, ax = dv.get_box_stratified_plot(var="age", df=df, col="diagnosis", save_fig=True)
61
+
62
+ # Wide crosstab (rows: diagnosis categories, columns: one block per gender), saved as CSV
63
+ wide = dv.get_count_stratified_stats_df(
64
+ "diagnosis", df, col="gender", wide=True, save_table=True, table_format="csv"
65
+ )
66
+ ```
67
+
68
+ ## Missing values and category order
69
+
70
+ All categorical inputs go through the same preprocessing (`prepare_categorical`):
71
+
72
+ - `None`, `np.nan` and `pd.NA` are treated as missing. With `show_nan=True` (default) they form
73
+ a single category labelled `"NaN"` (configurable through `DataVisualiser(nan_label=...)`) that is
74
+ always placed last. With `show_nan=False` the records are excluded from that plot/table, and
75
+ percentages are computed over the remaining records.
76
+ - Categories are sorted on their native values, so numeric codes are ordered numerically.
77
+ Integral floats are labelled without decimals (`10.0` -> `"10"`).
78
+ - `forced_order` (and `forced_order_col` for the stratification variable) imposes an explicit
79
+ order and may list categories absent from the data (they appear with a count of 0). It must
80
+ cover every value present, otherwise a `ValueError` is raised.
81
+
82
+ Distribution tables report `n_nan` and `nan_perc` next to the usual `describe()` statistics.
83
+
84
+ ## Example
85
+
86
+ Here's a complete example of how to use the DataVisualiser class:
87
+
88
+ ```python
89
+ import pandas as pd
90
+ import numpy as np
91
+ from data_visualiser_package import DataVisualiser
92
+
93
+ # Create a sample dataset
94
+ np.random.seed(42)
95
+ n = 1000
96
+
97
+ data = {
98
+ 'age': np.random.normal(50, 15, n),
99
+ 'gender': np.random.choice(['Male', 'Female'], n),
100
+ 'diagnosis': np.random.choice(['Healthy', 'Condition A', 'Condition B', None], n, p=[0.6, 0.2, 0.15, 0.05]),
101
+ 'heart_rate': np.random.normal(80, 10, n),
102
+ 'blood_pressure': np.random.normal(120, 15, n)
103
+ }
104
+ df = pd.DataFrame(data)
105
+
106
+ dv = DataVisualiser(
107
+ record_unit_name="patient",
108
+ figures_dirpath="./output/figures",
109
+ tables_dirpath="./output/tables",
110
+ create_dirs=True
111
+ )
112
+
113
+ # Plots
114
+ dv.get_count_plot('gender', df, save_fig=True)
115
+ dv.get_count_plot('diagnosis', df, save_fig=True)
116
+ dv.get_count_stratified_plot('diagnosis', df, col='gender', save_fig=True)
117
+ dv.get_dist_plot('age', df, save_fig=True)
118
+ dv.get_dist_stratified_plot('age', df, col='gender', save_fig=True)
119
+ dv.get_dist_hued_plot('age', df, hue='gender', save_fig=True)
120
+ dv.get_box_stratified_plot('heart_rate', df, col='diagnosis', kind='violin', save_fig=True)
121
+
122
+ # Statistics tables
123
+ dv.get_count_stats_df('diagnosis', df, save_table=True, add_total=True)
124
+ dv.get_count_stratified_stats_df('diagnosis', df, col='gender', wide=True, save_table=True)
125
+ dv.get_dist_stats_df('age', df, save_table=True)
126
+ dv.get_dist_stratified_stats_df('age', df, col='gender', save_table=True, table_format="markdown")
127
+
128
+ print("All visualizations and tables have been generated successfully!")
129
+ ```
130
+
131
+ See `examples/demo.py` for a runnable version.
132
+
133
+ ## API Reference
134
+
135
+ ### DataVisualiser Class
136
+
137
+ ```python
138
+ class DataVisualiser(
139
+ record_unit_name="patient",
140
+ figures_dirpath=None,
141
+ tables_dirpath=None,
142
+ create_dirs=False,
143
+ nan_label="NaN",
144
+ fig_format="png",
145
+ dpi=None,
146
+ table_format="latex",
147
+ )
148
+ ```
149
+
150
+ `save_fig=True` / `save_table=True` require the corresponding directory to be configured and raise a `ValueError` otherwise. Saved figures are closed after writing.
151
+
152
+ #### Count Visualizations
153
+
154
+ - `get_count_plot(var, df, show_nan=True, save_fig=False, plot_kwargs=None, xlabels_rotation=None, forced_order=None)` -> `(fig, ax)`
155
+ - `get_count_stratified_plot(var, df, col, show_nan=True, show_nan_col=True, save_fig=False, col_wrap=3, plot_kwargs=None, xlabels_rotation=None, forced_order=None, forced_order_col=None)` -> `FacetGrid`
156
+ - `get_count_stats_df(var, df, show_nan=True, save_table=False, percentage=True, add_total=False, round_n_digits=1, forced_order=None, table_format=None)` -> `DataFrame`
157
+ - `get_count_stratified_stats_df(var, df, col, show_nan=True, show_nan_col=True, save_table=False, percentage=True, round_n_digits=1, forced_order=None, forced_order_col=None, wide=False, table_format=None)` -> `DataFrame` (percentages are within each stratum; `wide=True` returns strata as columns)
158
+
159
+ #### Distribution Visualizations
160
+
161
+ - `get_dist_plot(var, df, save_fig=False, plot_kwargs=None, xlabels_rotation=None)` -> `(fig, ax)` (`plot_kwargs` defaults to `{"kde": True}`)
162
+ - `get_dist_stratified_plot(var, df, col, show_nan_col=True, save_fig=False, col_wrap=3, plot_kwargs=None, xlabels_rotation=None, forced_order_col=None)` -> `FacetGrid`
163
+ - `get_dist_hued_plot(var, df, hue, show_nan_col=True, save_fig=False, plot_kwargs=None, xlabels_rotation=None, forced_order_col=None)` -> `FacetGrid`
164
+ - `get_box_stratified_plot(var, df, col, kind="box", show_nan_col=True, save_fig=False, plot_kwargs=None, xlabels_rotation=None, forced_order_col=None)` -> `(fig, ax)` (`kind` is `"box"` or `"violin"`)
165
+ - `get_dist_stats_df(var, df, save_table=False, round_n_digits=None, table_format=None)` -> `DataFrame`
166
+ - `get_dist_stratified_stats_df(var, df, col, show_nan_col=True, save_table=False, forced_order_col=None, round_n_digits=None, table_format=None)` -> `DataFrame`
167
+
168
+ The distribution methods require a numeric `var` (a `TypeError` is raised otherwise). When `round_n_digits` is `None`, the number of decimals written to the table is derived from the standard deviation of the data.
169
+
170
+ #### Output files
171
+
172
+ | Method | File name (in `figures_dirpath` / `tables_dirpath`) |
173
+ | --- | --- |
174
+ | `get_count_plot` | `count_plot_{var}.{fig_format}` |
175
+ | `get_count_stratified_plot` | `count_plot_{var}_for_each_{col}.{fig_format}` |
176
+ | `get_dist_plot` | `dist_plot_{var}.{fig_format}` |
177
+ | `get_dist_stratified_plot` | `dist_plot_{var}_for_each_{col}.{fig_format}` |
178
+ | `get_dist_hued_plot` | `dist_plot_{var}_for_hue_{hue}.{fig_format}` |
179
+ | `get_box_stratified_plot` | `{kind}_plot_{var}_for_each_{col}.{fig_format}` |
180
+ | `get_count_stats_df` | `count_stats_table_{var}.{tex,csv,md,html}` |
181
+ | `get_count_stratified_stats_df` | `count_stats_table_{var}_for_each_{col}.{tex,csv,md,html}` |
182
+ | `get_dist_stats_df` | `dist_stats_table_{var}.{tex,csv,md,html}` |
183
+ | `get_dist_stratified_stats_df` | `dist_stats_table_{var}_for_each_{col}.{tex,csv,md,html}` |
184
+
185
+ LaTeX tables use `booktabs` rules (`\toprule`, `\midrule`, `\bottomrule`), bold column titles and escaped special characters (`#`, `%`, `_`, `&`, ...).
186
+
187
+ ### Helper functions
188
+
189
+ - `prepare_categorical(series, show_nan=True, forced_order=None, nan_label="NaN")` -> ordered categorical `Series` of string labels
190
+ - `categorise_variable_in_df(var, df, forced_order=None, nan_replacement_str="NaN")` -> categorises a column in place
191
+ - `dataframe_to_latex(df, bold_header=True, round_n_digits=None, na_rep="", column_format=None)` -> `str`
192
+ - `dataframe_to_markdown(df, round_n_digits=None, na_rep="")` -> `str`
193
+
194
+ ## Development
195
+
196
+ ```bash
197
+ uv sync --group dev # or: pip install -e ".[dev]"
198
+ uv run pytest
199
+ uv run flake8 src tests examples --max-line-length=120
200
+ ```
201
+
202
+ ## License
203
+
204
+ MIT
205
+
206
+ ## Contributing
207
+
208
+ Contributions are welcome! Please feel free to submit a Pull Request.