Weather Sampling
This guide explains how to align raw weather time steps with model requirements.
Why Sampling?
A model often needs weather at a different temporal scale than the source data. Examples:
- source meteo is hourly, model step is 3 hours
- source meteo is sub-hourly, model step is daily
- a simulation needs both rolling and calendar-based aggregates
The sampling API makes these choices explicit and testable.
Sampling Workflow
- define the source weather table
- prepare a sampler object (with optional caching)
- choose a window specification
- sample one step or materialize full sampled tables
1. Create a Small, Predictable Weather Series
For this example we'll use simple values so aggregation effects are easy to verify. We make 48 hourly rows (two days) with monotonic T:
using PlantMeteo
using Dates
using Statistics
base = DateTime(2025, 1, 1)
meteo = Weather([
Atmosphere(
date = base + Hour(i),
duration = Hour(1),
T = 10.0 + i,
Wind = 1.0,
Rh = 0.50 + 0.005 * i,
P = 100.0,
Ri_SW_f = 100.0 + 10.0 * i
)
for i in 0:47
])
meteo| TimeStepTable{Atmosphere{(:date, :duration,...}(48 x 22): | ||||||||||||||||||||||
| date | duration | T | Wind | P | Rh | Precipitations | Cₐ | e | eₛ | VPD | ρ | λ | γ | ε | Δ | clearness | Ri_SW_f | Ri_PAR_f | Ri_NIR_f | Ri_TIR_f | Ri_custom_f | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| DateTime | Hour | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | |
| 1 | 2025-01-01T00:00:00 | 1 hour | 10.0 | 1.0 | 100.0 | 0.5 | 0.0 | 400.0 | 0.61635 | 1.2327 | 0.61635 | 1.23031 | 2.47735e6 | 0.0657403 | 0.516341 | 0.0827838 | Inf | 100.0 | Inf | Inf | Inf | Inf |
| 2 | 2025-01-01T01:00:00 | 1 hour | 11.0 | 1.0 | 100.0 | 0.505 | 0.0 | 400.0 | 0.665446 | 1.31772 | 0.652269 | 1.22598 | 2.47498e6 | 0.0658031 | 0.521763 | 0.08779 | Inf | 110.0 | Inf | Inf | Inf | Inf |
| 3 | 2025-01-01T02:00:00 | 1 hour | 12.0 | 1.0 | 100.0 | 0.51 | 0.0 | 400.0 | 0.718005 | 1.40785 | 0.689848 | 1.22168 | 2.47262e6 | 0.0658661 | 0.527195 | 0.0930528 | Inf | 120.0 | Inf | Inf | Inf | Inf |
| 4 | 2025-01-01T03:00:00 | 1 hour | 13.0 | 1.0 | 100.0 | 0.515 | 0.0 | 400.0 | 0.774236 | 1.50337 | 0.729135 | 1.21741 | 2.47026e6 | 0.0659291 | 0.532638 | 0.0985828 | Inf | 130.0 | Inf | Inf | Inf | Inf |
| 5 | 2025-01-01T04:00:00 | 1 hour | 14.0 | 1.0 | 100.0 | 0.52 | 0.0 | 400.0 | 0.834363 | 1.60454 | 0.770181 | 1.21317 | 2.46789e6 | 0.0659923 | 0.538091 | 0.104391 | Inf | 140.0 | Inf | Inf | Inf | Inf |
| 6 | 2025-01-01T05:00:00 | 1 hour | 15.0 | 1.0 | 100.0 | 0.525 | 0.0 | 400.0 | 0.898619 | 1.71165 | 0.813036 | 1.20896 | 2.46552e6 | 0.0660556 | 0.543555 | 0.110488 | Inf | 150.0 | Inf | Inf | Inf | Inf |
| 7 | 2025-01-01T06:00:00 | 1 hour | 16.0 | 1.0 | 100.0 | 0.53 | 0.0 | 400.0 | 0.967248 | 1.825 | 0.857749 | 1.20478 | 2.46316e6 | 0.066119 | 0.549028 | 0.116887 | Inf | 160.0 | Inf | Inf | Inf | Inf |
| 8 | 2025-01-01T07:00:00 | 1 hour | 17.0 | 1.0 | 100.0 | 0.535 | 0.0 | 400.0 | 1.04051 | 1.94488 | 0.904368 | 1.20062 | 2.4608e6 | 0.0661826 | 0.554511 | 0.123598 | Inf | 170.0 | Inf | Inf | Inf | Inf |
| 9 | 2025-01-01T08:00:00 | 1 hour | 18.0 | 1.0 | 100.0 | 0.54 | 0.0 | 400.0 | 1.11867 | 2.07162 | 0.952943 | 1.1965 | 2.45843e6 | 0.0662462 | 0.560003 | 0.130635 | Inf | 180.0 | Inf | Inf | Inf | Inf |
| 10 | 2025-01-01T09:00:00 | 1 hour | 19.0 | 1.0 | 100.0 | 0.545 | 0.0 | 400.0 | 1.20202 | 2.20554 | 1.00352 | 1.1924 | 2.45606e6 | 0.06631 | 0.565504 | 0.138009 | Inf | 190.0 | Inf | Inf | Inf | Inf |
| 11 | 2025-01-01T10:00:00 | 1 hour | 20.0 | 1.0 | 100.0 | 0.55 | 0.0 | 400.0 | 1.29085 | 2.347 | 1.05615 | 1.18834 | 2.4537e6 | 0.0663739 | 0.571015 | 0.145734 | Inf | 200.0 | Inf | Inf | Inf | Inf |
| 12 | 2025-01-01T11:00:00 | 1 hour | 21.0 | 1.0 | 100.0 | 0.555 | 0.0 | 400.0 | 1.38547 | 2.49634 | 1.11087 | 1.1843 | 2.45134e6 | 0.066438 | 0.576534 | 0.153823 | Inf | 210.0 | Inf | Inf | Inf | Inf |
| 13 | 2025-01-01T12:00:00 | 1 hour | 22.0 | 1.0 | 100.0 | 0.56 | 0.0 | 400.0 | 1.48621 | 2.65394 | 1.16773 | 1.18028 | 2.44897e6 | 0.0665021 | 0.582061 | 0.162289 | Inf | 220.0 | Inf | Inf | Inf | Inf |
| 14 | 2025-01-01T13:00:00 | 1 hour | 23.0 | 1.0 | 100.0 | 0.565 | 0.0 | 400.0 | 1.5934 | 2.82018 | 1.22678 | 1.1763 | 2.4466e6 | 0.0665664 | 0.587597 | 0.171147 | Inf | 230.0 | Inf | Inf | Inf | Inf |
| 15 | 2025-01-01T14:00:00 | 1 hour | 24.0 | 1.0 | 100.0 | 0.57 | 0.0 | 400.0 | 1.70741 | 2.99546 | 1.28805 | 1.17234 | 2.44424e6 | 0.0666308 | 0.593142 | 0.180411 | Inf | 240.0 | Inf | Inf | Inf | Inf |
| 16 | 2025-01-01T15:00:00 | 1 hour | 25.0 | 1.0 | 100.0 | 0.575 | 0.0 | 400.0 | 1.82861 | 3.1802 | 1.35158 | 1.16841 | 2.44188e6 | 0.0666954 | 0.598694 | 0.190095 | Inf | 250.0 | Inf | Inf | Inf | Inf |
| 17 | 2025-01-01T16:00:00 | 1 hour | 26.0 | 1.0 | 100.0 | 0.58 | 0.0 | 400.0 | 1.95739 | 3.37481 | 1.41742 | 1.1645 | 2.43951e6 | 0.06676 | 0.604253 | 0.200215 | Inf | 260.0 | Inf | Inf | Inf | Inf |
| 18 | 2025-01-01T17:00:00 | 1 hour | 27.0 | 1.0 | 100.0 | 0.585 | 0.0 | 400.0 | 2.09415 | 3.57974 | 1.48559 | 1.16062 | 2.43714e6 | 0.0668248 | 0.609821 | 0.210787 | Inf | 270.0 | Inf | Inf | Inf | Inf |
| 19 | 2025-01-01T18:00:00 | 1 hour | 28.0 | 1.0 | 100.0 | 0.59 | 0.0 | 400.0 | 2.23932 | 3.79546 | 1.55614 | 1.15677 | 2.43478e6 | 0.0668897 | 0.615395 | 0.221826 | Inf | 280.0 | Inf | Inf | Inf | Inf |
| 20 | 2025-01-01T19:00:00 | 1 hour | 29.0 | 1.0 | 100.0 | 0.595 | 0.0 | 400.0 | 2.39334 | 4.02243 | 1.62908 | 1.15294 | 2.43242e6 | 0.0669547 | 0.620977 | 0.233348 | Inf | 290.0 | Inf | Inf | Inf | Inf |
| 21 | 2025-01-01T20:00:00 | 1 hour | 30.0 | 1.0 | 100.0 | 0.6 | 0.0 | 400.0 | 2.55669 | 4.26114 | 1.70446 | 1.14914 | 2.43005e6 | 0.0670199 | 0.626565 | 0.24537 | Inf | 300.0 | Inf | Inf | Inf | Inf |
| 22 | 2025-01-01T21:00:00 | 1 hour | 31.0 | 1.0 | 100.0 | 0.605 | 0.0 | 400.0 | 2.72983 | 4.51211 | 1.78228 | 1.14536 | 2.42768e6 | 0.0670852 | 0.632161 | 0.257909 | Inf | 310.0 | Inf | Inf | Inf | Inf |
| 23 | 2025-01-01T22:00:00 | 1 hour | 32.0 | 1.0 | 100.0 | 0.61 | 0.0 | 400.0 | 2.91328 | 4.77586 | 1.86259 | 1.14161 | 2.42532e6 | 0.0671506 | 0.637763 | 0.270983 | Inf | 320.0 | Inf | Inf | Inf | Inf |
| 24 | 2025-01-01T23:00:00 | 1 hour | 33.0 | 1.0 | 100.0 | 0.615 | 0.0 | 400.0 | 3.10755 | 5.05293 | 1.94538 | 1.13788 | 2.42296e6 | 0.0672162 | 0.643371 | 0.28461 | Inf | 330.0 | Inf | Inf | Inf | Inf |
| 25 | 2025-01-02T00:00:00 | 1 hour | 34.0 | 1.0 | 100.0 | 0.62 | 0.0 | 400.0 | 3.31321 | 5.34388 | 2.03067 | 1.13417 | 2.42059e6 | 0.0672818 | 0.648985 | 0.298807 | Inf | 340.0 | Inf | Inf | Inf | Inf |
| 26 | 2025-01-02T01:00:00 | 1 hour | 35.0 | 1.0 | 100.0 | 0.625 | 0.0 | 400.0 | 3.53081 | 5.64929 | 2.11848 | 1.13049 | 2.41822e6 | 0.0673476 | 0.654605 | 0.313593 | Inf | 350.0 | Inf | Inf | Inf | Inf |
| 27 | 2025-01-02T02:00:00 | 1 hour | 36.0 | 1.0 | 100.0 | 0.63 | 0.0 | 400.0 | 3.76095 | 5.96976 | 2.20881 | 1.12683 | 2.41586e6 | 0.0674136 | 0.660231 | 0.328987 | Inf | 360.0 | Inf | Inf | Inf | Inf |
| 28 | 2025-01-02T03:00:00 | 1 hour | 37.0 | 1.0 | 100.0 | 0.635 | 0.0 | 400.0 | 4.00425 | 6.30591 | 2.30166 | 1.1232 | 2.4135e6 | 0.0674796 | 0.665863 | 0.34501 | Inf | 370.0 | Inf | Inf | Inf | Inf |
| 29 | 2025-01-02T04:00:00 | 1 hour | 38.0 | 1.0 | 100.0 | 0.64 | 0.0 | 400.0 | 4.26135 | 6.65836 | 2.39701 | 1.11959 | 2.41113e6 | 0.0675458 | 0.6715 | 0.36168 | Inf | 380.0 | Inf | Inf | Inf | Inf |
| 30 | 2025-01-02T05:00:00 | 1 hour | 39.0 | 1.0 | 100.0 | 0.645 | 0.0 | 400.0 | 4.53292 | 7.02779 | 2.49487 | 1.116 | 2.40876e6 | 0.0676121 | 0.677142 | 0.379018 | Inf | 390.0 | Inf | Inf | Inf | Inf |
| 31 | 2025-01-02T06:00:00 | 1 hour | 40.0 | 1.0 | 100.0 | 0.65 | 0.0 | 400.0 | 4.81966 | 7.41486 | 2.5952 | 1.11244 | 2.4064e6 | 0.0676786 | 0.68279 | 0.397045 | Inf | 400.0 | Inf | Inf | Inf | Inf |
| 32 | 2025-01-02T07:00:00 | 1 hour | 41.0 | 1.0 | 100.0 | 0.655 | 0.0 | 400.0 | 5.12228 | 7.82028 | 2.698 | 1.1089 | 2.40404e6 | 0.0677452 | 0.688442 | 0.415781 | Inf | 410.0 | Inf | Inf | Inf | Inf |
| 33 | 2025-01-02T08:00:00 | 1 hour | 42.0 | 1.0 | 100.0 | 0.66 | 0.0 | 400.0 | 5.44154 | 8.24476 | 2.80322 | 1.10538 | 2.40167e6 | 0.0678119 | 0.694099 | 0.435249 | Inf | 420.0 | Inf | Inf | Inf | Inf |
| 34 | 2025-01-02T09:00:00 | 1 hour | 43.0 | 1.0 | 100.0 | 0.665 | 0.0 | 400.0 | 5.77822 | 8.68905 | 2.91083 | 1.10188 | 2.3993e6 | 0.0678787 | 0.69976 | 0.455471 | Inf | 430.0 | Inf | Inf | Inf | Inf |
| 35 | 2025-01-02T10:00:00 | 1 hour | 44.0 | 1.0 | 100.0 | 0.67 | 0.0 | 400.0 | 6.13311 | 9.1539 | 3.02079 | 1.09841 | 2.39694e6 | 0.0679457 | 0.705426 | 0.476468 | Inf | 440.0 | Inf | Inf | Inf | Inf |
| 36 | 2025-01-02T11:00:00 | 1 hour | 45.0 | 1.0 | 100.0 | 0.675 | 0.0 | 400.0 | 6.50707 | 9.64011 | 3.13304 | 1.09496 | 2.39458e6 | 0.0680128 | 0.711096 | 0.498264 | Inf | 450.0 | Inf | Inf | Inf | Inf |
| 37 | 2025-01-02T12:00:00 | 1 hour | 46.0 | 1.0 | 100.0 | 0.68 | 0.0 | 400.0 | 6.90097 | 10.1485 | 3.24751 | 1.09153 | 2.39221e6 | 0.06808 | 0.71677 | 0.520882 | Inf | 460.0 | Inf | Inf | Inf | Inf |
| 38 | 2025-01-02T13:00:00 | 1 hour | 47.0 | 1.0 | 100.0 | 0.685 | 0.0 | 400.0 | 7.3157 | 10.6799 | 3.36415 | 1.08812 | 2.38984e6 | 0.0681474 | 0.722448 | 0.544346 | Inf | 470.0 | Inf | Inf | Inf | Inf |
| 39 | 2025-01-02T14:00:00 | 1 hour | 48.0 | 1.0 | 100.0 | 0.69 | 0.0 | 400.0 | 7.7522 | 11.2351 | 3.48287 | 1.08473 | 2.38748e6 | 0.0682149 | 0.72813 | 0.56868 | Inf | 480.0 | Inf | Inf | Inf | Inf |
| 40 | 2025-01-02T15:00:00 | 1 hour | 49.0 | 1.0 | 100.0 | 0.695 | 0.0 | 400.0 | 8.21145 | 11.815 | 3.60359 | 1.08136 | 2.38512e6 | 0.0682826 | 0.733815 | 0.593908 | Inf | 490.0 | Inf | Inf | Inf | Inf |
| 41 | 2025-01-02T16:00:00 | 1 hour | 50.0 | 1.0 | 100.0 | 0.7 | 0.0 | 400.0 | 8.69444 | 12.4206 | 3.72619 | 1.07802 | 2.38275e6 | 0.0683503 | 0.739504 | 0.620055 | Inf | 500.0 | Inf | Inf | Inf | Inf |
| 42 | 2025-01-02T17:00:00 | 1 hour | 51.0 | 1.0 | 100.0 | 0.705 | 0.0 | 400.0 | 9.20222 | 13.0528 | 3.85058 | 1.07469 | 2.38038e6 | 0.0684182 | 0.745196 | 0.647148 | Inf | 510.0 | Inf | Inf | Inf | Inf |
| 43 | 2025-01-02T18:00:00 | 1 hour | 52.0 | 1.0 | 100.0 | 0.71 | 0.0 | 400.0 | 9.73587 | 13.7125 | 3.97662 | 1.07139 | 2.37802e6 | 0.0684863 | 0.750891 | 0.675211 | Inf | 520.0 | Inf | Inf | Inf | Inf |
| 44 | 2025-01-02T19:00:00 | 1 hour | 53.0 | 1.0 | 100.0 | 0.715 | 0.0 | 400.0 | 10.2965 | 14.4007 | 4.1042 | 1.0681 | 2.37566e6 | 0.0685545 | 0.756588 | 0.704272 | Inf | 530.0 | Inf | Inf | Inf | Inf |
| 45 | 2025-01-02T20:00:00 | 1 hour | 54.0 | 1.0 | 100.0 | 0.72 | 0.0 | 400.0 | 10.8853 | 15.1184 | 4.23316 | 1.06484 | 2.37329e6 | 0.0686228 | 0.762289 | 0.734356 | Inf | 540.0 | Inf | Inf | Inf | Inf |
| 46 | 2025-01-02T21:00:00 | 1 hour | 55.0 | 1.0 | 100.0 | 0.725 | 0.0 | 400.0 | 11.5034 | 15.8667 | 4.36334 | 1.06159 | 2.37092e6 | 0.0686912 | 0.767992 | 0.765492 | Inf | 550.0 | Inf | Inf | Inf | Inf |
| 47 | 2025-01-02T22:00:00 | 1 hour | 56.0 | 1.0 | 100.0 | 0.73 | 0.0 | 400.0 | 12.152 | 16.6466 | 4.49458 | 1.05837 | 2.36856e6 | 0.0687598 | 0.773698 | 0.797707 | Inf | 560.0 | Inf | Inf | Inf | Inf |
| 48 | 2025-01-02T23:00:00 | 1 hour | 57.0 | 1.0 | 100.0 | 0.735 | 0.0 | 400.0 | 12.8325 | 17.4592 | 4.62669 | 1.05516 | 2.3662e6 | 0.0688285 | 0.779406 | 0.831029 | Inf | 570.0 | Inf | Inf | Inf | Inf |
2. Prepare Sampler State
We can normalize transforms and enable query-level memoization with prepare_weather_sampler. This is optional but can speed up repeated calls with the same windows. This is done so that repeated identical calls can return cached objects (lazy=true).
prepared = prepare_weather_sampler(meteo) # lazy cache enabled by default
typeof(prepared)PreparedWeather{TimeStepTable{Atmosphere{(:date, :duration, :T, :Wind, :P, :Rh, :Precipitations, :Cₐ, :e, :eₛ, :VPD, :ρ, :λ, :γ, :ε, :Δ, :clearness, :Ri_SW_f, :Ri_PAR_f, :Ri_NIR_f, :Ri_TIR_f, :Ri_custom_f), Tuple{DateTime, Hour, Vararg{Float64, 20}}}}, Vector{MeteoTransform}, Dict{Tuple{Int64, UInt64, UInt64}, Any}, Dict{UInt64, Any}}3. Rolling Window Sampling
We can then aggregate over a trailing window in source-step units using sample_weather, which returns one aggregated Atmosphere.
window2 = RollingWindow(2.0)
row3 = sample_weather(prepared, 3; window = window2)
row3_cached = sample_weather(prepared, 3; window = window2)
(row3.T, row3.Tmin, row3.Tmax, row3_cached === row3)(11.5, 11.0, 12.0, true)sample_weather provides default transforms for common variables (i.e. the ones in Atmosphere), but you can override them with custom rules (see next section).
4. Override Variable-wise Aggregation Rules
We can match aggregation logic to model semantics by overriding variable-wise aggregation rules. Custom transforms are normalized and applied for this call.
custom = (
T = MeanWeighted(),
Tmax = (source = :T, reducer = MaxReducer()),
Tsum = (source = :T, reducer = SumReducer())
)
row_custom = sample_weather(prepared, 3; window = window2, transforms = custom)
(row_custom.T, row_custom.Tmax, row_custom.Tsum)(11.5, 12.0, 23.0)5. Calendar Window Sampling
Sometimes model logic is tied to civil periods (day/week/month) rather than fixed trailing windows. In this case, we can use CalendarWindow to aggregate all source steps that fall within the same period. This is useful for e.g. daily models that need to aggregate all hourly steps that fall within the same day, regardless of how many there are or where they fall in the source data. For example if you need the average temperature for the day, you can use a CalendarWindow anchored to the current period:
window_day = CalendarWindow(:day)
day_sample = sample_weather(prepared, 5; window = window_day)
(day_sample.T, day_sample.Tmin, day_sample.Tmax, day_sample.duration)(21.5, 10.0, 33.0, Millisecond(86400000))We sample the fifth hour of the series, which falls on the first day. The sampled T is the average of the 24 hourly steps that fall within that day, which matches our expectation for a daily aggregation:
mean(meteo[i].T for i in 1:24)21.5The result would be the same for any hour from 1 to 24, since they all fall in the same day:
day_sample2 = sample_weather(prepared, 20; window = window_day)
day_sample2.T == day_sample.TtrueThe aggregated results are cached, so repeated calls with the same window and query step will return the same values, and it means that performance will be ensured for long simulation loops with repeated calls:
day_sample_cached = sample_weather(prepared, 6; window = window_day)
day_sample_cached.T === day_sample.TtrueThe returned Atmosphere is different though, since the date (and sometimes duration) are different for each query step, even if the aggregated values are the same:
day_sample.date, day_sample_cached.date, day_sample2.date(DateTime("2025-01-01T04:00:00"), DateTime("2025-01-01T05:00:00"), DateTime("2025-01-01T19:00:00"))Note that CalendarWindow expects a date::DateTime column in the weather. By default, it checks for completeness of the period, but you can use completeness = :allow_partial to authorize incomplete periods. This is also faster since it doesn't have to perform checks.
6. Precompute for Simulation Loops
When running long simulations, you can precompute all sampled weather tables upfront rather than sampling on-demand at each step. This trades a one-time preprocessing cost for faster lookups during the simulation loop. The materialize_weather function returns one sampled table per requested sampling window, allowing efficient access throughout your simulation.
windows = [window2, window_day]
tables = materialize_weather(prepared; windows = windows)
(length(tables), length(tables[window2]), tables[window2][3].T ≈ row3.T)(2, 48, true)This is typically the best approach when you perform many simulations with the same weather, e.g. for model calibration, sensitivity analysis, or system optimization.