|
1 | | -""" pyplots.ai |
| 1 | +""" anyplot.ai |
2 | 2 | andrews-curves: Andrews Curves for Multivariate Data |
3 | | -Library: plotly 6.5.0 | Python 3.13.11 |
4 | | -Quality: 91/100 | Created: 2025-12-31 |
| 3 | +Library: plotly 6.7.0 | Python 3.13.13 |
| 4 | +Quality: 85/100 | Updated: 2026-05-15 |
5 | 5 | """ |
6 | 6 |
|
| 7 | +import os |
| 8 | + |
7 | 9 | import numpy as np |
8 | 10 | import plotly.graph_objects as go |
9 | 11 | from sklearn.datasets import load_iris |
10 | 12 |
|
11 | 13 |
|
12 | | -# Data - Load Iris dataset |
| 14 | +THEME = os.getenv("ANYPLOT_THEME", "light") |
| 15 | +PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" |
| 16 | +ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420" |
| 17 | +INK = "#1A1A17" if THEME == "light" else "#F0EFE8" |
| 18 | +INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" |
| 19 | +GRID = "rgba(26,26,23,0.10)" if THEME == "light" else "rgba(240,239,232,0.10)" |
| 20 | + |
| 21 | +OKABE_ITO = ["#009E73", "#D55E00", "#0072B2"] |
| 22 | +BRAND = OKABE_ITO[0] |
| 23 | + |
| 24 | +# Data |
13 | 25 | iris = load_iris() |
14 | | -X = iris.data # 150 samples, 4 features |
| 26 | +X = iris.data |
15 | 27 | y = iris.target |
16 | | -species_names = ["Setosa", "Versicolor", "Virginica"] |
17 | | -colors = ["#306998", "#FFD43B", "#E74C3C"] # Python Blue, Python Yellow, Red |
| 28 | +species_names = iris.target_names |
18 | 29 |
|
19 | | -# Normalize data to prevent dominant variables |
| 30 | +# Normalize |
20 | 31 | X_normalized = (X - X.mean(axis=0)) / X.std(axis=0) |
21 | 32 |
|
22 | | -# Andrews curve transformation |
23 | | -# f(t) = x1/sqrt(2) + x2*sin(t) + x3*cos(t) + x4*sin(2t) + x5*cos(2t) + ... |
| 33 | +# Andrews curve transformation parameter |
24 | 34 | t = np.linspace(-np.pi, np.pi, 200) |
25 | 35 |
|
26 | | - |
27 | | -def andrews_curve(x, t_vals): |
28 | | - """Transform a single observation to Andrews curve values.""" |
29 | | - n_features = len(x) |
30 | | - curve = np.ones_like(t_vals) * x[0] / np.sqrt(2) |
31 | | - for i in range(1, n_features): |
32 | | - freq = (i + 1) // 2 |
33 | | - if i % 2 == 1: |
34 | | - curve += x[i] * np.sin(freq * t_vals) |
35 | | - else: |
36 | | - curve += x[i] * np.cos(freq * t_vals) |
37 | | - return curve |
38 | | - |
39 | | - |
40 | | -# Create figure |
| 36 | +# Plot |
41 | 37 | fig = go.Figure() |
42 | 38 |
|
43 | | -# Plot Andrews curves for each sample, colored by species |
| 39 | +# Plot curves for each sample, colored by species |
44 | 40 | for species_idx in range(3): |
45 | 41 | species_mask = y == species_idx |
46 | 42 | X_species = X_normalized[species_mask] |
47 | 43 |
|
48 | 44 | for i, x in enumerate(X_species): |
49 | | - curve_y = andrews_curve(x, t) |
| 45 | + # Inline Andrews curve transformation |
| 46 | + n_features = len(x) |
| 47 | + curve = np.ones_like(t) * x[0] / np.sqrt(2) |
| 48 | + for j in range(1, n_features): |
| 49 | + freq = (j + 1) // 2 |
| 50 | + if j % 2 == 1: |
| 51 | + curve += x[j] * np.sin(freq * t) |
| 52 | + else: |
| 53 | + curve += x[j] * np.cos(freq * t) |
| 54 | + |
50 | 55 | fig.add_trace( |
51 | 56 | go.Scatter( |
52 | 57 | x=t, |
53 | | - y=curve_y, |
| 58 | + y=curve, |
54 | 59 | mode="lines", |
55 | | - line=dict(color=colors[species_idx], width=2), |
| 60 | + line=dict(color=OKABE_ITO[species_idx], width=2), |
56 | 61 | opacity=0.4, |
57 | 62 | name=species_names[species_idx], |
58 | 63 | legendgroup=species_names[species_idx], |
59 | | - showlegend=(i == 0), # Only show legend for first curve of each species |
| 64 | + showlegend=(i == 0), |
60 | 65 | hovertemplate=f"{species_names[species_idx]}<br>t: %{{x:.2f}}<br>f(t): %{{y:.2f}}<extra></extra>", |
61 | 66 | ) |
62 | 67 | ) |
63 | 68 |
|
64 | | -# Update layout for 4800x2700 canvas |
| 69 | +# Layout |
65 | 70 | fig.update_layout( |
66 | | - title=dict(text="Iris Dataset · andrews-curves · plotly · pyplots.ai", font=dict(size=32), x=0.5, xanchor="center"), |
| 71 | + title=dict(text="andrews-curves · plotly · anyplot.ai", font=dict(size=28, color=INK), x=0.5, xanchor="center"), |
67 | 72 | xaxis=dict( |
68 | | - title=dict(text="Parameter t (radians)", font=dict(size=24)), |
69 | | - tickfont=dict(size=18), |
70 | | - gridcolor="rgba(128, 128, 128, 0.3)", |
71 | | - gridwidth=1, |
| 73 | + title=dict(text="Parameter t (radians)", font=dict(size=22, color=INK)), |
| 74 | + tickfont=dict(size=18, color=INK_SOFT), |
| 75 | + gridcolor=GRID, |
| 76 | + gridwidth=0.5, |
72 | 77 | zeroline=True, |
73 | | - zerolinecolor="rgba(128, 128, 128, 0.5)", |
| 78 | + zerolinecolor=GRID, |
74 | 79 | zerolinewidth=1, |
| 80 | + linecolor=INK_SOFT, |
75 | 81 | range=[-np.pi, np.pi], |
76 | 82 | tickvals=[-np.pi, -np.pi / 2, 0, np.pi / 2, np.pi], |
77 | 83 | ticktext=["-π", "-π/2", "0", "π/2", "π"], |
78 | 84 | ), |
79 | 85 | yaxis=dict( |
80 | | - title=dict(text="f(t) (normalized units)", font=dict(size=24)), |
81 | | - tickfont=dict(size=18), |
82 | | - gridcolor="rgba(128, 128, 128, 0.3)", |
83 | | - gridwidth=1, |
| 86 | + title=dict(text="f(t) (normalized units)", font=dict(size=22, color=INK)), |
| 87 | + tickfont=dict(size=18, color=INK_SOFT), |
| 88 | + gridcolor=GRID, |
| 89 | + gridwidth=0.5, |
84 | 90 | zeroline=True, |
85 | | - zerolinecolor="rgba(128, 128, 128, 0.5)", |
| 91 | + zerolinecolor=GRID, |
86 | 92 | zerolinewidth=1, |
| 93 | + linecolor=INK_SOFT, |
87 | 94 | ), |
88 | | - template="plotly_white", |
89 | 95 | legend=dict( |
90 | | - font=dict(size=20), |
| 96 | + font=dict(size=16, color=INK_SOFT), |
91 | 97 | x=0.98, |
92 | 98 | y=0.98, |
93 | 99 | xanchor="right", |
94 | 100 | yanchor="top", |
95 | | - bgcolor="rgba(255, 255, 255, 0.8)", |
96 | | - bordercolor="rgba(128, 128, 128, 0.3)", |
| 101 | + bgcolor=ELEVATED_BG, |
| 102 | + bordercolor=INK_SOFT, |
97 | 103 | borderwidth=1, |
98 | 104 | ), |
99 | | - margin=dict(l=100, r=80, t=100, b=80), |
100 | | - plot_bgcolor="white", |
101 | | - paper_bgcolor="white", |
| 105 | + margin=dict(l=120, r=80, t=100, b=100), |
| 106 | + plot_bgcolor=PAGE_BG, |
| 107 | + paper_bgcolor=PAGE_BG, |
| 108 | + font=dict(color=INK), |
102 | 109 | ) |
103 | 110 |
|
104 | | -# Save as PNG (4800 x 2700 px) |
105 | | -fig.write_image("plot.png", width=1600, height=900, scale=3) |
106 | | - |
107 | | -# Save interactive HTML version |
108 | | -fig.write_html("plot.html", include_plotlyjs=True, full_html=True) |
| 111 | +# Save |
| 112 | +fig.write_image(f"plot-{THEME}.png", width=1600, height=900, scale=3) |
| 113 | +fig.write_html(f"plot-{THEME}.html", include_plotlyjs="cdn") |
0 commit comments