Skip to content

Commit

Permalink
refactored PICNN added plot functionalities
Browse files Browse the repository at this point in the history
  • Loading branch information
nepslor committed Nov 22, 2023
1 parent 89b0331 commit 83a0a17
Show file tree
Hide file tree
Showing 6 changed files with 404 additions and 109 deletions.
330 changes: 262 additions & 68 deletions pyforecaster/forecasting_models/neural_forecasters.py

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions pyforecaster/formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,8 @@ def _simulate_transform(self, x=None):
for tr in self.target_transformers:
_ = tr.transform(x, simulate=True)

def plot_transformed_feature(self, x, feature, frames=100):
def plot_transformed_feature(self, x, feature, frames=100, ax_labels=None, legend_kwargs={},
remove_spines=True, **kwargs):
x_tr, target = self.transform(x)
all_feats = pd.concat([x_tr, target], axis=1)
fs = []
Expand All @@ -289,7 +290,8 @@ def plot_transformed_feature(self, x, feature, frames=100):
end_times.append(
t.metadata.loc[derived_features.index[derived_features['function'] == function], 'end_time'])

ani = ts_animation_bars(fs, start_times, end_times, names, frames=frames)
ani = ts_animation_bars(fs, start_times, end_times, frames=frames, ax_labels=ax_labels,
legend_kwargs=legend_kwargs, remove_spines=remove_spines, **kwargs)
return ani

def cat_encoding(self, df_train, df_test, target, encoder=None, cat_features=None):
Expand Down
87 changes: 66 additions & 21 deletions pyforecaster/plot_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import matplotlib.colors as colors
from matplotlib.gridspec import GridSpec
import networkx as nx

from pyforecaster.scenred import plot_from_graph

def basic_setup(subplots_tuple, width, height, b=0.15, l=0.15, w=0.22, r=None, style ='seaborn', **kwargs):
plt.style.use(style)
Expand Down Expand Up @@ -190,14 +190,25 @@ def animate(i):
return ani


"""
def tree_animation(ys:list, y_gt=None, times=None, frames=150):

def plot_trees(ys:list, y_gt=None, times=None, frames=150, ax_labels=None, legend_kwargs={},
remove_spines=True, savepath=None, **kwargs):
"plot the first n_rows of the two y_te and y_hat matrices"
fig, ax = plt.subplots(1)
fig, ax = plt.subplots(1, **kwargs)
f_min = np.min([np.min(np.array(list(nx.get_node_attributes(y, 'v').values()))) for y in ys])
f_max = np.max([np.max(np.array(list(nx.get_node_attributes(y, 'v').values()))) for y in ys])
lines = None
lines = scrd.plot_from_graph(ys[0], alpha=0.2, linewidth=1)
lines = plot_from_graph(ys[0], alpha=0.2, linewidth=1, ax=ax, color='r')
if ax_labels is not None:
for k, v in ax_labels.items():
if k == 'x':
ax.set_xlabel(v)
elif k == 'y':
ax.set_ylabel(v)
if remove_spines:
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)

if y_gt is not None:
t = np.arange(len(y_gt[0][0])) if times is None else times
lines_ground_truth = []
Expand All @@ -207,25 +218,37 @@ def tree_animation(ys:list, y_gt=None, times=None, frames=150):
ax.set_ylim(f_min - np.abs(f_min) * 0.1, f_max + np.abs(f_max) * 0.1)

def animate(i):
_ = scrd.plot_from_graph(ys[i], lines, alpha=0.2)
_ = plot_from_graph(ys[i], lines, alpha=0.2, ax=ax, color='r')
if y_gt is not None:
for y, l in zip(y_gt, lines_ground_truth):
l.set_data(t, y[i, :])
return
if savepath is not None:
writervideo = animation.FFMpegWriter(fps=60)
animation.FuncAnimation(fig, animate, blit=False, frames=np.minimum(len(ys) - 1, frames), interval=100,
repeat=False).save(savepath, writer=writervideo)
else:
return animation.FuncAnimation(fig, animate, blit=False, frames=np.minimum(len(ys)-1, frames), interval=100,
repeat=False)

plt.pause(1e-5)
ani = animation.FuncAnimation(fig, animate, blit=False, frames=np.minimum(len(ys)-1, frames), interval=100, repeat=False)
return ani
"""

