data-visualiser-package 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data_visualiser_package/__init__.py +12 -0
- data_visualiser_package/data_visualiser.py +643 -0
- data_visualiser_package/export_latex_tables.py +102 -0
- data_visualiser_package/py.typed +0 -0
- data_visualiser_package-0.1.0.dist-info/METADATA +181 -0
- data_visualiser_package-0.1.0.dist-info/RECORD +8 -0
- data_visualiser_package-0.1.0.dist-info/WHEEL +4 -0
- data_visualiser_package-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Data Visualiser Package
|
|
3
|
+
|
|
4
|
+
A utility package for visualizing and analyzing data with Matplotlib and Seaborn.
|
|
5
|
+
Creates plots and statistics tables for data exploration and presentation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "0.1.0"
|
|
9
|
+
|
|
10
|
+
from .data_visualiser import DataVisualiser, categorise_variable_in_df, uncategorise
|
|
11
|
+
|
|
12
|
+
__all__ = ["DataVisualiser", "categorise_variable_in_df", "uncategorise"]
|
|
@@ -0,0 +1,643 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Data Visualiser Package
|
|
3
|
+
|
|
4
|
+
A utility package for visualizing and analyzing data with Matplotlib and Seaborn.
|
|
5
|
+
Creates plots and statistics tables for data exploration and presentation.
|
|
6
|
+
|
|
7
|
+
Quick import example:
|
|
8
|
+
```python
|
|
9
|
+
from data_visualiser_package import DataVisualiser
|
|
10
|
+
dv = DataVisualiser()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
"""
|
|
15
|
+
import os
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Dict, List, Optional, Tuple, Union
|
|
18
|
+
|
|
19
|
+
import matplotlib.pyplot as plt
|
|
20
|
+
import numpy as np
|
|
21
|
+
import pandas as pd
|
|
22
|
+
import seaborn as sns
|
|
23
|
+
|
|
24
|
+
from .export_latex_tables import get_latex_table_bold_col_header, get_n_decimals_to_include
|
|
25
|
+
|
|
26
|
+
# Constants
|
|
27
|
+
RECORD_UNIT_NAME = "patient" # What a single record represents (e.g., patient in clinical data)
|
|
28
|
+
NAN_REPLACEMENT_STR = "NaN" # String to use when replacing NaN values for display
|
|
29
|
+
HEIGHT = 4 # Default plot height in inches
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def uncategorise(col: pd.Series) -> pd.Series:
|
|
33
|
+
"""
|
|
34
|
+
Convert categorical columns back to their original data types.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
col: The pandas Series to convert
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
The uncategorized pandas Series
|
|
41
|
+
"""
|
|
42
|
+
if col.dtype.name == "category":
|
|
43
|
+
try:
|
|
44
|
+
return col.astype(col.cat.categories.dtype)
|
|
45
|
+
except:
|
|
46
|
+
# In case there is pd.NA (pandas >= 1.0), Int64 should be used instead of int64
|
|
47
|
+
return col.astype(col.cat.categories.dtype.name.title())
|
|
48
|
+
else:
|
|
49
|
+
return col
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def categorise_variable_in_df(
|
|
53
|
+
var: str,
|
|
54
|
+
df: pd.DataFrame,
|
|
55
|
+
forced_order: Optional[List[str]] = None,
|
|
56
|
+
nan_replacement_str: str = NAN_REPLACEMENT_STR
|
|
57
|
+
) -> None:
|
|
58
|
+
"""
|
|
59
|
+
Categorize a variable in a DataFrame, handling NaN values and custom ordering.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
var: Variable name to categorize
|
|
63
|
+
df: DataFrame containing the variable
|
|
64
|
+
forced_order: Optional custom ordering of categories
|
|
65
|
+
nan_replacement_str: String used to replace NaN values
|
|
66
|
+
|
|
67
|
+
Note:
|
|
68
|
+
This function modifies the DataFrame in-place.
|
|
69
|
+
"""
|
|
70
|
+
if (df[var] == nan_replacement_str).any():
|
|
71
|
+
if forced_order is not None:
|
|
72
|
+
assert (set(df[var]) - {nan_replacement_str}).issubset(
|
|
73
|
+
forced_order
|
|
74
|
+
), f"forced_order for var {var} is incomplete: {(set(df[var]) - set([nan_replacement_str])) - set(forced_order)}"
|
|
75
|
+
categories = forced_order + [nan_replacement_str]
|
|
76
|
+
else:
|
|
77
|
+
categories = sorted(set(df[var].unique()) - {nan_replacement_str}) + [
|
|
78
|
+
nan_replacement_str
|
|
79
|
+
]
|
|
80
|
+
else:
|
|
81
|
+
if forced_order is not None:
|
|
82
|
+
assert (set(df[var]) - {np.nan}).issubset(
|
|
83
|
+
forced_order
|
|
84
|
+
), f"forced_order for var {var} is incomplete: {(set(df[var]) - set([np.nan])) - set(forced_order)}"
|
|
85
|
+
categories = forced_order
|
|
86
|
+
else:
|
|
87
|
+
categories = sorted(set(df[var]) - {np.nan})
|
|
88
|
+
|
|
89
|
+
df[var] = pd.Categorical(df[var], categories=categories)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class DataVisualiser:
|
|
93
|
+
"""
|
|
94
|
+
A class for creating and saving data visualizations and statistics tables.
|
|
95
|
+
|
|
96
|
+
This class provides methods to generate common statistical visualizations and tables,
|
|
97
|
+
including count plots, distribution plots, and their stratified versions.
|
|
98
|
+
|
|
99
|
+
Attributes:
|
|
100
|
+
record_unit_name: Name of what a single record represents (e.g., "patient")
|
|
101
|
+
count_colname: Column name used for count statistics
|
|
102
|
+
figures_dirpath: Directory path for saving figures
|
|
103
|
+
tables_dirpath: Directory path for saving tables
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
def __init__(
|
|
107
|
+
self,
|
|
108
|
+
record_unit_name: str = RECORD_UNIT_NAME,
|
|
109
|
+
figures_dirpath: Optional[str] = None,
|
|
110
|
+
tables_dirpath: Optional[str] = None,
|
|
111
|
+
create_dirs: bool = False,
|
|
112
|
+
):
|
|
113
|
+
"""
|
|
114
|
+
Initialize DataVisualiser with paths and record unit information.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
record_unit_name: Name of what a single record represents (e.g., "patient")
|
|
118
|
+
figures_dirpath: Directory path for saving figures
|
|
119
|
+
tables_dirpath: Directory path for saving tables
|
|
120
|
+
create_dirs: Whether to create the figures and tables directories if they don't exist
|
|
121
|
+
"""
|
|
122
|
+
self.record_unit_name = record_unit_name
|
|
123
|
+
self.count_colname = f"# {record_unit_name}s"
|
|
124
|
+
self.figures_dirpath = figures_dirpath
|
|
125
|
+
self.tables_dirpath = tables_dirpath
|
|
126
|
+
|
|
127
|
+
if create_dirs and figures_dirpath and tables_dirpath:
|
|
128
|
+
Path(figures_dirpath).mkdir(parents=True, exist_ok=True)
|
|
129
|
+
Path(tables_dirpath).mkdir(parents=True, exist_ok=True)
|
|
130
|
+
|
|
131
|
+
def get_count_plot(
|
|
132
|
+
self,
|
|
133
|
+
var: str,
|
|
134
|
+
df: pd.DataFrame,
|
|
135
|
+
show_nan: bool = True,
|
|
136
|
+
save_fig: bool = False,
|
|
137
|
+
plot_kwargs: Dict = {},
|
|
138
|
+
xlabels_rotation: Optional[int] = None,
|
|
139
|
+
forced_order: Optional[List[str]] = None,
|
|
140
|
+
) -> Tuple[plt.Figure, plt.Axes]:
|
|
141
|
+
"""
|
|
142
|
+
Create a count plot for a categorical variable.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
var: Variable to plot
|
|
146
|
+
df: DataFrame containing the data
|
|
147
|
+
show_nan: Whether to show NaN values as a separate category
|
|
148
|
+
save_fig: Whether to save the figure to disk
|
|
149
|
+
plot_kwargs: Additional keyword arguments to pass to seaborn's countplot
|
|
150
|
+
xlabels_rotation: Rotation angle for x-axis labels
|
|
151
|
+
forced_order: Optional custom ordering of categories
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Tuple of (figure, axes)
|
|
155
|
+
"""
|
|
156
|
+
# Preprocess df
|
|
157
|
+
df = df[var].astype(str).to_frame().copy()
|
|
158
|
+
if show_nan:
|
|
159
|
+
df = df[var].fillna(NAN_REPLACEMENT_STR).to_frame().copy()
|
|
160
|
+
# Order variables alphabetically
|
|
161
|
+
categorise_variable_in_df(var, df, forced_order=forced_order)
|
|
162
|
+
|
|
163
|
+
fig, ax = plt.subplots()
|
|
164
|
+
sns.countplot(y=var, data=df, **plot_kwargs)
|
|
165
|
+
ax.set_title(f"Number of {self.record_unit_name}s separated by {var}")
|
|
166
|
+
|
|
167
|
+
if xlabels_rotation:
|
|
168
|
+
plt.xticks(rotation=xlabels_rotation)
|
|
169
|
+
|
|
170
|
+
if save_fig and self.figures_dirpath:
|
|
171
|
+
plt.savefig(
|
|
172
|
+
os.path.join(self.figures_dirpath, f"count_plot_{var}.png"),
|
|
173
|
+
bbox_inches="tight",
|
|
174
|
+
)
|
|
175
|
+
plt.close()
|
|
176
|
+
|
|
177
|
+
return fig, ax
|
|
178
|
+
|
|
179
|
+
def get_count_stratified_plot(
|
|
180
|
+
self,
|
|
181
|
+
var: str,
|
|
182
|
+
df: pd.DataFrame,
|
|
183
|
+
col: str = "sexe_desc",
|
|
184
|
+
show_nan: bool = True,
|
|
185
|
+
show_nan_col: bool = True,
|
|
186
|
+
save_fig: bool = False,
|
|
187
|
+
col_wrap: int = 3,
|
|
188
|
+
plot_kwargs: Dict = {},
|
|
189
|
+
xlabels_rotation: Optional[int] = None,
|
|
190
|
+
forced_order: Optional[List[str]] = None,
|
|
191
|
+
forced_order_col: Optional[List[str]] = None,
|
|
192
|
+
) -> sns.FacetGrid:
|
|
193
|
+
"""
|
|
194
|
+
Create stratified count plots for a categorical variable.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
var: Variable to plot
|
|
198
|
+
df: DataFrame containing the data
|
|
199
|
+
col: Column to stratify by
|
|
200
|
+
show_nan: Whether to show NaN values in var as a separate category
|
|
201
|
+
show_nan_col: Whether to show NaN values in col as a separate category
|
|
202
|
+
save_fig: Whether to save the figure to disk
|
|
203
|
+
col_wrap: Number of facets per row
|
|
204
|
+
plot_kwargs: Additional keyword arguments to pass to seaborn's catplot
|
|
205
|
+
xlabels_rotation: Rotation angle for x-axis labels
|
|
206
|
+
forced_order: Optional custom ordering of categories for var
|
|
207
|
+
forced_order_col: Optional custom ordering of categories for col
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
Seaborn FacetGrid object
|
|
211
|
+
"""
|
|
212
|
+
# Preprocess df
|
|
213
|
+
df = df[[var, col]].astype(str).copy()
|
|
214
|
+
if show_nan:
|
|
215
|
+
df[var] = df[var].fillna(NAN_REPLACEMENT_STR)
|
|
216
|
+
if show_nan_col:
|
|
217
|
+
df[col] = df[col].fillna(NAN_REPLACEMENT_STR)
|
|
218
|
+
# Order variables alphabetically
|
|
219
|
+
categorise_variable_in_df(var, df, forced_order=forced_order)
|
|
220
|
+
categorise_variable_in_df(col, df, forced_order=forced_order_col)
|
|
221
|
+
|
|
222
|
+
# Use height from plot_kwargs if specified, otherwise use HEIGHT
|
|
223
|
+
if plot_kwargs.get("height") is None:
|
|
224
|
+
plot_kwargs["height"] = HEIGHT
|
|
225
|
+
|
|
226
|
+
fg = sns.catplot(
|
|
227
|
+
y=var, col=col, col_wrap=col_wrap, kind="count", data=df, **plot_kwargs
|
|
228
|
+
)
|
|
229
|
+
fg.fig.suptitle(
|
|
230
|
+
f"Number of {self.record_unit_name}s separated by {var} in each {col}",
|
|
231
|
+
y=1.02,
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
if xlabels_rotation:
|
|
235
|
+
fg.tick_params(axis="x", rotation=xlabels_rotation)
|
|
236
|
+
|
|
237
|
+
if save_fig and self.figures_dirpath:
|
|
238
|
+
plt.savefig(
|
|
239
|
+
os.path.join(
|
|
240
|
+
self.figures_dirpath, f"count_plot_{var}_for_each_{col}.png"
|
|
241
|
+
),
|
|
242
|
+
bbox_inches="tight",
|
|
243
|
+
)
|
|
244
|
+
plt.close()
|
|
245
|
+
|
|
246
|
+
return fg
|
|
247
|
+
|
|
248
|
+
def get_count_stats_df(
|
|
249
|
+
self,
|
|
250
|
+
var: str,
|
|
251
|
+
df: pd.DataFrame,
|
|
252
|
+
show_nan: bool = True,
|
|
253
|
+
save_table: bool = False,
|
|
254
|
+
percentage: bool = True,
|
|
255
|
+
add_total: bool = False,
|
|
256
|
+
round_n_digits: int = 1,
|
|
257
|
+
forced_order: Optional[List[str]] = None,
|
|
258
|
+
) -> pd.DataFrame:
|
|
259
|
+
"""
|
|
260
|
+
Create a DataFrame with count statistics for a categorical variable.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
var: Variable to analyze
|
|
264
|
+
df: DataFrame containing the data
|
|
265
|
+
show_nan: Whether to include NaN values as a separate category
|
|
266
|
+
save_table: Whether to save the statistics as a LaTeX table
|
|
267
|
+
percentage: Whether to include percentage column
|
|
268
|
+
add_total: Whether to add a "Total" row
|
|
269
|
+
round_n_digits: Number of decimal places to round to
|
|
270
|
+
forced_order: Optional custom ordering of categories
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
DataFrame with count statistics
|
|
274
|
+
"""
|
|
275
|
+
# Preprocess df
|
|
276
|
+
df = df[var].astype(str).to_frame().copy()
|
|
277
|
+
if show_nan:
|
|
278
|
+
df = df[var].fillna(NAN_REPLACEMENT_STR).to_frame().copy()
|
|
279
|
+
# Order variables alphabetically
|
|
280
|
+
categorise_variable_in_df(var, df, forced_order=forced_order)
|
|
281
|
+
|
|
282
|
+
# Compute stats df
|
|
283
|
+
stats_df = df.groupby(var).size().rename(self.count_colname).to_frame()
|
|
284
|
+
|
|
285
|
+
if percentage:
|
|
286
|
+
stats_df = pd.concat(
|
|
287
|
+
[
|
|
288
|
+
stats_df,
|
|
289
|
+
(100 * stats_df / stats_df.sum()).rename(
|
|
290
|
+
columns={self.count_colname: "%"}
|
|
291
|
+
),
|
|
292
|
+
],
|
|
293
|
+
axis=1,
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
if add_total:
|
|
297
|
+
stats_df = pd.concat(
|
|
298
|
+
[
|
|
299
|
+
stats_df,
|
|
300
|
+
stats_df.sum(axis=0).rename("Total").to_frame().transpose(),
|
|
301
|
+
],
|
|
302
|
+
axis=0,
|
|
303
|
+
)
|
|
304
|
+
# Convert count_colname back to int type
|
|
305
|
+
stats_df[self.count_colname] = stats_df[self.count_colname].astype(int)
|
|
306
|
+
|
|
307
|
+
if save_table and self.tables_dirpath:
|
|
308
|
+
filepath = os.path.join(self.tables_dirpath, f"count_stats_table_{var}.tex")
|
|
309
|
+
with open(filepath, "w") as f:
|
|
310
|
+
f.write(stats_df.round(round_n_digits).to_latex())
|
|
311
|
+
|
|
312
|
+
return stats_df
|
|
313
|
+
|
|
314
|
+
def get_count_stratified_stats_df(
|
|
315
|
+
self,
|
|
316
|
+
var: str,
|
|
317
|
+
df: pd.DataFrame,
|
|
318
|
+
col: str = "sexe_desc",
|
|
319
|
+
show_nan: bool = True,
|
|
320
|
+
show_nan_col: bool = True,
|
|
321
|
+
save_table: bool = False,
|
|
322
|
+
percentage: bool = True,
|
|
323
|
+
round_n_digits: int = 1,
|
|
324
|
+
forced_order: Optional[List[str]] = None,
|
|
325
|
+
forced_order_col: Optional[List[str]] = None,
|
|
326
|
+
) -> pd.DataFrame:
|
|
327
|
+
"""
|
|
328
|
+
Create a DataFrame with stratified count statistics for a categorical variable.
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
var: Variable to analyze
|
|
332
|
+
df: DataFrame containing the data
|
|
333
|
+
col: Column to stratify by
|
|
334
|
+
show_nan: Whether to include NaN values in var as a separate category
|
|
335
|
+
show_nan_col: Whether to include NaN values in col as a separate category
|
|
336
|
+
save_table: Whether to save the statistics as a LaTeX table
|
|
337
|
+
percentage: Whether to include percentage column
|
|
338
|
+
round_n_digits: Number of decimal places to round to
|
|
339
|
+
forced_order: Optional custom ordering of categories for var
|
|
340
|
+
forced_order_col: Optional custom ordering of categories for col
|
|
341
|
+
|
|
342
|
+
Returns:
|
|
343
|
+
DataFrame with stratified count statistics
|
|
344
|
+
"""
|
|
345
|
+
# Preprocess df
|
|
346
|
+
df = df[[var, col]].astype(str).copy()
|
|
347
|
+
if show_nan:
|
|
348
|
+
df[var] = df[var].fillna(NAN_REPLACEMENT_STR)
|
|
349
|
+
if show_nan_col:
|
|
350
|
+
df[col] = df[col].fillna(NAN_REPLACEMENT_STR)
|
|
351
|
+
# Order variables alphabetically
|
|
352
|
+
categorise_variable_in_df(var, df, forced_order=forced_order)
|
|
353
|
+
categorise_variable_in_df(col, df, forced_order=forced_order_col)
|
|
354
|
+
|
|
355
|
+
# Compute stats df
|
|
356
|
+
stratified_stats_df = pd.DataFrame(
|
|
357
|
+
df.groupby([col, var]).size(), columns=[self.count_colname]
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
if percentage:
|
|
361
|
+
stratified_stats_df = pd.concat(
|
|
362
|
+
[
|
|
363
|
+
stratified_stats_df,
|
|
364
|
+
(
|
|
365
|
+
100
|
|
366
|
+
* stratified_stats_df
|
|
367
|
+
/ stratified_stats_df.groupby(level=0).sum()
|
|
368
|
+
).rename(columns={self.count_colname: "%"}),
|
|
369
|
+
],
|
|
370
|
+
axis=1,
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
if save_table and self.tables_dirpath:
|
|
374
|
+
filepath = os.path.join(
|
|
375
|
+
self.tables_dirpath,
|
|
376
|
+
f"count_stats_table_{var}_for_each_{col}.tex",
|
|
377
|
+
)
|
|
378
|
+
with open(filepath, "w") as f:
|
|
379
|
+
f.write(stratified_stats_df.round(round_n_digits).to_latex())
|
|
380
|
+
|
|
381
|
+
return stratified_stats_df
|
|
382
|
+
|
|
383
|
+
def get_dist_plot(
|
|
384
|
+
self,
|
|
385
|
+
var: str,
|
|
386
|
+
df: pd.DataFrame,
|
|
387
|
+
save_fig: bool = False,
|
|
388
|
+
plot_kwargs: Dict = {"kde": True},
|
|
389
|
+
xlabels_rotation: Optional[int] = None
|
|
390
|
+
) -> Tuple[plt.Figure, plt.Axes]:
|
|
391
|
+
"""
|
|
392
|
+
Create a distribution plot for a numerical variable.
|
|
393
|
+
|
|
394
|
+
Args:
|
|
395
|
+
var: Variable to plot
|
|
396
|
+
df: DataFrame containing the data
|
|
397
|
+
save_fig: Whether to save the figure to disk
|
|
398
|
+
plot_kwargs: Additional keyword arguments to pass to seaborn's histplot
|
|
399
|
+
xlabels_rotation: Rotation angle for x-axis labels
|
|
400
|
+
|
|
401
|
+
Returns:
|
|
402
|
+
Tuple of (figure, axes)
|
|
403
|
+
"""
|
|
404
|
+
fig, ax = plt.subplots()
|
|
405
|
+
sns.histplot(data=df, x=var, ax=ax, **plot_kwargs)
|
|
406
|
+
ax.set_title(f"Distribution of {var}")
|
|
407
|
+
|
|
408
|
+
if xlabels_rotation:
|
|
409
|
+
plt.xticks(rotation=xlabels_rotation)
|
|
410
|
+
|
|
411
|
+
if save_fig and self.figures_dirpath:
|
|
412
|
+
plt.savefig(
|
|
413
|
+
os.path.join(self.figures_dirpath, f"dist_plot_{var}.png"),
|
|
414
|
+
bbox_inches="tight",
|
|
415
|
+
)
|
|
416
|
+
plt.close()
|
|
417
|
+
|
|
418
|
+
return fig, ax
|
|
419
|
+
|
|
420
|
+
def get_dist_stratified_plot(
|
|
421
|
+
self,
|
|
422
|
+
var: str,
|
|
423
|
+
df: pd.DataFrame,
|
|
424
|
+
col: str = "sexe_desc",
|
|
425
|
+
show_nan_col: bool = True,
|
|
426
|
+
save_fig: bool = False,
|
|
427
|
+
col_wrap: int = 3,
|
|
428
|
+
plot_kwargs: Dict = {"kde": True},
|
|
429
|
+
xlabels_rotation: Optional[int] = None,
|
|
430
|
+
forced_order_col: Optional[List[str]] = None,
|
|
431
|
+
) -> sns.FacetGrid:
|
|
432
|
+
"""
|
|
433
|
+
Create stratified distribution plots for a numerical variable.
|
|
434
|
+
|
|
435
|
+
Args:
|
|
436
|
+
var: Variable to plot
|
|
437
|
+
df: DataFrame containing the data
|
|
438
|
+
col: Column to stratify by
|
|
439
|
+
show_nan_col: Whether to show NaN values in col as a separate category
|
|
440
|
+
save_fig: Whether to save the figure to disk
|
|
441
|
+
col_wrap: Number of facets per row
|
|
442
|
+
plot_kwargs: Additional keyword arguments to pass to seaborn's displot
|
|
443
|
+
xlabels_rotation: Rotation angle for x-axis labels
|
|
444
|
+
forced_order_col: Optional custom ordering of categories for col
|
|
445
|
+
|
|
446
|
+
Returns:
|
|
447
|
+
Seaborn FacetGrid object
|
|
448
|
+
"""
|
|
449
|
+
# Preprocess df
|
|
450
|
+
df = df[[var, col]].copy()
|
|
451
|
+
df[col] = df[col].astype(str)
|
|
452
|
+
if show_nan_col:
|
|
453
|
+
df[col] = df[col].fillna(NAN_REPLACEMENT_STR)
|
|
454
|
+
# Order variables alphabetically
|
|
455
|
+
categorise_variable_in_df(col, df, forced_order=forced_order_col)
|
|
456
|
+
|
|
457
|
+
# Use height from plot_kwargs if specified, otherwise use HEIGHT
|
|
458
|
+
if plot_kwargs.get("height") is None:
|
|
459
|
+
plot_kwargs["height"] = HEIGHT
|
|
460
|
+
|
|
461
|
+
fg = sns.displot(data=df, x=var, col=col, col_wrap=col_wrap, **plot_kwargs)
|
|
462
|
+
fg.fig.suptitle(f"Distribution of {var} for each {col}", y=1.02)
|
|
463
|
+
|
|
464
|
+
if xlabels_rotation:
|
|
465
|
+
fg.set_xticklabels(rotation=xlabels_rotation)
|
|
466
|
+
|
|
467
|
+
if save_fig and self.figures_dirpath:
|
|
468
|
+
plt.savefig(
|
|
469
|
+
os.path.join(
|
|
470
|
+
self.figures_dirpath, f"dist_plot_{var}_for_each_{col}.png"
|
|
471
|
+
),
|
|
472
|
+
bbox_inches="tight",
|
|
473
|
+
)
|
|
474
|
+
plt.close()
|
|
475
|
+
|
|
476
|
+
return fg
|
|
477
|
+
|
|
478
|
+
def get_dist_hued_plot(
|
|
479
|
+
self,
|
|
480
|
+
var: str,
|
|
481
|
+
df: pd.DataFrame,
|
|
482
|
+
hue: str = "sexe_desc",
|
|
483
|
+
show_nan_col: bool = True,
|
|
484
|
+
save_fig: bool = False,
|
|
485
|
+
plot_kwargs: Dict = {"kde": True},
|
|
486
|
+
xlabels_rotation: Optional[int] = None,
|
|
487
|
+
forced_order_col: Optional[List[str]] = None,
|
|
488
|
+
) -> sns.FacetGrid:
|
|
489
|
+
"""
|
|
490
|
+
Create a distribution plot for a numerical variable with hue for categories.
|
|
491
|
+
|
|
492
|
+
Args:
|
|
493
|
+
var: Variable to plot
|
|
494
|
+
df: DataFrame containing the data
|
|
495
|
+
hue: Column to use for color encoding
|
|
496
|
+
show_nan_col: Whether to show NaN values in hue as a separate category
|
|
497
|
+
save_fig: Whether to save the figure to disk
|
|
498
|
+
plot_kwargs: Additional keyword arguments to pass to seaborn's displot
|
|
499
|
+
xlabels_rotation: Rotation angle for x-axis labels
|
|
500
|
+
forced_order_col: Optional custom ordering of categories for hue
|
|
501
|
+
|
|
502
|
+
Returns:
|
|
503
|
+
Seaborn FacetGrid object
|
|
504
|
+
"""
|
|
505
|
+
# Preprocess df
|
|
506
|
+
df = df[[var, hue]].copy()
|
|
507
|
+
df[hue] = df[hue].astype(str)
|
|
508
|
+
if show_nan_col:
|
|
509
|
+
df[hue] = df[hue].fillna(NAN_REPLACEMENT_STR)
|
|
510
|
+
# Order variables alphabetically
|
|
511
|
+
categorise_variable_in_df(hue, df, forced_order=forced_order_col)
|
|
512
|
+
|
|
513
|
+
# Use height from plot_kwargs if specified, otherwise use HEIGHT
|
|
514
|
+
if plot_kwargs.get("height") is None:
|
|
515
|
+
plot_kwargs["height"] = HEIGHT
|
|
516
|
+
|
|
517
|
+
fg = sns.displot(data=df, x=var, hue=hue, **plot_kwargs)
|
|
518
|
+
fg.fig.suptitle(f"Distribution of {var} for each {hue}", y=1.02)
|
|
519
|
+
|
|
520
|
+
if xlabels_rotation:
|
|
521
|
+
fg.set_xticklabels(rotation=xlabels_rotation)
|
|
522
|
+
|
|
523
|
+
if save_fig and self.figures_dirpath:
|
|
524
|
+
plt.savefig(
|
|
525
|
+
os.path.join(
|
|
526
|
+
self.figures_dirpath, f"dist_plot_{var}_for_hue_{hue}.png"
|
|
527
|
+
),
|
|
528
|
+
bbox_inches="tight",
|
|
529
|
+
)
|
|
530
|
+
plt.close()
|
|
531
|
+
|
|
532
|
+
return fg
|
|
533
|
+
|
|
534
|
+
def get_dist_stats_df(
|
|
535
|
+
self,
|
|
536
|
+
var: str,
|
|
537
|
+
df: pd.DataFrame,
|
|
538
|
+
save_table: bool = False
|
|
539
|
+
) -> pd.DataFrame:
|
|
540
|
+
"""
|
|
541
|
+
Create a DataFrame with distribution statistics for a numerical variable.
|
|
542
|
+
|
|
543
|
+
Args:
|
|
544
|
+
var: Variable to analyze
|
|
545
|
+
df: DataFrame containing the data
|
|
546
|
+
save_table: Whether to save the statistics as a LaTeX table
|
|
547
|
+
|
|
548
|
+
Returns:
|
|
549
|
+
DataFrame with distribution statistics
|
|
550
|
+
"""
|
|
551
|
+
stats_df = pd.DataFrame(df[var].describe()).transpose()
|
|
552
|
+
|
|
553
|
+
# Convert 'count' column into 'int' type
|
|
554
|
+
stats_df["count"] = stats_df["count"].astype(int)
|
|
555
|
+
|
|
556
|
+
# Add number and percentage of NaNs
|
|
557
|
+
stats_df.insert(1, "nan_perc", value=100 * df[var].isna().mean())
|
|
558
|
+
stats_df.insert(1, "n_nan", value=df[var].isna().sum())
|
|
559
|
+
|
|
560
|
+
if save_table and self.tables_dirpath:
|
|
561
|
+
# Get number of decimals to include
|
|
562
|
+
std = stats_df.squeeze()["std"]
|
|
563
|
+
round_n_digits = get_n_decimals_to_include(std)
|
|
564
|
+
|
|
565
|
+
filepath = os.path.join(self.tables_dirpath, f"dist_stats_table_{var}.tex")
|
|
566
|
+
with open(filepath, "w") as f:
|
|
567
|
+
f.write(
|
|
568
|
+
get_latex_table_bold_col_header(
|
|
569
|
+
stats_df, round_n_digits=round_n_digits
|
|
570
|
+
)
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
return stats_df
|
|
574
|
+
|
|
575
|
+
def get_dist_stratified_stats_df(
|
|
576
|
+
self,
|
|
577
|
+
var: str,
|
|
578
|
+
df: pd.DataFrame,
|
|
579
|
+
col: str = "sexe_desc",
|
|
580
|
+
show_nan_col: bool = True,
|
|
581
|
+
save_table: bool = False,
|
|
582
|
+
forced_order_col: Optional[List[str]] = None,
|
|
583
|
+
) -> pd.DataFrame:
|
|
584
|
+
"""
|
|
585
|
+
Create a DataFrame with stratified distribution statistics for a numerical variable.
|
|
586
|
+
|
|
587
|
+
Args:
|
|
588
|
+
var: Variable to analyze
|
|
589
|
+
df: DataFrame containing the data
|
|
590
|
+
col: Column to stratify by
|
|
591
|
+
show_nan_col: Whether to show NaN values in col as a separate category
|
|
592
|
+
save_table: Whether to save the statistics as a LaTeX table
|
|
593
|
+
forced_order_col: Optional custom ordering of categories for col
|
|
594
|
+
|
|
595
|
+
Returns:
|
|
596
|
+
DataFrame with stratified distribution statistics
|
|
597
|
+
"""
|
|
598
|
+
# Preprocess df
|
|
599
|
+
df = df[[var, col]].copy()
|
|
600
|
+
df[col] = df[col].astype(str)
|
|
601
|
+
if show_nan_col:
|
|
602
|
+
df[col] = df[col].fillna(NAN_REPLACEMENT_STR)
|
|
603
|
+
# Order variables alphabetically
|
|
604
|
+
categorise_variable_in_df(col, df, forced_order=forced_order_col)
|
|
605
|
+
|
|
606
|
+
stratified_stats_df = pd.DataFrame(df.groupby(col)[var].describe())
|
|
607
|
+
|
|
608
|
+
# Convert 'count' column into 'int' type
|
|
609
|
+
stratified_stats_df["count"] = stratified_stats_df["count"].astype(int)
|
|
610
|
+
|
|
611
|
+
# Add number and percentage of NaNs
|
|
612
|
+
stratified_stats_df.insert(
|
|
613
|
+
1,
|
|
614
|
+
"nan_perc",
|
|
615
|
+
value=100 * df.groupby(col)[var].apply(lambda x: x.isna().mean()),
|
|
616
|
+
)
|
|
617
|
+
stratified_stats_df.insert(
|
|
618
|
+
1, "n_nan", value=df.groupby(col)[var].apply(lambda x: x.isna().sum())
|
|
619
|
+
)
|
|
620
|
+
|
|
621
|
+
if save_table and self.tables_dirpath:
|
|
622
|
+
# Get number of decimals to include
|
|
623
|
+
std = stratified_stats_df["std"].min()
|
|
624
|
+
round_n_digits = get_n_decimals_to_include(std)
|
|
625
|
+
|
|
626
|
+
filepath = os.path.join(
|
|
627
|
+
self.tables_dirpath,
|
|
628
|
+
f"dist_stats_table_{var}_for_each_{col}.tex",
|
|
629
|
+
)
|
|
630
|
+
with open(filepath, "w") as f:
|
|
631
|
+
f.write(
|
|
632
|
+
get_latex_table_bold_col_header(
|
|
633
|
+
stratified_stats_df, round_n_digits=round_n_digits
|
|
634
|
+
)
|
|
635
|
+
)
|
|
636
|
+
|
|
637
|
+
return stratified_stats_df
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
if __name__ == '__main__':
|
|
641
|
+
print("DataVisualiser: A tool for data visualization and analysis")
|
|
642
|
+
print("Import this module to use the functionality")
|
|
643
|
+
print("Example: from data_visualiser_package import DataVisualiser")
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def remove_toprule(s):
|
|
7
|
+
return s.replace("\\toprule\n", "")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def remove_bottomrule(s):
|
|
11
|
+
return s.replace("\\bottomrule\n", "")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def replace_midrule_with_hline(s):
|
|
15
|
+
return s.replace("\\midrule", "\\hline")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def replace_bottomrule_with_hline(s):
|
|
19
|
+
return s.replace("\\bottomrule", "\\hline")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def change_header(s, header_list):
|
|
23
|
+
lines = s.split("\n")
|
|
24
|
+
header_line = lines[2]
|
|
25
|
+
new_header_line = " & ".join(header_list) + " \\\\"
|
|
26
|
+
new_lines = lines[:2] + [new_header_line] + lines[3:]
|
|
27
|
+
new_s = "\n".join(new_lines)
|
|
28
|
+
|
|
29
|
+
return new_s
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def change_values_title(s, new_values_title):
|
|
33
|
+
lines = s.split("\n")
|
|
34
|
+
values_line = lines[-4]
|
|
35
|
+
values_list = values_line.split("&")
|
|
36
|
+
|
|
37
|
+
new_values_list = [new_values_title + " "] + values_list[1:]
|
|
38
|
+
new_values_line = "&".join(new_values_list)
|
|
39
|
+
new_lines = lines[:4] + [new_values_line] + lines[5:]
|
|
40
|
+
s = "\n".join(new_lines)
|
|
41
|
+
|
|
42
|
+
return s
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def add_hline(s, line_number_from_end=3):
|
|
46
|
+
"""Add hline at line_number_from_end.
|
|
47
|
+
|
|
48
|
+
The default '3' corresponds to
|
|
49
|
+
adding a line before the last line of the table.
|
|
50
|
+
"""
|
|
51
|
+
lines = s.split("\n")
|
|
52
|
+
new_lines = (
|
|
53
|
+
lines[:-line_number_from_end] + ["\\hline"] + lines[-line_number_from_end:]
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
return "\n".join(new_lines)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def format_table(s, header_list=None, new_values_title=None):
|
|
60
|
+
if header_list is not None:
|
|
61
|
+
s = change_header(s, header_list)
|
|
62
|
+
if new_values_title is not None:
|
|
63
|
+
s = change_values_title(s, new_values_title)
|
|
64
|
+
s = remove_toprule(s)
|
|
65
|
+
s = remove_bottomrule(s)
|
|
66
|
+
s = replace_midrule_with_hline(s)
|
|
67
|
+
# s = replace_bottomrule_with_hline(s)
|
|
68
|
+
return s
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def write_table_to_file(s, variable, dirpath):
|
|
72
|
+
with open(os.path.join(dirpath, variable + "_table.tex"), "w") as f:
|
|
73
|
+
f.write(s)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def boldify_line(line, words_to_boldify):
|
|
77
|
+
for word in words_to_boldify:
|
|
78
|
+
line = line.replace(word, "\\textbf{" + f"{word}" + "}")
|
|
79
|
+
return line
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def get_latex_table_bold_col_header(df2, round_n_digits=1):
|
|
83
|
+
table_lines = df2.round(round_n_digits).to_latex().split("\\\\")
|
|
84
|
+
|
|
85
|
+
col_headers = list(df2.columns.str.replace("%", "\\%").str.replace("_", "\\_"))
|
|
86
|
+
new_line0 = boldify_line(table_lines[0], col_headers)
|
|
87
|
+
|
|
88
|
+
boldified_table = "\\\\".join([new_line0] + table_lines[1:])
|
|
89
|
+
return boldified_table
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def get_n_decimals_to_include(std, min_n_decimals=1):
|
|
93
|
+
# If std == 0, return min_n_decimals, otherwise we get an error when applying the log
|
|
94
|
+
if std == 0:
|
|
95
|
+
return min_n_decimals
|
|
96
|
+
|
|
97
|
+
n_decimals_to_include = -(np.log10(std).round() - 1)
|
|
98
|
+
|
|
99
|
+
if n_decimals_to_include < 1:
|
|
100
|
+
return min_n_decimals
|
|
101
|
+
else:
|
|
102
|
+
return int(n_decimals_to_include)
|
|
File without changes
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: data-visualiser-package
|
|
3
|
+
Version: 0.1.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.7
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
21
|
+
Requires-Python: >=3.7
|
|
22
|
+
Requires-Dist: matplotlib>=3.3.0
|
|
23
|
+
Requires-Dist: numpy>=1.19.0
|
|
24
|
+
Requires-Dist: pandas>=1.0.0
|
|
25
|
+
Requires-Dist: seaborn>=0.11.0
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# Data Visualiser Package
|
|
29
|
+
|
|
30
|
+
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.
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install data-visualiser-package
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Features
|
|
39
|
+
|
|
40
|
+
- Create count plots and distribution plots
|
|
41
|
+
- Generate stratified visualizations by categorical variables
|
|
42
|
+
- Compute and display statistical tables for numerical and categorical variables
|
|
43
|
+
- Save plots and tables to disk (including LaTeX export)
|
|
44
|
+
- Handling of missing values
|
|
45
|
+
|
|
46
|
+
## Quick Start
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
import pandas as pd
|
|
50
|
+
from data_visualiser_package import DataVisualiser
|
|
51
|
+
|
|
52
|
+
# Create a DataVisualiser instance
|
|
53
|
+
dv = DataVisualiser(
|
|
54
|
+
record_unit_name="patient", # What a single record represents
|
|
55
|
+
figures_dirpath="./figures", # Where to save figures
|
|
56
|
+
tables_dirpath="./tables", # Where to save tables
|
|
57
|
+
create_dirs=True # Create directories if they don't exist
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Load your data
|
|
61
|
+
df = pd.read_csv("your_data.csv")
|
|
62
|
+
|
|
63
|
+
# Create a count plot
|
|
64
|
+
fig, ax = dv.get_count_plot(
|
|
65
|
+
var="diagnosis", # Categorical variable to plot
|
|
66
|
+
df=df, # DataFrame containing the data
|
|
67
|
+
show_nan=True, # Show NaN values as a separate category
|
|
68
|
+
save_fig=True # Save the figure to disk
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# Create a distribution plot
|
|
72
|
+
fig, ax = dv.get_dist_plot(
|
|
73
|
+
var="age", # Numerical variable to plot
|
|
74
|
+
df=df, # DataFrame containing the data
|
|
75
|
+
save_fig=True # Save the figure to disk
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Generate statistics tables
|
|
79
|
+
stats_df = dv.get_count_stats_df(
|
|
80
|
+
var="diagnosis", # Categorical variable to analyze
|
|
81
|
+
df=df, # DataFrame containing the data
|
|
82
|
+
save_table=True # Save the table to disk as LaTeX
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# Create stratified visualizations
|
|
86
|
+
fg = dv.get_dist_stratified_plot(
|
|
87
|
+
var="age", # Numerical variable to plot
|
|
88
|
+
df=df, # DataFrame containing the data
|
|
89
|
+
col="gender", # Categorical variable to stratify by
|
|
90
|
+
save_fig=True # Save the figure to disk
|
|
91
|
+
)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Example
|
|
95
|
+
|
|
96
|
+
Here's a complete example of how to use the DataVisualiser class:
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
import pandas as pd
|
|
100
|
+
import numpy as np
|
|
101
|
+
from data_visualiser_package import DataVisualiser
|
|
102
|
+
|
|
103
|
+
# Create a sample dataset
|
|
104
|
+
np.random.seed(42)
|
|
105
|
+
n = 1000
|
|
106
|
+
|
|
107
|
+
# Generate sample data
|
|
108
|
+
data = {
|
|
109
|
+
'age': np.random.normal(50, 15, n),
|
|
110
|
+
'gender': np.random.choice(['Male', 'Female'], n),
|
|
111
|
+
'diagnosis': np.random.choice(['Healthy', 'Condition A', 'Condition B', None], n, p=[0.6, 0.2, 0.15, 0.05]),
|
|
112
|
+
'heart_rate': np.random.normal(80, 10, n),
|
|
113
|
+
'blood_pressure': np.random.normal(120, 15, n)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# Create a DataFrame
|
|
117
|
+
df = pd.DataFrame(data)
|
|
118
|
+
|
|
119
|
+
# Initialize the DataVisualiser
|
|
120
|
+
dv = DataVisualiser(
|
|
121
|
+
record_unit_name="patient",
|
|
122
|
+
figures_dirpath="./output/figures",
|
|
123
|
+
tables_dirpath="./output/tables",
|
|
124
|
+
create_dirs=True
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# Create plots
|
|
128
|
+
dv.get_count_plot('gender', df, save_fig=True)
|
|
129
|
+
dv.get_count_plot('diagnosis', df, save_fig=True)
|
|
130
|
+
|
|
131
|
+
# Create stratified count plots
|
|
132
|
+
dv.get_count_stratified_plot('diagnosis', df, col='gender', save_fig=True)
|
|
133
|
+
|
|
134
|
+
# Distribution plots
|
|
135
|
+
dv.get_dist_plot('age', df, save_fig=True)
|
|
136
|
+
dv.get_dist_stratified_plot('age', df, col='gender', save_fig=True)
|
|
137
|
+
dv.get_dist_hued_plot('age', df, hue='gender', save_fig=True)
|
|
138
|
+
|
|
139
|
+
# Generate statistics tables
|
|
140
|
+
dv.get_count_stats_df('diagnosis', df, save_table=True)
|
|
141
|
+
dv.get_dist_stats_df('age', df, save_table=True)
|
|
142
|
+
dv.get_dist_stratified_stats_df('age', df, col='gender', save_table=True)
|
|
143
|
+
|
|
144
|
+
print("All visualizations and tables have been generated successfully!")
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## API Reference
|
|
148
|
+
|
|
149
|
+
### DataVisualiser Class
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
class DataVisualiser(
|
|
153
|
+
record_unit_name="patient",
|
|
154
|
+
figures_dirpath=None,
|
|
155
|
+
tables_dirpath=None,
|
|
156
|
+
create_dirs=False
|
|
157
|
+
)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
#### Count Visualizations
|
|
161
|
+
|
|
162
|
+
- `get_count_plot(var, df, show_nan=True, save_fig=False, plot_kwargs={}, xlabels_rotation=None, forced_order=None)`
|
|
163
|
+
- `get_count_stratified_plot(var, df, col="gender", show_nan=True, show_nan_col=True, save_fig=False, col_wrap=3, plot_kwargs={}, xlabels_rotation=None, forced_order=None, forced_order_col=None)`
|
|
164
|
+
- `get_count_stats_df(var, df, show_nan=True, save_table=False, percentage=True, add_total=False, round_n_digits=1, forced_order=None)`
|
|
165
|
+
- `get_count_stratified_stats_df(var, df, col="gender", show_nan=True, show_nan_col=True, save_table=False, percentage=True, round_n_digits=1, forced_order=None, forced_order_col=None)`
|
|
166
|
+
|
|
167
|
+
#### Distribution Visualizations
|
|
168
|
+
|
|
169
|
+
- `get_dist_plot(var, df, save_fig=False, plot_kwargs={"kde": True}, xlabels_rotation=None)`
|
|
170
|
+
- `get_dist_stratified_plot(var, df, col="gender", show_nan_col=True, save_fig=False, col_wrap=3, plot_kwargs={"kde": True}, xlabels_rotation=None, forced_order_col=None)`
|
|
171
|
+
- `get_dist_hued_plot(var, df, hue="gender", show_nan_col=True, save_fig=False, plot_kwargs={"kde": True}, xlabels_rotation=None, forced_order_col=None)`
|
|
172
|
+
- `get_dist_stats_df(var, df, save_table=False)`
|
|
173
|
+
- `get_dist_stratified_stats_df(var, df, col="gender", show_nan_col=True, save_table=False, forced_order_col=None)`
|
|
174
|
+
|
|
175
|
+
## License
|
|
176
|
+
|
|
177
|
+
MIT
|
|
178
|
+
|
|
179
|
+
## Contributing
|
|
180
|
+
|
|
181
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
data_visualiser_package/__init__.py,sha256=T1CoedsuYqnuk2XDHUAOdza90JWoiJsQG4Cj3sB1UQc,374
|
|
2
|
+
data_visualiser_package/data_visualiser.py,sha256=3yBLInKSc0BPIIZvCA6h1ybtG5IO6Ezm7V4aH71RLRg,22981
|
|
3
|
+
data_visualiser_package/export_latex_tables.py,sha256=cN9H_cey6NFvc1S9LOQWmK8qu-w0YenFvmO85riW0pk,2715
|
|
4
|
+
data_visualiser_package/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
data_visualiser_package-0.1.0.dist-info/METADATA,sha256=YIqDWOmIqxr3OMxCiVqKf_hgXJlStYFolSf6Q8tj-gY,6459
|
|
6
|
+
data_visualiser_package-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
7
|
+
data_visualiser_package-0.1.0.dist-info/licenses/LICENSE,sha256=IU2SLcxqnhJpreQXofrrzughr5gqNgcOoxb7CF585AY,1071
|
|
8
|
+
data_visualiser_package-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Jonathan Doenz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|