Weather Sampling
Use weather sampling when your model needs more than simple one-row-per-day aggregation. Sampling lets you define rolling or calendar windows, control reducers per variable, and cache repeated queries for long simulation loops.
When To Use Sampling Instead Of to_daily
Use to_daily when you want standard daily weather summaries and one row per civil day.
Use sampling when:
- the model timestep is not just daily
- you need a trailing window such as "the last 24 hourly steps"
- you need calendar windows such as current day, previous week, or current month
- different variables require different reducers
- repeated simulation queries should be cached
Sampling Workflow
- define the source weather table
- prepare a sampler object
- 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[1:6]| TimeStepTable{Atmosphere{(:date, :duration,...}(6 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 |
2. Prepare Sampler State
We can normalize transforms and enable query-level memoization with prepare_weather_sampler. This is optional but useful when the same weather is sampled many times during a simulation.
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)Atmosphere(date = DateTime("2025-01-01T02:00:00"), duration = Millisecond(7200000), T = 11.5, Wind = 1.0, P = 100.0, Rh = 0.5075, Precipitations = 0.0, Cₐ = 400.0, e = 0.6917254565512273, eₛ = 1.3627838142874376, VPD = 0.6710583577362105, ρ = 1.2238257574344642, λ = 2.4738025e6, γ = 0.0658345900883861, ε = 0.5244788008257141, Δ = 0.09042138305283774, clearness = Inf, Ri_SW_f = 115.0, Ri_PAR_f = Inf, Ri_NIR_f = Inf, Ri_TIR_f = Inf, Ri_custom_f = Inf, Rhmax = 0.51, Rhmin = 0.505, Ri_NIR_q = Inf, Ri_PAR_q = Inf, Ri_SW_q = 0.828, Ri_TIR_q = Inf, Ri_custom_q = Inf, Tmax = 12.0, Tmin = 11.0)row3.T11.5row3.Tmin11.0row3.Tmax12.0row3_cached === row3truesample_weather provides default transforms for common atmospheric variables, but you can override them when your model semantics differ.
4. Override Variable-wise Aggregation Rules
We can match aggregation logic to model semantics by overriding variable-wise aggregation rules.
custom = (
T = MeanWeighted(),
Tmax = (source = :T, reducer = MaxReducer()),
Tsum = (source = :T, reducer = SumReducer())
)
row_custom = sample_weather(prepared, 3; window = window2, transforms = custom)Atmosphere(date = DateTime("2025-01-01T02:00:00"), duration = Millisecond(7200000), T = 11.5, Wind = 1.0, P = 100.0, Rh = 0.51, Precipitations = 0.0, Cₐ = 400.0, e = 0.6946850911337292, eₛ = 1.3621276296739788, VPD = 0.6674425385402496, ρ = 1.223821981388938, λ = 2.4738025e6, γ = 0.06583457504566559, ε = 0.5248457833468954, Δ = 0.09038865037775068, clearness = Inf, Ri_SW_f = Inf, Ri_PAR_f = Inf, Ri_NIR_f = Inf, Ri_TIR_f = Inf, Ri_custom_f = Inf, Tmax = 12.0, Tsum = 23.0)row_custom.T11.5row_custom.Tmax12.0row_custom.Tsum23.05. Calendar Window Sampling
Sometimes model logic is tied to civil periods (day/week/month) rather than fixed trailing windows. In that case, use CalendarWindow to aggregate all source timesteps that fall within the same period.
window_day = CalendarWindow(:day)
day_sample = sample_weather(prepared, 5; window = window_day)Atmosphere(date = DateTime("2025-01-01T04:00:00"), duration = Millisecond(86400000), T = 21.5, Wind = 1.0, P = 100.0, Rh = 0.5575, Precipitations = 0.0, Cₐ = 400.0, e = 1.5882901485029282, eₛ = 2.7788527315526586, VPD = 1.1905625830497304, ρ = 1.1829403837798358, λ = 2.4501525e6, γ = 0.06647300839817989, ε = 0.5794989608153686, Δ = 0.16728180969800288, clearness = Inf, Ri_SW_f = 215.0, Ri_PAR_f = Inf, Ri_NIR_f = Inf, Ri_TIR_f = Inf, Ri_custom_f = Inf, Rhmax = 0.615, Rhmin = 0.5, Ri_NIR_q = Inf, Ri_PAR_q = Inf, Ri_SW_q = 18.576, Ri_TIR_q = Inf, Ri_custom_q = Inf, Tmax = 33.0, Tmin = 10.0)day_sample.T21.5day_sample.Tmin10.0day_sample.Tmax33.0day_sample.duration86400000 millisecondsWe 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.date2025-01-01T04:00:00day_sample_cached.date2025-01-01T05:00:00day_sample2.date2025-01-01T19:00:00Note 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)Dict{AbstractSamplingWindow, TimeStepTable{Atmosphere}} with 2 entries:
CalendarWindow(:day, :cu… => TimeStepTable{Atmosphere}((:date, :duration, :T,…
RollingWindow(2.0) => TimeStepTable{Atmosphere}((:date, :duration, :T,…length(tables)2length(tables[window2])48tables[window2][3].T ≈ row3.TtrueThis is typically the best approach when you perform many simulations with the same weather, for example model calibration, sensitivity analysis, or repeated scenario runs.