def ts_animation_bars(ys:list, start_t:list, end_t:list, names:list, frames=150):
def ts_animation_bars(ys:list, start_t:list, end_t:list, frames=150, ax_labels=None, legend_kwargs={},
remove_spines=True, savepath=None, **kwargs):
"plot the first n_rows of the two y_te and y_hat matrices"
fig, ax = plt.subplots(1)
fig, ax = plt.subplots(1, **kwargs)
lines = []
f_min = np.min([np.min(y) for y in ys])
f_max = np.max([np.max(y) for y in ys])
cm = plt.get_cmap('Set1')
if ax_labels is not None:
for k, v in ax_labels.items():
if k == 'x':
ax.set_xlabel(v)
elif k == 'y':
ax.set_ylabel(v)
if remove_spines:
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
def init():
for y, s_t, e_t, idx in zip(ys, start_t, end_t, range(len(ys))):
l = [ax.stairs(y_j.ravel(), np.array([s_t.iloc[j]/np.timedelta64(3600*24,'s'), e_t.iloc[j]/np.timedelta64(3600*24,'s')]), color=cm(idx)) for j, y_j in enumerate(y[0, :])]
Expand All @@ -238,18 +261,23 @@ def animate(i):
for y, l, s_t, e_t in zip(ys, lines, start_t, end_t):
for j, l_j in enumerate(l):
l_j.set_data(y[i, j].ravel(), np.array([s_t.iloc[j]/np.timedelta64(3600*24,'s'), e_t.iloc[j]/np.timedelta64(3600*24,'s')]))
ax.legend(**legend_kwargs)
return [item for sublist in lines for item in sublist]

plt.pause(1e-5)
ani = animation.FuncAnimation(fig, animate, init_func=init, blit=False, frames=np.minimum(ys[0].shape[0]-1, frames), interval=100, repeat=False)

return ani
if savepath is not None:
writervideo = animation.FFMpegWriter(fps=60)
animation.FuncAnimation(fig, animate, init_func=init, blit=False, frames=np.minimum(ys[0].shape[0] - 1, frames),
interval=100, repeat=False).save(savepath, writer=writervideo)
else:
return animation.FuncAnimation(fig, animate, init_func=init, blit=False,
frames=np.minimum(ys[0].shape[0] - 1, frames), interval=100, repeat=False)

def plot_quantiles(signals, qs, labels, n_rows=50, interval=1, step=1, repeat=False):
def plot_quantiles(signals, qs, labels, n_rows=50, interval=1, step=1, repeat=False, ax_labels=None, legend_kwargs={},
remove_spines=True, savepath=None, **kwargs):
n_max = np.minimum(signals[0].shape[0], int(n_rows*step))
n_rows = np.minimum(int(np.floor(signals[0].shape[0]/step)), n_rows)
qs = qs.values if isinstance(qs, pd.DataFrame) else qs
fig, ax = plt.subplots(1)
fig, ax = plt.subplots(1, **kwargs)
signals = signals if isinstance(signals, list) else [signals]
t = np.arange(signals[0].shape[1])
lines= []
Expand All @@ -260,21 +288,38 @@ def plot_quantiles(signals, qs, labels, n_rows=50, interval=1, step=1, repeat=Fa
lineq = ax.plot(np.squeeze(qs[0, :, :]), 'r', lw=2, alpha=0.3)
ax.set_ylim(np.min(qs[:n_max]), np.max(qs[:n_max])*1.05)

if ax_labels is not None:
for k, v in ax_labels.items():
if k == 'x':
ax.set_xlabel(v)
elif k == 'y':
ax.set_ylabel(v)
if remove_spines:
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)


def animate(i):
i = i * step
for l, s in zip(lines, signals):
l.set_data(t, s[i, :])
[lineq[j].set_data(t, qsi) for j, qsi in enumerate(qs[i, :, :].T)]
ax.legend(**legend_kwargs)
return (*lines, *lineq, )

def init():
lines[0].set_data([], [])
plt.legend()
return (lines[0],)

