|
| 1 | +""" pyplots.ai |
| 2 | +scatter-lag: Lag Plot for Time Series Autocorrelation Diagnosis |
| 3 | +Library: plotly 6.7.0 | Python 3.14.3 |
| 4 | +Quality: 89/100 | Created: 2026-04-12 |
| 5 | +""" |
| 6 | + |
| 7 | +import numpy as np |
| 8 | +import plotly.graph_objects as go |
| 9 | + |
| 10 | + |
| 11 | +# Data - synthetic AR(1) temperature process with strong autocorrelation |
| 12 | +np.random.seed(42) |
| 13 | +n_points = 500 |
| 14 | +phi = 0.85 |
| 15 | +noise = np.random.normal(0, 1, n_points) |
| 16 | +temperature = np.zeros(n_points) |
| 17 | +temperature[0] = 20.0 |
| 18 | +for i in range(1, n_points): |
| 19 | + temperature[i] = phi * temperature[i - 1] + (1 - phi) * 20.0 + noise[i] |
| 20 | + |
| 21 | +lag = 1 |
| 22 | +y_t = temperature[:-lag] |
| 23 | +y_t_lag = temperature[lag:] |
| 24 | +time_index = np.arange(len(y_t)) |
| 25 | + |
| 26 | +# Correlation coefficient |
| 27 | +correlation = np.corrcoef(y_t, y_t_lag)[0, 1] |
| 28 | + |
| 29 | +# Regression line through the data |
| 30 | +slope, intercept = np.polyfit(y_t, y_t_lag, 1) |
| 31 | +x_fit = np.array([y_t.min(), y_t.max()]) |
| 32 | +y_fit = slope * x_fit + intercept |
| 33 | + |
| 34 | +# Plot |
| 35 | +fig = go.Figure() |
| 36 | + |
| 37 | +# Scatter points with Plasma for stronger visual contrast |
| 38 | +fig.add_trace( |
| 39 | + go.Scatter( |
| 40 | + x=y_t, |
| 41 | + y=y_t_lag, |
| 42 | + mode="markers", |
| 43 | + marker={ |
| 44 | + "size": 7, |
| 45 | + "color": time_index, |
| 46 | + "colorscale": "Plasma", |
| 47 | + "colorbar": { |
| 48 | + "title": {"text": "Time Index", "font": {"size": 18, "color": "#444444"}}, |
| 49 | + "tickfont": {"size": 16, "color": "#666666"}, |
| 50 | + "thickness": 18, |
| 51 | + "len": 0.65, |
| 52 | + "outlinewidth": 0, |
| 53 | + "y": 0.5, |
| 54 | + }, |
| 55 | + "opacity": 0.55, |
| 56 | + "line": {"width": 0.3, "color": "rgba(255,255,255,0.6)"}, |
| 57 | + }, |
| 58 | + hovertemplate=( |
| 59 | + "<b>Time %{customdata}</b><br>Temp at t: %{x:.1f} °C<br>Temp at t+1: %{y:.1f} °C<extra></extra>" |
| 60 | + ), |
| 61 | + customdata=time_index, |
| 62 | + ) |
| 63 | +) |
| 64 | + |
| 65 | +# Diagonal reference line (y = x) |
| 66 | +data_min = min(y_t.min(), y_t_lag.min()) |
| 67 | +data_max = max(y_t.max(), y_t_lag.max()) |
| 68 | +padding = (data_max - data_min) * 0.05 |
| 69 | +line_min = data_min - padding |
| 70 | +line_max = data_max + padding |
| 71 | + |
| 72 | +fig.add_trace( |
| 73 | + go.Scatter( |
| 74 | + x=[line_min, line_max], |
| 75 | + y=[line_min, line_max], |
| 76 | + mode="lines", |
| 77 | + line={"color": "rgba(0,0,0,0.15)", "width": 1.5, "dash": "dot"}, |
| 78 | + showlegend=False, |
| 79 | + hoverinfo="skip", |
| 80 | + name="y = x", |
| 81 | + ) |
| 82 | +) |
| 83 | + |
| 84 | +# Regression trend line |
| 85 | +fig.add_trace( |
| 86 | + go.Scatter( |
| 87 | + x=x_fit, |
| 88 | + y=y_fit, |
| 89 | + mode="lines", |
| 90 | + line={"color": "#306998", "width": 2.5}, |
| 91 | + showlegend=False, |
| 92 | + hoverinfo="skip", |
| 93 | + name="trend", |
| 94 | + ) |
| 95 | +) |
| 96 | + |
| 97 | +# Layout with custom background and removed axis borders |
| 98 | +fig.update_layout( |
| 99 | + title={ |
| 100 | + "text": "scatter-lag · plotly · pyplots.ai", |
| 101 | + "font": {"size": 28, "color": "#333333"}, |
| 102 | + "x": 0.5, |
| 103 | + "xanchor": "center", |
| 104 | + "y": 0.96, |
| 105 | + }, |
| 106 | + xaxis={ |
| 107 | + "title": {"text": "Temperature (°C) at time t", "font": {"size": 22, "color": "#444444"}}, |
| 108 | + "tickfont": {"size": 18, "color": "#666666"}, |
| 109 | + "showgrid": True, |
| 110 | + "gridcolor": "rgba(0,0,0,0.06)", |
| 111 | + "gridwidth": 1, |
| 112 | + "zeroline": False, |
| 113 | + "showline": False, |
| 114 | + "ticks": "", |
| 115 | + }, |
| 116 | + yaxis={ |
| 117 | + "title": {"text": f"Temperature (°C) at time t+{lag}", "font": {"size": 22, "color": "#444444"}}, |
| 118 | + "tickfont": {"size": 18, "color": "#666666"}, |
| 119 | + "showgrid": True, |
| 120 | + "gridcolor": "rgba(0,0,0,0.06)", |
| 121 | + "gridwidth": 1, |
| 122 | + "zeroline": False, |
| 123 | + "showline": False, |
| 124 | + "ticks": "", |
| 125 | + }, |
| 126 | + template="plotly_white", |
| 127 | + plot_bgcolor="#FFFFFF", |
| 128 | + paper_bgcolor="#F8F9FA", |
| 129 | + showlegend=False, |
| 130 | + margin={"l": 90, "r": 130, "t": 100, "b": 90}, |
| 131 | +) |
| 132 | + |
| 133 | +# Subtitle annotation for context |
| 134 | +fig.add_annotation( |
| 135 | + text="AR(1) process | lag = 1 | 500 observations", |
| 136 | + xref="paper", |
| 137 | + yref="paper", |
| 138 | + x=0.5, |
| 139 | + y=1.06, |
| 140 | + showarrow=False, |
| 141 | + font={"size": 16, "color": "#999999"}, |
| 142 | + xanchor="center", |
| 143 | +) |
| 144 | + |
| 145 | +# Correlation annotation - prominent and well-styled |
| 146 | +fig.add_annotation( |
| 147 | + text=f"<b>r = {correlation:.3f}</b>", |
| 148 | + xref="paper", |
| 149 | + yref="paper", |
| 150 | + x=0.03, |
| 151 | + y=0.97, |
| 152 | + showarrow=False, |
| 153 | + font={"size": 24, "color": "#306998"}, |
| 154 | + bgcolor="rgba(255,255,255,0.9)", |
| 155 | + bordercolor="rgba(48,105,152,0.3)", |
| 156 | + borderwidth=1.5, |
| 157 | + borderpad=10, |
| 158 | +) |
| 159 | + |
| 160 | +# Save |
| 161 | +fig.write_image("plot.png", width=1600, height=900, scale=3) |
| 162 | +fig.write_html("plot.html", include_plotlyjs="cdn") |
0 commit comments