Skip to content

Commit 7f21b1b

Browse files
committed
add solutions to block 3
1 parent 898658f commit 7f21b1b

9 files changed

+450
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
from pathlib import Path
2+
3+
import matplotlib.pyplot as plt
4+
import numpy as np
5+
import requests
6+
7+
8+
def download_data(url):
9+
response = requests.get(url)
10+
11+
temp = Path("temp")
12+
temp.write_bytes(response.content)
13+
timeseries = np.genfromtxt("temp")
14+
temp.unlink()
15+
16+
return timeseries
17+
18+
19+
def moving_average(t, x, w=12):
20+
n = len(x)
21+
m = np.zeros(len(x) - w)
22+
m[0] = sum(x[:w]) / w
23+
for i in range(1, n - w):
24+
m[i] = m[i - 1] + (x[i + w] - x[i - 1]) / w
25+
26+
return t[:-w], m
27+
28+
29+
def determine_trend(y):
30+
x = np.arange(y.size) + 1
31+
mx = np.mean(x)
32+
my = np.mean(y)
33+
b = np.cov(y, x, bias=y.mean())[0, 1] / np.var(x)
34+
a = my - b * mx
35+
trend = a + b * x
36+
37+
return trend
38+
39+
40+
def nrmse(y, z):
41+
n = np.size(y)
42+
mse = np.sum(np.abs(y - z)) / n
43+
msemean = np.sum(np.abs(y - np.mean(y))) / n
44+
print(mse, msemean)
45+
46+
return np.sqrt(mse / msemean)
47+
48+
49+
def plot_timeseries(x, ax=None):
50+
if ax is None:
51+
ax = plt.gca()
52+
53+
t = np.arange(x.size)
54+
ax.plot(t, x, linewidth=1)
55+
56+
ax.plot(*moving_average(t, x), linewidth=2)
57+
58+
trend = determine_trend(x)
59+
rmse = nrmse(x, trend)
60+
ax.plot(t, trend, linewidth=2, linestyle=":", label=f"nrmse={rmse:.3f}")
61+
ax.legend()
62+
63+
64+
def main():
65+
url = "https://raw.githubusercontent.com/JuliaDynamics/NonlinearDynamicsTextbook/master/exercise_data/11.csv"
66+
timeseries = download_data(url)
67+
68+
fig, axes = plt.subplots(nrows=2)
69+
for ts, ax in zip(timeseries.T, axes.flatten()):
70+
plot_timeseries(ts, ax=ax)
71+
plt.show()
72+
73+
74+
if __name__ == "__main__":
75+
main()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# plot timeseries
2+
# Here is a descritpion for the script.
3+
include("plot_timeseries_source.jl")
4+
url = raw"https://raw.githubusercontent.com/JuliaDynamics/NonlinearDynamicsTextbook/master/exercise_data/11.csv"
5+
w = 12
6+
7+
timeseries = download_timeseries(url)
8+
moving_averaged = moving_average.(timeseries, w)
9+
trends = fit_trend.(timeseries)
10+
nrmses = nrmse.(trends, timeseries)
11+
12+
fig = figure()
13+
for i in 1:length(timeseries)
14+
ax = subplot(2, 1, i)
15+
x = timeseries[i]
16+
t = 1:length(timeseries[i])
17+
plot(t, timeseries[i]; linewidth = 1)
18+
plot(t[1:end-w], moving_averaged[i]; linewidth = 2)
19+
plot(t, trends[1]; linewidth = 2, linestyle = ":", label = "nrmse=$(nrmses[i])")
20+
ylabel("quantity $i")
21+
legend()
22+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using DelimitedFiles
2+
using Downloads
3+
function download_timeseries(url)
4+
response = Downloads.request(url)
5+
@assert response.status == 200 "URL doesn't exist!"
6+
Downloads.download(url, "temp")
7+
X = try
8+
readdlm("temp")
9+
catch err
10+
throw(ArgumentError("Downloaded file isn't tabular text format!"))
11+
end
12+
rm("temp")
13+
timeseries = eachcol(x)
14+
end
15+
16+
function moving_average(x, w)
17+
n = length(x)
18+
m = zeros(length(x)-w)
19+
m[1] = sum(x[1:w])/w
20+
for i in 2:n-w
21+
m[i] = m[i-1] + (x[i+w] - x[i-1])/w
22+
end
23+
return m
24+
end
25+
26+
using Statistics: mean, covm, varm
27+
function fit_trend(y, x = 1:length(y))
28+
x = 1:length(y)
29+
mx = mean(x)
30+
my = mean(y)
31+
b = covm(x, mx, y, my)/varm(x, mx)
32+
a = my - b*mx
33+
trend = @. a + b*x
34+
return trend, nrmse(y, trend, my)
35+
end
36+
37+
function nrmse(y, z, my = mean(y))
38+
n = length(y)
39+
mse = sum(abs2(z[i] - y[i]) for i in 1:n) / n
40+
msemean = sum(abs2(y[i] - my) for i in 1:n) / n
41+
nrmse = sqrt(mse/msemean)
42+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import matplotlib.pyplot as plt
2+
import numpy as np
3+
import requests
4+
from io import StringIO
5+
6+
url = "https://raw.githubusercontent.com/JuliaDynamics/NonlinearDynamicsTextbook/master/exercise_data/11.csv"
7+
w = 12
8+
9+
response = requests.get(url)
10+
timeseries = np.genfromtxt(StringIO(response.text))
11+
12+
fig, axes = plt.subplots(nrows=2)
13+
for ts, ax in zip(timeseries.T, axes):
14+
x = np.arange(ts.shape[0])
15+
moving_average = np.convolve(ts, np.ones(w) / w, mode="same")
16+
popt = np.polyfit(x, ts, deg=1)
17+
rmse = np.sqrt(np.mean((np.polyval(popt, x) - ts) ** 2))
18+
19+
ax.plot(x, ts)
20+
ax.plot(x, moving_average)
21+
ax.plot(x, np.polyval(popt, x), ls=":", label=f"nrmse={rmse:.3f}")
22+
ax.legend()
23+
plt.show()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import matplotlib.pyplot as plt
2+
import numpy as np
3+
import pandas as pd
4+
import xarray as xr
5+
6+
7+
url = "https://raw.githubusercontent.com/JuliaDynamics/NonlinearDynamicsTextbook/master/exercise_data/11.csv"
8+
w = 12
9+
10+
ds = pd.read_csv(url, delimiter="\t", names=["var1", "var2"]).to_xarray()
11+
12+
fig, axes = plt.subplots(nrows=2)
13+
for var, ax in zip(ds, axes):
14+
ds[var].plot(ax=ax)
15+
ds[var].rolling(index=w).mean().plot(ax=ax)
16+
p = ds[var].polyfit(deg=1, dim="index")
17+
rmse = np.sqrt(
18+
np.mean((xr.polyval(ds.index, p.polyfit_coefficients) - ds[var]) ** 2)
19+
).data
20+
xr.polyval(ds.index, p.polyfit_coefficients).plot(
21+
ls=":", label=f"nrmse={rmse:.3f}", ax=ax
22+
)
23+
ax.legend()
24+
plt.show()

