pie
ExamplesDocs

Topics

  • Canvas
  • Reactivity
  • Broadcasting
  • Autodiff
  • Animation
  • Python

Python in the browser

  • Making an editor
    • Collapsed editors
  • Anscombe's quartet
  • Fitting a curve

Making an editor

Write a code fence with the language python-editor instead of python:

```python-editor
print("Hello from Python")
```

It becomes this editor. Press Run, or Shift+Enter inside it:

print("Hello from Python")

A plain python fence stays a highlighted block of code that can't run.

The code runs in your browser with Pyodide, a version of Python made for web pages, so no server is involved. numpy, scipy, matplotlib and other packages load the first time they are imported, which takes a few seconds. plt.show() draws a matplotlib figure under the editor. All editors on a page share their variables, so run the ones below in order. This page has no canvas: the plots come from matplotlib.

Collapsed editors

Add collapsed after python-editor and the editor starts closed, showing only its buttons:

```python-editor collapsed
x = 42
```

It still runs. The arrow on the right opens it. This is handy for long setup code the reader doesn't need to see, like the data in the next editor.


Anscombe's quartet

In 1973 the statistician Francis Anscombe made four small datasets. Each has 11 points. The editor below holds them and starts collapsed: run it, or open it to see the numbers.

import numpy as np

x = np.array([10, 8, 13, 9, 11, 14, 6, 4, 12, 7, 5])
x4 = np.array([8, 8, 8, 8, 8, 8, 8, 19, 8, 8, 8])
quartet = {
    "I":   (x,  np.array([8.04, 6.95, 7.58, 8.81, 8.33, 9.96, 7.24, 4.26, 10.84, 4.82, 5.68])),
    "II":  (x,  np.array([9.14, 8.14, 8.74, 8.77, 9.26, 8.10, 6.13, 3.10, 9.13, 7.26, 4.74])),
    "III": (x,  np.array([7.46, 6.77, 12.74, 7.11, 7.81, 8.84, 6.08, 5.39, 8.15, 6.42, 5.73])),
    "IV":  (x4, np.array([6.58, 5.76, 7.71, 8.84, 8.47, 7.04, 5.25, 12.50, 5.56, 7.91, 6.89])),
}

Fit a straight line to each with scipy.stats.linregress:

from scipy import stats

print("set   mean x  mean y  var y   slope  intercept  r")
for name, (xs, ys) in quartet.items():
    fit = stats.linregress(xs, ys)
    print(f"{name:4}  {xs.mean():6.2f}  {ys.mean():6.2f}  {ys.var(ddof=1):5.2f}  {fit.slope:5.2f}  {fit.intercept:9.2f}  {fit.rvalue:.3f}")

The four rows are nearly the same: the same means, the same spread, the same best line, the same correlation. Now plot them:

import matplotlib.pyplot as plt

fig, axes = plt.subplots(2, 2, figsize=(8, 6), sharex=True, sharey=True)
line_x = np.array([3, 20])
for ax, (name, (xs, ys)) in zip(axes.flat, quartet.items()):
    fit = stats.linregress(xs, ys)
    ax.scatter(xs, ys, color="tab:orange", zorder=3)
    ax.plot(line_x, fit.intercept + fit.slope * line_x, color="tab:blue")
    ax.set_title(f"Dataset {name}")
fig.tight_layout()
plt.show()

They look nothing alike. I is a noisy line. II is a curve. III is a perfect line with one point off it. IV is a single column plus one far point that alone sets the slope. The summary numbers can't tell these apart; a plot can.


Fitting a curve

scipy.optimize.curve_fit finds the parameters of any formula that best match the data. Here: a damped spring, made up with known values plus noise, so we can check the fit gets them back.

from scipy.optimize import curve_fit

def spring(t, amplitude, decay, freq):
    return amplitude * np.exp(-decay * t) * np.cos(2 * np.pi * freq * t)

rng = np.random.default_rng(0)
t = np.linspace(0, 5, 80)
measured = spring(t, 2.0, 0.6, 1.2) + rng.normal(0, 0.1, t.size)

params, _ = curve_fit(spring, t, measured, p0=[1, 1, 1])
print("true:   amplitude 2.00, decay 0.60, freq 1.20")
print("fitted: amplitude {:.2f}, decay {:.2f}, freq {:.2f}".format(*params))

fine = np.linspace(0, 5, 400)
plt.figure(figsize=(8, 3))
plt.plot(t, measured, ".", color="tab:orange", label="measured")
plt.plot(fine, spring(fine, *params), color="tab:blue", label="fitted")
plt.xlabel("time (s)")
plt.legend()
plt.show()

p0 is the starting guess. Try p0=[1, 1, 3]: the fit returns a frequency of -1.20. That is the same curve, because cos(−x) = cos(x), so the data can't tell the two apart.