ani = animation.FuncAnimation(fig, animate, init_func=init, frames=n_rows, interval=interval, blit=True,

if savepath is not None:
writervideo = animation.FFMpegWriter(fps=60)
animation.FuncAnimation(fig, animate, init_func=init, frames=n_rows, interval=interval, blit=True,
repeat=repeat).save(savepath, writer=writervideo)
else:
return animation.FuncAnimation(fig, animate, init_func=init, frames=n_rows, interval=interval, blit=True,
repeat=repeat)
return ani


def plot_scenarios_from_multilevel(scens, i=0, ax=None):
Expand Down
33 changes: 32 additions & 1 deletion tests/test_boosters.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import logging
from pyforecaster.forecasting_models.gradientboosters import LGBMHybrid
from pyforecaster.forecaster import LGBForecaster, LinearForecaster
from pyforecaster.plot_utils import plot_quantiles
from pyforecaster.plot_utils import plot_quantiles, plot_trees
from pyforecaster.formatter import Formatter


Expand Down Expand Up @@ -44,5 +44,36 @@ def test_linear_val_split(self):
plot_quantiles([y_te.iloc[:10, :], y_hat_lin.iloc[:10, :], y_hat_lgbh.iloc[:10, :], y_hat_lgb.iloc[:10, :]], q[:10, :, :], ['y_te', 'y_lin', 'y_lgbhybrid_1', 'y_hat_lgb'])
plt.close('all')

def do_not_test_linear_val_split(self):

formatter = Formatter(logger=self.logger).add_transform(['all'], lags=np.arange(144),
relative_lags=True)
formatter.add_transform(['all'], lags=np.arange(144)+144*6)
formatter.add_transform(['all'], ['min', 'max'], agg_bins=[1, 2, 15, 24, 48, 144])
formatter.add_target_transform(['all'], lags=-np.arange(144)-1)

x, y = formatter.transform(self.data)
n_tr = int(len(x) * 0.8)
x_tr, x_te, y_tr, y_te = [x.iloc[:n_tr, :].copy(), x.iloc[n_tr:, :].copy(), y.iloc[:n_tr].copy(),
y.iloc[n_tr:].copy()]

m_lin = LinearForecaster(q_vect=np.linspace(0.01, 0.99, 11), cov_est_method='vanilla', val_ratio=0.6, online_tree_reduction=False,
nodes_at_step=np.logspace(0.5, 2, 144,base=5).astype(int)).fit(x_tr, y_tr)
y_hat_lgbh = m_lin.predict(x_te)
q = m_lin.predict_quantiles(x_te)

trees = m_lin.predict_trees(x_te.iloc[:500, :])
plot_trees(trees, [y_te.iloc[:500, :].values], frames=500,
ax_labels={'x':"step [10 min]", "y": 'P [kW]'}, layout='tight', figsize=(8, 4),
legend_kwargs={'loc':'upper right'}, savepath='linear_preds_tree.mp4')
plt.close('all')
plot_quantiles([y_te, y_hat_lgbh], q, ['y_te', 'y_hat'], n_rows=500,
ax_labels={'x':"step [10 min]", "y": 'P [kW]'}, layout='tight', figsize=(8, 4),
legend_kwargs={'loc':'upper right'}, savepath='linear_preds.mp4')


formatter.plot_transformed_feature(self.data, 'all',
ax_labels={'x':"step [1 day]", "y": 'P [kW]'}, layout='tight', figsize=(8, 4),
legend_kwargs={'loc':'upper right'}, frames=500, savepath='transformed_features.mp4')
if __name__ == '__main__':
unittest.main()
16 changes: 8 additions & 8 deletions tests/test_formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,26 +136,26 @@ def test_aggregated_transform(self):
formatter.add_transform([0], ['min', 'max'], agg_bins=[-10, -15, -20], nested=False)
formatter.add_target_transform([0], lags=-np.arange(30)-1)
x_transformed, y_transformed = formatter.transform(self.x2)
formatter.plot_transformed_feature(self.x2, 0, frames=100)
#formatter.plot_transformed_feature(self.x2, 0, frames=100)

# test nested transform (much faster)
formatter = pyf.Formatter().add_transform([0], lags=np.arange(10), agg_freq='20min',
relative_lags=True)
formatter.add_transform([0], ['min', 'max'], agg_bins=[-10, -15, -20], nested=True)
formatter.add_target_transform([0], lags=-np.arange(30) - 1)
x_transformed_nested, y_transformed = formatter.transform(self.x2)
formatter.plot_transformed_feature(self.x2, 0, frames=100)
#formatter.plot_transformed_feature(self.x2, 0, frames=100)


formatter = pyf.Formatter().add_transform([0], ['min', 'max'], agg_bins=[0, 1, 4, 10])
formatter.add_transform([0], ['min', 'max'], agg_bins=[-10, -15, -20])
formatter.add_transform([0], ['mean'], lags=np.arange(10), agg_freq='20min', relative_lags=True)
formatter.add_target_transform([0], lags=-np.arange(30)-1)
x_transformed, y_transformed = formatter.transform(self.x3)
formatter.plot_transformed_feature(self.x3, 0, frames=30)
#formatter.plot_transformed_feature(self.x3, 0, frames=30)

x_transformed, y_transformed = formatter.transform(self.x2)
formatter.plot_transformed_feature(self.x2, 0, frames=100)
#formatter.plot_transformed_feature(self.x2, 0, frames=100)
plt.close('all')

def test_speed(self):
Expand Down Expand Up @@ -288,11 +288,11 @@ def test_normalizers_complex(self):


def test_normalizers_impossible(self):
x_private = pd.DataFrame(np.random.randn(500, 15),
index=pd.date_range('01-01-2020', '01-05-2020', 500, tz='Europe/Zurich'),
x_private = pd.DataFrame(np.random.randn(100, 15),
index=pd.date_range('01-01-2020', '01-05-2020', 100, tz='Europe/Zurich'),
columns=pd.MultiIndex.from_product([['b1', 'b2', 'b3'], ['a', 'b', 'c', 'd', 'e']]))
x_shared = pd.DataFrame(np.random.randn(500, 5),
index=pd.date_range('01-01-2020', '01-05-2020', 500, tz='Europe/Zurich'),
x_shared = pd.DataFrame(np.random.randn(100, 5),
index=pd.date_range('01-01-2020', '01-05-2020', 100, tz='Europe/Zurich'),
columns=pd.MultiIndex.from_product([['shared'], [0, 1, 2, 3, 4]]))

df_mi = pd.concat([x_private, x_shared], axis=1)
Expand Down
41 changes: 32 additions & 9 deletions tests/test_nns.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import pandas as pd
import numpy as np
import logging
from pyforecaster.forecasting_models.neural_forecasters import PICNN, RecStablePICNN
from pyforecaster.forecasting_models.neural_forecasters import PICNN, RecStablePICNN, NN
from pyforecaster.trainer import hyperpar_optimizer
from pyforecaster.formatter import Formatter
from pyforecaster.metrics import nmae
Expand Down Expand Up @@ -37,6 +37,27 @@ def test_ffnn(self):

savepath_tr_plots = 'tests/results/ffnn_tr_plots'

# if not there, create directory savepath_tr_plots
if not exists(savepath_tr_plots):
makedirs(savepath_tr_plots)

m = NN(learning_rate=1e-3, batch_size=1000, load_path=None, n_hidden_x=200,
n_out=y_tr.shape[1], n_layers=3).fit(x_tr,y_tr, n_epochs=1, savepath_tr_plots=savepath_tr_plots, stats_step=40)

y_hat_1 = m.predict(x_te.iloc[:100, :])


def test_picnn(self):
# normalize inputs
x = (self.x - self.x.mean(axis=0)) / (self.x.std(axis=0)+0.01)
y = (self.y - self.y.mean(axis=0)) / (self.y.std(axis=0)+0.01)

n_tr = int(len(x) * 0.8)
x_tr, x_te, y_tr, y_te = [x.iloc[:n_tr, :].copy(), x.iloc[n_tr:, :].copy(), y.iloc[:n_tr].copy(),
y.iloc[n_tr:].copy()]

savepath_tr_plots = 'tests/results/ffnn_tr_plots'

# if not there, create directory savepath_tr_plots
if not exists(savepath_tr_plots):
makedirs(savepath_tr_plots)
Expand All @@ -45,18 +66,20 @@ def test_ffnn(self):
optimization_vars = x_tr.columns[:100]


m = PICNN(learning_rate=1e-3, batch_size=1000, load_path=None, n_hidden_x=200, n_hidden_y=200,
n_out=y_tr.shape[1], n_layers=3, optimization_vars=optimization_vars).fit(x_tr,
y_tr,
n_epochs=1,
savepath_tr_plots=savepath_tr_plots,
stats_step=40)
m_1 = PICNN(learning_rate=1e-3, batch_size=1000, load_path=None, n_hidden_x=200, n_hidden_y=200,
n_out=y_tr.shape[1], n_layers=3, optimization_vars=optimization_vars).fit(x_tr,
y_tr,
n_epochs=1,
savepath_tr_plots=savepath_tr_plots,
stats_step=40)

y_hat_1 = m_1.predict(x_te)
m_1.save('tests/results/ffnn_model.pk')

y_hat_1 = m.predict(x_te.iloc[:100, :])
m.save('tests/results/ffnn_model.pk')
n = PICNN(load_path='tests/results/ffnn_model.pk')
y_hat_2 = n.predict(x_te.iloc[:100, :])


m = RecStablePICNN(learning_rate=1e-3, batch_size=1000, load_path=None, n_hidden_x=200, n_hidden_y=200,
n_out=1, n_layers=3, optimization_vars=optimization_vars).fit(x_tr,y_tr.iloc[:, [0]],
n_epochs=1,
Expand Down

0 comments on commit 83a0a17

Please sign in to comment.