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