ResampleTransformer
Brings every column of a frame to one regular frequency. It aggregates when a column is finer than the target (15 min → 1 h) and repeats a value over its own period when the column is coarser (1 h → 15 min). It does not fill missing data and never uses a later value to fill an earlier slot.
Basic Usage
from epftoolbox2.data.transformers import ResampleTransformer
transformer = ResampleTransformer(freq="1h")df = transformer.transform(df)Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
freq | str | "1h" | Target pandas frequency ("15min", "1h", "1D", …) |
agg | str | dict | "mean" | Aggregation used when downsampling: mean, sum, first, last, min, max, median, or a dict {column: aggregation} |
fill | str | None | dict | "ffill" | How slots created by upsampling are filled: "ffill" (repeat the value inside its own period) or None (leave them empty), or a dict {column: fill} |
In a dict, columns that are not listed use the "default" entry, or "mean" / "ffill" when there is none:
# Prices and load are averaged, energy is summedResampleTransformer(freq="1h", agg={"default": "mean", "energy_mwh": "sum"})
# Keep the hourly price only at the full hour when going to 15 minutesResampleTransformer(freq="15min", fill={"price": None})Non-numeric columns (e.g. weekday_name) use "first" when the aggregation is numeric-only (mean, sum, median).
How it works
Every value is the value of a period that starts at its timestamp and lasts as long as the column’s own spacing: 15 minutes, one hour, or one local calendar day for daily columns. The spacing is worked out per column from the timestamps of its non-null values, so a frame that mixes hourly prices, 15-minute load and daily A31 values is handled column by column. A column can even change resolution part-way (hourly day-ahead prices before the 15-minute switch, 15-minute prices after it).
Upsampling (column coarser than freq): each finer slot inside a value’s period gets that value, so an hourly price becomes a step: 10:00, 10:15, 10:30 and 10:45 all hold the 10:00 price. Dummies stay 0/1. The last period is completed, so an hourly day becomes 96 quarter-hours. For sum columns the value is split evenly across the slots, so totals are kept. With fill=None the new slots stay empty.
Downsampling (column finer than freq): the values inside each target period are combined with agg. A period that is not completely covered gives NaN: a missing quarter-hour makes its hour NaN, and the partial first and last local days make a daily mean NaN. You never get a partial sum or a sum of zero for an empty period.
Nothing else is filled. A value missing in the source (an outage, a price that isn’t published yet) stays missing, and nothing is extrapolated past a column’s last value. Gaps therefore stay visible to validators and are dropped by the models instead of being scored against invented actuals.
Pipeline order: Timezone → Resample → Lag
from epftoolbox2.pipelines import DataPipelinefrom epftoolbox2.data.sources import EntsoeSourcefrom epftoolbox2.data.transformers import TimezoneTransformer, ResampleTransformer, LagTransformer
pipeline = ( DataPipeline() .add_source(EntsoeSource(country_code="PL", api_key="...", type=["load", "price"])) .add_transformer(TimezoneTransformer(target_tz="Europe/Warsaw")) # local time, daily values on local midnight .add_transformer(ResampleTransformer(freq="1h")) # one regular grid .add_transformer(LagTransformer(columns=["price"], lags=[1, 7], freq="day")))TimezoneTransformerfirst, so that daily bins (freq="1D") and daily columns follow local calendar days and daily values sit on local midnight. Resampling a UTC frame to"1D"aggregates UTC days, and resampling daily columns before the timezone step makesTimezoneTransformerraise.ResampleTransformernext, to put every column on the same grid.LagTransformerlast, on the regular local grid.
What happens on DST days
After TimezoneTransformer, a local day has 23 hours on the spring switch day and 25 on the autumn one. ResampleTransformer keeps that: the spring day has 23 hourly rows (92 quarter-hours) and no 02:00 row, because that local time doesn’t exist. The autumn day has 25 hourly rows (100 quarter-hours) with 02:00 twice. Nothing is filled in for the “missing” hour, and a daily value covers all 23 or 25 hours of its day.
Notes
- The input must have a
DatetimeIndexwithout duplicate timestamps. An unsorted index is sorted. - Timestamps are period starts (ENTSO-E convention).
- Output values are not rounded.
- Numeric columns come out as
float64.