Skip to content

Commit 466d85e

Browse files
committed
Plot an idealized stability curve for illustrative purposes.
1 parent bd6c331 commit 466d85e

1 file changed

Lines changed: 114 additions & 0 deletions

File tree

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
"""
2+
Idealized percent-still-unchanged curve for fixed (delta, lambda) = (0.05, 0.5).
3+
4+
Mimics the axis and theme styling of scripts/osm_data/data_viz.py but plots
5+
only the analytic trend implied by the ZIE turnover model, with no observed
6+
data and no uncertainty band:
7+
8+
P(still unchanged at t) = (1 - delta) * exp(-lambda * t)
9+
10+
with t in years.
11+
"""
12+
13+
from pathlib import Path
14+
15+
import numpy as np
16+
import pandas as pd
17+
18+
import matplotlib
19+
matplotlib.use("Agg") # noqa: E402
20+
import plotnine as gg # noqa: E402
21+
22+
# ----------------------------------------------------------------------------------------
23+
# Fixed parameters
24+
# ----------------------------------------------------------------------------------------
25+
26+
DELTA = 0.05
27+
LAMBDA = 0.25 # per year
28+
YEAR_RANGE = 10
29+
N_POINTS = 1001
30+
31+
OUT_PATH = Path("~/data/openpois/exploratory/idealized_stability_curve.png").expanduser()
32+
33+
# ----------------------------------------------------------------------------------------
34+
# Plot construction
35+
# ----------------------------------------------------------------------------------------
36+
37+
38+
def idealized_curve(
39+
delta: float,
40+
lam: float,
41+
year_range: float,
42+
n_points: int = 1001,
43+
) -> pd.DataFrame:
44+
"""Return a DataFrame with columns (year, y) for the analytic trend."""
45+
year = np.linspace(0, year_range, n_points)
46+
y = (1.0 - delta) * np.exp(-lam * year)
47+
return pd.DataFrame({'year': year, 'y': y})
48+
49+
50+
def idealized_plot_create(
51+
df: pd.DataFrame,
52+
title: str | None = None,
53+
subtitle: str | None = None,
54+
x_label: str = '',
55+
y_label: str = '',
56+
year_range: float = 10,
57+
) -> gg.ggplot:
58+
"""Single-trend stability plot, styled to match change_plot_create."""
59+
fig = (
60+
gg.ggplot(
61+
data = df,
62+
mapping = gg.aes(x = 'year', y = 'y'),
63+
) +
64+
gg.geom_line(color = 'darkred', size = 1) +
65+
gg.labs(
66+
title = title,
67+
subtitle = subtitle,
68+
x = x_label,
69+
y = y_label,
70+
) +
71+
gg.scale_y_continuous(
72+
limits = (0, 1.01),
73+
breaks = np.arange(0, 1.01, 0.25),
74+
labels = [f"{x * 100:.0f}%" for x in np.arange(0, 1.01, 0.25)],
75+
) +
76+
gg.scale_x_continuous(
77+
limits = (0, year_range + 0.01),
78+
breaks = np.arange(year_range + 1),
79+
labels = [f"{x:.0f}" for x in np.arange(year_range + 1)],
80+
) +
81+
gg.theme_bw()
82+
)
83+
return fig
84+
85+
86+
# ----------------------------------------------------------------------------------------
87+
# Main workflow
88+
# ----------------------------------------------------------------------------------------
89+
90+
if __name__ == "__main__":
91+
df = idealized_curve(
92+
delta = DELTA,
93+
lam = LAMBDA,
94+
year_range = YEAR_RANGE,
95+
n_points = N_POINTS,
96+
)
97+
fig = idealized_plot_create(
98+
df = df,
99+
title = "Idealized stability curve",
100+
subtitle = f"δ = {DELTA}, λ = {LAMBDA} / year",
101+
x_label = "Years since tag",
102+
y_label = "Proportion remaining unchanged",
103+
year_range = YEAR_RANGE,
104+
)
105+
OUT_PATH.parent.mkdir(parents = True, exist_ok = True)
106+
fig.save(
107+
filename = OUT_PATH,
108+
width = 10,
109+
height = 6,
110+
units = 'in',
111+
dpi = 300,
112+
verbose = False,
113+
)
114+
print(f"Saved: {OUT_PATH}")

0 commit comments

Comments
 (0)