tesorotools-python 0.0.26__py3-none-any.whl → 0.0.28__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.
@@ -139,6 +139,13 @@ class StackedBarPlot:
139
139
 
140
140
  Positive and negative values are stacked separately so
141
141
  that bars extend in both directions from the baseline.
142
+
143
+ The ``plot`` method delegates to overridable hooks so
144
+ subclasses can customise individual steps without
145
+ reimplementing the full render pipeline:
146
+
147
+ * ``_prepare_data`` — slice, scale, resample
148
+ * ``_format_xticks`` — tick positions, labels, rotation
142
149
  """
143
150
 
144
151
  def __init__(
@@ -156,6 +163,8 @@ class StackedBarPlot:
156
163
  figsize: tuple[float, float] | None = None,
157
164
  overlay_series: dict[str, str] | None = None,
158
165
  plot_size: tuple[float, float] | None = None,
166
+ bar_width: float = 0.7,
167
+ x_rotation: float = 0,
159
168
  ) -> None:
160
169
  if out_path.suffix != ".png":
161
170
  raise ValueError(f"out_path must be .png: {out_path}")
@@ -171,6 +180,8 @@ class StackedBarPlot:
171
180
  self.plot_size = plot_size
172
181
  self.figsize = figsize
173
182
  self.overlay_series = overlay_series or {}
183
+ self.bar_width = bar_width
184
+ self.x_rotation = x_rotation
174
185
 
175
186
  @classmethod
176
187
  def from_yaml(cls, loader: TemplateLoader, node: MappingNode) -> Self:
@@ -182,7 +193,13 @@ class StackedBarPlot:
182
193
  cfg["data"] = pd.read_feather(cfg.pop("data_path"))
183
194
  return cls(**cfg)
184
195
 
185
- def plot(self) -> Axes:
196
+ # -- Overridable hooks -----------------------------------
197
+
198
+ def _prepare_data(self) -> pd.DataFrame:
199
+ """Slice, scale, and return plot-ready DataFrame.
200
+
201
+ Override to resample, change the index type, etc.
202
+ """
186
203
  start = (
187
204
  pd.Timestamp(self.start_date)
188
205
  if self.start_date
@@ -193,10 +210,35 @@ class StackedBarPlot:
193
210
  if self.end_date
194
211
  else self.data.index.max()
195
212
  )
196
-
197
213
  all_cols = list(self.series.keys()) + list(self.overlay_series.keys())
198
214
  plot_data = self.data.loc[start:end, all_cols].dropna()
199
- plot_data = plot_data * self.scale
215
+ return plot_data * self.scale
216
+
217
+ def _format_xticks(
218
+ self,
219
+ ax: Axes,
220
+ plot_data: pd.DataFrame,
221
+ x: np.ndarray[tuple[int], np.dtype[np.intp]],
222
+ ) -> None:
223
+ """Set tick positions, labels, and rotation.
224
+
225
+ Override for custom labels (e.g. string index).
226
+ """
227
+ dates = plot_data.index
228
+ step = max(1, len(dates) // 12)
229
+ tick_pos = list(range(0, len(dates), step))
230
+ tick_labels = [dates[i].strftime("%Y") for i in tick_pos]
231
+ ax.set_xticks( # type: ignore[reportUnknownMemberType]
232
+ tick_pos
233
+ )
234
+ ax.set_xticklabels( # type: ignore[reportUnknownMemberType]
235
+ tick_labels, rotation=self.x_rotation
236
+ )
237
+
238
+ # -- Main render -----------------------------------------
239
+
240
+ def plot(self) -> Axes:
241
+ plot_data = self._prepare_data()
200
242
 
201
243
  fig_kw = dict(FIG_CONFIG)
202
244
  if self.figsize is not None:
@@ -210,7 +252,6 @@ class StackedBarPlot:
210
252
  labels = list(self.series.values())
211
253
 
212
254
  x = np.arange(len(plot_data))
213
- bar_width = 0.7
214
255
 
215
256
  pos_bottom: np.ndarray[tuple[int], np.dtype[np.float64]] = np.zeros(
216
257
  len(plot_data)
@@ -235,7 +276,7 @@ class StackedBarPlot:
235
276
  x,
236
277
  pos,
237
278
  bottom=pos_bottom,
238
- width=bar_width,
279
+ width=self.bar_width,
239
280
  label=label,
240
281
  )
241
282
  .patches[0]
@@ -245,7 +286,7 @@ class StackedBarPlot:
245
286
  x,
246
287
  neg,
247
288
  bottom=neg_bottom,
248
- width=bar_width,
289
+ width=self.bar_width,
249
290
  color=color,
250
291
  )
251
292
  pos_bottom = pos_bottom + pos
@@ -262,16 +303,7 @@ class StackedBarPlot:
262
303
  zorder=10,
263
304
  )
264
305
 
265
- dates = plot_data.index
266
- step = max(1, len(dates) // 12)
267
- tick_pos = list(range(0, len(dates), step))
268
- tick_labels = [dates[i].strftime("%Y") for i in tick_pos]
269
- ax.set_xticks( # type: ignore[reportUnknownMemberType]
270
- tick_pos
271
- )
272
- ax.set_xticklabels( # type: ignore[reportUnknownMemberType]
273
- tick_labels
274
- )
306
+ self._format_xticks(ax, plot_data, x)
275
307
 
276
308
  style_spines(
277
309
  ax,
@@ -51,6 +51,12 @@ def apply_transformations(
51
51
  ) -> pd.DataFrame:
52
52
  """Apply rules sequentially to a DataFrame.
