|
| 1 | +""" pyplots.ai |
| 2 | +coefficient-confidence: Coefficient Plot with Confidence Intervals |
| 3 | +Library: highcharts unknown | Python 3.13.11 |
| 4 | +Quality: 92/100 | Created: 2026-01-09 |
| 5 | +""" |
| 6 | + |
| 7 | +import json |
| 8 | +import tempfile |
| 9 | +import time |
| 10 | +import urllib.request |
| 11 | +from pathlib import Path |
| 12 | + |
| 13 | +import numpy as np |
| 14 | +from selenium import webdriver |
| 15 | +from selenium.webdriver.chrome.options import Options |
| 16 | + |
| 17 | + |
| 18 | +# Data: Coefficients from a regression model predicting housing prices |
| 19 | +np.random.seed(42) |
| 20 | + |
| 21 | +variables = [ |
| 22 | + "Square Footage", |
| 23 | + "Number of Bedrooms", |
| 24 | + "Number of Bathrooms", |
| 25 | + "Garage Spaces", |
| 26 | + "Lot Size (acres)", |
| 27 | + "Year Built", |
| 28 | + "Distance to City Center", |
| 29 | + "School Rating", |
| 30 | + "Crime Rate Index", |
| 31 | + "Property Tax Rate", |
| 32 | +] |
| 33 | + |
| 34 | +# Generate realistic coefficients with varying significance |
| 35 | +coefficients = np.array([0.45, 0.12, 0.28, 0.15, 0.08, 0.02, -0.22, 0.35, -0.18, -0.05]) |
| 36 | +std_errors = np.array([0.08, 0.09, 0.07, 0.06, 0.10, 0.04, 0.05, 0.06, 0.07, 0.08]) |
| 37 | + |
| 38 | +# 95% confidence intervals |
| 39 | +ci_lower = coefficients - 1.96 * std_errors |
| 40 | +ci_upper = coefficients + 1.96 * std_errors |
| 41 | + |
| 42 | +# Determine significance (CI does not cross zero) |
| 43 | +significant = (ci_lower > 0) | (ci_upper < 0) |
| 44 | + |
| 45 | +# Sort by coefficient magnitude (highest at top, so reverse for display) |
| 46 | +sort_idx = np.argsort(coefficients)[::-1] |
| 47 | +variables = [variables[i] for i in sort_idx] |
| 48 | +coefficients = coefficients[sort_idx] |
| 49 | +ci_lower = ci_lower[sort_idx] |
| 50 | +ci_upper = ci_upper[sort_idx] |
| 51 | +significant = significant[sort_idx] |
| 52 | + |
| 53 | +# Download Highcharts JS |
| 54 | +highcharts_url = "https://code.highcharts.com/highcharts.js" |
| 55 | +with urllib.request.urlopen(highcharts_url, timeout=30) as response: |
| 56 | + highcharts_js = response.read().decode("utf-8") |
| 57 | + |
| 58 | +highcharts_more_url = "https://code.highcharts.com/highcharts-more.js" |
| 59 | +with urllib.request.urlopen(highcharts_more_url, timeout=30) as response: |
| 60 | + highcharts_more_js = response.read().decode("utf-8") |
| 61 | + |
| 62 | +xrange_url = "https://code.highcharts.com/modules/xrange.js" |
| 63 | +with urllib.request.urlopen(xrange_url, timeout=30) as response: |
| 64 | + xrange_js = response.read().decode("utf-8") |
| 65 | + |
| 66 | +# Build data arrays for JavaScript |
| 67 | +sig_points = [] |
| 68 | +nonsig_points = [] |
| 69 | + |
| 70 | +for i, (var, coef, sig) in enumerate(zip(variables, coefficients, significant, strict=True)): |
| 71 | + point = {"x": float(coef), "y": i, "name": var} |
| 72 | + if sig: |
| 73 | + sig_points.append(point) |
| 74 | + else: |
| 75 | + nonsig_points.append(point) |
| 76 | + |
| 77 | +# Convert to JS format using json for proper escaping |
| 78 | +categories_js = json.dumps(variables) |
| 79 | +sig_points_js = json.dumps(sig_points) |
| 80 | +nonsig_points_js = json.dumps(nonsig_points) |
| 81 | + |
| 82 | +# Build CI data for xrange series |
| 83 | +ci_data = [] |
| 84 | +for i, (lower, upper, sig) in enumerate(zip(ci_lower, ci_upper, significant, strict=True)): |
| 85 | + ci_data.append({"x": float(lower), "x2": float(upper), "y": i, "color": "#306998" if sig else "#B8860B"}) |
| 86 | + |
| 87 | +ci_data_js = json.dumps(ci_data) |
| 88 | + |
| 89 | +# Calculate x-axis range to ensure all points are visible |
| 90 | +x_min = float(min(ci_lower)) - 0.05 |
| 91 | +x_max = float(max(ci_upper)) + 0.05 |
| 92 | + |
| 93 | +chart_js = f""" |
| 94 | +Highcharts.chart('container', {{ |
| 95 | + chart: {{ |
| 96 | + width: 4800, |
| 97 | + height: 2700, |
| 98 | + backgroundColor: '#ffffff', |
| 99 | + marginLeft: 420, |
| 100 | + marginRight: 180, |
| 101 | + marginTop: 180, |
| 102 | + marginBottom: 280, |
| 103 | + spacingBottom: 40 |
| 104 | + }}, |
| 105 | + title: {{ |
| 106 | + text: 'coefficient-confidence · highcharts · pyplots.ai', |
| 107 | + style: {{ fontSize: '56px', fontWeight: 'bold' }} |
| 108 | + }}, |
| 109 | + subtitle: {{ |
| 110 | + text: 'Housing Price Regression Model Coefficients with 95% Confidence Intervals', |
| 111 | + style: {{ fontSize: '36px', color: '#555555' }} |
| 112 | + }}, |
| 113 | + xAxis: {{ |
| 114 | + min: {x_min}, |
| 115 | + max: {x_max}, |
| 116 | + title: {{ |
| 117 | + text: 'Coefficient Estimate (Standardized)', |
| 118 | + style: {{ fontSize: '40px' }}, |
| 119 | + margin: 25 |
| 120 | + }}, |
| 121 | + labels: {{ |
| 122 | + style: {{ fontSize: '32px' }}, |
| 123 | + format: '{{value:.1f}}' |
| 124 | + }}, |
| 125 | + gridLineWidth: 1, |
| 126 | + gridLineColor: '#e0e0e0', |
| 127 | + tickInterval: 0.1, |
| 128 | + plotLines: [{{ |
| 129 | + value: 0, |
| 130 | + color: '#666666', |
| 131 | + width: 4, |
| 132 | + dashStyle: 'Dash', |
| 133 | + zIndex: 3, |
| 134 | + label: {{ |
| 135 | + text: 'Null (β = 0)', |
| 136 | + style: {{ fontSize: '28px', color: '#666666', fontWeight: 'bold' }}, |
| 137 | + rotation: 0, |
| 138 | + y: 30, |
| 139 | + x: 10 |
| 140 | + }} |
| 141 | + }}] |
| 142 | + }}, |
| 143 | + yAxis: {{ |
| 144 | + title: {{ text: null }}, |
| 145 | + categories: {categories_js}, |
| 146 | + labels: {{ |
| 147 | + style: {{ fontSize: '32px' }} |
| 148 | + }}, |
| 149 | + gridLineWidth: 0, |
| 150 | + reversed: false |
| 151 | + }}, |
| 152 | + legend: {{ |
| 153 | + enabled: true, |
| 154 | + itemStyle: {{ fontSize: '36px' }}, |
| 155 | + verticalAlign: 'bottom', |
| 156 | + align: 'center', |
| 157 | + layout: 'horizontal', |
| 158 | + floating: false, |
| 159 | + y: -20, |
| 160 | + symbolRadius: 14, |
| 161 | + symbolHeight: 28, |
| 162 | + symbolWidth: 28, |
| 163 | + itemDistance: 100 |
| 164 | + }}, |
| 165 | + tooltip: {{ |
| 166 | + style: {{ fontSize: '28px' }}, |
| 167 | + useHTML: true, |
| 168 | + formatter: function() {{ |
| 169 | + if (this.series.type === 'xrange') {{ |
| 170 | + return '<b>' + this.yCategory + '</b><br/>CI: [' + this.point.x.toFixed(3) + ', ' + this.point.x2.toFixed(3) + ']'; |
| 171 | + }} |
| 172 | + return '<b>' + this.point.name + '</b><br/>Coefficient: ' + this.x.toFixed(3); |
| 173 | + }} |
| 174 | + }}, |
| 175 | + plotOptions: {{ |
| 176 | + scatter: {{ |
| 177 | + marker: {{ |
| 178 | + radius: 22, |
| 179 | + symbol: 'circle' |
| 180 | + }}, |
| 181 | + zIndex: 10 |
| 182 | + }}, |
| 183 | + xrange: {{ |
| 184 | + borderRadius: 0, |
| 185 | + pointWidth: 8, |
| 186 | + zIndex: 5 |
| 187 | + }} |
| 188 | + }}, |
| 189 | + series: [{{ |
| 190 | + type: 'xrange', |
| 191 | + name: '95% CI', |
| 192 | + data: {ci_data_js}, |
| 193 | + showInLegend: false, |
| 194 | + enableMouseTracking: true |
| 195 | + }}, {{ |
| 196 | + type: 'scatter', |
| 197 | + name: 'Significant (p < 0.05)', |
| 198 | + color: '#306998', |
| 199 | + data: {sig_points_js}, |
| 200 | + marker: {{ |
| 201 | + fillColor: '#306998', |
| 202 | + lineWidth: 0, |
| 203 | + radius: 22 |
| 204 | + }}, |
| 205 | + zIndex: 10, |
| 206 | + clip: false |
| 207 | + }}, {{ |
| 208 | + type: 'scatter', |
| 209 | + name: 'Not Significant', |
| 210 | + color: '#FFD43B', |
| 211 | + data: {nonsig_points_js}, |
| 212 | + marker: {{ |
| 213 | + fillColor: '#FFD43B', |
| 214 | + lineWidth: 4, |
| 215 | + lineColor: '#B8860B', |
| 216 | + radius: 22 |
| 217 | + }}, |
| 218 | + zIndex: 10, |
| 219 | + clip: false |
| 220 | + }}] |
| 221 | +}}); |
| 222 | +""" |
| 223 | + |
| 224 | +# HTML content with inline scripts |
| 225 | +html_content = f"""<!DOCTYPE html> |
| 226 | +<html> |
| 227 | +<head> |
| 228 | + <meta charset="utf-8"> |
| 229 | + <script>{highcharts_js}</script> |
| 230 | + <script>{highcharts_more_js}</script> |
| 231 | + <script>{xrange_js}</script> |
| 232 | +</head> |
| 233 | +<body style="margin:0; padding:0; background:#ffffff;"> |
| 234 | + <div id="container" style="width: 4800px; height: 2700px;"></div> |
| 235 | + <script>{chart_js}</script> |
| 236 | +</body> |
| 237 | +</html>""" |
| 238 | + |
| 239 | +# Write temp HTML and take screenshot |
| 240 | +with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f: |
| 241 | + f.write(html_content) |
| 242 | + temp_path = f.name |
| 243 | + |
| 244 | +# Save HTML for interactive version |
| 245 | +with open("plot.html", "w", encoding="utf-8") as f: |
| 246 | + f.write(html_content) |
| 247 | + |
| 248 | +# Chrome options for headless screenshot |
| 249 | +chrome_options = Options() |
| 250 | +chrome_options.add_argument("--headless") |
| 251 | +chrome_options.add_argument("--no-sandbox") |
| 252 | +chrome_options.add_argument("--disable-dev-shm-usage") |
| 253 | +chrome_options.add_argument("--disable-gpu") |
| 254 | +chrome_options.add_argument("--window-size=4800,2800") # Slightly larger to capture legend |
| 255 | + |
| 256 | +driver = webdriver.Chrome(options=chrome_options) |
| 257 | +driver.get(f"file://{temp_path}") |
| 258 | +time.sleep(5) # Wait for chart to render |
| 259 | + |
| 260 | +# Get the container element and take a screenshot of just that element |
| 261 | +container = driver.find_element("id", "container") |
| 262 | +container.screenshot("plot.png") |
| 263 | + |
| 264 | +driver.quit() |
| 265 | + |
| 266 | +Path(temp_path).unlink() # Clean up temp file |
0 commit comments