tesorotools-python 0.0.18__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.
- tesorotools/__init__.py +6 -0
- tesorotools/artists/__init__.py +5 -0
- tesorotools/artists/barh_plot.py +310 -0
- tesorotools/artists/line_plot.py +245 -0
- tesorotools/artists/table.py +200 -0
- tesorotools/artists/type_curve.py +218 -0
- tesorotools/assets/README.md +5 -0
- tesorotools/assets/fonts/CabinetGrotesk-Black.otf +0 -0
- tesorotools/assets/fonts/CabinetGrotesk-Bold.otf +0 -0
- tesorotools/assets/fonts/CabinetGrotesk-Extrabold.otf +0 -0
- tesorotools/assets/fonts/CabinetGrotesk-Extralight.otf +0 -0
- tesorotools/assets/fonts/CabinetGrotesk-Light.otf +0 -0
- tesorotools/assets/fonts/CabinetGrotesk-Medium.otf +0 -0
- tesorotools/assets/fonts/CabinetGrotesk-Regular.otf +0 -0
- tesorotools/assets/fonts/CabinetGrotesk-Thin.otf +0 -0
- tesorotools/assets/fonts/README.md +1 -0
- tesorotools/assets/plots.yaml +43 -0
- tesorotools/assets/tesoro.mplstyle +21 -0
- tesorotools/convert.py +99 -0
- tesorotools/data_sources/README.md +14 -0
- tesorotools/data_sources/__init__.py +0 -0
- tesorotools/data_sources/debug.py +26 -0
- tesorotools/data_sources/lseg.py +117 -0
- tesorotools/database/__init__.py +0 -0
- tesorotools/database/push.py +70 -0
- tesorotools/dependencies/__init__.py +0 -0
- tesorotools/dependencies/functions.py +11 -0
- tesorotools/dependencies/node.py +34 -0
- tesorotools/dependencies/resolution.py +118 -0
- tesorotools/main.py +37 -0
- tesorotools/offsets/__init__.py +0 -0
- tesorotools/offsets/offsets.py +439 -0
- tesorotools/offsets/outliers.py +15 -0
- tesorotools/render/__init__.py +17 -0
- tesorotools/render/content/__init__.py +0 -0
- tesorotools/render/content/content.py +17 -0
- tesorotools/render/content/images.py +147 -0
- tesorotools/render/content/section.py +53 -0
- tesorotools/render/content/subtitle.py +53 -0
- tesorotools/render/content/table.py +308 -0
- tesorotools/render/content/text.py +23 -0
- tesorotools/render/content/title.py +40 -0
- tesorotools/render/report.py +31 -0
- tesorotools/utils/__init__.py +0 -0
- tesorotools/utils/config.py +35 -0
- tesorotools/utils/globals.py +14 -0
- tesorotools/utils/matplotlib.py +38 -0
- tesorotools/utils/series.py +40 -0
- tesorotools/utils/shortcuts.py +32 -0
- tesorotools/utils/template.py +126 -0
- tesorotools_python-0.0.18.dist-info/METADATA +16 -0
- tesorotools_python-0.0.18.dist-info/RECORD +53 -0
- tesorotools_python-0.0.18.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
from math import floor
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
from tesorotools.dependencies.resolution import collect_series
|
|
8
|
+
from tesorotools.offsets.outliers import flag_outliers
|
|
9
|
+
from tesorotools.utils.globals import DEBUG
|
|
10
|
+
from tesorotools.utils.matplotlib import format_annotation, is_zero
|
|
11
|
+
|
|
12
|
+
# this file is by far the worst and most spaghettified, must be rewritten
|
|
13
|
+
|
|
14
|
+
# to global config
|
|
15
|
+
GOOD: str = "00c800"
|
|
16
|
+
BAD: str = "c80000"
|
|
17
|
+
THRESHOLD: float = 1
|
|
18
|
+
SHADE_LEVELS = 2
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _shade_intensity(
|
|
22
|
+
ratio: float, shade_levels: int = 2, continuous: bool = False
|
|
23
|
+
) -> str:
|
|
24
|
+
# intensity may vary from 150 (highest) to 255 (lowest), a grand difference of 105
|
|
25
|
+
# there are SHADE_LEVELS levels, so increments will be of 105/SHADE_LEVELS
|
|
26
|
+
corrected_ratio: float = min(ratio, shade_levels)
|
|
27
|
+
corrected_ratio: float = (
|
|
28
|
+
floor(corrected_ratio) if not continuous else corrected_ratio
|
|
29
|
+
)
|
|
30
|
+
increment: float = (corrected_ratio - 1) * (105 / shade_levels)
|
|
31
|
+
intensity: float = 255 - increment
|
|
32
|
+
intensity_hex: str = f"{int(intensity):x}"
|
|
33
|
+
return intensity_hex
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _generate_column(
|
|
37
|
+
column_data: pd.Series,
|
|
38
|
+
column_cfg: dict[str, Any],
|
|
39
|
+
outliers_flags: pd.Series | None = None,
|
|
40
|
+
):
|
|
41
|
+
# TODO: factor out
|
|
42
|
+
# data
|
|
43
|
+
if column_cfg["show_units_in_title"]:
|
|
44
|
+
column_data.name = f"{column_cfg["name"]} ({column_cfg["unit"]})"
|
|
45
|
+
else:
|
|
46
|
+
column_data.name = column_cfg["name"]
|
|
47
|
+
column_cfg["formatted_name"] = column_data.name
|
|
48
|
+
|
|
49
|
+
unit = (
|
|
50
|
+
column_cfg["unit"]
|
|
51
|
+
if column_cfg["show_units_in_cell"] and column_cfg["unit"] is not None
|
|
52
|
+
else ""
|
|
53
|
+
)
|
|
54
|
+
scaled_data: pd.Series = column_data * column_cfg["scale"]
|
|
55
|
+
formatted_data = scaled_data.apply(
|
|
56
|
+
lambda x: format_annotation(
|
|
57
|
+
x, decimals=column_cfg["decimals"], units=unit
|
|
58
|
+
)
|
|
59
|
+
)
|
|
60
|
+
zeros: pd.Series = scaled_data.apply(
|
|
61
|
+
lambda x: is_zero(x, decimals=column_cfg["decimals"])
|
|
62
|
+
)
|
|
63
|
+
positives: pd.Series = scaled_data > 0
|
|
64
|
+
negatives: pd.Series = scaled_data < 0
|
|
65
|
+
|
|
66
|
+
# colors
|
|
67
|
+
colors_cfg: bool = column_cfg["colors"]
|
|
68
|
+
color_data: pd.Series = pd.Series(
|
|
69
|
+
index=formatted_data.index, name=column_data.name, dtype=str
|
|
70
|
+
)
|
|
71
|
+
if colors_cfg:
|
|
72
|
+
positive_good: bool = column_cfg["positive_good"]
|
|
73
|
+
color_data[positives] = GOOD if positive_good else BAD
|
|
74
|
+
color_data.loc[negatives] = BAD if positive_good else GOOD
|
|
75
|
+
color_data.loc[zeros.values] = pd.NA
|
|
76
|
+
|
|
77
|
+
# shades
|
|
78
|
+
shade_data: pd.Series = pd.Series(
|
|
79
|
+
index=formatted_data.index, name=column_data.name, dtype=str
|
|
80
|
+
)
|
|
81
|
+
if outliers_flags is not None:
|
|
82
|
+
thresholds: pd.Series = abs(outliers_flags / THRESHOLD)
|
|
83
|
+
intensities: pd.Series = thresholds.apply(
|
|
84
|
+
lambda x: _shade_intensity(x, SHADE_LEVELS)
|
|
85
|
+
)
|
|
86
|
+
shade_data[(thresholds >= 1) & (outliers_flags > 0)] = intensities[
|
|
87
|
+
(thresholds >= 1) & (outliers_flags > 0)
|
|
88
|
+
].apply(lambda x: f"00{x}00" if positive_good else f"{x}0000")
|
|
89
|
+
shade_data[(thresholds >= 1) & (outliers_flags < 0)] = intensities[
|
|
90
|
+
(thresholds >= 1) & (outliers_flags < 0)
|
|
91
|
+
].apply(lambda x: f"{x}0000" if positive_good else f"00{x}00")
|
|
92
|
+
|
|
93
|
+
return formatted_data, color_data, shade_data
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _generate_block(block_data: pd.DataFrame, block_cfg: dict[str, Any]):
|
|
97
|
+
columns: dict[str, Any] = block_cfg["columns"]
|
|
98
|
+
formatted_columns: list[pd.Series] = []
|
|
99
|
+
color_columns: list[pd.Series] = []
|
|
100
|
+
shade_columns: list[pd.Series] = []
|
|
101
|
+
sort_idx: pd.Index | None = None
|
|
102
|
+
for column_name, column_cfg in columns.items():
|
|
103
|
+
last_date: pd.Timestamp = block_data.index.get_level_values(
|
|
104
|
+
level=0
|
|
105
|
+
).max()
|
|
106
|
+
block_data = block_data.rename(columns=block_cfg["series"])
|
|
107
|
+
offset: str = column_cfg["offset"]
|
|
108
|
+
difference: str = column_cfg["difference"]
|
|
109
|
+
stat: str = column_cfg["stat"]
|
|
110
|
+
outliers: bool = column_cfg["outliers"]
|
|
111
|
+
|
|
112
|
+
stat_data = block_data.loc[
|
|
113
|
+
(last_date, offset, difference, slice(None)), :
|
|
114
|
+
]
|
|
115
|
+
stat_data.index = stat_data.index.get_level_values(level=-1)
|
|
116
|
+
|
|
117
|
+
outliers_flags: pd.Series | None = None
|
|
118
|
+
if outliers:
|
|
119
|
+
outliers_flags = flag_outliers(stat_data.T)
|
|
120
|
+
|
|
121
|
+
column_data: pd.Series = stat_data.loc[stat, :]
|
|
122
|
+
# sort capability
|
|
123
|
+
sort: str = block_cfg.get("sort", None)
|
|
124
|
+
if sort is not None and column_name == sort:
|
|
125
|
+
column_data: pd.Series = column_data.sort_values(ascending=False)
|
|
126
|
+
sort_idx = column_data.index
|
|
127
|
+
formatted_column, color_column, shade_column = _generate_column(
|
|
128
|
+
column_data, column_cfg, outliers_flags
|
|
129
|
+
)
|
|
130
|
+
formatted_columns.append(formatted_column)
|
|
131
|
+
color_columns.append(color_column)
|
|
132
|
+
shade_columns.append(shade_column)
|
|
133
|
+
|
|
134
|
+
if sort_idx is not None:
|
|
135
|
+
formatted_columns = [s.reindex(sort_idx) for s in formatted_columns]
|
|
136
|
+
color_columns = [s.reindex(sort_idx) for s in color_columns]
|
|
137
|
+
shade_columns = [s.reindex(sort_idx) for s in shade_columns]
|
|
138
|
+
|
|
139
|
+
formatted_block: pd.DataFrame = pd.concat(formatted_columns, axis=1)
|
|
140
|
+
color_block: pd.DataFrame = pd.concat(color_columns, axis=1)
|
|
141
|
+
shade_block: pd.DataFrame = pd.concat(shade_columns, axis=1)
|
|
142
|
+
|
|
143
|
+
formatted_block.columns.name = block_cfg["title"]
|
|
144
|
+
color_block.columns.name = block_cfg["title"]
|
|
145
|
+
shade_block.columns.name = block_cfg["title"]
|
|
146
|
+
return formatted_block, color_block, shade_block
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def generate_table(table_data: pd.DataFrame, table_cfg: dict[str, Any]):
|
|
150
|
+
blocks: dict[str, dict] = table_cfg["blocks"]
|
|
151
|
+
formatted_blocks: list[pd.DataFrame] = []
|
|
152
|
+
color_blocks: list[pd.DataFrame] = []
|
|
153
|
+
shade_blocks: list[pd.DataFrame] = []
|
|
154
|
+
axis = 0 if table_cfg["axis"] == "vertical" else 1
|
|
155
|
+
# sorting capabilities
|
|
156
|
+
for block_name, block_cfg in blocks.items():
|
|
157
|
+
series_dict: dict[str, str] = block_cfg["series"]
|
|
158
|
+
block_data: pd.DataFrame = table_data.loc[:, series_dict.keys()]
|
|
159
|
+
formatted_block, color_block, shade_block = _generate_block(
|
|
160
|
+
block_data, block_cfg
|
|
161
|
+
)
|
|
162
|
+
formatted_blocks.append(formatted_block)
|
|
163
|
+
color_blocks.append(color_block)
|
|
164
|
+
shade_blocks.append(shade_block)
|
|
165
|
+
formatted_table = pd.concat(
|
|
166
|
+
formatted_blocks,
|
|
167
|
+
axis=axis,
|
|
168
|
+
keys=[
|
|
169
|
+
formatted_block.columns.name for formatted_block in formatted_blocks
|
|
170
|
+
],
|
|
171
|
+
)
|
|
172
|
+
color_table = pd.concat(
|
|
173
|
+
color_blocks,
|
|
174
|
+
axis=axis,
|
|
175
|
+
keys=[
|
|
176
|
+
formatted_block.columns.name for formatted_block in formatted_blocks
|
|
177
|
+
],
|
|
178
|
+
)
|
|
179
|
+
shade_table = pd.concat(
|
|
180
|
+
shade_blocks,
|
|
181
|
+
axis=axis,
|
|
182
|
+
keys=[
|
|
183
|
+
formatted_block.columns.name for formatted_block in formatted_blocks
|
|
184
|
+
],
|
|
185
|
+
)
|
|
186
|
+
return formatted_table, color_table, shade_table
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def generate_tables_from_flash(
|
|
190
|
+
out_path: Path, flash: pd.DataFrame, config_dicts: dict[str, dict]
|
|
191
|
+
):
|
|
192
|
+
for table_name, table_cfg in config_dicts.items():
|
|
193
|
+
series: list[str] = list(collect_series(table_cfg))
|
|
194
|
+
table_data: pd.DataFrame = flash.loc[:, series]
|
|
195
|
+
formatted_table, color_table, shade_table = generate_table(
|
|
196
|
+
table_data, table_cfg
|
|
197
|
+
)
|
|
198
|
+
formatted_table.to_feather(out_path / f"{table_name}.feather")
|
|
199
|
+
color_table.to_feather(out_path / f"{table_name}_color.feather")
|
|
200
|
+
shade_table.to_feather(out_path / f"{table_name}_shade.feather")
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# pending to assure stylesheet data and fonts are only loaded once
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import matplotlib.pyplot as plt
|
|
7
|
+
import pandas as pd
|
|
8
|
+
from matplotlib.ticker import FuncFormatter
|
|
9
|
+
from pandas import Timestamp
|
|
10
|
+
|
|
11
|
+
from tesorotools.utils.config import merge
|
|
12
|
+
from tesorotools.utils.globals import DEBUG
|
|
13
|
+
from tesorotools.utils.matplotlib import (
|
|
14
|
+
PLOT_CONFIG,
|
|
15
|
+
format_annotation,
|
|
16
|
+
load_fonts,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
TYPE_CURVE_CONFIG: dict[str, Any] = PLOT_CONFIG["type_curve"]
|
|
20
|
+
AX_CONFIG: dict[str, Any] = PLOT_CONFIG["ax"]
|
|
21
|
+
FIG_CONFIG: dict[str, Any] = PLOT_CONFIG["figure"]
|
|
22
|
+
|
|
23
|
+
load_fonts()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _style_spines(
|
|
27
|
+
ax: plt.Axes,
|
|
28
|
+
decimals: int,
|
|
29
|
+
units: str,
|
|
30
|
+
*,
|
|
31
|
+
color: str,
|
|
32
|
+
linewidth: str,
|
|
33
|
+
):
|
|
34
|
+
ax.grid(visible=True, axis="y")
|
|
35
|
+
for spine in ax.spines.values():
|
|
36
|
+
spine.set_color(color)
|
|
37
|
+
spine.set_linewidth(linewidth)
|
|
38
|
+
ax.yaxis.tick_right()
|
|
39
|
+
ax.yaxis.set_major_formatter(
|
|
40
|
+
FuncFormatter(lambda y, _: format_annotation(y, decimals, units))
|
|
41
|
+
)
|
|
42
|
+
ax.tick_params(axis="both", which="major")
|
|
43
|
+
ax.set_xticks(
|
|
44
|
+
ax.get_xticks(), ax.get_xticklabels(), rotation=45, ha="right"
|
|
45
|
+
)
|
|
46
|
+
for tick in ax.get_xticklines():
|
|
47
|
+
tick.set_markeredgecolor(color)
|
|
48
|
+
for tick in ax.get_yticklines():
|
|
49
|
+
tick.set_markeredgecolor(color)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _style_baseline(ax: plt.Axes, **baseline_config):
|
|
53
|
+
color: str = baseline_config["color"]
|
|
54
|
+
bottom_lim, top_lim = ax.get_ylim()
|
|
55
|
+
ax.set_ylim(bottom=min(0, bottom_lim), top=max(0, top_lim))
|
|
56
|
+
bottom_lim, top_lim = ax.get_ylim()
|
|
57
|
+
if bottom_lim == 0:
|
|
58
|
+
ax.spines["bottom"].set_edgecolor(color)
|
|
59
|
+
elif top_lim == 0:
|
|
60
|
+
ax.spines["top"].set_edgecolor(color)
|
|
61
|
+
else:
|
|
62
|
+
ax.axhline(y=0, **baseline_config)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _format_data(data: pd.DataFrame) -> dict[str, Any]:
|
|
66
|
+
# metadata
|
|
67
|
+
date_index: pd.DatetimeIndex = data.index
|
|
68
|
+
current_date: Timestamp = date_index.max()
|
|
69
|
+
current_year: int = current_date.year
|
|
70
|
+
last_year: int = (current_date - pd.DateOffset(years=1)).year
|
|
71
|
+
|
|
72
|
+
# current data
|
|
73
|
+
current_data: pd.Series = data.loc[current_date, :]
|
|
74
|
+
current_data.name = "current_data"
|
|
75
|
+
|
|
76
|
+
# current year
|
|
77
|
+
current_year_data: pd.DataFrame = data.loc[
|
|
78
|
+
date_index.year == current_year, :
|
|
79
|
+
]
|
|
80
|
+
current_year_max: pd.Series = current_year_data.max()
|
|
81
|
+
current_year_max.name = "current_year_max"
|
|
82
|
+
current_year_min: pd.Series = current_year_data.min()
|
|
83
|
+
current_year_min.name = "current_year_min"
|
|
84
|
+
|
|
85
|
+
# last year
|
|
86
|
+
last_year_data: pd.DataFrame = data.loc[date_index.year == last_year, :]
|
|
87
|
+
last_year_max: pd.Series = last_year_data.max()
|
|
88
|
+
last_year_max.name = "last_year_max"
|
|
89
|
+
last_year_min: pd.Series = last_year_data.min()
|
|
90
|
+
last_year_min.name = "last_year_min"
|
|
91
|
+
|
|
92
|
+
formatted_data: pd.DataFrame = pd.concat(
|
|
93
|
+
[
|
|
94
|
+
last_year_max,
|
|
95
|
+
last_year_min,
|
|
96
|
+
current_year_max,
|
|
97
|
+
current_year_min,
|
|
98
|
+
current_data,
|
|
99
|
+
],
|
|
100
|
+
axis=1,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
"data": formatted_data,
|
|
105
|
+
"current_date": current_date.strftime("%d/%m/%Y"),
|
|
106
|
+
"current_year": current_year,
|
|
107
|
+
"last_year": last_year,
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _plot_current_data(
|
|
112
|
+
ax: plt.Axes,
|
|
113
|
+
data: pd.DataFrame,
|
|
114
|
+
date_fmt: str,
|
|
115
|
+
*,
|
|
116
|
+
linewidth: float,
|
|
117
|
+
marker: str,
|
|
118
|
+
points_to_mark: list[str],
|
|
119
|
+
color: str,
|
|
120
|
+
decimals: int,
|
|
121
|
+
units: str,
|
|
122
|
+
):
|
|
123
|
+
data.plot(
|
|
124
|
+
ax=ax,
|
|
125
|
+
color=color,
|
|
126
|
+
linewidth=linewidth,
|
|
127
|
+
label=date_fmt,
|
|
128
|
+
)
|
|
129
|
+
for point in points_to_mark:
|
|
130
|
+
value = data.loc[point]
|
|
131
|
+
ax.plot(
|
|
132
|
+
point,
|
|
133
|
+
value,
|
|
134
|
+
marker=marker,
|
|
135
|
+
color=color,
|
|
136
|
+
)
|
|
137
|
+
ax.annotate(
|
|
138
|
+
format_annotation(value, decimals, units),
|
|
139
|
+
(point, value),
|
|
140
|
+
textcoords="offset points",
|
|
141
|
+
xytext=(0, 10),
|
|
142
|
+
ha="center",
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def plot_type_curve(
|
|
147
|
+
data: pd.DataFrame,
|
|
148
|
+
out_file: Path,
|
|
149
|
+
**config,
|
|
150
|
+
):
|
|
151
|
+
config: dict[str, Any] = merge(config, TYPE_CURVE_CONFIG)
|
|
152
|
+
|
|
153
|
+
if config["yaxis"]["units"] == "p.b.":
|
|
154
|
+
data = data * 100
|
|
155
|
+
|
|
156
|
+
formatted_assets: dict[str, Any] = _format_data(data)
|
|
157
|
+
formatted_data: pd.DataFrame = formatted_assets["data"]
|
|
158
|
+
due_index: pd.Index = formatted_data.index
|
|
159
|
+
|
|
160
|
+
fig = plt.figure(**FIG_CONFIG)
|
|
161
|
+
ax: plt.Axes = fig.add_subplot()
|
|
162
|
+
|
|
163
|
+
last_config: dict[str, Any] = config["last"]
|
|
164
|
+
ax.fill_between(
|
|
165
|
+
due_index,
|
|
166
|
+
formatted_data["last_year_min"],
|
|
167
|
+
formatted_data["last_year_max"],
|
|
168
|
+
alpha=last_config["alpha"],
|
|
169
|
+
color=last_config["color"],
|
|
170
|
+
edgecolor=None,
|
|
171
|
+
label=f"Rango {formatted_assets['last_year']}",
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
current_config: dict[str, Any] = config["current"]
|
|
175
|
+
ax.fill_between(
|
|
176
|
+
due_index,
|
|
177
|
+
formatted_data["current_year_min"],
|
|
178
|
+
formatted_data["current_year_max"],
|
|
179
|
+
alpha=current_config["alpha"],
|
|
180
|
+
color=current_config["color"],
|
|
181
|
+
edgecolor=None,
|
|
182
|
+
label=f"Rango {formatted_assets['current_year']}",
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
_plot_current_data(
|
|
186
|
+
ax,
|
|
187
|
+
formatted_data["current_data"],
|
|
188
|
+
formatted_assets["current_date"],
|
|
189
|
+
**config["line"],
|
|
190
|
+
)
|
|
191
|
+
_style_spines(ax, **config["yaxis"], **AX_CONFIG["spines"])
|
|
192
|
+
_style_baseline(ax, **AX_CONFIG["baseline"])
|
|
193
|
+
ax.legend(
|
|
194
|
+
loc="upper center",
|
|
195
|
+
bbox_to_anchor=(0.5, config["legend_sep"]),
|
|
196
|
+
ncol=3,
|
|
197
|
+
)
|
|
198
|
+
fig.savefig(out_file)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
# data is expected to be a simple time series data, columns are series and rows represents dates
|
|
202
|
+
def plot_type_curves(
|
|
203
|
+
out_path: Path, data: pd.DataFrame, config_dicts: dict[str, Any]
|
|
204
|
+
):
|
|
205
|
+
for name, config in config_dicts.items():
|
|
206
|
+
if not name.startswith("."): # aux entries
|
|
207
|
+
series: dict[str, str] = config["series"]
|
|
208
|
+
if len(series) < 2:
|
|
209
|
+
raise ValueError(
|
|
210
|
+
f"In plot {name}: A type curve must have at least two due periods. Given periods: {series.keys()}"
|
|
211
|
+
)
|
|
212
|
+
trimmed_data: pd.DataFrame = data.loc[:, series.keys()]
|
|
213
|
+
trimmed_data: pd.DataFrame = trimmed_data.rename(columns=series)
|
|
214
|
+
plot_type_curve(
|
|
215
|
+
data=trimmed_data,
|
|
216
|
+
out_file=out_path / f"{name}.png",
|
|
217
|
+
**config,
|
|
218
|
+
)
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
En esta carpeta se guardarán todos los *assets* del proyecto, como
|
|
2
|
+
|
|
3
|
+
- [fonts](fonts/): Fuentes (archivos *.otf).
|
|
4
|
+
- [plots.yaml](plots.yaml): Archivo de configuración general para los gráficos.
|
|
5
|
+
- [tesoro.mplstyle](tesoro.mplstyle): Archivo de estilos *matplotlib* para los gráficos.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Archivos *.otf de fuentes. No los incluimos en el control de versiones.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# general plot config file
|
|
2
|
+
|
|
3
|
+
style:
|
|
4
|
+
font: Cabinet Grotesk
|
|
5
|
+
|
|
6
|
+
figure:
|
|
7
|
+
dpi: 500
|
|
8
|
+
layout: constrained
|
|
9
|
+
|
|
10
|
+
ax:
|
|
11
|
+
spines:
|
|
12
|
+
color: gray
|
|
13
|
+
linewidth: 1
|
|
14
|
+
baseline:
|
|
15
|
+
color: "#d9e1fc"
|
|
16
|
+
linestyle: solid
|
|
17
|
+
linewidth: 1
|
|
18
|
+
zorder: 1
|
|
19
|
+
|
|
20
|
+
type_curve:
|
|
21
|
+
last:
|
|
22
|
+
alpha: 0.2
|
|
23
|
+
color: C1
|
|
24
|
+
current:
|
|
25
|
+
alpha: 0.3
|
|
26
|
+
color: C0
|
|
27
|
+
line:
|
|
28
|
+
linewidth: 2
|
|
29
|
+
marker: D
|
|
30
|
+
color: C0
|
|
31
|
+
legend_sep: -0.1
|
|
32
|
+
|
|
33
|
+
barh:
|
|
34
|
+
highlight_factor: 0.4
|
|
35
|
+
padding: 5
|
|
36
|
+
|
|
37
|
+
line:
|
|
38
|
+
legend_sep: -0.125
|
|
39
|
+
ncol: 5
|
|
40
|
+
|
|
41
|
+
table:
|
|
42
|
+
style: Light Shading Accent 1
|
|
43
|
+
autofit: False
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# fonts
|
|
2
|
+
font.size: 12.0
|
|
3
|
+
xtick.labelsize: 12.0
|
|
4
|
+
ytick.labelsize: 12.0
|
|
5
|
+
legend.fontsize: 12.0
|
|
6
|
+
|
|
7
|
+
# modified dashed_pattern
|
|
8
|
+
lines.dashed_pattern: 10, 5
|
|
9
|
+
|
|
10
|
+
# markers
|
|
11
|
+
lines.markersize: 8
|
|
12
|
+
|
|
13
|
+
# grids
|
|
14
|
+
grid.color: d9e1fc
|
|
15
|
+
grid.linestyle: dashed
|
|
16
|
+
grid.linewidth: 0.5
|
|
17
|
+
grid.alpha: 0.5
|
|
18
|
+
|
|
19
|
+
# color palette
|
|
20
|
+
axes.prop_cycle: cycler('color', ['001e93', 'ffbd4c', '6d6d6d', '000000', 'e6821e', '2957ff', 'fba737', '5da6ff', '00ebb2', '6d0000', '009200', 'df2871', 'd9e1fc', 'ffddab'])
|
|
21
|
+
|
tesorotools/convert.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# this file may migrate to the utils package
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
from tesorotools.artists.barh_plot import plot_barh_charts_from_flash
|
|
8
|
+
from tesorotools.artists.line_plot import plot_line_charts
|
|
9
|
+
from tesorotools.artists.table import generate_tables_from_flash
|
|
10
|
+
from tesorotools.artists.type_curve import plot_type_curves
|
|
11
|
+
from tesorotools.dependencies.resolution import (
|
|
12
|
+
compute_derivate_series,
|
|
13
|
+
concat_derivate_series,
|
|
14
|
+
resolve_series,
|
|
15
|
+
)
|
|
16
|
+
from tesorotools.offsets.offsets import trim
|
|
17
|
+
from tesorotools.utils.config import read_config
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def index_replace(old: str, new: str, index: pd.MultiIndex) -> pd.MultiIndex:
|
|
21
|
+
return pd.MultiIndex.from_tuples(
|
|
22
|
+
[
|
|
23
|
+
tuple(
|
|
24
|
+
[
|
|
25
|
+
x.replace(old, new) if isinstance(x, str) else x
|
|
26
|
+
for x in tuple_index
|
|
27
|
+
]
|
|
28
|
+
)
|
|
29
|
+
for tuple_index in index
|
|
30
|
+
]
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def cheap_convert(old_file: Path) -> pd.DataFrame:
|
|
35
|
+
old = pd.read_feather(old_file)
|
|
36
|
+
|
|
37
|
+
trimmed = old.loc[(slice(None), "no", "absolute", "value"), :].copy()
|
|
38
|
+
|
|
39
|
+
old = old.loc[
|
|
40
|
+
(
|
|
41
|
+
slice(None),
|
|
42
|
+
["day", "mtd", "week", "year", "tariff_crisis"],
|
|
43
|
+
slice(None),
|
|
44
|
+
slice(None),
|
|
45
|
+
),
|
|
46
|
+
:,
|
|
47
|
+
]
|
|
48
|
+
old.index = index_replace("day", "bday", old.index)
|
|
49
|
+
old.index = index_replace("week", "ftd", old.index)
|
|
50
|
+
old.index = index_replace("year", "ytd", old.index)
|
|
51
|
+
old.index = index_replace("roll_var", "roll_std", old.index)
|
|
52
|
+
old.index = index_replace("tariff_crisis", "2025-04-02", old.index)
|
|
53
|
+
|
|
54
|
+
trimmed.index = index_replace("absolute", "no", trimmed.index)
|
|
55
|
+
|
|
56
|
+
new = pd.concat([old, trimmed])
|
|
57
|
+
return new
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
if __name__ == "__main__":
|
|
61
|
+
preprocess = True
|
|
62
|
+
barh_config_dicts = read_config(Path("examples") / "barh_plots.yaml")
|
|
63
|
+
line_config_dicts = read_config(Path("examples") / "line_plots.yaml")
|
|
64
|
+
type_config_dicts = read_config(Path("examples") / "type_curves.yaml")
|
|
65
|
+
table_config_dicts = read_config(Path("examples") / "tables.yaml")
|
|
66
|
+
|
|
67
|
+
if preprocess:
|
|
68
|
+
old_file: Path = Path("debug") / "flash.feather"
|
|
69
|
+
dependencies_cfg = read_config(Path("examples") / "dependencies.yaml")
|
|
70
|
+
flash: pd.DataFrame = cheap_convert(old_file)
|
|
71
|
+
resolved_dict = resolve_series(
|
|
72
|
+
[
|
|
73
|
+
barh_config_dicts,
|
|
74
|
+
line_config_dicts,
|
|
75
|
+
type_config_dicts,
|
|
76
|
+
table_config_dicts,
|
|
77
|
+
],
|
|
78
|
+
dependencies_cfg,
|
|
79
|
+
)
|
|
80
|
+
independent_full_df = flash.loc[:, list(resolved_dict["independent"])]
|
|
81
|
+
independent_trimmed_df = trim(independent_full_df)
|
|
82
|
+
dependent_trimmed_df = compute_derivate_series(
|
|
83
|
+
resolved_dict["dependent"], independent_trimmed_df
|
|
84
|
+
)
|
|
85
|
+
offsets_config = read_config(Path("examples") / "offsets.yaml")
|
|
86
|
+
full_df = concat_derivate_series(
|
|
87
|
+
independent_full_df,
|
|
88
|
+
dependent_trimmed_df,
|
|
89
|
+
offsets_config,
|
|
90
|
+
# force_trim=True,
|
|
91
|
+
)
|
|
92
|
+
full_df.to_feather("derivates.feather")
|
|
93
|
+
|
|
94
|
+
full_df = pd.read_feather("derivates.feather")
|
|
95
|
+
trimmed_df = trim(full_df)
|
|
96
|
+
plot_barh_charts_from_flash(full_df, barh_config_dicts)
|
|
97
|
+
plot_line_charts(trimmed_df, line_config_dicts)
|
|
98
|
+
plot_type_curves(trimmed_df, type_config_dicts)
|
|
99
|
+
generate_tables_from_flash(full_df, table_config_dicts)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# LSEG Data Library for Python
|
|
2
|
+
|
|
3
|
+
## Capa de acceso
|
|
4
|
+
|
|
5
|
+
## Capa de contenido
|
|
6
|
+
|
|
7
|
+
## Capa de reparto
|
|
8
|
+
|
|
9
|
+
## Capa de sesión
|
|
10
|
+
|
|
11
|
+
## Referencias
|
|
12
|
+
|
|
13
|
+
- https://cdn.refinitiv.com/public/lseg-lib-python-doc/2.0.0.2/book/en/index.html
|
|
14
|
+
- https://pypi.org/project/lseg-data/
|
|
File without changes
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
|
|
4
|
+
from tesorotools.utils.config import read_config
|
|
5
|
+
from tesorotools.utils.globals import DEBUG, EXAMPLES
|
|
6
|
+
|
|
7
|
+
# just mocking
|
|
8
|
+
CATALOG: dict[str, str] = read_config(EXAMPLES / "data.yaml")["debug"]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_series(start: str, end: str, series: list[str]) -> pd.DataFrame:
|
|
12
|
+
# series is a list of agnostic ids
|
|
13
|
+
specific_ids = []
|
|
14
|
+
for agnostic_id in series:
|
|
15
|
+
specific_id: str | None = CATALOG.get(agnostic_id, None)
|
|
16
|
+
if specific_id is None:
|
|
17
|
+
raise KeyError(f"{agnostic_id} not found in debug configuration")
|
|
18
|
+
specific_ids.append(specific_id)
|
|
19
|
+
|
|
20
|
+
index: pd.DatetimeIndex = pd.date_range(start=start, end=end).sort_values()
|
|
21
|
+
df: pd.DataFrame = pd.DataFrame(
|
|
22
|
+
data=np.random.randn(len(index), len(series)),
|
|
23
|
+
index=index,
|
|
24
|
+
columns=series,
|
|
25
|
+
)
|
|
26
|
+
return df
|