Python in the browser
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.