From d4d6b922d0abf2a058fc645cc23ffedc7391b17e Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Sun, 7 Dec 2025 00:49:15 +0000 Subject: [PATCH] feat(plotly): implement box-basic Implements basic box plot for plotly library following KISS principles. Uses go.Box traces with colors from style guide. - 4 groups with normal distributions - Outlier display enabled - Clean plotly_white template - 4800x2700px output (scale=3) --- plots/plotly/box/box-basic/default.py | 243 +++++--------------------- 1 file changed, 45 insertions(+), 198 deletions(-) diff --git a/plots/plotly/box/box-basic/default.py b/plots/plotly/box/box-basic/default.py index 1cf05b7dfe..d916ce3173 100644 --- a/plots/plotly/box/box-basic/default.py +++ b/plots/plotly/box/box-basic/default.py @@ -1,206 +1,53 @@ """ box-basic: Basic Box Plot -Implementation for: plotly -Variant: default -Python: 3.10+ +Library: plotly """ -from typing import TYPE_CHECKING, Optional - import numpy as np import pandas as pd -import plotly.express as px - - -if TYPE_CHECKING: - from plotly.graph_objects import Figure - - -def create_plot( - data: pd.DataFrame, - values: str, - groups: str, - title: Optional[str] = None, - xlabel: Optional[str] = None, - ylabel: Optional[str] = None, - color_discrete_sequence: Optional[list] = None, - height: int = 900, - width: int = 1600, - showlegend: bool = False, - **kwargs, -) -> Figure: - """ - Create an interactive box plot showing statistical distribution of multiple groups using plotly. - - Args: - data: Input DataFrame with required columns - values: Column name containing numeric values - groups: Column name containing group categories - title: Plot title (optional) - xlabel: Custom x-axis label (optional, defaults to groups column name) - ylabel: Custom y-axis label (optional, defaults to values column name) - color_discrete_sequence: List of colors for each box (optional) - height: Figure height in pixels (default: 900) - width: Figure width in pixels (default: 1600) - showlegend: Whether to show legend (default: False) - **kwargs: Additional parameters passed to plotly box trace - - Returns: - Plotly Figure object - - Raises: - ValueError: If data is empty - KeyError: If required columns not found - - Example: - >>> data = pd.DataFrame({ - ... 'Group': ['A', 'A', 'B', 'B', 'C', 'C'], - ... 'Value': [1, 2, 2, 3, 3, 4] - ... }) - >>> fig = create_plot(data, values='Value', groups='Group') - """ - # Input validation - if data.empty: - raise ValueError("Data cannot be empty") - - # Check required columns - for col in [values, groups]: - if col not in data.columns: - available = ", ".join(data.columns) - raise KeyError(f"Column '{col}' not found. Available columns: {available}") - - # Use plotly.express for easier box plot creation - fig = px.box( - data, - x=groups, - y=values, - color=groups, - color_discrete_sequence=color_discrete_sequence or px.colors.qualitative.Set2, - notched=False, - points="outliers", # Show only outliers as points - **kwargs, - ) - - # Update traces for better styling - fig.update_traces( - boxmean="sd", # Show mean and standard deviation - marker={"size": 8, "opacity": 0.5, "line": {"width": 1}}, - line={"width": 1.5}, - fillcolor=None, - opacity=0.7, - ) - - # Update layout - fig.update_layout( - title={ - "text": title or "Box Plot Distribution", - "font": {"size": 16, "family": "Arial, sans-serif"}, - "x": 0.5, - "xanchor": "center", - }, - xaxis={ - "title": xlabel or groups, - "gridcolor": "lightgray", - "gridwidth": 0.5, - "showgrid": False, - "zeroline": False, - }, - yaxis={ - "title": ylabel or values, - "gridcolor": "lightgray", - "gridwidth": 0.5, - "showgrid": True, - "zeroline": True, - "zerolinewidth": 1, - "zerolinecolor": "lightgray", - }, - plot_bgcolor="white", - paper_bgcolor="white", - height=height, - width=width, - showlegend=showlegend, - hovermode="x unified", - hoverlabel={"bgcolor": "white", "font_size": 12, "font_family": "Arial, sans-serif"}, - ) - - # Add annotations with sample sizes - group_counts = data.groupby(groups)[values].count() - annotations = [] - for _i, (group_name, count) in enumerate(group_counts.items()): - annotations.append( - { - "x": group_name, - "y": data[data[groups] == group_name][values].min() - (data[values].max() - data[values].min()) * 0.05, - "text": f"n={count}", - "showarrow": False, - "font": {"size": 10, "color": "gray"}, - "xanchor": "center", - "yanchor": "top", - } - ) - - fig.update_layout(annotations=annotations) - - # Update hover template for better information - fig.update_traces( - hovertemplate="%{x}
" - + "Max: %{y}
" - + "Q3: %{upperfence}
" - + "Median: %{median}
" - + "Q1: %{lowerfence}
" - + "Min: %{y}
" - + "" - ) - - return fig - - -if __name__ == "__main__": - # Sample data for testing with different distributions per group - np.random.seed(42) # For reproducibility - - # Generate sample data with 4 groups - data_dict = {"Group": [], "Value": []} - - # Group A: Normal distribution, mean=50, std=10 - group_a_data = np.random.normal(50, 10, 40) - # Add some outliers - group_a_data = np.append(group_a_data, [80, 85, 15]) - - # Group B: Normal distribution, mean=60, std=15 - group_b_data = np.random.normal(60, 15, 35) - # Add outliers - group_b_data = np.append(group_b_data, [100, 10]) - - # Group C: Normal distribution, mean=45, std=8 - group_c_data = np.random.normal(45, 8, 45) - - # Group D: Skewed distribution - group_d_data = np.random.gamma(2, 2, 30) + 40 - # Add outliers - group_d_data = np.append(group_d_data, [75, 78, 20]) - - # Combine all data - for group, values in zip( - ["Group A", "Group B", "Group C", "Group D"], - [group_a_data, group_b_data, group_c_data, group_d_data], - strict=False, - ): - data_dict["Group"].extend([group] * len(values)) - data_dict["Value"].extend(values) - - data = pd.DataFrame(data_dict) - - # Create plot - fig = create_plot( - data, - values="Value", - groups="Group", - title="Statistical Distribution Comparison Across Groups", - ylabel="Measurement Value", - xlabel="Categories", +import plotly.graph_objects as go + + +# Data +np.random.seed(42) +data = pd.DataFrame( + { + "group": ["A"] * 50 + ["B"] * 50 + ["C"] * 50 + ["D"] * 50, + "value": np.concatenate( + [ + np.random.normal(50, 10, 50), + np.random.normal(60, 15, 50), + np.random.normal(45, 8, 50), + np.random.normal(70, 20, 50), + ] + ), + } +) + +# Colors from style guide +colors = ["#306998", "#FFD43B", "#DC2626", "#059669"] + +# Create figure +fig = go.Figure() + +# Add box trace for each group +for i, group in enumerate(["A", "B", "C", "D"]): + group_data = data[data["group"] == group]["value"] + fig.add_trace( + go.Box(y=group_data, name=f"Group {group}", marker_color=colors[i], boxpoints="outliers", line_width=2) ) - # Save as PNG - fig.write_image("plot.png", width=1600, height=900, scale=2) - print("Plot saved to plot.png") +# Layout +fig.update_layout( + title={"text": "Basic Box Plot", "font": {"size": 40}, "x": 0.5, "xanchor": "center"}, + xaxis_title={"text": "Group", "font": {"size": 32}}, + yaxis_title={"text": "Value", "font": {"size": 32}}, + template="plotly_white", + showlegend=False, + font={"size": 26}, + xaxis={"tickfont": {"size": 26}}, + yaxis={"tickfont": {"size": 26}, "gridwidth": 1, "gridcolor": "rgba(0,0,0,0.1)"}, +) + +# Save +fig.write_image("plot.png", width=1600, height=900, scale=3)