openretailscience 0.45.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.
Files changed (60) hide show
  1. openretailscience/__init__.py +3 -0
  2. openretailscience/analysis/__init__.py +0 -0
  3. openretailscience/analysis/cohort.py +185 -0
  4. openretailscience/analysis/composite_rank.py +372 -0
  5. openretailscience/analysis/cross_shop.py +354 -0
  6. openretailscience/analysis/customer.py +459 -0
  7. openretailscience/analysis/customer_decision_hierarchy.py +359 -0
  8. openretailscience/analysis/gain_loss.py +340 -0
  9. openretailscience/analysis/haversine.py +50 -0
  10. openretailscience/analysis/product_association.py +418 -0
  11. openretailscience/analysis/revenue_tree.py +553 -0
  12. openretailscience/assets/fonts/Poppins-Bold.ttf +0 -0
  13. openretailscience/assets/fonts/Poppins-LightItalic.ttf +0 -0
  14. openretailscience/assets/fonts/Poppins-Medium.ttf +0 -0
  15. openretailscience/assets/fonts/Poppins-Regular.ttf +0 -0
  16. openretailscience/assets/fonts/Poppins-SemiBold.ttf +0 -0
  17. openretailscience/constants.py +295 -0
  18. openretailscience/metrics/__init__.py +0 -0
  19. openretailscience/metrics/base.py +29 -0
  20. openretailscience/metrics/distribution/__init__.py +0 -0
  21. openretailscience/metrics/distribution/acv.py +81 -0
  22. openretailscience/metrics/distribution/pct_of_stores.py +118 -0
  23. openretailscience/options.py +842 -0
  24. openretailscience/plots/__init__.py +0 -0
  25. openretailscience/plots/area.py +114 -0
  26. openretailscience/plots/bar.py +375 -0
  27. openretailscience/plots/broken_timeline.py +197 -0
  28. openretailscience/plots/cohort.py +94 -0
  29. openretailscience/plots/heatmap.py +134 -0
  30. openretailscience/plots/histogram.py +276 -0
  31. openretailscience/plots/index.py +505 -0
  32. openretailscience/plots/line.py +379 -0
  33. openretailscience/plots/period_on_period.py +144 -0
  34. openretailscience/plots/price.py +275 -0
  35. openretailscience/plots/scatter.py +382 -0
  36. openretailscience/plots/styles/__init__.py +1 -0
  37. openretailscience/plots/styles/colors.py +215 -0
  38. openretailscience/plots/styles/font_utils.py +62 -0
  39. openretailscience/plots/styles/graph_utils.py +834 -0
  40. openretailscience/plots/styles/styling_helpers.py +155 -0
  41. openretailscience/plots/time.py +160 -0
  42. openretailscience/plots/tree_diagram.py +813 -0
  43. openretailscience/plots/venn.py +127 -0
  44. openretailscience/plots/waterfall.py +217 -0
  45. openretailscience/plugin.py +238 -0
  46. openretailscience/segmentation/__init__.py +0 -0
  47. openretailscience/segmentation/hml.py +97 -0
  48. openretailscience/segmentation/nlr.py +218 -0
  49. openretailscience/segmentation/rfm.py +307 -0
  50. openretailscience/segmentation/segstats.py +1177 -0
  51. openretailscience/segmentation/threshold.py +165 -0
  52. openretailscience/utils/__init__.py +0 -0
  53. openretailscience/utils/date.py +226 -0
  54. openretailscience/utils/filter_and_label.py +52 -0
  55. openretailscience/utils/label.py +79 -0
  56. openretailscience/utils/validation.py +45 -0
  57. openretailscience-0.45.0.dist-info/METADATA +364 -0
  58. openretailscience-0.45.0.dist-info/RECORD +60 -0
  59. openretailscience-0.45.0.dist-info/WHEEL +4 -0
  60. openretailscience-0.45.0.dist-info/licenses/LICENSE +93 -0
