Skip to content

Commit e654ddc

Browse files
feat: implement box-basic plot for all 8 libraries
- Created spec file specs/box-basic.md defining requirements for basic box plots - Implemented matplotlib version with customizable colors and outlier display - Implemented seaborn version with statistical annotations - Implemented plotly version with interactive hover information - Implemented bokeh version with HTML output and customizable whiskers - Implemented altair version with declarative Vega-Lite approach - Implemented plotnine version with ggplot2-style grammar of graphics - Implemented pygal version with SVG output - Implemented highcharts version with interactive web visualization All implementations: - Show quartiles (Q1, median, Q3) as boxes - Display whiskers extending to 1.5 * IQR - Mark outliers as individual points - Include sample size annotations - Support multiple groups for comparison - Use deterministic sample data with fixed seed Closes #41
1 parent a8c8bc8 commit e654ddc

9 files changed

Lines changed: 1766 additions & 0 deletions

File tree

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
"""
2+
box-basic: Basic Box Plot
3+
Implementation for: altair
4+
Variant: default
5+
Python: 3.10+
6+
"""
7+
8+
import altair as alt
9+
import pandas as pd
10+
import numpy as np
11+
from typing import TYPE_CHECKING, Optional
12+
13+
if TYPE_CHECKING:
14+
from altair import Chart
15+
16+
17+
def create_plot(
18+
data: pd.DataFrame,
19+
values: str,
20+
groups: str,
21+
title: Optional[str] = None,
22+
xlabel: Optional[str] = None,
23+
ylabel: Optional[str] = None,
24+
color_scheme: str = 'set2',
25+
width: int = 600,
26+
height: int = 400,
27+
**kwargs
28+
) -> Chart:
29+
"""
30+
Create a basic box plot showing statistical distribution of multiple groups using altair.
31+
32+
Args:
33+
data: Input DataFrame with required columns
34+
values: Column name containing numeric values
35+
groups: Column name containing group categories
36+
title: Plot title (optional)
37+
xlabel: Custom x-axis label (optional, defaults to groups column name)
38+
ylabel: Custom y-axis label (optional, defaults to values column name)
39+
color_scheme: Color scheme for boxes (default: 'set2')
40+
width: Figure width in pixels (default: 600)
41+
height: Figure height in pixels (default: 400)
42+
**kwargs: Additional parameters for altair chart configuration
43+
44+
Returns:
45+
Altair Chart object
46+
47+
Raises:
48+
ValueError: If data is empty
49+
KeyError: If required columns not found
50+
51+
Example:
52+
>>> data = pd.DataFrame({
53+
... 'Group': ['A', 'A', 'B', 'B', 'C', 'C'],
54+
... 'Value': [1, 2, 2, 3, 3, 4]
55+
... })
56+
>>> chart = create_plot(data, values='Value', groups='Group')
57+
"""
58+
# Input validation
59+
if data.empty:
60+
raise ValueError("Data cannot be empty")
61+
62+
# Check required columns
63+
for col in [values, groups]:
64+
if col not in data.columns:
65+
available = ", ".join(data.columns)
66+
raise KeyError(f"Column '{col}' not found. Available columns: {available}")
67+
68+
# Create the box plot using Altair's mark_boxplot
69+
base = alt.Chart(data).mark_boxplot(
70+
extent=1.5, # 1.5 * IQR for whiskers
71+
outliers=True,
72+
size=40,
73+
opacity=0.7
74+
).encode(
75+
x=alt.X(
76+
f'{groups}:N',
77+
title=xlabel or groups,
78+
axis=alt.Axis(
79+
labelAngle=0 if data[groups].nunique() <= 5 else -45,
80+
labelLimit=200
81+
)
82+
),
83+
y=alt.Y(
84+
f'{values}:Q',
85+
title=ylabel or values,
86+
scale=alt.Scale(zero=False)
87+
),
88+
color=alt.Color(
89+
f'{groups}:N',
90+
scale=alt.Scale(scheme=color_scheme),
91+
legend=None # Hide legend as it's redundant with x-axis
92+
),
93+
tooltip=[
94+
alt.Tooltip(f'{groups}:N', title='Group'),
95+
alt.Tooltip(f'count({values}):Q', title='Count'),
96+
alt.Tooltip(f'min({values}):Q', title='Min', format='.2f'),
97+
alt.Tooltip(f'q1({values}):Q', title='Q1', format='.2f'),
98+
alt.Tooltip(f'median({values}):Q', title='Median', format='.2f'),
99+
alt.Tooltip(f'q3({values}):Q', title='Q3', format='.2f'),
100+
alt.Tooltip(f'max({values}):Q', title='Max', format='.2f')
101+
]
102+
)
103+
104+
# Add sample size annotations
105+
text = alt.Chart(data).mark_text(
106+
align='center',
107+
baseline='top',
108+
dy=10,
109+
fontSize=10,
110+
opacity=0.7
111+
).encode(
112+
x=alt.X(f'{groups}:N'),
113+
y=alt.Y(f'min({values}):Q'),
114+
text=alt.Text('count():Q', format='d')
115+
).transform_aggregate(
116+
count='count()',
117+
groupby=[groups]
118+
)
119+
120+
# Combine box plot with annotations
121+
chart = (base + text).properties(
122+
width=width,
123+
height=height,
124+
title=alt.TitleParams(
125+
text=title or 'Box Plot Distribution',
126+
fontSize=16,
127+
anchor='middle'
128+
)
129+
).configure_view(
130+
strokeWidth=0
131+
).configure_axis(
132+
grid=True,
133+
gridOpacity=0.3,
134+
gridDash=[3, 3],
135+
domainWidth=1,
136+
tickWidth=1
137+
).configure_boxplot(
138+
median=dict(color='red', strokeWidth=2),
139+
box=dict(strokeWidth=1.5),
140+
outliers=dict(fill='red', fillOpacity=0.5, size=50)
141+
)
142+
143+
return chart
144+
145+
146+
if __name__ == '__main__':
147+
# Sample data for testing with different distributions per group
148+
np.random.seed(42) # For reproducibility
149+
150+
# Generate sample data with 4 groups
151+
data_dict = {
152+
'Group': [],
153+
'Value': []
154+
}
155+
156+
# Group A: Normal distribution, mean=50, std=10
157+
group_a_data = np.random.normal(50, 10, 40)
158+
# Add some outliers
159+
group_a_data = np.append(group_a_data, [80, 85, 15])
160+
161+
# Group B: Normal distribution, mean=60, std=15
162+
group_b_data = np.random.normal(60, 15, 35)
163+
# Add outliers
164+
group_b_data = np.append(group_b_data, [100, 10])
165+
166+
# Group C: Normal distribution, mean=45, std=8
167+
group_c_data = np.random.normal(45, 8, 45)
168+
169+
# Group D: Skewed distribution
170+
group_d_data = np.random.gamma(2, 2, 30) + 40
171+
# Add outliers
172+
group_d_data = np.append(group_d_data, [75, 78, 20])
173+
174+
# Combine all data
175+
for group, values in zip(
176+
['Group A', 'Group B', 'Group C', 'Group D'],
177+
[group_a_data, group_b_data, group_c_data, group_d_data]
178+
):
179+
data_dict['Group'].extend([group] * len(values))
180+
data_dict['Value'].extend(values)
181+
182+
data = pd.DataFrame(data_dict)
183+
184+
# Create plot
185+
chart = create_plot(
186+
data,
187+
values='Value',
188+
groups='Group',
189+
title='Statistical Distribution Comparison Across Groups',
190+
ylabel='Measurement Value',
191+
xlabel='Categories'
192+
)
193+
194+
# Save for inspection
195+
chart.save('plot.html')
196+
print("Interactive plot saved to plot.html")
197+
198+
# Also save as PNG
199+
chart.save('plot.png', scale_factor=2.0)
200+
print("Static plot saved to plot.png")

0 commit comments

Comments
 (0)