Skip to content

Commit 03e9aa7

Browse files
feat(plotly): implement scatter-regression-lowess (#2880)
## Implementation: `scatter-regression-lowess` - plotly Implements the **plotly** version of `scatter-regression-lowess`. **File:** `plots/scatter-regression-lowess/implementations/plotly.py` **Parent Issue:** #2855 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20608460845)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 3495112 commit 03e9aa7

2 files changed

Lines changed: 143 additions & 0 deletions

File tree

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
""" pyplots.ai
2+
scatter-regression-lowess: Scatter Plot with LOWESS Regression
3+
Library: plotly 6.5.0 | Python 3.13.11
4+
Quality: 91/100 | Created: 2025-12-30
5+
"""
6+
7+
import numpy as np
8+
import plotly.graph_objects as go
9+
10+
11+
# Data - Generate non-linear relationship with varying patterns
12+
np.random.seed(42)
13+
n_points = 200
14+
x = np.linspace(0, 10, n_points)
15+
# Complex non-linear relationship: combination of sinusoidal and polynomial
16+
y = 2 * np.sin(x) + 0.3 * x**2 - x + np.random.normal(0, 1.5, n_points)
17+
18+
# LOWESS smoothing (Locally Weighted Scatterplot Smoothing)
19+
frac = 0.3
20+
n = len(x)
21+
k = int(np.ceil(frac * n)) # Number of neighbors to use
22+
23+
# Sort data by x for processing
24+
sorted_idx = np.argsort(x)
25+
x_sorted = x[sorted_idx]
26+
y_sorted = y[sorted_idx]
27+
y_smooth = np.zeros(n)
28+
29+
# Calculate smoothed values for each point
30+
for i in range(n):
31+
# Distances from current point to all points
32+
distances = np.abs(x_sorted - x_sorted[i])
33+
34+
# Find k nearest neighbors
35+
neighbor_idx = np.argsort(distances)[:k]
36+
max_dist = distances[neighbor_idx[-1]]
37+
38+
# Tricube weight function: w = (1 - (d/max_d)^3)^3
39+
if max_dist > 0:
40+
u = distances[neighbor_idx] / max_dist
41+
weights = (1 - u**3) ** 3
42+
else:
43+
weights = np.ones(k)
44+
45+
# Weighted least squares regression
46+
x_neighbors = x_sorted[neighbor_idx]
47+
y_neighbors = y_sorted[neighbor_idx]
48+
49+
sum_w = np.sum(weights)
50+
sum_wx = np.sum(weights * x_neighbors)
51+
sum_wy = np.sum(weights * y_neighbors)
52+
sum_wxx = np.sum(weights * x_neighbors**2)
53+
sum_wxy = np.sum(weights * x_neighbors * y_neighbors)
54+
55+
denom = sum_w * sum_wxx - sum_wx**2
56+
if np.abs(denom) > 1e-10:
57+
b = (sum_w * sum_wxy - sum_wx * sum_wy) / denom
58+
a = (sum_wy - b * sum_wx) / sum_w
59+
y_smooth[i] = a + b * x_sorted[i]
60+
else:
61+
y_smooth[i] = sum_wy / sum_w if sum_w > 0 else y_sorted[i]
62+
63+
x_lowess = x_sorted
64+
y_lowess = y_smooth
65+
66+
# Create figure
67+
fig = go.Figure()
68+
69+
# Add scatter points
70+
fig.add_trace(
71+
go.Scatter(
72+
x=x,
73+
y=y,
74+
mode="markers",
75+
name="Data Points",
76+
marker=dict(
77+
size=10,
78+
color="#306998", # Python Blue
79+
opacity=0.6,
80+
),
81+
)
82+
)
83+
84+
# Add LOWESS curve
85+
fig.add_trace(
86+
go.Scatter(
87+
x=x_lowess,
88+
y=y_lowess,
89+
mode="lines",
90+
name="LOWESS Curve",
91+
line=dict(
92+
color="#FFD43B", # Python Yellow
93+
width=4,
94+
),
95+
)
96+
)
97+
98+
# Update layout for large canvas
99+
fig.update_layout(
100+
title=dict(text="scatter-regression-lowess · plotly · pyplots.ai", font=dict(size=28), x=0.5, xanchor="center"),
101+
xaxis=dict(
102+
title=dict(text="X Value", font=dict(size=22)), tickfont=dict(size=18), gridcolor="rgba(0,0,0,0.1)", gridwidth=1
103+
),
104+
yaxis=dict(
105+
title=dict(text="Y Value", font=dict(size=22)), tickfont=dict(size=18), gridcolor="rgba(0,0,0,0.1)", gridwidth=1
106+
),
107+
template="plotly_white",
108+
legend=dict(font=dict(size=18), x=0.02, y=0.98, xanchor="left", yanchor="top", bgcolor="rgba(255,255,255,0.8)"),
109+
margin=dict(l=80, r=40, t=80, b=80),
110+
)
111+
112+
# Save as PNG (4800 x 2700 px)
113+
fig.write_image("plot.png", width=1600, height=900, scale=3)
114+
115+
# Save as HTML for interactivity
116+
fig.write_html("plot.html")
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
library: plotly
2+
specification_id: scatter-regression-lowess
3+
created: '2025-12-30T23:54:51Z'
4+
updated: '2025-12-31T00:01:00Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20608460845
7+
issue: 2855
8+
python_version: 3.13.11
9+
library_version: 6.5.0
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/scatter-regression-lowess/plotly/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/scatter-regression-lowess/plotly/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/scatter-regression-lowess/plotly/plot.html
13+
quality_score: 91
14+
review:
15+
strengths:
16+
- Excellent implementation of LOWESS algorithm from scratch with proper tricube
17+
weighting
18+
- Clear visual distinction between scatter points (blue, transparent) and LOWESS
19+
curve (yellow, solid line)
20+
- Perfect title format and professional plotly_white template
21+
- Good font sizes throughout ensuring readability at 4800x2700 resolution
22+
- Well-chosen smoothing fraction (0.3) that captures the trend without overfitting
23+
weaknesses:
24+
- Axis labels lack units or more descriptive context (e.g., could use domain-specific
25+
labels)
26+
- Manual LOWESS implementation is educational but verbose; could leverage statsmodels
27+
for production code

0 commit comments

Comments
 (0)