@@ -0,0 +1,94 @@
1
+ """This module provides functionality for creating cohort plots from pandas DataFrames.
2
+
3
+ It is designed to visualize data distributions using color-coded heatmaps, helping to highlight
4
+ trends and comparisons between different groups.
5
+
6
+ ### Core Features
7
+
8
+ - **Color Mapping**: Uses a predefined colormap for visualizing data.
9
+ - **Customizable Labels**: Supports custom labels for x-axis, y-axis, title, and colorbar.
10
+ - **Source Text**: Provides an option to add source attribution to the plot.
11
+ - **Grid and Tick Customization**: Applies standard styling for better readability.
12
+
13
+ ### Use Cases
14
+
15
+ - **Cohort Analysis**: Visualize how different groups behave over time.
16
+ - **Category-Based Heatmaps**: Compare values across different categories.
17
+
18
+ ### Default Behavior
19
+
20
+ - **Percentage Display**: By default, values are displayed as percentages (e.g., "50%").
21
+ Set `percentage=False` for raw number display (e.g., "0.50").
22
+
23
+ ### Limitations and Warnings
24
+
25
+ - **Data Aggregation Required**: The module does not perform data aggregation; data should be
26
+ pre-aggregated before being passed to the function.
27
+ - **Fixed Color Mapping**: The module uses a predefined colormap without dynamic adjustments.
28
+ """
29
+
30
+ import pandas as pd
31
+ from matplotlib import ticker
32
+ from matplotlib.axes import Axes, SubplotBase
33
+
34
+ from openretailscience.plots import heatmap
35
+
36
+
37
+ def plot(
38
+ df: pd.DataFrame,
39
+ cbar_label: str,
40
+ x_label: str | None = None,
41
+ y_label: str | None = None,
42
+ title: str | None = None,
43
+ ax: Axes | None = None,
44
+ source_text: str | None = None,
45
+ percentage: bool = True,
46
+ figsize: tuple[int, int] | None = None,
47
+ **kwargs: dict[str, any],
48
+ ) -> SubplotBase:
49
+ """Plots a cohort plot for the given DataFrame.
50
+
51
+ Args:
52
+ df (pd.DataFrame): Dataframe containing cohort analysis data.
53
+ cbar_label (str): Label for the colorbar.
54
+ x_label (str, optional): Label for x-axis.
55
+ y_label (str, optional): Label for y-axis.
56
+ title (str, optional): Title of the plot.
57
+ ax (Axes, optional): Matplotlib axes object to plot on.
58
+ source_text (str, optional): Additional source text annotation.
59
+ percentage (bool, optional): If True, displays cohort values as percentages. Defaults to True.
60
+ figsize (tuple[int, int], optional): The size of the plot. Defaults to None.
61
+ **kwargs: Additional keyword arguments for cohort styling.
62
+
63
+ Returns:
64
+ SubplotBase: The matplotlib axes object.
65
+ """
66
+ # Use generic heatmap
67
+ ax = heatmap.plot(
68
+ df=df,
69
+ cbar_label=cbar_label,
70
+ x_label=x_label,
71
+ y_label=y_label,
72
+ title=title,
73
+ ax=ax,
74
+ source_text=source_text,
75
+ figsize=figsize,
76
+ **kwargs,
77
+ )
78
+
79
+ # Add cohort-specific styling
80
+ if percentage:
81
+ # 1. Update colorbar format for percentages
82
+ cbar = ax.figure.axes[-1] # Get the colorbar axes
83
+ cbar.yaxis.set_major_formatter(ticker.PercentFormatter(1.0))
84
+
85
+ # 2. Update cell text to show percentages
86
+ for text in ax.texts:
87
+ # Re-format text as percentage
88
+ value = float(text.get_text())
89
+ text.set_text(f"{value:.0%}")
90
+
91
+ # 3. Add cohort-specific horizontal line
92
+ ax.hlines(y=3 - 0.5, xmin=-0.5, xmax=df.shape[1] - 0.5, color="white", linewidth=4)
93
+
94
+ return ax
@@ -0,0 +1,134 @@
1
+ """This module provides functionality for creating generic heatmap plots from pandas DataFrames.
2
+
3
+ This module is designed to create flexible heatmap visualizations suitable for various use cases
4
+ including migration matrices, confusion matrices, correlation matrices, and other 2D data
5
+ visualizations. It provides a clean, reusable interface without domain-specific assumptions.
6
+
7
+ ### Core Features
8
+
9
+ - **Generic Design**: No domain-specific assumptions or hardcoded elements
10
+ - **Color Mapping**: Uses Tailwind green colormap for consistent visualization
11
+ - **Auto-contrast Text**: Text color automatically switches between black and white based on cell intensity
12
+ - **Customizable Labels**: Supports custom labels for x-axis, y-axis, title, and colorbar
13
+ - **Grid Styling**: White grid lines between cells for clear separation
14
+ - **Flexible Data**: Displays values as-is without formatting assumptions
15
+
16
+ ### Use Cases
17
+
18
+ - **Migration Matrices**: Visualize customer movement between segments
19
+ - **Correlation Matrices**: Show relationships between variables
20
+ - **Confusion Matrices**: Display classification results
21
+ - **Any 2D Data**: Generic support for any tabular data visualization
22
+
23
+ ### Design Principles
24
+
25
+ - Display values as-is from the DataFrame (no percentage or other formatting assumptions)
26
+ - Consistent with existing OpenRetailScience plotting modules (line.py, bar.py)
27
+ - Minimal parameters with **kwargs for advanced customization
28
+ - Match visual style of existing plots while remaining generic
29
+ """
30
+
31
+ import matplotlib.pyplot as plt
32
+ import numpy as np
33
+ import pandas as pd
34
+ from matplotlib.axes import Axes, SubplotBase
35
+
36
+ import openretailscience.plots.styles.graph_utils as gu
37
+ from openretailscience.plots.styles.colors import get_heatmap_cmap
38
+
39
+ _LABEL_ROTATION_THRESHOLD = 10
40
+
41
+
42
+ def plot(
43
+ df: pd.DataFrame,
44
+ cbar_label: str,
45
+ x_label: str | None = None,
46
+ y_label: str | None = None,
47
+ title: str | None = None,
48
+ ax: Axes | None = None,
49
+ source_text: str | None = None,
50
+ figsize: tuple[int, int] | None = None,
51
+ cbar_format: str = "{x:.2f}",
52
+ **kwargs: dict[str, any],
53
+ ) -> SubplotBase:
54
+ """Creates a generic heatmap visualization from a pandas DataFrame.
55
+
56
+ This function creates a color-coded heatmap with cell values displayed as text. It is suitable
57
+ for visualizing any 2D data structure including migration matrices, confusion matrices,
58
+ correlation matrices, or cohort analysis data.
59
+
60
+ Args:
61
+ df (pd.DataFrame): DataFrame to visualize. Index becomes y-axis, columns become x-axis.
62
+ cbar_label (str): Label for the colorbar.
63
+ x_label (str, optional): Label for x-axis.
64
+ y_label (str, optional): Label for y-axis.
65
+ title (str, optional): Title of the plot.
66
+ ax (Axes, optional): Matplotlib axes object to plot on.
67
+ source_text (str, optional): Additional source text annotation.
68
+ figsize (tuple[int, int], optional): The size of the plot. Defaults to None.
69
+ cbar_format (str, optional): Format string for colorbar values. Defaults to "{x:.2f}".
70
+ **kwargs: Additional keyword arguments passed to matplotlib's imshow function.
71
+
72
+ Returns:
73
+ SubplotBase: The matplotlib axes object.
74
+ """
75
+ if ax is None:
76
+ _, ax = plt.subplots(figsize=figsize)
77
+
78
+ cmap = get_heatmap_cmap()
79
+ im = ax.imshow(df, cmap=cmap, **kwargs)
80
+
81
+ cbar = ax.figure.colorbar(im, ax=ax, format=cbar_format)
82
+ cbar.ax.set_ylabel(cbar_label, rotation=-90, va="bottom", fontsize="x-large")
83
+
84
+ # Set up ticks and labels
85
+ ax.set_xticks(np.arange(df.shape[1]))
86
+ ax.set_yticks(np.arange(df.shape[0]))
87
+
88
+ # Handle long labels with rotation and proper alignment
89
+ x_labels = df.columns.astype(str).to_list()
90
+ y_labels = df.index.astype(str).to_list()
91
+
92
+ # Determine if we need rotation based on label length
93
+ max_x_label_length = max(len(label) for label in x_labels) if x_labels else 0
94
+ rotation_angle = 45 if max_x_label_length > _LABEL_ROTATION_THRESHOLD else 0
95
+
96
+ ax.set_xticklabels(x_labels, rotation=rotation_angle, ha="right" if rotation_angle > 0 else "center")
97
+ ax.set_yticklabels(y_labels)
98
+
99
+ # Position x-axis labels on bottom with extra padding for rotated labels
100
+ ax.tick_params(top=False, bottom=True, labeltop=False, labelbottom=True)
101
+
102
+ # Add extra padding at bottom if labels are rotated
103
+ if rotation_angle > 0:
104
+ ax.tick_params(axis="x", which="major", pad=10)
105
+
106
+ # Create grid lines between cells
107
+ ax.set_xticks(np.arange(df.shape[1] + 1) - 0.5, minor=True)
108
+ ax.set_yticks(np.arange(df.shape[0] + 1) - 0.5, minor=True)
109
+ ax.grid(which="minor", color="w", linestyle="-", linewidth=3)
110
+ ax.tick_params(which="minor", bottom=False, left=False)
111
+
112
+ # Calculate threshold for auto-contrast text
113
+ threshold = im.norm(df.to_numpy().max()) / 2.0
114
+ textcolors = ("black", "white")
115
+
116
+ # Add text to each cell with auto-contrast
117
+ for i in range(df.shape[0]):
118
+ for j in range(df.shape[1]):
119
+ value = df.iloc[i, j]
120
+ color = textcolors[int(im.norm(value) > threshold)]
121
+ ax.text(j, i, f"{value:.2f}", ha="center", va="center", color=color, fontsize=7)
122
+
123
+ ax = gu.standard_graph_styles(
124
+ ax=ax,
125
+ title=title,
126
+ x_label=x_label,
127
+ y_label=y_label,
128
+ )
129
+ ax.grid(False)
130
+
131
+ if source_text:
132
+ gu.add_source_text(ax=ax, source_text=source_text)
133
+
134
+ return gu.standard_tick_styles(ax)
@@ -0,0 +1,276 @@
1
+ """This module provides flexible functionality for creating histograms from pandas DataFrames or Series.
2
+
3
+ It allows you to visualize distributions of one or more value columns and optionally group them by a categorical column.
4
+ The module is designed to handle both DataFrames and Series, allowing you to create simple histograms or compare
5
+ distributions across categories by splitting the data into multiple histograms.
6
+
7
+ ### Core Features
8
+
9
+ - **Single or Multiple Histograms**: Plot one or more value columns (**`value_col`**) as histograms.
10
+ For example, visualize the distribution of a single metric or compare multiple metrics simultaneously.
11
+ - **Grouped Histograms**: Create separate histograms for each unique value in **`group_col`** (e.g., product categories
12
+ or regions), allowing for easy comparison of distributions across groups.
13
+ - **Range Clipping and Filling**: Use **`range_lower`** and **`range_upper`** to limit the values being plotted by
14
+ clipping them or filling values outside the range with **NaN**. This is particularly useful when visualizing specific
15
+ data ranges.
16
+ - **Comprehensive Customization**: Customize plot titles, axis labels, and legends, with the option to
17
+ move the legend outside the plot.
18
+
19
+ ### Use Cases
20
+
21
+ - **Distribution Analysis**: Visualize the distribution of key metrics like revenue, sales, or user activity using
22
+ single or multiple histograms.
23
+ - **Group Comparisons**: Compare distributions across different groups, such as product categories,
24
+ geographic regions, or customer segments. For instance, plot histograms to show how sales vary across
25
+ different product categories.
26
+ - **Trends and Ranges**: Use **range_lower** and **range_upper** to visualize data within specific ranges, filtering out
27
+ outliers or focusing on key metrics for analysis.
28
+
29
+ ### Limitations and Handling of Data
30
+
31
+ - **Pre-Aggregated Data Required**: This module does not perform any data aggregation, so all data must
32
+ be pre-aggregated before being passed in for plotting.
33
+ - **Grouped Histograms**: If **`group_col`** is provided, the data will be pivoted so that each unique value in
34
+ **`group_col`** becomes a separate histogram. Otherwise, a single histogram is plotted.
35
+ - **Series Support**: The module can also handle pandas Series, though **`group_col`** cannot be provided
36
+ when plotting a Series.
37
+
38
+ ### Additional Features
39
+
40
+ - **Range Clipping or Filling**: You can control how the data is visualized by specifying bounds. If data points fall
41
+ outside the defined range, you can either clip them to the boundary values or fill them with **NaN** for exclusion.
42
+ - **Legend Customization**: For multiple histograms, you can add legends, including the option to move the
43
+ legend outside the plot for clarity.
44
+
45
+ """
46
+
47
+ from typing import Any, Literal
48
+
49
+ import numpy as np
50
+ import pandas as pd
51
+ from matplotlib.axes import Axes, SubplotBase
52
+
53
+ import openretailscience.plots.styles.graph_utils as gu
54
+ from openretailscience.plots.styles.colors import get_plot_colors
55
+
56
+
57
+ def plot(
58
+ df: pd.DataFrame | pd.Series,
59
+ value_col: str | list[str] | None = None,
60
+ group_col: str | None = None,
61
+ title: str | None = None,
62
+ x_label: str | None = None,
63
+ y_label: str | None = None,
64
+ legend_title: str | None = None,
65
+ ax: Axes | None = None,
66
+ source_text: str | None = None,
67
+ move_legend_outside: bool = False,
68
+ range_lower: float | None = None,
69
+ range_upper: float | None = None,
70
+ range_method: Literal["clip", "fillna"] = "clip",
71
+ use_hatch: bool = False,
72
+ **kwargs: dict[str, Any],
73
+ ) -> SubplotBase:
74
+ """Plots a histogram of `value_col`, optionally split by `group_col`.
75
+
76
+ Args:
77
+ df (pd.DataFrame | pd.Series): The dataframe (or series) to plot.
78
+ value_col (str or list of str, optional): The column(s) to plot.
79
+ Can be a list of columns for multiple histograms.
80
+ group_col (str, optional): The column used to define different histograms.
81
+ title (str, optional): The title of the plot.
82
+ x_label (str, optional): The x-axis label.
83
+ y_label (str, optional): The y-axis label.
84
+ legend_title (str, optional): The title of the legend.
85
+ ax (Axes, optional): Matplotlib axes object to plot on.
86
+ source_text (str, optional): The source text to add to the plot.
87
+ move_legend_outside (bool, optional): Move the legend outside the plot.
88
+ range_lower (float, optional): Lower bound for clipping or filling NA values.
89
+ range_upper (float, optional): Upper bound for clipping or filling NA values.
90
+ range_method (str, optional): Whether to "clip" values outside the range or "fillna". Defaults to "clip".
91
+ use_hatch (bool, optional): Whether to use hatching for the bars.
92
+ **kwargs: Additional keyword arguments for Pandas' `plot` function.
93
+
94
+ Returns:
95
+ SubplotBase: The matplotlib axes object.
96
+ """
97
+ if isinstance(value_col, list) and group_col is not None:
98
+ raise ValueError("`value_col` cannot be a list when `group_col` is provided. Please choose one or the other.")
99
+
100
+ value_col = _prepare_value_col(df=df, value_col=value_col)
101
+
102
+ if isinstance(df, pd.Series):
103
+ df = df.to_frame(name=value_col[0])
104
+
105
+ if (range_lower is not None) or (range_upper is not None):
106
+ df = _apply_range_clipping(
107
+ df=df,
108
+ value_col=value_col,
109
+ range_lower=range_lower,
110
+ range_upper=range_upper,
111
+ range_method=range_method,
112
+ )
113
+
114
+ num_histograms = _get_num_histograms(df=df, value_col=value_col, group_col=group_col)
115
+
116
+ default_colors = get_plot_colors(num_histograms)
117
+ colors = kwargs.pop("color", default_colors)
118
+
119
+ ax = _plot_histogram(
120
+ df=df,
121
+ value_col=value_col,
122
+ group_col=group_col,
123
+ ax=ax,
124
+ colors=colors,
125
+ num_histograms=num_histograms,
126
+ **kwargs,
127
+ )
128
+
129
+ ax = gu.standard_graph_styles(
130
+ ax=ax,
131
+ title=title,
132
+ x_label=x_label,
133
+ y_label=y_label,
134
+ legend_title=legend_title,
135
+ move_legend_outside=move_legend_outside,
136
+ )
137
+
138
+ if use_hatch:
139
+ ax = gu.apply_hatches(ax=ax, num_segments=num_histograms)
140
+
141
+ if source_text:
142
+ gu.add_source_text(ax=ax, source_text=source_text)
143
+
144
+ return gu.standard_tick_styles(ax=ax)
145
+
146
+
147
+ def _prepare_value_col(df: pd.DataFrame | pd.Series, value_col: str | list[str] | None) -> list[str]:
148
+ """Ensures that value_col is properly handled and returned as a list.
149
+
150
+ Args:
151
+ df (pd.DataFrame | pd.Series): The input dataframe or series.
152
+ value_col (str or list of str, optional): The column(s) to plot. If a single string, it is converted to a list.
153
+
154
+ Returns:
155
+ list[str]: The processed value_col as a list of strings.
156
+ """
157
+ if isinstance(df, pd.Series):
158
+ return ["value"] if value_col is None else [value_col]
159
+
160
+ if value_col is None:
161
+ raise ValueError("Please provide a value column to plot")
162
+
163
+ if isinstance(value_col, str):
164
+ value_col = [value_col]
165
+
166
+ return value_col
167
+
168
+
169
+ def _apply_range_clipping(
170
+ df: pd.DataFrame,
171
+ value_col: list[str],
172
+ range_lower: float | None = None,
173
+ range_upper: float | None = None,
174
+ range_method: Literal["clip", "fillna"] = "fillna",
175
+ ) -> pd.DataFrame:
176
+ """Applies range clipping or filling based on the provided method and returns the modified dataframe.
177
+
178
+ Args:
179
+ df (pd.DataFrame): The dataframe to apply range clipping to.
180
+ value_col (list of str): The column(s) to apply clipping or filling to.
181
+ range_lower (float | None, optional): Lower bound for clipping or filling NA values.
182
+ range_upper (float | None, optional): Upper bound for clipping or filling NA values.
183
+ range_method (Literal, optional): Whether to "clip" values outside the range or "fillna". Defaults to "fillna".
184
+
185
+ Returns:
186
+ pd.DataFrame: The modified dataframe with the clipping or filling applied.
187
+ """
188
+ if range_method not in ["clip", "fillna"]:
189
+ error_msg = f"Invalid range_method: {range_method}. Expected 'clip' or 'fillna'."
190
+ raise ValueError(error_msg)
191
+
192
+ if range_method == "clip":
193
+ # Clip values based on the provided lower and upper bounds
194
+ return df.assign(**{col: df[col].clip(lower=range_lower, upper=range_upper) for col in value_col})
195
+
196
+ # For the "fillna" method, we will create a mask for the valid range and replace out-of-range values with NaN
197
+ def apply_mask(col: str) -> pd.Series:
198
+ mask = pd.Series([True] * len(df))
199
+ if range_lower is not None:
200
+ mask &= df[col] >= range_lower
201
+ if range_upper is not None:
202
+ mask &= df[col] <= range_upper
203
+ return df[col].where(mask, np.nan)
204
+
205
+ # Apply the mask to each column
206
+ return df.assign(**{col: apply_mask(col) for col in value_col})
207
+
208
+
209
+ def _get_num_histograms(df: pd.DataFrame, value_col: list[str], group_col: str | None) -> int:
210
+ """Calculates the number of histograms to be plotted.
211
+
212
+ Args:
213
+ df (pd.DataFrame): The dataframe being plotted.
214
+ value_col (list of str): The column(s) being plotted.
215
+ group_col (str, optional): The column used for grouping data into histograms.
216
+
217
+ Returns:
218
+ int: The number of histograms to plot.
219
+ """
220
+ num_histograms = len(value_col)
221
+
222
+ if group_col is not None:
223
+ num_histograms = max(num_histograms, df[group_col].nunique())
224
+
225
+ return num_histograms
226
+
227
+
228
+ def _plot_histogram(
229
+ df: pd.DataFrame,
230
+ value_col: list[str],
231
+ group_col: str | None,
232
+ ax: Axes | None,
233
+ colors: list[str],
234
+ num_histograms: int,
235
+ **kwargs: dict,
236
+ ) -> Axes:
237
+ """Plots histograms for the provided dataframe.
238
+
239
+ Args:
240
+ df (pd.DataFrame): The dataframe to plot.
241
+ value_col (list of str): The column(s) to plot.
242
+ group_col (str, optional): The column used to group data into multiple histograms.
243
+ ax (Axes, optional): Matplotlib axes object to plot on.
244
+ colors: The list of colors use for the plot.
245
+ num_histograms (int): The number of histograms being plotted.
246
+ **kwargs: Additional keyword arguments for Pandas' `plot` function.
247
+
248
+ Returns:
249
+ Axes: The matplotlib axes object with the plotted histogram.
250
+ """
251
+ is_multi_histogram = num_histograms > 1
252
+
253
+ alpha = kwargs.pop("alpha", 0.7) if is_multi_histogram else kwargs.pop("alpha", None)
254
+
255
+ if group_col is None:
256
+ return df[value_col].plot(
257
+ kind="hist",
258
+ ax=ax,
259
+ legend=is_multi_histogram,
260
+ color=colors,
261
+ alpha=alpha,
262
+ **kwargs,
263
+ )
264
+
265
+ # if group_col is provided, only use a single value_col
266
+ df_pivot = df.pivot(columns=group_col, values=value_col[0])
267
+
268
+ # Plot all columns at once
269
+ return df_pivot.plot(
270
+ kind="hist",
271
+ ax=ax,
272
+ legend=is_multi_histogram,
273
+ alpha=alpha,
274
+ color=colors,
275
+ **kwargs,
276
+ )