block3_softwaredev/running_mean.py

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import numpy as np
2+
import matplotlib.pyplot as plt
3+
4+
5+
def running_mean(x, win_size):
6+
"""Compute a running mean with a given window size.
7+
8+
Parameters:
9+
x (ndarray): Input data.
10+
win_size (int): Window size.
11+
12+
Returns:
13+
ndarray: Smoothened data.
14+
"""
15+
return np.convolve(x, np.ones(w) / w, mode="valid")
16+
17+
18+
def running_mean(x, win_size, win_type=np.ones):
19+
"""Compute a running mean with a given window size.
20+
21+
Parameters:
22+
x (ndarray): Input data.
23+
win_size (int): Window size.
24+
win_type (callable): A callable object that when passed a `win_size`
25+
will return an array of weights.
26+
27+
Returns:
28+
ndarray: Smoothened data.
29+
"""
30+
w = win_type(win_size)
31+
return np.convolve(x, w / w.sum(), mode="valid")
32+
33+
34+
def running_mean(x, win_size, win_type=np.ones, aggregation=None):
35+
"""Compute a running mean with a given window size.
36+
37+
Parameters:
38+
x (ndarray): Input data.
39+
win_size (int): Window size.
40+
win_type (callable): A callable object that when passed a `win_size`
41+
will return an array of weights.
42+
aggregartion (callable): A callable object which aggregrates the data
43+
within the window region. By default, the function computes a
44+
running mean.
45+
46+
Note:
47+
In the default configuration, i.e. a running mean, the function makes
48+
use of a convolution which is implemented in a very efficient way.
49+
When passing the `aggregation` keyword this approach is no
50+
longer feasible because the data has to be explicitly "grouped".
51+
52+
Returns:
53+
ndarray: Smoothened data.
54+
"""
55+
w = win_type(win_size)
56+
57+
if aggregation is None:
58+
# In the default case, we can use fast Fourier transform
59+
return np.convolve(x, w / w.sum(), mode="valid")
60+
else:
61+
# In the generic case, we have to group our array which is slower.
62+
# Therefore, it is good to have a separate API (i.e. keyword) for this case.
63+
return aggregation(
64+
np.lib.stride_tricks.sliding_window_view(x, win_size) * w,
65+
axis=1,
66+
) / w.mean()
67+
68+
69+
def main():
70+
windows = (
71+
np.ones,
72+
np.bartlett,
73+
np.blackman,
74+
np.hamming,
75+
)
76+
77+
np.random.seed(1)
78+
x = np.random.randn(256) + 2
79+
80+
fig, ax = plt.subplots(figsize=(10, 6))
81+
ax.plot(x, c="grey")
82+
for window in windows:
83+
ax.plot(
84+
running_mean(x, win_size=16, win_type=window),
85+
linewidth=2,
86+
label=window.__name__.capitalize(),
87+
)
88+
ax.legend()
89+
ax.set_ylim(np.percentile(x, [5, 95]))
90+
91+
plt.show()
92+
93+
94+
if __name__ == "__main__":
95+
main()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#=
2+
This version shows the code that generalizable and is practically a simplification
3+
of the code surrounding `monthlyagg` and co. in ClimateBase.jl.
4+
=#
5+
using Dates
6+
using Statistics
7+
8+
monthlymeans(t, x) = monthlyagg(t, x; agg = mean)
9+
10+
function monthlyagg(t, x; agg = mean)
11+
return temporal_aggregation(t, x; agg, info = Dates.month)
12+
end
13+
14+
function temporal_aggregation(t::AbstractVector{<:TimeType}, x::Vector;
15+
agg = mean, info = Dates.month
16+
)
17+
tranges = temporal_ranges(t, info)
18+
y = [agg(view(x, r)) for r in tranges]
19+
coarse_t = [middle_date(t[r[1]], t[r[end]]) for r in tranges]
20+
# TODO: We can have a `prettify_coarse_t` function to make
21+
# the time vector better in cases where it is possble,
22+
# e.g. like t[1]:Month(1):t[end]
23+
return coarse_t, y
24+
end
25+
26+
middle_date(t0, t1) = ((d0, d1) = DateTime.((t0, t1)); d0 + (d1 - d0)/2)
27+
28+
function temporal_ranges(t::AbstractArray{<:TimeType}, info = Dates.month)
29+
@assert issorted(t) "Sorted time required."
30+
L = length(t)
31+
r = Vector{UnitRange{Int}}()
32+
i, x = 1, info(t[1]) # previous entries
33+
for j in 2:L
34+
y = info(t[j])
35+
x == y && continue
36+
push!(r, i:(j-1))
37+
i, x = j, y
38+
end
39+
push!(r, i:L) # final range not included in for loop
40+
return r
41+
end
42+
43+
# Testing vectors
44+
t = Date(2015, 1, 1):Day(1):Date(2020, 12, 31)
45+
x = float.(month.(t))
46+
m, y = monthlymeans(t, x)
47+
48+
# Test with summer and winter
49+
summer(x) = month(x) (3,4,5,6,7,8)
50+
m, y = temporal_aggregation(t, x; info = summer)

0 commit comments

Comments
 (0)