Skip to content

Commit 1feaf9b

Browse files
Committing deprecated file
Marked as new file so committing so it is in history.
1 parent c37a964 commit 1feaf9b

1 file changed

Lines changed: 354 additions & 0 deletions

File tree

src/viz/_plotting.jl

Lines changed: 354 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,354 @@
1+
using Plots, StatsPlots
2+
using Plots.Measures
3+
import Plots: plot, plot!
4+
using DataFrames, Dates, Statistics, Distributions, LaTeXStrings
5+
import Bootstrap: bootstrap, BalancedSampling
6+
7+
import .Analysis: TemporalCrossSection
8+
9+
10+
function quickplot(node::NetworkNode)
11+
fig = plot(node.outflow)
12+
return fig
13+
end
14+
15+
16+
function quickplot(node::NetworkNode, climate::Climate)
17+
date = timesteps(climate)
18+
19+
@assert length(date) == length(node.outflow) || "Date length and result lengths do not match!"
20+
21+
fig = plot(date, node.outflow)
22+
23+
return fig
24+
end
25+
function quickplot(obs, node::NetworkNode, climate::Climate, label="", log=false; burn_in=1, limit=nothing, metric=Streamfall.mKGE)
26+
return quickplot(obs, node.outflow, climate, label, log; burn_in=burn_in, limit=limit, metric=metric)
27+
end
28+
function quickplot(obs::DataFrame, sim::Vector, climate::Climate, label="", log=false; burn_in=1, limit=nothing, metric=Streamfall.mKGE)
29+
return quickplot(Matrix(obs[:, Not("Date")])[:, 1], sim, climate, label, log; burn_in, limit, metric)
30+
end
31+
function quickplot(obs::Vector, sim::Vector, climate::Climate, label="", log=false; burn_in=1, limit=nothing, metric=Streamfall.mKGE)
32+
date = timesteps(climate)
33+
last_e = !isnothing(limit) ? limit : lastindex(obs)
34+
show_range = burn_in:last_e
35+
return quickplot(obs[show_range], sim[show_range], date[show_range], label, log; metric=metric)
36+
end
37+
function quickplot(obs::Vector, sim::Vector, xticklabels::Vector, label="Modeled", log=false; metric=Streamfall.mKGE)
38+
@assert length(xticklabels) == length(obs) || "x-axis tick label length and observed lengths do not match!"
39+
@assert length(xticklabels) == length(sim) || "x-axis tick label length and simulated lengths do not match!"
40+
41+
score = round(metric(obs, sim), digits=4)
42+
metric_name = String(Symbol(metric))
43+
44+
if log
45+
# Add small constant in case of 0-flow
46+
obs = obs .+ 1e-2
47+
sim = sim .+ 1e-2
48+
end
49+
50+
label = "$(label) ($(metric_name): $(score))"
51+
fig = plot(
52+
xticklabels, obs,
53+
label="Observed",
54+
legend=:best,
55+
ylabel="Streamflow",
56+
xlabel="Date",
57+
fg_legend=:transparent,
58+
bg_legend=:transparent
59+
)
60+
plot!(xticklabels, sim, label=label, alpha=0.5)
61+
62+
if log
63+
# modify yaxis
64+
yaxis!(fig, :log10)
65+
end
66+
67+
qqfig = qqplot(
68+
obs, sim,
69+
legend=false, markerstrokewidth=0.03, markerstrokealpha=0.1, markeralpha=0.2,
70+
xlabel="Observed", ylabel="Modeled"
71+
)
72+
73+
if log
74+
xaxis!(qqfig, :log10)
75+
yaxis!(qqfig, :log10)
76+
end
77+
78+
combined = plot(fig, qqfig, size=(1000, 500), left_margin=10mm, bottom_margin=5mm, layout=(1, 2))
79+
80+
return combined
81+
end
82+
83+
84+
"""
85+
plot_residuals(obs::Array, sim::Array; xlabel="", ylabel="", title="")
86+
87+
Plot residual between two sequences.
88+
89+
# Arguments
90+
- x : x-axis data
91+
- y : y-axis data
92+
- xlabel : x-axis label
93+
- ylabel : y-axis label
94+
- title : title text
95+
"""
96+
function plot_residuals(x::Array, y::Array; xlabel="", ylabel="", title="")
97+
# 1:1 Plot
98+
fig_1to1 = scatter(x, y, legend=false,
99+
markerstrokewidth=0, markerstrokealpha=0, alpha=0.2)
100+
plot!(x, y, color=:red, markersize=0.1, markerstrokewidth=0,
101+
xlabel=xlabel, ylabel=ylabel, title=title)
102+
103+
return fig_1to1
104+
end
105+
106+
107+
"""Symmetrical log values.
108+
109+
https://kar.kent.ac.uk/32810/2/2012_Bi-symmetric-log-transformation_v5.pdf
110+
https://discourse.julialang.org/t/symmetrical-log-plot/45709/3
111+
"""
112+
function symlog(y)
113+
return sign.(y) .* log10.(1.0 .+ abs.(y))
114+
end
115+
116+
117+
"""
118+
temporal_cross_section(dates, obs; ylabel=nothing, period::Function=monthday)
119+
120+
Provides indication of temporal variation and uncertainty across time, grouped by `period`.
121+
122+
Notes:
123+
Assumes daily data.
124+
Filters out leap days.
125+
126+
# Arguments
127+
- dates : Date of each observation
128+
- obs : observed data
129+
- ylabel : Optional replacement ylabel. Uses name of `func` if not provided.
130+
- `period::Function` : Method from `Dates` package to group (defaults to `monthday`)
131+
"""
132+
function temporal_cross_section(dates, obs;
133+
title="", ylabel="ME", label=nothing,
134+
period::Function=monthday,
135+
kwargs...) # show_extremes::Bool=false,
136+
if isnothing(label)
137+
label = ylabel
138+
end
139+
140+
arg_keys = keys(kwargs)
141+
format_func = y -> y
142+
logscale = [:log, :log10]
143+
tmp = nothing
144+
145+
xsect_res = TemporalCrossSection(dates, obs, period)
146+
147+
if :yscale in arg_keys || :yaxis in arg_keys
148+
tmp = (:yscale in arg_keys) ? kwargs[:yscale] : kwargs[:yaxis]
149+
150+
if tmp in logscale
151+
log_obs = symlog(copy(obs))
152+
153+
# Format function for y-axis tick labels (e.g., 10^x)
154+
format_func = y -> (y != 0) ? L"%$(Int(round(sign(y)) * 10))^{%$(round(abs(y), digits=1))}" : L"0"
155+
156+
log_xsect_res = TemporalCrossSection(dates, log_obs, period)
157+
target = log_xsect_res.cross_section
158+
else
159+
target = xsect_res.cross_section
160+
end
161+
else
162+
target = xsect_res.cross_section
163+
end
164+
165+
x_section = target.median
166+
lower_75 = target.lower_75
167+
upper_75 = target.upper_75
168+
lower_95 = target.lower_95
169+
upper_95 = target.upper_95
170+
171+
sp = target.subperiod
172+
xlabels = join.(sp, "-")
173+
174+
if !isnothing(tmp) & (tmp in logscale)
175+
# Remove keys so later plotting methods do not error
176+
kwargs = Dict(kwargs)
177+
delete!(kwargs, :yscale)
178+
delete!(kwargs, :yaxis)
179+
end
180+
181+
# Display indicator values using original data instead of log-transformed data
182+
med = xsect_res.cross_section.median
183+
m_ind = round(mean(med), digits=2)
184+
sd_ind = round(std(med), digits=2)
185+
186+
wr75_m_ind = round(xsect_res.mean_75, digits=2)
187+
wr75_sd_ind = round(xsect_res.std_75, digits=2)
188+
wr95_m_ind = round(xsect_res.mean_95, digits=2)
189+
wr95_sd_ind = round(xsect_res.std_95, digits=2)
190+
191+
fig = plot(xlabels, lower_95, fillrange=upper_95, color="lightblue", alpha=0.3, label="CI₉₅ μ: $(wr95_m_ind), σ: $(wr95_sd_ind)", linealpha=0)
192+
plot!(fig, xlabels, lower_75, fillrange=upper_75, color="lightblue", alpha=0.5, label="CI₇₅ μ: $(wr75_m_ind), σ: $(wr75_sd_ind)", linealpha=0)
193+
plot!(
194+
fig, xlabels, x_section,
195+
label="Mean of $(label) μ: $(m_ind), σ: $(sd_ind)",
196+
color="black",
197+
xlabel=nameof(period),
198+
ylabel=ylabel,
199+
legend=:bottomleft,
200+
legendfont=Plots.font(10),
201+
fg_legend=:transparent,
202+
bg_legend=:transparent,
203+
left_margin=5mm,
204+
bottom_margin=5mm,
205+
title=title,
206+
yformatter=format_func;
207+
kwargs...
208+
)
209+
210+
# if show_extremes
211+
# scatter!(fig, xlabels, min_section, label="", alpha=0.5, color="lightblue", markerstrokewidth=0; kwargs...)
212+
# scatter!(fig, xlabels, max_section, label="", alpha=0.5, color="lightblue", markerstrokewidth=0; kwargs...)
213+
# end
214+
215+
return fig
216+
end
217+
218+
219+
"""
220+
temporal_cross_section(
221+
dates, obs, sim;
222+
title="", ylabel="Median Error", label=nothing, period::Function=monthday, kwargs...
223+
)
224+
225+
Provides indication of temporal variation and uncertainty across time, grouped by `period`.
226+
227+
Notes:
228+
Assumes daily data.
229+
Filters out leap days.
230+
231+
# Arguments
232+
- `dates` : Date of each observation
233+
- `obs` : Observed data
234+
- `sim` : Simulated data
235+
- `title` : Optional plot title. Blank if not provided.
236+
- `ylabel` : Optional replacement ylabel. Uses name of `period` if not provided.
237+
- `label` : Optional legend label. Uses `ylabel` if not provided.
238+
- `period` : Method from `Dates` package to group (defaults to `month`)
239+
"""
240+
function temporal_cross_section(
241+
dates, obs, sim;
242+
title="", ylabel="Median Error", label=nothing, period::Function=monthday, kwargs...
243+
) # show_extremes::Bool=false,
244+
if isnothing(label)
245+
label = ylabel
246+
end
247+
248+
arg_keys = keys(kwargs)
249+
format_func = y -> y
250+
logscale = [:log, :log10]
251+
tmp = nothing
252+
253+
xsect_res = TemporalCrossSection(dates, obs, sim, period)
254+
target = xsect_res.cross_section
255+
256+
if :yscale in arg_keys || :yaxis in arg_keys
257+
tmp = (:yscale in arg_keys) ? kwargs[:yscale] : kwargs[:yaxis]
258+
259+
if tmp in logscale
260+
log_obs = symlog(copy(obs))
261+
log_sim = symlog(copy(sim))
262+
263+
# Format function for y-axis tick labels (e.g., 10^x)
264+
format_func = y -> (y != 0) ? L"%$(Int(round(sign(y)) * 10))^{%$(round(abs(y), digits=1))}" : L"0"
265+
266+
log_xsect_res = TemporalCrossSection(dates, log_obs, log_sim, period)
267+
target = log_xsect_res.cross_section
268+
end
269+
end
270+
271+
x_section = target.median
272+
lower_75 = target.lower_75
273+
upper_75 = target.upper_75
274+
lower_95 = target.lower_95
275+
upper_95 = target.upper_95
276+
277+
sp = target.subperiod
278+
xlabels = join.(sp, "-")
279+
280+
if !isnothing(tmp) & (tmp in logscale)
281+
# Remove keys so later plotting methods do not error
282+
kwargs = Dict(kwargs)
283+
delete!(kwargs, :yscale)
284+
delete!(kwargs, :yaxis)
285+
end
286+
287+
xsect_df = xsect_res.cross_section
288+
289+
# Display indicator values using original data instead of log-transformed data
290+
med = xsect_df.median
291+
m_ind = round(mean(med), digits=2)
292+
sd_ind = round(std(med), digits=2)
293+
294+
wr75_m_ind = round(xsect_res.mean_75, digits=2)
295+
wr75_sd_ind = round(xsect_res.std_75, digits=2)
296+
wr95_m_ind = round(xsect_res.mean_95, digits=2)
297+
wr95_sd_ind = round(xsect_res.std_95, digits=2)
298+
299+
fig = plot(xlabels, lower_95, fillrange=upper_95, color="lightblue", alpha=0.3, label="CI₉₅ μ: $(wr95_m_ind), σ: $(wr95_sd_ind)", linealpha=0)
300+
plot!(fig, xlabels, lower_75, fillrange=upper_75, color="lightblue", alpha=0.5, label="CI₇₅ μ: $(wr75_m_ind), σ: $(wr75_sd_ind)", linealpha=0)
301+
plot!(
302+
fig, xlabels, x_section,
303+
label="$(label) μ: $(m_ind), σ: $(sd_ind)",
304+
color="black",
305+
xlabel=nameof(period),
306+
ylabel=ylabel,
307+
legend=:bottomleft,
308+
legendfont=Plots.font(10),
309+
fg_legend=:transparent,
310+
bg_legend=:transparent,
311+
left_margin=5mm,
312+
bottom_margin=5mm,
313+
title=title,
314+
yformatter=format_func;
315+
kwargs...
316+
)
317+
318+
# if show_extremes
319+
# scatter!(fig, xlabels, min_section, label="", alpha=0.5, color="lightblue", markerstrokewidth=0; kwargs...)
320+
# scatter!(fig, xlabels, max_section, label="", alpha=0.5, color="lightblue", markerstrokewidth=0; kwargs...)
321+
# end
322+
323+
return fig
324+
end
325+
326+
function plot(node::NetworkNode, climate::Climate)
327+
return plot(
328+
timesteps(climate),
329+
node.outflow,
330+
label=node.name,
331+
xlabel="Date",
332+
ylabel="Outflows",
333+
)
334+
end
335+
336+
function plot!(node::NetworkNode, climate::Climate)
337+
plot!(
338+
timesteps(climate),
339+
node.outflow,
340+
label=node.name
341+
)
342+
end
343+
function plot!(f, node::NetworkNode, climate::Climate)
344+
plot!(f, timesteps(climate), node.outflow, label=node.name)
345+
end
346+
347+
function plot(node::DamNode, climate::Climate)
348+
levels = plot(timesteps(climate), node.level, xlabel="Date", ylabel="Dam Level", label=node.name)
349+
outflows = plot(timesteps(climate), node.outflow, xlabel="Date", ylabel="Discharge", label=node.name)
350+
351+
combined = plot(levels, outflows, size=(1000, 400), left_margin=10mm, bottom_margin=5mm, layout=(1, 2))
352+
353+
return combined
354+
end

0 commit comments

Comments
 (0)