53
53
 
54
+ New columns are accumulated in a dict and merged into
55
+ *result* only when a later rule reads from them (or at
56
+ the end). This avoids per-rule fragmentation of the
57
+ DataFrame and pandas' ``PerformanceWarning`` when many
58
+ rules (100+) are applied.
59
+
54
60
  Parameters
55
61
  ----------
56
62
  df
@@ -64,8 +70,10 @@ def apply_transformations(
64
70
  Copy of *df* with new columns added by the rules.
65
71
  """
66
72
  result = df.copy()
73
+ pending: dict[str, pd.Series[float]] = {}
67
74
  for rule in rules:
68
- missing = [d for d in rule.dependencies if d not in result.columns]
75
+ available = set(result.columns) | set(pending)
76
+ missing = [d for d in rule.dependencies if d not in available]
69
77
  if missing:
70
78
  logger.warning(
71
79
  "Skipping %s: missing dependencies %s",
@@ -73,5 +81,21 @@ def apply_transformations(
73
81
  missing,
74
82
  )
75
83
  continue
76
- result[rule.output_name] = rule.compute(result)
84
+ # rule.compute() reads from result; flush pending
85
+ # into result if this rule needs any pending column.
86
+ if any(d in pending for d in rule.dependencies):
87
+ result = pd.concat(
88
+ [
89
+ result,
90
+ pd.DataFrame(pending, index=result.index),
91
+ ],
92
+ axis=1,
93
+ )
94
+ pending = {}
95
+ pending[rule.output_name] = rule.compute(result)
96
+ if pending:
97
+ result = pd.concat(
98
+ [result, pd.DataFrame(pending, index=result.index)],
99
+ axis=1,
100
+ )
77
101
  return result
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tesorotools-python
3
- Version: 0.0.26
3
+ Version: 0.0.28
4
4
  Requires-Python: >=3.13
5
5
  Requires-Dist: babel>=2.17
6
6
  Requires-Dist: eikon>=1.1
@@ -6,7 +6,7 @@ tesorotools/artists/__init__.py,sha256=f9xOKEGJxQUE9nWwLft7cwtvPVmeyZRaF94oONKMZ
6
6
  tesorotools/artists/barh.md,sha256=VUYF4fnVrZo4BOET7l8uyMPAopEvoSMgfzBppF-AYug,4539
7
7
  tesorotools/artists/barh_plot.py,sha256=WcZl3rDdF-Jmf4uNYswpNn-JBPASwJWSEnCFWrlZFYk,16931
8
8
  tesorotools/artists/line_plot.py,sha256=_V7bKJFVQL-yMKDdhZX4hFizZs6FoW1wHmq7M_joY0Q,12992
9
- tesorotools/artists/stacked.py,sha256=skx92Ntjyk7DYlriwwXfPKStDNLYAhd0dnRKALJe6Ts,9563
9
+ tesorotools/artists/stacked.py,sha256=YnsOm-l7hXcnBvfvaz43vUomoyplo_BF6IBa1bHELvU,10647
10
10
  tesorotools/artists/table.py,sha256=pyICq6yFEOSWGMc-3rhhgReodRImkBUg53kx8Msjf-c,8144
11
11
  tesorotools/artists/type_curve.py,sha256=raBdWt-2SpqujlIf7UQKAbKR1haln_zB7PJafUQmoLc,5963
12
12
  tesorotools/assets/README.md,sha256=pB77vD_MvE26ftVNcZuj3iS0y-6g-WzqHb5OScDkQ3I,289
@@ -36,7 +36,7 @@ tesorotools/offsets/offsets.py,sha256=-DOXy7gjI0r_UOxPtKv3OS10Z797dkeFMbjl8oWItP
36
36
  tesorotools/offsets/outliers.py,sha256=C2L-7d7jwx-Li7A73QO80lLzA4Mjp0mQBWbOqDppByM,677
37
37
  tesorotools/pipeline/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
38
  tesorotools/pipeline/diagnose.py,sha256=Ji_T3rAn-z4NhSdkpyPsnwQ1CTl4u5-1gnJZC8d1Sxg,1470
39
- tesorotools/pipeline/engine.py,sha256=R2sqpgUGRvNwMdTIn1HAMJzv0z_-XvqNgthDRKHrDEs,2030
39
+ tesorotools/pipeline/engine.py,sha256=D4gKIv1bO6pw2gUMWJ7CVRv7e_kWWrWpZCzupeZCx0k,2929
40
40
  tesorotools/pipeline/rules.py,sha256=J5pevYdb8IixM87Gg6pSWNIpELVI770bjT6NnyvmGRw,5238
41
41
  tesorotools/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
42
42
  tesorotools/providers/base.py,sha256=IORHn8nHRcCDDnU_79q1Gc2tvuZ-X00f2yax24VX-f4,2201
@@ -59,6 +59,6 @@ tesorotools/utils/matplotlib.py,sha256=lItlkJwJEDGALt0J1UZioTz2GOOFPPe7ffQ4Hzxl7
59
59
  tesorotools/utils/series.py,sha256=AEf5DhneYjzQ0nvZD5b1IU3hop0Xgb3Bw2xWs_G3Lhw,1207
60
60
  tesorotools/utils/shortcuts.py,sha256=9sNrJHSNzCoZgBLWnG8U_dK6jTEjJUFgHPzUye7kU2U,860
61
61
  tesorotools/utils/template.py,sha256=jRT6ilDs87GuOmLltbCAP5-Lan7UeMQ_D_KuP-WwjIc,5343
62
- tesorotools_python-0.0.26.dist-info/METADATA,sha256=WRyRs9MvSy9NJDyuk9h6CVVGuvNv5UnzewrT8LyQ7b4,532
63
- tesorotools_python-0.0.26.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
64
- tesorotools_python-0.0.26.dist-info/RECORD,,
62
+ tesorotools_python-0.0.28.dist-info/METADATA,sha256=eXhh10WZle1az2cXgxz27wM9zCm0ax2ew-XtL3T_4Y0,532
63
+ tesorotools_python-0.0.28.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
64
+ tesorotools_python-0.0.28.dist-info/RECORD,,