modelflowib 2.73__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- modelBLfunk.py +180 -0
- model_Excel.py +332 -0
- model_cvx.py +139 -0
- model_dynare.py +173 -0
- model_financial_stability.py +88 -0
- model_latex.py +497 -0
- model_latex_class.py +808 -0
- model_parquet_mixin.py +424 -0
- modelclass.py +9828 -0
- modelconstruct.py +1496 -0
- modelconstruct_estimation.py +2872 -0
- modeldash.py +265 -0
- modeldashboot.py +202 -0
- modeldashsidebar.py +456 -0
- modeldekom.py +651 -0
- modeldiff.py +561 -0
- modeldisplay.py +550 -0
- modelestimation.py +1776 -0
- modelestimator_new.py +2613 -0
- modelflowib-2.73.dist-info/METADATA +156 -0
- modelflowib-2.73.dist-info/RECORD +44 -0
- modelflowib-2.73.dist-info/WHEEL +5 -0
- modelflowib-2.73.dist-info/licenses/license.md +10 -0
- modelflowib-2.73.dist-info/top_level.txt +39 -0
- modelgrab.py +318 -0
- modelgrabgdx.py +584 -0
- modelgrabwf2.py +1107 -0
- modelhelp.py +543 -0
- modelhtml.py +606 -0
- modelinvert.py +250 -0
- modeljupyter.py +824 -0
- modeljupytermagic.py +813 -0
- modelmacrograb.py +98 -0
- modelmanipulation.py +1461 -0
- modelmf.py +349 -0
- modelnet.py +114 -0
- modelnewton.py +2178 -0
- modelnormalize.py +430 -0
- modelpattern.py +428 -0
- modelreport.py +2187 -0
- modeluserfunk.py +97 -0
- modelvis.py +1038 -0
- modelwidget.py +718 -0
- modelwidget_input.py +1933 -0
modelvis.py
ADDED
|
@@ -0,0 +1,1038 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Created on Fri May 12 11:07:02 2017
|
|
4
|
+
|
|
5
|
+
@author: hanseni
|
|
6
|
+
|
|
7
|
+
This module creates functions and classes for visualizing results.
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
import pandas as pd
|
|
14
|
+
import matplotlib.pyplot as plt
|
|
15
|
+
import matplotlib as mpl
|
|
16
|
+
import seaborn as sns
|
|
17
|
+
import fnmatch
|
|
18
|
+
from matplotlib import dates
|
|
19
|
+
import matplotlib.ticker as ticker
|
|
20
|
+
from IPython.display import display
|
|
21
|
+
from dataclasses import dataclass, field
|
|
22
|
+
from typing import Any, List, Dict, Optional
|
|
23
|
+
|
|
24
|
+
from subprocess import run
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
import webbrowser as wb
|
|
27
|
+
import types
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
import numpy
|
|
32
|
+
|
|
33
|
+
from modelhelp import cutout,finddec
|
|
34
|
+
|
|
35
|
+
##%%
|
|
36
|
+
def meltdim(df,dims=['dima','dimb'],source='Latest'):
|
|
37
|
+
''' Melts a wide dataframe the variable names are split to dimensions acording
|
|
38
|
+
to the list of texts in dims. in variablenames
|
|
39
|
+
the tall dataframe have a variable name for each dimensions
|
|
40
|
+
also values and source are introduced ac column names in the dataframe '''
|
|
41
|
+
splitstring = (r'\|').join(['(?P<'+d+'>[A-Z0-9_]*)' for d in dims])
|
|
42
|
+
melted = pd.melt(df.reset_index().rename(columns={'index':'quarter'}),id_vars='quarter')
|
|
43
|
+
vardf = (melted
|
|
44
|
+
.assign(source=source)
|
|
45
|
+
.assign(varname = lambda df_ :df_.variable.str.replace('__','|',len(dims)-1)) # just to make the next step more easy
|
|
46
|
+
.pipe(lambda df_ : pd.concat([df_ ,df_.varname.str.extract(splitstring,expand=True)],axis=1)))
|
|
47
|
+
return vardf
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class DummyVis:
|
|
52
|
+
def __init__(self, *args, **kwargs):
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
def __getattr__(self, name):
|
|
56
|
+
# Ignore special methods for Jupyter's display system and other internal methods
|
|
57
|
+
ignored_methods = [
|
|
58
|
+
'_ipython_canary_method_should_not_exist_',
|
|
59
|
+
'_repr_', # covers all _repr_*_ methods
|
|
60
|
+
'__', # covers all special __*__ methods
|
|
61
|
+
]
|
|
62
|
+
if any(name.startswith(prefix) for prefix in ignored_methods):
|
|
63
|
+
return lambda *args, **kwargs: None
|
|
64
|
+
|
|
65
|
+
# Return a callable that prints a message and returns another DummyVis instance
|
|
66
|
+
def dummy_method(*args, **kwargs):
|
|
67
|
+
# print(f"Attempt to call '{name}' on an uninitialized vis instance.")
|
|
68
|
+
return DummyVis()
|
|
69
|
+
|
|
70
|
+
return dummy_method
|
|
71
|
+
|
|
72
|
+
def __call__(self, *args, **kwargs):
|
|
73
|
+
# Define behavior when the instance is called as a function
|
|
74
|
+
# print("DummyVis instance called as a function.")
|
|
75
|
+
return DummyVis
|
|
76
|
+
|
|
77
|
+
def __repr__(self):
|
|
78
|
+
return "<Try again>"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class vis():
|
|
82
|
+
''' Visualization class. used as a method on a model instance.
|
|
83
|
+
|
|
84
|
+
The purpose is to select variables acording to a pattern, potential with wildcards
|
|
85
|
+
'''
|
|
86
|
+
def __init__(self, model=None, pat='',names=None,df=None):
|
|
87
|
+
self.model = model
|
|
88
|
+
self.__pat__ = pat
|
|
89
|
+
if type(names) == type(None):
|
|
90
|
+
self.names = self.model.vlist(self.__pat__)
|
|
91
|
+
else:
|
|
92
|
+
self.names = names
|
|
93
|
+
|
|
94
|
+
if not len(self.names):
|
|
95
|
+
raise ValueError(f'The variable specification:"{pat}" did not generate any matches')
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
if isinstance(df,pd.DataFrame):
|
|
99
|
+
self.thisdf = df
|
|
100
|
+
else:
|
|
101
|
+
try:
|
|
102
|
+
self.thisdf = self.model.lastdf.loc[:,self.names]
|
|
103
|
+
except Exception as e:
|
|
104
|
+
print(e)
|
|
105
|
+
print('No data in the model instance, so only structure information from model.[<something>] ')
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def explain(self,**kwargs):
|
|
116
|
+
for var in self.names:
|
|
117
|
+
x = self.model.explain(var,**kwargs)
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
def draw(self,**kwargs):
|
|
121
|
+
for var in self.names:
|
|
122
|
+
x = self.model.draw(var,**kwargs)
|
|
123
|
+
|
|
124
|
+
def dekomp(self,**kwargs):
|
|
125
|
+
self.model.dekomp.cache_clear()
|
|
126
|
+
for var in self.names:
|
|
127
|
+
x = self.model.dekomp(var,**kwargs)
|
|
128
|
+
|
|
129
|
+
def heat(self,*args, **kwargs):
|
|
130
|
+
''' Displays a heatmap of the resulting dataframe'''
|
|
131
|
+
|
|
132
|
+
name = kwargs.pop('title',self.__pat__)
|
|
133
|
+
a = heatshow(self.thisdf.loc[self.model.current_per,:].T,
|
|
134
|
+
name=name,*args, **kwargs)
|
|
135
|
+
display(a)
|
|
136
|
+
return a
|
|
137
|
+
|
|
138
|
+
def plot(self,*args, **kwargs):
|
|
139
|
+
''' Displays a plot for each of the columns in the resulting dataframe '''
|
|
140
|
+
|
|
141
|
+
name = kwargs.get('title','Title')
|
|
142
|
+
a = plotshow(self.thisdf.loc[self.model.current_per,:],
|
|
143
|
+
name=name,*args,**kwargs)
|
|
144
|
+
display(a)
|
|
145
|
+
return a
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def rplot(self,*args,**kwargs):
|
|
149
|
+
"""
|
|
150
|
+
Generates a table display configuration based on specified parameters and data types, including dynamic
|
|
151
|
+
adjustments of display options using both standard and keyword arguments.
|
|
152
|
+
|
|
153
|
+
Parameters:
|
|
154
|
+
pat (str): Pattern or identifier used to select data for the line, defaulting to '#Headline'.
|
|
155
|
+
title (str): Title of the table, passed directly to Options, defaulting to 'Table'.
|
|
156
|
+
datatype (str): Type of data transformation to apply (e.g., 'growth', 'level'), defaulting to 'growth'.
|
|
157
|
+
custom_description (dict): Custom descriptions to augment or override default descriptions, empty by default.
|
|
158
|
+
dec (int): Number of decimal places for numerical output, passed directly to Line configuration, defaulting to 2.
|
|
159
|
+
heading (str): Optional heading line for the table, empty by default.
|
|
160
|
+
name (str): Name for the display, defaults to 'A_small_table'.
|
|
161
|
+
foot (str): Footer text.
|
|
162
|
+
rename (bool): Allows renaming of data columns
|
|
163
|
+
decorate (bool): Decorates row descriptions based on the showtype, defaulting to False.
|
|
164
|
+
width (int): Specifies the width for formatting output in characters, efaulting to 5.
|
|
165
|
+
chunk_size (int): Number of columns per chunk in the display output, defaulting to 0.
|
|
166
|
+
timeslice (List[int]): Time slice for data display, empty by default.
|
|
167
|
+
max_cols (int): Maximum columns when displayed as a string, faulting to the system wide setting.
|
|
168
|
+
last_cols (int): Specifies the number of last columns to include in a display slice, particularly in Latex.
|
|
169
|
+
col_desc (str): text centered on columns
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
DisplayVarTableDef: Configured table definition object ready for rendering, which includes detailed specifications
|
|
173
|
+
such as units and type of transformation based on the datatype.
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
return self.model.plot(self.names,*args,**kwargs)
|
|
182
|
+
|
|
183
|
+
def rtable(self,*args,**kwargs):
|
|
184
|
+
"""
|
|
185
|
+
Generates a table display configuration based on specified parameters and data types, including dynamic
|
|
186
|
+
adjustments of display options using both standard and keyword arguments.
|
|
187
|
+
|
|
188
|
+
Parameters:
|
|
189
|
+
pat (str): Pattern or identifier used to select data for the line, defaulting to '#Headline'.
|
|
190
|
+
datatype (str): Type of data transformation to apply (e.g., 'growth', 'level'), defaulting to 'growth'.
|
|
191
|
+
title (str): Title of the table, passed directly to Options, defaulting to 'Table'.
|
|
192
|
+
custom_description (dict): Custom descriptions to augment or override default descriptions, empty by default.
|
|
193
|
+
ncol (int):
|
|
194
|
+
Returns:
|
|
195
|
+
DisplayVarTableDef: Configured table definition object ready for rendering, which includes detailed specifications
|
|
196
|
+
such as units and type of transformation based on the datatype.
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
"""
|
|
201
|
+
return self.model.table(self.names,*args,**kwargs)
|
|
202
|
+
|
|
203
|
+
def plot_alt(self,title='Title',*args, **kwargs):
|
|
204
|
+
''' Displays a plot for each of the columns in the resulting dataframe '''
|
|
205
|
+
|
|
206
|
+
if hasattr(self.model,'var_description'):
|
|
207
|
+
vtrans = self.model.var_description
|
|
208
|
+
else:
|
|
209
|
+
vtrans = {}
|
|
210
|
+
a = vis_alt(self.model.basedf.loc[self.model.current_per,self.names].rename(columns=vtrans) ,
|
|
211
|
+
self.model.lastdf.loc[self.model.current_per,self.names].rename(columns=vtrans) ,
|
|
212
|
+
title=title,*args,**kwargs)
|
|
213
|
+
return a
|
|
214
|
+
|
|
215
|
+
def box(self):
|
|
216
|
+
''' Displays a boxplot comparing basedf and lastdf '''
|
|
217
|
+
return compvis(model=self.model,pat=self.__pat__).box()
|
|
218
|
+
def violin(self):
|
|
219
|
+
''' Displays a violinplot comparing basedf and lastdf '''
|
|
220
|
+
return compvis(model=self.model,pat=self.__pat__).violin()
|
|
221
|
+
def swarm(self):
|
|
222
|
+
''' Displays a swarmlot comparing basedf and lastdf '''
|
|
223
|
+
return compvis(model=self.model,pat=self.__pat__).swarm()
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
@property
|
|
227
|
+
def df(self):
|
|
228
|
+
''' Returns the result of this instance as a dataframe'''
|
|
229
|
+
return self.thisdf.loc[self.model.current_per,:]
|
|
230
|
+
|
|
231
|
+
@property
|
|
232
|
+
def base(self):
|
|
233
|
+
''' Returns basedf '''
|
|
234
|
+
return vis(model=self.model,df=self.model.basedf.loc[:,self.names],pat=self.__pat__)
|
|
235
|
+
|
|
236
|
+
@property
|
|
237
|
+
def pct(self):
|
|
238
|
+
'''Returns the pct change'''
|
|
239
|
+
return vis(model=self.model,df=self.thisdf.loc[:,self.names].pct_change()*100.,pat=self.__pat__)
|
|
240
|
+
@property
|
|
241
|
+
def growth(self):
|
|
242
|
+
'''Returns the pct growth'''
|
|
243
|
+
return vis(model=self.model,df=self.thisdf.loc[:,self.names].pct_change()*100.,pat=self.__pat__)
|
|
244
|
+
|
|
245
|
+
@property
|
|
246
|
+
def year_pct(self):
|
|
247
|
+
'''Returns the pct change over 4 periods (used for quarterly data) '''
|
|
248
|
+
return vis(model=self.model,df=self.thisdf.loc[:,self.names].pct_change(periods=4)*100..loc[:,self.names],pat=self.__pat__)
|
|
249
|
+
|
|
250
|
+
@property
|
|
251
|
+
def yoy_growth(self):
|
|
252
|
+
'''Returns the pct change over 4 periods (used for quarterly data) '''
|
|
253
|
+
return vis(model=self.model,df=self.thisdf.loc[:,self.names].pct_change(periods=4)*100..loc[:,self.names],pat=self.__pat__)
|
|
254
|
+
|
|
255
|
+
@property
|
|
256
|
+
def qoq_ar(self):
|
|
257
|
+
'''Returns the pct change over 4 periods (used for quarterly data) '''
|
|
258
|
+
df = ((1.+self.thisdf.loc[:,self.names].pct_change().loc[:,self.names])**4-1.)**100.
|
|
259
|
+
return vis(model=self.model,df=df,pat=self.__pat__)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
@property
|
|
263
|
+
def frml(self):
|
|
264
|
+
'''Returns formulas '''
|
|
265
|
+
def getfrml(var,l):
|
|
266
|
+
if var in self.model.endogene:
|
|
267
|
+
t=self.model.allvar[var]['frml'].replace('\n',' ').replace(' ',' ')
|
|
268
|
+
return f'{var:<{l}} : {t}'
|
|
269
|
+
else:
|
|
270
|
+
return f'{var:{l}} : Exogenous'
|
|
271
|
+
mlength = max([len(v) for v in self.names])
|
|
272
|
+
out = '\n'.join(getfrml(var,mlength) for var in self.names)
|
|
273
|
+
print(out)
|
|
274
|
+
|
|
275
|
+
@property
|
|
276
|
+
def des(self):
|
|
277
|
+
'''Returns variable descriptions '''
|
|
278
|
+
def getdes(var,l):
|
|
279
|
+
return f'{var:<{l}} : {self.model.var_description[var]}'
|
|
280
|
+
mlength = max([len(v) for v in self.names])
|
|
281
|
+
|
|
282
|
+
out = '\n'.join(getdes(var,mlength) for var in self.names)
|
|
283
|
+
print(out)
|
|
284
|
+
|
|
285
|
+
@property
|
|
286
|
+
def eviews(self):
|
|
287
|
+
'''Returns variable descriptions '''
|
|
288
|
+
nl = '\n'
|
|
289
|
+
def geteviews(var,l):
|
|
290
|
+
if var in self.model.endogene:
|
|
291
|
+
ev = self.model.eviews_dict.get(var,'Not avaible')
|
|
292
|
+
return f'{var:<{l}} : {nl}{ev}'
|
|
293
|
+
else:
|
|
294
|
+
return f'{var:<{l}} : Exogen'
|
|
295
|
+
|
|
296
|
+
mlength = max([len(v) for v in self.names])
|
|
297
|
+
out = '\n \n'.join(geteviews(var,mlength) for var in self.names)
|
|
298
|
+
print(out)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
@property
|
|
302
|
+
def dif(self):
|
|
303
|
+
''' Returns the differens between the basedf and lastdf'''
|
|
304
|
+
difdf = self.thisdf-self.model.basedf.loc[:,self.names]
|
|
305
|
+
return vis(model=self.model,df=difdf,pat=self.__pat__)
|
|
306
|
+
|
|
307
|
+
@property
|
|
308
|
+
def difpctlevel(self):
|
|
309
|
+
''' Returns the differens between the basedf and lastdf in percent '''
|
|
310
|
+
difdf = ((self.thisdf-self.model.basedf.loc[:,self.names])/ self.model.basedf.loc[:,self.names])*100.
|
|
311
|
+
return vis(model=self.model,df=difdf,pat=self.__pat__)
|
|
312
|
+
|
|
313
|
+
@property
|
|
314
|
+
def difpct(self):
|
|
315
|
+
''' Returns the differens between the pct changes in basedf and lastdf'''
|
|
316
|
+
difdf = (self.thisdf.pct_change()-self.model.basedf.loc[:,self.names].pct_change())*100.
|
|
317
|
+
return vis(model=self.model,df=difdf,pat=self.__pat__)
|
|
318
|
+
@property
|
|
319
|
+
def difgrowth(self):
|
|
320
|
+
''' Returns the differens between the pct changes in basedf and lastdf'''
|
|
321
|
+
difdf = (self.thisdf.pct_change()-self.model.basedf.loc[:,self.names].pct_change())*100.
|
|
322
|
+
return vis(model=self.model,df=difdf,pat=self.__pat__)
|
|
323
|
+
|
|
324
|
+
@property
|
|
325
|
+
def endo(self):
|
|
326
|
+
'''Selects only endogenous variables'''
|
|
327
|
+
endonames = [v for v in self.names if v in self.model.endogene]
|
|
328
|
+
thisdf = self.thisdf.loc[:,endonames]
|
|
329
|
+
|
|
330
|
+
return vis(model=self.model,df = thisdf, names= endonames, pat= self.__pat__ )
|
|
331
|
+
@property
|
|
332
|
+
def exo(self):
|
|
333
|
+
'''Selects only exogenous variables'''
|
|
334
|
+
endonames = [v for v in self.names if v in self.model.exogene]
|
|
335
|
+
thisdf = self.thisdf.loc[:,endonames]
|
|
336
|
+
|
|
337
|
+
return vis(model=self.model,df = thisdf, names= endonames, pat= self.__pat__ )
|
|
338
|
+
|
|
339
|
+
@property
|
|
340
|
+
def print(self):
|
|
341
|
+
''' prints the current result'''
|
|
342
|
+
print('\n',self.thisdf.loc[self.model.current_per,:].to_string())
|
|
343
|
+
return
|
|
344
|
+
|
|
345
|
+
def __repr__(self):
|
|
346
|
+
|
|
347
|
+
# return self.thisdf.loc[self.model.current_per,:].to_string()
|
|
348
|
+
|
|
349
|
+
if self.model.in_notebook():
|
|
350
|
+
# from modelwidget import visshow
|
|
351
|
+
# visshow(self.model,self.__pat__)
|
|
352
|
+
return ''
|
|
353
|
+
else:
|
|
354
|
+
return self.thisdf.loc[self.model.current_per,:].to_string()
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def _repr_html_(self):
|
|
359
|
+
'''Displays a nice summary of the results when called in a Jupyter enviorement'''
|
|
360
|
+
|
|
361
|
+
from modelwidget import visshow
|
|
362
|
+
pat = ' '.join(self.names)
|
|
363
|
+
# visshow(self.model,self.__pat__)
|
|
364
|
+
visshow(self.model,pat)
|
|
365
|
+
return ''
|
|
366
|
+
|
|
367
|
+
# return self.model.ibsstyle(self.thisdf.loc[self.model.current_per,:]).to_html(doctype_html=True)
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
# @property
|
|
371
|
+
# def show(self):
|
|
372
|
+
# if self.model.in_notebook():
|
|
373
|
+
# display(self.model.ibsstyle(self.thisdf.loc[self.model.current_per,:],transpose=True))
|
|
374
|
+
# else:
|
|
375
|
+
# print(self.thisdf.loc[self.model.current_per,:].to_string())
|
|
376
|
+
@property
|
|
377
|
+
def show(self):
|
|
378
|
+
if self.model.in_notebook():
|
|
379
|
+
pat = ' '.join(self.names)
|
|
380
|
+
|
|
381
|
+
from modelwidget import visshow
|
|
382
|
+
visshow(self.model,pat)
|
|
383
|
+
else:
|
|
384
|
+
print(self.thisdf.loc[self.model.current_per,:].to_string())
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def __mul__(self,other):
|
|
388
|
+
''' Multiply the curent result with other '''
|
|
389
|
+
muldf = self.thisdf * other
|
|
390
|
+
return vis(model=self.model,df=muldf,pat=self.__pat__)
|
|
391
|
+
|
|
392
|
+
def rename(self,other=None):
|
|
393
|
+
''' rename columns '''
|
|
394
|
+
if type(other) == type(None):
|
|
395
|
+
if hasattr(self.model,'var_description'):
|
|
396
|
+
vtrans = self.model.var_description
|
|
397
|
+
else:
|
|
398
|
+
vtrans = {}
|
|
399
|
+
else:
|
|
400
|
+
vtrans = other
|
|
401
|
+
muldf = self.thisdf.rename(columns=vtrans)
|
|
402
|
+
return vis(model=self.model,df=muldf,pat=self.__pat__)
|
|
403
|
+
|
|
404
|
+
@property
|
|
405
|
+
def endo(self):
|
|
406
|
+
''' only endogennous variables. columns '''
|
|
407
|
+
endovar = [v for v in self.names if v in self.model.endogene]
|
|
408
|
+
muldf = self.thisdf.loc[:,endovar]
|
|
409
|
+
# print(f'{endovar=}')
|
|
410
|
+
return vis(model=self.model,df=muldf,pat = ' '.join( endovar ))
|
|
411
|
+
|
|
412
|
+
@property
|
|
413
|
+
def endo_nofit(self):
|
|
414
|
+
''' only endogennous variables. columns '''
|
|
415
|
+
endovar = [v for v in self.names if v in self.model.endogene and not v.endswith('_FITTED')]
|
|
416
|
+
muldf = self.thisdf.loc[:,endovar]
|
|
417
|
+
# print(f'{endovar=}')
|
|
418
|
+
return vis(model=self.model,df=muldf,pat = ' '.join( endovar ))
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def mul(self,other):
|
|
422
|
+
''' Multiply the curent result with other '''
|
|
423
|
+
return self.__mul__(other)
|
|
424
|
+
|
|
425
|
+
@property
|
|
426
|
+
def mul100(self):
|
|
427
|
+
'''Multiply the current result with 1, used to be 100- '''
|
|
428
|
+
raise Exception ('mul100 cnat be used any more')
|
|
429
|
+
return self.__mul__(1.0)
|
|
430
|
+
|
|
431
|
+
def __getattr__(self, name):
|
|
432
|
+
if name.startswith('_'):
|
|
433
|
+
return
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
allowed = {
|
|
437
|
+
'names' : 'Variable names',
|
|
438
|
+
'des' : 'Descriptions',
|
|
439
|
+
'frml' : 'Normalized equations',
|
|
440
|
+
'eviews':'Eviews equations',
|
|
441
|
+
'base' : 'basedf values',
|
|
442
|
+
'growth' : 'Growth',
|
|
443
|
+
'difgrowth' : 'Difference in growth',
|
|
444
|
+
'dif' : 'Diffference in values',
|
|
445
|
+
'difpctlevel' : 'Diffference in pct in values',
|
|
446
|
+
'difgrowth' : 'Difgrowth',
|
|
447
|
+
'yoy_growth' : 'Year on year growth (quarterly data)',
|
|
448
|
+
'qoq_ar' : 'Quarter to quarter annual rate growth (quarterly data)',
|
|
449
|
+
'show':'Display widget with summary of variables - default output in jupyter',
|
|
450
|
+
'df':'Returns DataFrame',
|
|
451
|
+
'print':'Print DataFrame as string',
|
|
452
|
+
'plot()':'Plot the results',
|
|
453
|
+
'rplot()':"Report plot variables - default datatype='growth'",
|
|
454
|
+
'rtable()':"Report table variables - default datatype='growth'",
|
|
455
|
+
'rename()':'Rename variables to their descriptions',
|
|
456
|
+
'endo':'Limit to endogeneous variables',
|
|
457
|
+
'endo_nofit':'Limit to endogeneous variables not ending in "_FITTED"',
|
|
458
|
+
'exo':'Limit to exogenous variables',
|
|
459
|
+
|
|
460
|
+
}
|
|
461
|
+
print(f'<{name}> not allowed try one of these:')
|
|
462
|
+
# Find the maximum length of the keys for alignment
|
|
463
|
+
max_key_length = max(len(key) for key in allowed)
|
|
464
|
+
|
|
465
|
+
# Print each key-value pair aligned
|
|
466
|
+
for key, value in allowed.items():
|
|
467
|
+
print(f"{key:<{max_key_length}} : {value}")
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
class compvis() :
|
|
471
|
+
''' Class to compare to runs in boxplots'''
|
|
472
|
+
def __init__(self, model=None, pat=None):
|
|
473
|
+
''' Combines basedf and lastdf to one tall dataframe useful for the Seaborn library'''
|
|
474
|
+
self.model = model
|
|
475
|
+
self.__pat__ = pat
|
|
476
|
+
self.names = self.model.vlist(self.__pat__)
|
|
477
|
+
self.lastdf = self.model.lastdf.loc[self.model.current_per,self.names]
|
|
478
|
+
self.basedf = self.model.basedf.loc[self.model.current_per,self.names]
|
|
479
|
+
self.lastmelt = melt(self.lastdf,source='Scenario')
|
|
480
|
+
self.basemelt = melt(self.basedf ,source='Base')
|
|
481
|
+
self.melted = pd.concat([self.lastmelt,self.basemelt])
|
|
482
|
+
return
|
|
483
|
+
|
|
484
|
+
def box(self,*args, **kwargs):
|
|
485
|
+
'''Displays a boxplot'''
|
|
486
|
+
fig, ax = plt.subplots(figsize=(12,6))
|
|
487
|
+
ax = sns.boxplot(x='time',y='value',data=self.melted,hue='source',ax=ax)
|
|
488
|
+
ax.set_title(self.__pat__)
|
|
489
|
+
def swarm(self,*args, **kwargs):
|
|
490
|
+
'''Displays a swarmplot '''
|
|
491
|
+
fig, ax = plt.subplots(figsize=(12,6))
|
|
492
|
+
ax = sns.swarmplot(x='time',y='value',data=self.melted,hue='source',ax=ax)
|
|
493
|
+
ax.set_title(self.__pat__)
|
|
494
|
+
def violin(self,*args, **kwargs):
|
|
495
|
+
'''Displays a violinplot'''
|
|
496
|
+
fig, ax = plt.subplots(figsize=(12,6))
|
|
497
|
+
ax = sns.violinplot(x='time',y='value',data=self.melted,hue='source',ax=ax)
|
|
498
|
+
ax.set_title(self.__pat__)
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
class container():
|
|
502
|
+
'''A container, used if to izualize dataframes without a model'''
|
|
503
|
+
|
|
504
|
+
def __init__(self,lastdf,basedf):
|
|
505
|
+
self.lastdf = lastdf
|
|
506
|
+
self.basedf = basedf
|
|
507
|
+
|
|
508
|
+
def smpl(self,start='',end='',df=None):
|
|
509
|
+
''' Defines the model.current_per which is used for calculation period/index
|
|
510
|
+
when no parameters are issues the current current period is returned \n
|
|
511
|
+
Either none or all parameters have to be provided '''
|
|
512
|
+
if start =='' and end == '':
|
|
513
|
+
pass
|
|
514
|
+
else:
|
|
515
|
+
istart,iend= self.lastdf.index.slice_locs(start,end)
|
|
516
|
+
per=self.lastdf.index[istart:iend]
|
|
517
|
+
self.current_per = per
|
|
518
|
+
return self.current_per
|
|
519
|
+
def vlist(self,pat):
|
|
520
|
+
'''returns a list of variable matching the pattern'''
|
|
521
|
+
if isinstance(pat,list):
|
|
522
|
+
ipat=pat
|
|
523
|
+
else:
|
|
524
|
+
ipat = [pat]
|
|
525
|
+
out = [v for p in ipat for v in sorted(fnmatch.filter(self.lastdf.columns,p.upper()))]
|
|
526
|
+
return out
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
##%%
|
|
530
|
+
class varvis():
|
|
531
|
+
''' Visualization class. used as a method on a model instance.
|
|
532
|
+
|
|
533
|
+
The purpose is to select variables acording to a pattern, potential with wildcards
|
|
534
|
+
'''
|
|
535
|
+
def __init__(self, model=None, var=''):
|
|
536
|
+
# print(f' varvis called {var=}')
|
|
537
|
+
self.model = model
|
|
538
|
+
self.var = var
|
|
539
|
+
if var not in model.allvar:
|
|
540
|
+
raise ValueError(f'The specification:"{var}" did not match a method, property or variable name')
|
|
541
|
+
self.endo = model.allvar[var]['endo']
|
|
542
|
+
def explain(self,**kwargs):
|
|
543
|
+
x = self.model.explain(self.var,**kwargs)
|
|
544
|
+
return x
|
|
545
|
+
|
|
546
|
+
def draw(self,**kwargs):
|
|
547
|
+
x = self.model.draw(self.var,**kwargs)
|
|
548
|
+
|
|
549
|
+
def tracedep(self,down=1,**kwargs):
|
|
550
|
+
'''Trace dependensies of name down to level down'''
|
|
551
|
+
self.model.draw(self.var,down=down,up=0,source=self.var,**kwargs)
|
|
552
|
+
|
|
553
|
+
def tracepre(self,up=1,**kwargs):
|
|
554
|
+
'''Trace dependensies of name down to level down
|
|
555
|
+
- `showdata|sd=True` will include a table of values for each variable
|
|
556
|
+
- `showdata|sd='pattern of variable names'` will include a table of values for each variable matching the pattern (including wildcharts
|
|
557
|
+
- `attshow|ats = True` will include a table of attributions for each variable
|
|
558
|
+
- `growthshow|gs = True` will include a table of growth for each variable
|
|
559
|
+
- `HR = True` will reorient the dependency graph
|
|
560
|
+
- `up = <integer>` will determine how many levels of parents to include
|
|
561
|
+
- `png = True` will display as a png picture
|
|
562
|
+
- `svg = True` will display as a svg picture which can be zoomed
|
|
563
|
+
- `pdf = True` will display as a pdf picture
|
|
564
|
+
- `eps = True` will create a eps file
|
|
565
|
+
- `browser = True` will open a browser with the resulting dependency graph - useful for zooming on a big graph
|
|
566
|
+
- `saveas = <a file name without extension>` will save the picture wit the filename with an added extension reflection the picture type
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
To allow the use of the display in presentations or publications The resulting file(s) are placed in the graph/subfolder
|
|
570
|
+
'''
|
|
571
|
+
self.model.draw(self.var,down=0,up=up,sink=self.var,**kwargs)
|
|
572
|
+
|
|
573
|
+
def dash(self,port=5001):
|
|
574
|
+
"""
|
|
575
|
+
Launch a dashboard to trace dependencies of the specified variable.
|
|
576
|
+
|
|
577
|
+
This method initializes a dashboard to visualize and trace dependencies
|
|
578
|
+
of a specific variable within the model. The dashboard provides insights
|
|
579
|
+
into how the selected variable is influenced by other components at various
|
|
580
|
+
levels of the dependency hierarchy.
|
|
581
|
+
|
|
582
|
+
Parameters:
|
|
583
|
+
----------
|
|
584
|
+
port : int, optional
|
|
585
|
+
The port on which to launch the dashboard. Default is 5001.
|
|
586
|
+
|
|
587
|
+
Returns:
|
|
588
|
+
-------
|
|
589
|
+
None
|
|
590
|
+
"""
|
|
591
|
+
self.model.modeldash(self.var,port=port)
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
def get_att(self,start='',end='',dec=None,bare=True,**kwargs):
|
|
596
|
+
'''
|
|
597
|
+
Retrieve and display the attribution for a variable within a specified period.
|
|
598
|
+
|
|
599
|
+
Parameters:
|
|
600
|
+
start (str): Start date of the period (default: '').
|
|
601
|
+
end (str): End date of the period (default: '').
|
|
602
|
+
dec (int): Number of decimal places for formatting (default: None).
|
|
603
|
+
bare (bool): If True, display only the attribution result; if False, display both the difference and attribution results (default: True).
|
|
604
|
+
type (str) : One of 'pct', 'growth', 'level' (default: 'pct')
|
|
605
|
+
**kwargs: Additional keyword arguments for specifying attribution type and other options.
|
|
606
|
+
|
|
607
|
+
Returns:
|
|
608
|
+
None (displays the attribution result)
|
|
609
|
+
|
|
610
|
+
Note:
|
|
611
|
+
- The method retrieves the difference and attribution results using the specified period and attribution type.
|
|
612
|
+
- The `dec` parameter controls the number of decimal places for formatting. If not provided, the default number of decimal places is determined based on the attribution type.
|
|
613
|
+
- The `bare` parameter determines whether to display only the attribution result or both the difference and attribution results.
|
|
614
|
+
- The `type` parameter wether to display the level, pct or growth attribution determines whether to display level, growth or pct attribution
|
|
615
|
+
'''
|
|
616
|
+
|
|
617
|
+
diff = self.model.get_att_diff(self.var,start=start,end=end,**kwargs)
|
|
618
|
+
res = self.model.get_att(self.var,start=start,end=end,**kwargs)
|
|
619
|
+
percent=(kwargs.get('type','pct') in {'growth'}) or \
|
|
620
|
+
((kwargs.get('type','pct') in {'pct'}) and bare)
|
|
621
|
+
if type(dec) == type(None):
|
|
622
|
+
xdec = 2 if (kwargs.get('type','pct') in {'level','growth'}) else 0
|
|
623
|
+
else:
|
|
624
|
+
xdec = dec
|
|
625
|
+
if bare:
|
|
626
|
+
out = res
|
|
627
|
+
out.index.name= 'Growth percent' if kwargs.get('type','pct') == 'growth' else 'Percent att.' if kwargs.get('type','pct') == 'pct' else 'Level att.'
|
|
628
|
+
|
|
629
|
+
else:
|
|
630
|
+
out = pd.concat([diff,res])
|
|
631
|
+
out.index.name= 'Growth percent' if kwargs.get('type','pct') == 'growth' else 'Level/percent' if kwargs.get('type','pct') == 'pct' else 'Level/level'
|
|
632
|
+
|
|
633
|
+
sout = self.model.ibsstyle(out,percent=percent,dec=xdec )
|
|
634
|
+
display(sout)
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
def dekomp(self,**kwargs):
|
|
638
|
+
if kwargs.get('lprint','False'):
|
|
639
|
+
self.model.dekomp.cache_clear()
|
|
640
|
+
x = self.model.dekomp(self.var,**kwargs)
|
|
641
|
+
return x
|
|
642
|
+
|
|
643
|
+
def var_des(self,var):
|
|
644
|
+
des = self.model.var_description[var]
|
|
645
|
+
return des if des != var else ''
|
|
646
|
+
|
|
647
|
+
def _showall_old(self,all=1,dif=0,last=0,show_all=True):
|
|
648
|
+
if self.endo:
|
|
649
|
+
des_string = self.model.get_eq_des(self.var,show_all)
|
|
650
|
+
out1,out2 = '',''
|
|
651
|
+
out0 = f'Endogeneous: {self.var}: {self.var_des(self.var)} \nFormular: {self.model.allvar[self.var]["frml"]}\n\n{des_string}\n'
|
|
652
|
+
try:
|
|
653
|
+
if dif:
|
|
654
|
+
out0 = out0+f'\nValues : \n{self.model.get_values(self.var)}\n'
|
|
655
|
+
|
|
656
|
+
if all:
|
|
657
|
+
out0 = out0+f'\nValues : \n{self.model.get_values(self.var)}\n'
|
|
658
|
+
out1 = f'\nInput last run: \n {self.model.get_eq_values(self.var)}\n\nInput base run: \n {self.model.get_eq_values(self.var,last=False)}\n'
|
|
659
|
+
elif last:
|
|
660
|
+
out0 = out0+f'\nValues : \n{self.model.get_values(self.var)}\n'
|
|
661
|
+
out1 = f'\nInput last run: \n {self.model.get_eq_values(self.var)}\n'
|
|
662
|
+
if all or dif:
|
|
663
|
+
out2 = f'\nDifference for input variables: \n {self.model.get_eq_dif(self.var,filter=False)}'
|
|
664
|
+
except Exception as e:
|
|
665
|
+
print(e)
|
|
666
|
+
pass
|
|
667
|
+
out=out0+out1+out2
|
|
668
|
+
else:
|
|
669
|
+
out = f'Exogeneous : {self.var}: {self.var_des(self.var)} \n Values : \n{self.model.get_values(self.var)}\n'
|
|
670
|
+
return out
|
|
671
|
+
|
|
672
|
+
# def ibsstyle(self,df,dec=2):
|
|
673
|
+
# ''' display a dataframe with tooltip'''
|
|
674
|
+
|
|
675
|
+
# tt = pd.DataFrame([[self.model.var_description[v] for c in df.columns] for v in df.index ],index=df.index,columns=df.columns)
|
|
676
|
+
# xdec = f'{dec}'
|
|
677
|
+
# result = df.style.format('{:.'+xdec+'f}').\
|
|
678
|
+
# set_tooltips(tt, props='visibility: hidden; position: absolute; z-index: 1; border: 1px solid #000066;'
|
|
679
|
+
# 'background-color: white; color: #000066; font-size: 0.8em;'
|
|
680
|
+
# 'transform: translate(0px, -24px); padding: 0.6em; border-radius: 0.5em;')
|
|
681
|
+
# return result
|
|
682
|
+
|
|
683
|
+
def _showall(self,all=1,dif=0,last=0,show_all=True):
|
|
684
|
+
from IPython.display import SVG, display, Image, IFrame, HTML, Markdown
|
|
685
|
+
if self.endo:
|
|
686
|
+
des_string = self.model.get_eq_des(self.var,show_all)
|
|
687
|
+
out0,out1,out2 = '','',''
|
|
688
|
+
print(f'Endogeneous: {self.var}: {self.var_des(self.var)}')
|
|
689
|
+
print(f'Formular: {self.model.allvar[self.var]["frml"]}\n\n{des_string}\n')
|
|
690
|
+
try:
|
|
691
|
+
if dif or all or last: print('Values :')
|
|
692
|
+
if dif or all or last: display(HTML(self.model.ibsstyle(self.model.get_values(self.var)).to_html() ))
|
|
693
|
+
if all or last: print('Input last run:')
|
|
694
|
+
if all or last: display(self.model.ibsstyle(self.model.get_eq_values(self.var)))
|
|
695
|
+
if all : print('Input base run:')
|
|
696
|
+
if all : display(self.model.ibsstyle(self.model.get_eq_values(self.var,last=False)))
|
|
697
|
+
if all or dif: print('Difference for input variables')
|
|
698
|
+
if all or dif: display(self.model.ibsstyle(self.model.get_eq_dif(self.var,filter=False)))
|
|
699
|
+
except Exception as e:
|
|
700
|
+
print(e)
|
|
701
|
+
pass
|
|
702
|
+
out=out0+out1+out2
|
|
703
|
+
else:
|
|
704
|
+
out = f'Exogeneous : {self.var}: {self.var_des(self.var)} \n Values : \n{self.model.get_values(self.var)}\n'
|
|
705
|
+
return out
|
|
706
|
+
|
|
707
|
+
|
|
708
|
+
@property
|
|
709
|
+
def show(self):
|
|
710
|
+
out = self._showall(all=1)
|
|
711
|
+
print(out)
|
|
712
|
+
return
|
|
713
|
+
|
|
714
|
+
@property
|
|
715
|
+
def showdif(self):
|
|
716
|
+
out = self._showall(all=0,dif=1)
|
|
717
|
+
print(out)
|
|
718
|
+
return
|
|
719
|
+
@property
|
|
720
|
+
def frml(self):
|
|
721
|
+
out = self._showall(all=0,dif=0)
|
|
722
|
+
print(out)
|
|
723
|
+
return
|
|
724
|
+
@property
|
|
725
|
+
def eviews(self):
|
|
726
|
+
out = self.model.eviews_dict.get(self.var,'Not avaiable')
|
|
727
|
+
print(out)
|
|
728
|
+
return
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
def __repr__(self):
|
|
732
|
+
|
|
733
|
+
out = self._showall(all=0,last=1)
|
|
734
|
+
return out
|
|
735
|
+
|
|
736
|
+
def __getattr__(self, name):
|
|
737
|
+
if name.startswith('_'):
|
|
738
|
+
return
|
|
739
|
+
allowed = {
|
|
740
|
+
'show' : 'show frml and data',
|
|
741
|
+
'showdif' : 'show oly differences ',
|
|
742
|
+
'frml' : 'Normalized equations',
|
|
743
|
+
'eviews':'Eviews equations',
|
|
744
|
+
'dash' : 'causality dashboard',
|
|
745
|
+
'get_att()' : 'get impact attrribution ',
|
|
746
|
+
'tracepre()' : 'trace preceding variables',
|
|
747
|
+
'tracedep()' : 'trace dependent variables',
|
|
748
|
+
}
|
|
749
|
+
print(f'<{name}> not allowed try one of these:')
|
|
750
|
+
# Find the maximum length of the keys for alignment
|
|
751
|
+
max_key_length = max(len(key) for key in allowed)
|
|
752
|
+
|
|
753
|
+
# Print each key-value pair aligned
|
|
754
|
+
for key, value in allowed.items():
|
|
755
|
+
print(f"{key:<{max_key_length}} : {value}")
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
|
|
761
|
+
|
|
762
|
+
|
|
763
|
+
|
|
764
|
+
def vis_alt(grund,mul,title='Show variables',ttop=None):
|
|
765
|
+
''' Graph of one of more variables each variable is displayed for 3 banks'''
|
|
766
|
+
avar = grund.columns
|
|
767
|
+
antal=len(avar)
|
|
768
|
+
fig, axes = plt.subplots(nrows=antal, ncols=1,figsize=(15,antal*6)) #,sharex='col' ,sharey='row')
|
|
769
|
+
fig.suptitle(title, fontsize=20)
|
|
770
|
+
ax2 = [axes] if antal == 1 else axes
|
|
771
|
+
for i,(var,ax) in enumerate(zip(avar,ax2)):
|
|
772
|
+
grunddata = grund.loc[:,var]
|
|
773
|
+
muldata = mul.loc[:,var]
|
|
774
|
+
# breakpoint()
|
|
775
|
+
grunddata.plot(ax=ax,legend=False,fontsize=14)
|
|
776
|
+
muldata.plot (ax=ax,legend=False,fontsize=14)
|
|
777
|
+
ax.set_title(var,fontsize=14)
|
|
778
|
+
x_pos = grunddata.index[-1]
|
|
779
|
+
ax.text(x_pos, grunddata.values[-1],'Baseline',fontsize=14)
|
|
780
|
+
ax.text(x_pos, muldata.values[-1] ,'Alternative',fontsize=14)
|
|
781
|
+
ax.yaxis.set_major_formatter(ticker.FuncFormatter(lambda value,number: f'{value:,}'))
|
|
782
|
+
|
|
783
|
+
ax.xaxis.set_minor_locator(plt.NullLocator())
|
|
784
|
+
ax.tick_params(axis='x', labelleft=True)
|
|
785
|
+
fig.subplots_adjust(top=ttop if type(ttop) != type(None) else 0.98-(0.2/antal))
|
|
786
|
+
|
|
787
|
+
return fig
|
|
788
|
+
|
|
789
|
+
|
|
790
|
+
def plotshow(df,name='',ppos=-1,kind='line',colrow=2,sharey=False,top=None,
|
|
791
|
+
splitchar='__',savefig='',*args,**kwargs):
|
|
792
|
+
'''
|
|
793
|
+
|
|
794
|
+
Plots a subplot for each column in a datafra.
|
|
795
|
+
ppos determins which split by __ to use
|
|
796
|
+
kind determins which kind of matplotlib chart to use
|
|
797
|
+
|
|
798
|
+
Args:
|
|
799
|
+
df (TYPE): Dataframe .
|
|
800
|
+
name (TYPE, optional): title. Defaults to ''.
|
|
801
|
+
ppos (TYPE, optional): # of position to use if split. Defaults to -1.
|
|
802
|
+
kind (TYPE, optional): matplotlib kind . Defaults to 'line'.
|
|
803
|
+
colrow/ncol (TYPE, optional): columns per row . Defaults to 2.
|
|
804
|
+
sharey (TYPE, optional): Share y axis between plots. Defaults to True.
|
|
805
|
+
splitchar (TYPE, optional): if the name should be split . Defaults to '__'.
|
|
806
|
+
savefig (TYPE, optional): save figure. Defaults to ''.
|
|
807
|
+
xsize (TYPE, optional): x size default to 10
|
|
808
|
+
ysize (TYPE, optional): y size per row, defaults to 2
|
|
809
|
+
|
|
810
|
+
Returns:
|
|
811
|
+
a matplotlib fig.
|
|
812
|
+
|
|
813
|
+
note: ncol can be used instead of colrow to compatible with keep_plot
|
|
814
|
+
|
|
815
|
+
'''
|
|
816
|
+
|
|
817
|
+
xcolrow = kwargs.get('ncol',colrow)
|
|
818
|
+
plt.ioff()
|
|
819
|
+
if splitchar:
|
|
820
|
+
out=df.pipe(lambda df_: df_.rename(columns={v: v.split(splitchar)[ppos] for v in df_.columns}))
|
|
821
|
+
else:
|
|
822
|
+
out=df
|
|
823
|
+
number = out.shape[1]
|
|
824
|
+
row=-((-number)//xcolrow)
|
|
825
|
+
# breakpoint()
|
|
826
|
+
|
|
827
|
+
axes=out.plot(kind=kind,subplots=True,layout=(row,xcolrow),figsize = (kwargs.get('xsize',10), row*kwargs.get('ysize',2)),
|
|
828
|
+
use_index=True,title=name,sharey=sharey)
|
|
829
|
+
for ax in axes.flatten():
|
|
830
|
+
pass
|
|
831
|
+
# dec=finddec(dfatt)
|
|
832
|
+
# ax.yaxis.set_major_formatter(ticker.FuncFormatter(lambda value,number: f'{value:,}'))
|
|
833
|
+
|
|
834
|
+
ax.xaxis.set_minor_locator(plt.NullLocator())
|
|
835
|
+
ax.tick_params(axis='x', labelleft=True)
|
|
836
|
+
if out.index.dtype == 'int64':
|
|
837
|
+
fmtr = ticker.StrMethodFormatter('{x:.0f}')
|
|
838
|
+
ax.xaxis.set_major_formatter(fmtr)
|
|
839
|
+
|
|
840
|
+
fig = axes.flatten()[0].get_figure()
|
|
841
|
+
fig.set_constrained_layout(True)
|
|
842
|
+
fig.suptitle(name,fontsize=20)
|
|
843
|
+
# fig.tight_layout()
|
|
844
|
+
|
|
845
|
+
# top = (row*(2-0.1)-0.2)/(row*(2-0.1))
|
|
846
|
+
# print(top)
|
|
847
|
+
# fig.subplots_adjust(top=top if type(top) != type(None) else 0.98-(0.2/row))
|
|
848
|
+
if savefig:
|
|
849
|
+
fig.savefig(savefig)
|
|
850
|
+
# plt.subplot_tool()
|
|
851
|
+
plt.ion()
|
|
852
|
+
plt.close('all')
|
|
853
|
+
return fig
|
|
854
|
+
|
|
855
|
+
def melt(df,source='Latest'):
|
|
856
|
+
''' melts a wide dataframe to a tall dataframe , appends a soruce column '''
|
|
857
|
+
melted = pd.melt(df.reset_index().rename(columns={'index':'time'}),id_vars='time').assign(source=source)
|
|
858
|
+
return melted
|
|
859
|
+
|
|
860
|
+
def heatshow(df,name='',cmap="Reds",mul=1.,annot=False,size=(11.69,8.27),dec=0,cbar=True,linewidths=.5):
|
|
861
|
+
''' A heatmap of a dataframe problems in 3.12'''
|
|
862
|
+
xx=(df.astype('float'))*mul
|
|
863
|
+
# fig, ax = plt.subplots(figsize=(11,8))
|
|
864
|
+
fig, ax = plt.subplots(figsize=size) #A4
|
|
865
|
+
sns.heatmap(xx,cmap=cmap,ax=ax,fmt="."+str(dec)+"f",annot=annot,annot_kws ={"ha": 'center'},linewidths=linewidths,cbar=cbar)
|
|
866
|
+
ax.set_title(name, fontsize=20)
|
|
867
|
+
|
|
868
|
+
ax.set_yticklabels(ax.yaxis.get_majorticklabels(), ha = 'left',rotation=0)
|
|
869
|
+
yax = ax.get_yaxis()
|
|
870
|
+
|
|
871
|
+
yticklabels = ax.get_yticklabels()
|
|
872
|
+
widths = [label.get_window_extent().width for label in yticklabels]
|
|
873
|
+
pad = max(e for e in widths)
|
|
874
|
+
|
|
875
|
+
yax.set_tick_params(pad=pad)
|
|
876
|
+
ax.set_xticklabels(ax.xaxis.get_majorticklabels(), va = 'top' ,rotation=70.)
|
|
877
|
+
fig.subplots_adjust(bottom=0.15)
|
|
878
|
+
#ax.tick_paraOms(axis='y',direction='out', length=3, width=2, colors='b',labelleft=True)
|
|
879
|
+
plt.close('all')
|
|
880
|
+
return fig
|
|
881
|
+
|
|
882
|
+
|
|
883
|
+
##%%
|
|
884
|
+
def attshow(df,treshold=False,head=5000,tail=0,t=True,annot=False,showsum=False,sort=True,size=(11.69,8.27),title='',
|
|
885
|
+
tshow=True,dec=0,cbar=True,cmap='jet',savefig=''):
|
|
886
|
+
'''Shows heatmap of impacts of exogeneous variables
|
|
887
|
+
:df: Dataframe with impact
|
|
888
|
+
:treshold: Take exogeneous variables with max impact of treshold or larger
|
|
889
|
+
:numhigh: take the numhigh largest impacts
|
|
890
|
+
:t: transpose the heatmap
|
|
891
|
+
:annot: Annotate the heatmap
|
|
892
|
+
:head: take the head largest
|
|
893
|
+
.tail: take the tail smalest
|
|
894
|
+
:showsum: Add a column with the sum
|
|
895
|
+
:sort: Sort the data
|
|
896
|
+
.tshow: Show a longer title
|
|
897
|
+
:cbar: if a colorbar shoud be displayes
|
|
898
|
+
:cmap: the colormap
|
|
899
|
+
:save: Save the chart (in png format) '''
|
|
900
|
+
|
|
901
|
+
|
|
902
|
+
selectmin = df.min().sort_values(ascending=False).tail(tail).index.tolist()
|
|
903
|
+
selectmax = df.max().sort_values(ascending=False).head(head).index.tolist()
|
|
904
|
+
select=selectmax+selectmin
|
|
905
|
+
yy = df[select].pipe(
|
|
906
|
+
lambda df_ : df_[select] if sort else df_[sorted(list(df_.columns))])
|
|
907
|
+
if showsum:
|
|
908
|
+
asum= yy.sum(axis=1)
|
|
909
|
+
asum.name = '_Sum'
|
|
910
|
+
yy = pd.concat([yy,asum],axis=1)
|
|
911
|
+
|
|
912
|
+
yy2 = yy.T if t else yy
|
|
913
|
+
if sort and tshow:
|
|
914
|
+
txt= ' Impact from exogeneous variables. '+(
|
|
915
|
+
str(head)+' highest. ' if head >= 1 else '')+( str(tail)+' smallest. ' if tail >= 1 else '')
|
|
916
|
+
else:
|
|
917
|
+
txt= ''
|
|
918
|
+
f=heatshow(yy2,cmap=cmap,name=title+txt ,annot=annot,mul=1.,size=size,dec=dec,cbar=cbar)
|
|
919
|
+
f.subplots_adjust(bottom=0.16)
|
|
920
|
+
if savefig:
|
|
921
|
+
f.savefig(savefig)
|
|
922
|
+
return yy2
|
|
923
|
+
|
|
924
|
+
|
|
925
|
+
|
|
926
|
+
def attshowone(df,name,pre='',head=5,tail=5):
|
|
927
|
+
''' shows the contribution to row=name from each column
|
|
928
|
+
the coulumns can optional be selected as starting with pre'''
|
|
929
|
+
|
|
930
|
+
res = df.loc[name,[n for n in df.columns if n.startswith(pre)]].sort_values(ascending=False).pipe(
|
|
931
|
+
lambda df_: df_.head(head).append(df_.tail(tail)))
|
|
932
|
+
ax = res.plot(kind='bar')
|
|
933
|
+
txt= ( str(head)+' highest. ' if head >= 1 else '')+( str(tail)+' smallest. ' if tail >= 1 else '')
|
|
934
|
+
ax.set_title('Contributions to '+name+'. '+txt)
|
|
935
|
+
return ax
|
|
936
|
+
|
|
937
|
+
|
|
938
|
+
def water(serxinput,sort=False,ascending =True,autosum=False,allsort=False,threshold=0.0):
|
|
939
|
+
''' Creates a dataframe with information for a watrfall diagram
|
|
940
|
+
|
|
941
|
+
:serx: the input serie of values
|
|
942
|
+
:sort: True if the bars except the first and last should be sorted (default = False)
|
|
943
|
+
:allsort: True if all bars should be sorted (default = False)
|
|
944
|
+
:autosum: True if a Total bar are added in the end
|
|
945
|
+
:ascending: True if sortorder = ascending
|
|
946
|
+
|
|
947
|
+
Returns a dataframe with theese columns:
|
|
948
|
+
|
|
949
|
+
:hbegin: Height of the first bar
|
|
950
|
+
:hend: Height of the last bar
|
|
951
|
+
:hpos: Height of positive bars
|
|
952
|
+
:hneg: Height of negative bars
|
|
953
|
+
:start: Ofset at which each bar starts
|
|
954
|
+
:height: Height of each bar (just for information)
|
|
955
|
+
'''
|
|
956
|
+
# get the height of first and last column
|
|
957
|
+
|
|
958
|
+
|
|
959
|
+
total=serxinput.sum()
|
|
960
|
+
serx = cutout(serxinput,threshold)
|
|
961
|
+
if sort or allsort : # sort rows except the first and last
|
|
962
|
+
endslice = None if allsort else -1
|
|
963
|
+
startslice = None if allsort else 1
|
|
964
|
+
i = serx[startslice:endslice].sort_values(ascending =ascending ).index
|
|
965
|
+
if allsort:
|
|
966
|
+
newi =i.tolist()
|
|
967
|
+
else:
|
|
968
|
+
newi =[serx.index.tolist()[0]] + i.tolist() + [serx.index.tolist()[-1]]# Get the head and tail
|
|
969
|
+
ser=serx[newi]
|
|
970
|
+
else:
|
|
971
|
+
ser=serx.copy()
|
|
972
|
+
|
|
973
|
+
if autosum:
|
|
974
|
+
ser['Total'] = total
|
|
975
|
+
|
|
976
|
+
ser = ser.astype('float')
|
|
977
|
+
|
|
978
|
+
hbegin = ser.copy()
|
|
979
|
+
hbegin[1:]=0.0
|
|
980
|
+
hend = ser.copy()
|
|
981
|
+
hend[:-1] = 0.0
|
|
982
|
+
|
|
983
|
+
height = ser
|
|
984
|
+
start = ser.cumsum().shift().fillna(0.0) # the starting point for each bar
|
|
985
|
+
start.iloc[-1] = start.iloc[-1] if allsort and not autosum else 0 # the last bar should start at 0
|
|
986
|
+
end = start + ser
|
|
987
|
+
|
|
988
|
+
hpos= height*(height>=0.0)
|
|
989
|
+
hneg= height*(height<=0.0)
|
|
990
|
+
dfatt = pd.DataFrame({'start':start,'hbegin':hbegin,'hpos':hpos,'hneg':hneg,'hend':hend,'height':height}).loc[ser.index,:]
|
|
991
|
+
|
|
992
|
+
return dfatt
|
|
993
|
+
|
|
994
|
+
def waterplot(basis,sort=True,ascending =True,autosum=False,bartype='bar',threshold=0.0,
|
|
995
|
+
allsort=False,title=f'Attribution ',top=0.9, desdic = {},zero=True, ysize=5,**kwarg):
|
|
996
|
+
att = [(name,water(ser,sort=sort,autosum=autosum,allsort=allsort,threshold=threshold))
|
|
997
|
+
for name,ser in basis.transpose().iterrows()]
|
|
998
|
+
# print(att[0][1])
|
|
999
|
+
fig, axis = plt.subplots(nrows=len(att),ncols=1,figsize=(10,ysize*len(att)),constrained_layout=True)
|
|
1000
|
+
width = 0.5 # the width of the barsser
|
|
1001
|
+
laxis = axis if isinstance(axis,numpy.ndarray) else [axis]
|
|
1002
|
+
for i,((name,dfatt),ax) in enumerate(zip(att,laxis)):
|
|
1003
|
+
_ = dfatt.hpos.plot(ax=ax,kind=bartype,bottom=dfatt.start,stacked=True,
|
|
1004
|
+
color='green',width=width)
|
|
1005
|
+
_ = dfatt.hneg.plot(ax=ax,kind=bartype,bottom=dfatt.start,stacked=True,
|
|
1006
|
+
color='red',width=width)
|
|
1007
|
+
_ = None if allsort else dfatt.hbegin.plot(ax=ax,kind=bartype,bottom=dfatt.start,
|
|
1008
|
+
stacked=True,color=('green' if dfatt.hbegin.iloc[0] > 0 else 'red') if zero else 'blue',width=width)
|
|
1009
|
+
_ = None if allsort and not autosum else dfatt.hend.plot(ax=ax,kind=bartype,bottom=dfatt.start,stacked=True,color='blue',width=width)
|
|
1010
|
+
ax.set_ylabel(name,fontsize='x-large')
|
|
1011
|
+
dec=finddec(dfatt)
|
|
1012
|
+
ax.yaxis.set_major_formatter(ticker.FuncFormatter(lambda value,number: f'{value:,.{dec}f}'))
|
|
1013
|
+
|
|
1014
|
+
ax.set_title(desdic.get(name,name))
|
|
1015
|
+
ax.set_xticklabels(dfatt.index.tolist(), rotation = 70,fontsize='x-large')
|
|
1016
|
+
# plt.xticks(rotation=45, horizontalalignment='right',
|
|
1017
|
+
# fontweight='light', fontsize='x-large' )
|
|
1018
|
+
fig.suptitle(title,fontsize=20)
|
|
1019
|
+
if 1:
|
|
1020
|
+
...
|
|
1021
|
+
# plt.tight_layout()
|
|
1022
|
+
# fig.subplots_adjust(top=top)
|
|
1023
|
+
|
|
1024
|
+
# plt.show()
|
|
1025
|
+
plt.close('all')
|
|
1026
|
+
|
|
1027
|
+
|
|
1028
|
+
return fig
|
|
1029
|
+
|
|
1030
|
+
vis.plot.__doc__ = plotshow.__doc__
|
|
1031
|
+
|
|
1032
|
+
|
|
1033
|
+
if __name__ == '__main__' and 1:
|
|
1034
|
+
basis = pd.DataFrame([[100,100.],[-10.0,-12], [12,-10],[-10,10]],index=['nii','cost','credit','fee'],columns=['ex','ex2'])
|
|
1035
|
+
basis.loc['total'] = basis.sum()
|
|
1036
|
+
waterplot(basis)
|
|
1037
|
+
|
|
1038
|
+
|