# define constants for plots generation
const VALUE_GROUPS = ("C", "O", "SE", "ST")
SCENARIOS = ["Baseline", "C-skewed", "O-skewed", "SE-skewed", "ST-skewed"]
SHOCKS = ["Missing", "Corridor", "Corridor-unique", "Expansionary"]
# base theme settings
fontsize_theme = Theme(fontsize = 18, font = :bold_italic)
attributes = Attributes(
Axis = (
xgridvisible = false,
ygridvisible = false,
ytickalign = 1,
xtickalign = 1,
),
)
set_theme!(merge(fontsize_theme, attributes))
## Auxiliary functions
function filtering(df::DataFrame, households::Bool, banks::Bool, value::Bool, status::Bool, relative::Bool, shock::String, vars_ib::Vector{Symbol})
modified_df =
if !relative
if banks
if value && !status
(@pipe df |> filter(:shock => ==(shock), _) |> dropmissing(_, vars_ib) |> filter(r -> r.status != "neutral", _))
elseif !value || status
(@pipe df |> filter(:shock => ==(shock), _) |> dropmissing(_, vars_ib))
else
@warn "Not implemented"
end
else # not banks; variable :consumption is common to firms and households
(@pipe df |> dropmissing(_, [:consumption]) |> filter([:id, :shock] => (x, y) -> ids_count(x, households) && y == shock, _))
end
else # relative
if banks
(@pipe df |> dropmissing(_, vars_ib) |> filter(:status => x -> x != "neutral", _))
else # not banks; variable :consumption is common to firms and households
(@pipe df |> dropmissing(_, [:consumption]) |> filter(:id => x -> ids_count(x, households), _))
end
end
return modified_df
end
# helper function
ids_count(x::Int64, households::Bool) = households ? (x >= 1 && x <= number_of_households) : (x > number_of_households && x <= number_of_households + number_of_firms)
function generate_df(df::DataFrame, m::DataFrame;
shock::String = "Missing",
households::Bool = false,
banks::Bool = false,
value::Bool = false,
status::Bool = false,
relative::Bool = false,
operation::Function = mean)
# helper constants
vars = (households && !banks) ? vars_hh : ((!households && banks) ? vars_ib : vars_firms)
grouping_factors =
if relative
[:step, :shock, :scenario]
else # not relative
if !value && !banks && !status
[:step, :scenario]
elseif !value && banks && !status
[:step, :status, :scenario]
elseif value && !banks && !status
[:step, :scenario, :value]
elseif value && banks && !status
[:step, :scenario, :value]
elseif value && banks && status
[:step, :status, :scenario, :value]
else
@warn "Not implemented"
end
end
filtered_df = @pipe df |> filtering(_, households, banks, value, status, relative, shock, vars) |>
groupby(_, grouping_factors) |>
combine(_, vars .=> operation, renamecols = false)
return filtered_df
end
function debt_to_gdp(df::DataFrame, df_firms::DataFrame)
# Add debt_to_gdp column
df[!, :debt_to_gdp] .= 0.0
# Iterate over scenarios
for scenario in unique(df.scenario)
# Filter df_firms_sum to get the total GDP for the current scenario
total_gdp = sum(filter(r -> r.scenario == scenario, df_firms).GDP)
# Calculate debt-to-GDP ratio for the current scenario
df[df.scenario .== scenario, :debt_to_gdp] .= (df[df.scenario .== scenario, :bills] ./ total_gdp) .* 100
end
return df
end
function standard_deviation_bands!(cycle, trend, colors)
# Compute residuals from cyclicality of the time series
residuals = cycle - trend
# Compute the standard deviation of the residuals
sigma = std(residuals)
# Calculate upper and lower bands
upper_bound = trend .+ sigma
lower_bound = trend .- sigma
# Plot the standard deviations as dashed lines
lines!(lower_bound; color = (colors, 0.3), linestyle = :dash)
lines!(upper_bound; color = (colors, 0.3), linestyle = :dash)
end
function add_lines!(gdf)
# Add dotted lines to the plot for the specified variables in gdf
lines!(gdf.icb; color = :grey, linewidth = 0.5)
lines!(gdf.icbd; color = :grey, linewidth = 0.5)
lines!(gdf.icbl; color = :grey, linewidth = 0.5)
end
function generate_dataframes(dataframes::Vector{DataFrame}, i::Int64, shock::String)
gdf1 = @pipe dataframes[1] |> filter(:shock => ==("Missing"), _) |> groupby(_, :scenario)
gdf1_shock = @pipe dataframes[1] |> filter(:shock => ==(shock), _) |> groupby(_, :scenario)
gdf2 = @pipe dataframes[2] |> filter(:shock => ==("Missing"), _) |> groupby(_, :scenario)
gdf2_shock = @pipe dataframes[2] |> filter(:shock => ==(shock), _) |> groupby(_, :scenario)
gdf = i == 1 ? gdf1 : gdf2
gdf_shock = i == 1 ? gdf1_shock : gdf2_shock
return gdf, gdf_shock
end
function generate_dataframes(df::DataFrame, shock::String)
gdf = @pipe df |> filter(:shock => ==("Missing"), _) |> groupby(_, :scenario)
gdf_shock = @pipe df |> filter(:shock => ==(shock), _) |> groupby(_, :scenario)
return gdf, gdf_shock
end
function invisible_yaxis!(fig, index)
# Hide y-axis labels and ticks for subplots starting from the second subplot
if index > 1
fig.content[index].yticklabelsvisible = false
fig.content[index].yticksvisible = false
end
end
function configure_axes_links(fig, gdf)
# Helper function to hide yaxis ticks and link content of multiple plots in a 5 plots fig
if length(gdf) == length(SCENARIOS)
start_idx = 2
end_idx = length(SCENARIOS)
linkyaxes!(fig.content[start_idx:end_idx]...)
invisible_yaxis!(fig, start_idx + 1)
invisible_yaxis!(fig, end_idx)
else
linkyaxes!(fig.content...)
for i in 1:length(fig.content)
invisible_yaxis!(fig, i)
end
end
end
# Set features of axes; currently not used but useful to have plots having (i, j) rows for i in 1:length(vars) and j in 1:length(gdf)
function set_axes!(fig, gdf, vars, ylabels; status::Bool = false)
index = 0
custom_length = status ? length(BY_STATUS) : length(vars)
for i in 1:custom_length
index += 1
# Take the first plots of each row, i.e. (1,1), (2,1), (3,1), (4,1) etc...
start_idx = (i - 1) * length(gdf) + 1
# Take the last plots of each row
end_idx = start_idx + length(gdf) - 1
# Link axes for each variable grouped by shock
linkyaxes!(fig.content[start_idx:end_idx]...)
# Write ylabels for each variable only in the first plots of each row
fig.content[start_idx].ylabel = ylabels[index]
# Set up the alignment of ylabels
fig.content[start_idx].alignmode = Mixed(left = 0)
# Apply invisible_yaxis! only on specific plots
for j in start_idx:end_idx
mod_val = (j - start_idx + 1) % 4
if mod_val == 2 || mod_val == 3 || mod_val == 0
invisible_yaxis!(fig, j)
end
end
# Set the x-label for the last group of subplots
if i == length(vars)
for k in (length(fig.content) - length(gdf) + 1):length(fig.content)
fig.content[k].xlabel = "Steps"
end
end
end
# Set titles only in the first row of plots
for i in 1:length(gdf)
fig.content[i].title = only(unique(gdf[i].shock))
end
end