python-esios 2.2.0__py3-none-any.whl → 2.3.0__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.
- esios/managers/indicators.py +25 -4
- esios/processing/i90.py +32 -11
- {python_esios-2.2.0.dist-info → python_esios-2.3.0.dist-info}/METADATA +1 -1
- {python_esios-2.2.0.dist-info → python_esios-2.3.0.dist-info}/RECORD +7 -7
- {python_esios-2.2.0.dist-info → python_esios-2.3.0.dist-info}/WHEEL +0 -0
- {python_esios-2.2.0.dist-info → python_esios-2.3.0.dist-info}/entry_points.txt +0 -0
- {python_esios-2.2.0.dist-info → python_esios-2.3.0.dist-info}/licenses/LICENSE +0 -0
esios/managers/indicators.py
CHANGED
|
@@ -142,6 +142,7 @@ class IndicatorHandle:
|
|
|
142
142
|
geo_agg: str | None = None,
|
|
143
143
|
time_trunc: str | None = None,
|
|
144
144
|
geo_trunc: str | None = None,
|
|
145
|
+
column_name: str | None = None,
|
|
145
146
|
) -> pd.DataFrame:
|
|
146
147
|
"""Fetch historical values as a DataFrame with DatetimeIndex.
|
|
147
148
|
|
|
@@ -151,6 +152,12 @@ class IndicatorHandle:
|
|
|
151
152
|
When multiple geo_ids are present (e.g. indicator 600 returns data for
|
|
152
153
|
several countries), the result is pivoted so each geo becomes a column
|
|
153
154
|
named by its geo_name. Use *geo_ids* to filter to specific geos.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
column_name: If provided, rename the output column(s) to this name.
|
|
158
|
+
Useful for single-column results where a stable name like
|
|
159
|
+
``"value"`` is preferred over the default geo_name or
|
|
160
|
+
indicator ID.
|
|
154
161
|
"""
|
|
155
162
|
base_params: dict[str, Any] = {
|
|
156
163
|
"locale": locale,
|
|
@@ -250,7 +257,7 @@ class IndicatorHandle:
|
|
|
250
257
|
if existing:
|
|
251
258
|
result = result[existing]
|
|
252
259
|
|
|
253
|
-
return self._finalize(result)
|
|
260
|
+
return self._finalize(result, column_name=column_name)
|
|
254
261
|
|
|
255
262
|
def _to_wide(self, values: list[dict]) -> pd.DataFrame:
|
|
256
263
|
"""Convert raw API value dicts to wide-format DataFrame.
|
|
@@ -283,16 +290,26 @@ class IndicatorHandle:
|
|
|
283
290
|
df = df.drop(columns=geo_drop, errors="ignore")
|
|
284
291
|
return df
|
|
285
292
|
|
|
286
|
-
def _finalize(
|
|
293
|
+
def _finalize(
|
|
294
|
+
self, df: pd.DataFrame, *, column_name: str | None = None,
|
|
295
|
+
) -> pd.DataFrame:
|
|
287
296
|
"""Prepare DataFrame for user-facing output.
|
|
288
297
|
|
|
289
298
|
Cache stores columns as str(geo_id). This method renames them to
|
|
290
299
|
human-readable geo_names at the very end, just before returning to
|
|
291
300
|
the caller. Single-value/single-geo indicators get the indicator ID.
|
|
301
|
+
|
|
302
|
+
If ``column_name`` is provided and the result has a single column,
|
|
303
|
+
that column is renamed to ``column_name`` (e.g. ``"value"``).
|
|
292
304
|
"""
|
|
293
305
|
if df.empty:
|
|
294
306
|
return df
|
|
295
307
|
|
|
308
|
+
# If caller wants a specific column name and there's a single column, use it
|
|
309
|
+
if column_name and len(df.columns) == 1:
|
|
310
|
+
df = df.rename(columns={df.columns[0]: column_name})
|
|
311
|
+
return df
|
|
312
|
+
|
|
296
313
|
if len(df.columns) == 1:
|
|
297
314
|
col = df.columns[0]
|
|
298
315
|
if col == "value":
|
|
@@ -305,11 +322,15 @@ class IndicatorHandle:
|
|
|
305
322
|
if rename:
|
|
306
323
|
df = df.rename(columns=rename)
|
|
307
324
|
|
|
325
|
+
# If caller wants a specific column name for multi-column, skip
|
|
326
|
+
# (ambiguous which column to rename)
|
|
327
|
+
if column_name and len(df.columns) == 1:
|
|
328
|
+
df = df.rename(columns={df.columns[0]: column_name})
|
|
329
|
+
return df
|
|
330
|
+
|
|
308
331
|
# Single-geo after rename: use indicator ID as column name
|
|
309
332
|
if len(df.columns) == 1:
|
|
310
333
|
col = df.columns[0]
|
|
311
|
-
# If the single column is a geo_name, keep it (user filtered to one geo)
|
|
312
|
-
# If it's still a geo_id string, rename to indicator ID
|
|
313
334
|
if col not in geo_map.values():
|
|
314
335
|
df = df.rename(columns={col: str(self.id)})
|
|
315
336
|
|
esios/processing/i90.py
CHANGED
|
@@ -166,11 +166,15 @@ class I90Sheet:
|
|
|
166
166
|
def _normalize_datetime_columns(self, columns: np.ndarray) -> np.ndarray:
|
|
167
167
|
"""Normalize time column headers to integer period indices.
|
|
168
168
|
|
|
169
|
-
Handles
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
169
|
+
Handles four column formats found in I90 files:
|
|
170
|
+
|
|
171
|
+
1. Sequential integers: 1–24 (hourly) or 1–96 (quarterly)
|
|
172
|
+
2. H-Q format: "1-1", "1-2", "1-3", "1-4", "2-1", …
|
|
173
|
+
3. NaN-filler format: [1, NaN, NaN, NaN, 2, …]
|
|
174
|
+
4. Range format (DST days): "00-01", "01-02", "02-03a", "02-03b", …
|
|
175
|
+
where the first number is the start hour and a/b suffix marks
|
|
176
|
+
the repeated hour on fall-back days. Detected by the first
|
|
177
|
+
column starting with "0" (e.g. "00-01").
|
|
174
178
|
"""
|
|
175
179
|
if any(pd.isna(columns)):
|
|
176
180
|
self._n_columns_totals = 3
|
|
@@ -178,6 +182,17 @@ class I90Sheet:
|
|
|
178
182
|
self._n_columns_totals = 2
|
|
179
183
|
|
|
180
184
|
series = pd.Series(columns, dtype=str).ffill()
|
|
185
|
+
|
|
186
|
+
# Range format (DST): "00-01", "01-02", "02-03a", "02-03b", ...
|
|
187
|
+
# Detected by first column starting with "0" (sequential ints start at 1).
|
|
188
|
+
first_val = str(columns[0]).strip()
|
|
189
|
+
if first_val.startswith("0") and "-" in first_val:
|
|
190
|
+
# Simply assign sequential 1-based indices.
|
|
191
|
+
# The count of columns (23, 24, or 25 for hourly; 92, 96, or 100
|
|
192
|
+
# for QH) already encodes the DST information. The datetime builder
|
|
193
|
+
# in _preprocess uses these as offsets from midnight UTC.
|
|
194
|
+
return np.arange(1, len(columns) + 1)
|
|
195
|
+
|
|
181
196
|
parts = series.str.split("-")
|
|
182
197
|
hours = parts.str[0].astype(float).astype(int)
|
|
183
198
|
|
|
@@ -251,12 +266,18 @@ class I90Sheet:
|
|
|
251
266
|
self.frequency = "hourly"
|
|
252
267
|
time_deltas = columns_date * 60 # minutes
|
|
253
268
|
|
|
254
|
-
# Build datetime index
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
269
|
+
# Build datetime index in UTC to avoid DST ambiguity.
|
|
270
|
+
# On fall-back days (Oct), I90 has 25 hourly periods (or 100 QH).
|
|
271
|
+
# Naïve offset arithmetic creates a single 02:00 that tz_localize
|
|
272
|
+
# cannot disambiguate. By anchoring midnight in Europe/Madrid,
|
|
273
|
+
# converting to UTC, then adding offsets, each period maps to a
|
|
274
|
+
# unique UTC instant — no ambiguity.
|
|
275
|
+
# On spring-forward days (Mar), I90 has 23 periods (or 92 QH)
|
|
276
|
+
# and this approach naturally skips the non-existent hour.
|
|
277
|
+
midnight_utc = pd.Timestamp(
|
|
278
|
+
self.metadata["date_data"], tz="Europe/Madrid"
|
|
279
|
+
).tz_convert("UTC")
|
|
280
|
+
columns_datetime = midnight_utc + pd.to_timedelta(time_deltas, unit="m")
|
|
260
281
|
|
|
261
282
|
data = pd.DataFrame(self.rows[idx + 1 :], columns=columns)
|
|
262
283
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-esios
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: A Python wrapper for the ESIOS API (Spanish electricity market)
|
|
5
5
|
Project-URL: Homepage, https://github.com/datons/python-esios
|
|
6
6
|
Project-URL: Repository, https://github.com/datons/python-esios
|
|
@@ -24,7 +24,7 @@ esios/data/time_periods.yaml,sha256=oyisKYYyOGA57eEAqkFFx6B3x9rdSl0DokZe5gNZfMw,
|
|
|
24
24
|
esios/managers/__init__.py,sha256=-1AwL7arUf7WEZn1RSiK_DZhY3j6U4GE9_dqjbukCJc,268
|
|
25
25
|
esios/managers/archives.py,sha256=PG-1gQYEiJUVQQtTKIZeEoWIsS-gkWT3ZHy89c8tTW8,9293
|
|
26
26
|
esios/managers/base.py,sha256=7XcdrUtUOPuqfHYlz4w562TD8o9cNdBWOgs4CHHonoo,835
|
|
27
|
-
esios/managers/indicators.py,sha256=
|
|
27
|
+
esios/managers/indicators.py,sha256=nbmKsvBTPO2w3FlcVYv9WOtCTU8xMjFvT_d1AcA2sbg,17506
|
|
28
28
|
esios/managers/offer_indicators.py,sha256=0MjEKkj77YC2fRSHVTEc7FW6E8AuwwciAXK-bOVEL5Q,4187
|
|
29
29
|
esios/models/__init__.py,sha256=oppuTASpf0Dh2KbGMXInULT0F4sELjeo-9UhPiPOZiA,289
|
|
30
30
|
esios/models/archive.py,sha256=P2LaT7_ff4ujwqVn_ofgQP3dbpf7jqON0R22dKwSJ_w,1062
|
|
@@ -32,10 +32,10 @@ esios/models/indicator.py,sha256=u1AJyEA3YeOqQFjV08_lzyMaofuCiMoLPjvosls9gfE,111
|
|
|
32
32
|
esios/models/offer_indicator.py,sha256=nA80Y7Yp0utDaDOdZ-ObcWTsAdhvuXlfJjJBpdVQ7Lo,758
|
|
33
33
|
esios/processing/__init__.py,sha256=1kLt_gO_wDhXM1BbY0zTyfAYo-CjYKW1ljgRRDZ7USM,278
|
|
34
34
|
esios/processing/dataframes.py,sha256=OitzBvAerssGP2VXNC-sSO48XsHdIB2nKTUgByN5eYQ,2524
|
|
35
|
-
esios/processing/i90.py,sha256=
|
|
35
|
+
esios/processing/i90.py,sha256=fI8DfY8CD2kF1_ZrAzuEDxN0m7Vh3CV3dIn32lxKffA,11687
|
|
36
36
|
esios/processing/zip.py,sha256=12LbFHJTdX_h3JG-clEgQ4Haj-kw0UjfopGLlCRXfGM,1913
|
|
37
|
-
python_esios-2.
|
|
38
|
-
python_esios-2.
|
|
39
|
-
python_esios-2.
|
|
40
|
-
python_esios-2.
|
|
41
|
-
python_esios-2.
|
|
37
|
+
python_esios-2.3.0.dist-info/METADATA,sha256=KtWOIGA-o9z7mxZD8vryWTOntVZbxNm_LIVaXYhFD3g,3169
|
|
38
|
+
python_esios-2.3.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
39
|
+
python_esios-2.3.0.dist-info/entry_points.txt,sha256=7ngseyIyvJ4buTHFL9htaZ4tTFHpG4zzJNkc8B5Jr8U,40
|
|
40
|
+
python_esios-2.3.0.dist-info/licenses/LICENSE,sha256=LorLs1-VeBW70Wo9fLAtLJN7nNd6Poy0xzvqdWVqFlE,35128
|
|
41
|
+
python_esios-2.3.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|