
Phenology Calibration with PhenoMeNals
Source:vignettes/phenology-calibration.Rmd
phenology-calibration.Rmdπ― Goal
This vignette demonstrates how to calibrate phenological parameters
for a grapevine variety and site using the
phenologyCalibration() function.
Weβll use sample datasets included in the package:
-
colliOrientali: daily weather data -
bbchSample: observed BBCH stages -
phenomenalsParameters: model parameters
π¦ These are the packages we will useβ¦.
library(phenomenals)
#> ββββ¬ β¬βββββββββββ¦βββββββββββ¬ βββ
#> β βββββ€ββ€ ββββ ββββββ€ ββββββ€β βββ
#> β© β΄ β΄ββββββββββ© β©βββββββ΄ β΄β΄βββββ
#>
library(kableExtra) #for tables
library(tidyverse) #for formatting
library(plotly) #for interactive plottingπ₯ Input parameters
1. weather_data
A data frame containing hourly or daily weather data. The function supports flexible column names (aliases are automatically detected).
| Column | Aliases (recognized) | Units | Time step | Mandatory? |
|---|---|---|---|---|
Site |
site, station, location | β | hourly/daily | Yes |
DateTime / Date
|
date, datetime, timestamp | Date (POSIXct or chr mm-dd-yyyy) |
hourly/daily | Yes |
Hour |
hour, hr | 0β23 | hourly only | Yes |
Temperature |
temp, temperature, t2m | Β°C | hourly only | Yes |
Tmax |
tmax, maxtemp, t2mmax | Β°C | daily | Yes |
Tmin |
tmin, mintemp, t2mmin | Β°C | daily | Yes |
Precipitation |
prec, rainfall, rain, prectotcorr | mm | hourly/daily | Yes |
RelativeHumidity |
rh, humidity, relhumidity | % | hourly | Optional |
RelativeHumidityMax |
rhmax, humiditymax, relhumiditymax, relativehumiditymax, hummax | % | daily | Optional |
RelativeHumidityMin |
rhmin, humiditymin, relhumiditymin, relativehumiditymin, hummin | % | daily | Optional |
WindSpeed |
wind, ws | m/s | hourly/daily | Optional |
Radiation |
rad, solar, solarrad | MJ/mΒ² (daily and hourly) | hourly/daily | Optional |
Latitude |
latitude, lat | decimal deg | hourly/daily | Yes |
Notes:
For daily data, only Tmax, Tmin, Precipitation and Latitude are strictly required.
For hourly data, only Temperature, Precipitation, Hour, DateTime and Latitude are required.
If RelativeHumidity or Radiation are missing, they are automatically estimated using Tmax, Tmin, precipitation, and latitude.
2. referenceBBCH
A data frame containing BBCH phenological observations. This dataset must include at least the following columns:
| Column | Description | Example |
|---|---|---|
Variety |
Variety name | CabernetS |
Site |
Site name (must match sites in weather_data) |
ColliOrientali |
Latitude |
Latitude of the site (decimal degrees) | 44.0 |
Longitude |
Longitude of the site (decimal degrees) | 11.0 |
Date |
Date of observation (format: yyyy-mm-dd) |
2007-04-02 |
BBCH |
BBCH growth stage (numeric code, see BBCH scale) | 8, 65 |
3. phenomenalsParameters
A nested list of model parameters. This list is typically loaded from
phenomenalsParameters
and can be customized.
Structure:
list[
species][[class]][[parameter]] = list(
min = <minimum calibration value>,
max = <maximum calibration value>,
value = <default value>,
calibration = <TRUE/FALSE>
)Notes:
Parameters marked with calibration = TRUE are optimized during calibration.
The list is automatically exported to the correct CSV format for the C# engine; you do not need to handle file conversions.
4. Other Arguments
| Argument | Description | Default |
|---|---|---|
start_year |
First year of the calibration period | 2000 |
end_year |
Last year of the calibration period | 2025 |
sites |
Site names (must match Site in
weather_data and referenceBBCH). Use
"all" to include all sites. |
"all" |
varieties |
Varieties to include (must match Variety in
referenceBBCH). Use "all" to include all
varieties. |
"all" |
iterations |
Number of iterations for the simplex algorithm | 100 |
timestep |
Time step of weather_data (βdailyβ or
βhourlyβ) |
"daily" |
π Inspect the inputs
kable(head(colliOrientali), caption = "Example weather data") %>%
kable_styling(font_size = 10)| Site | Date | Tmax | Tmin | Precipitation | WindSpeed | RelativeHumidityMax | RelativeHumidityMin | Radiation | Latitude | Longitude |
|---|---|---|---|---|---|---|---|---|---|---|
| ColliOrientali | 1/1/2004 | 10.00 | 3.88 | 0 | 4.606482 | 78 | 53 | 4.954 | 45.64986 | 12.44986 |
| ColliOrientali | 1/2/2004 | 7.01 | 2.64 | 0 | 5.162037 | 63 | 49 | 3.285 | 45.64986 | 12.44986 |
| ColliOrientali | 1/3/2004 | 5.55 | -0.71 | 0 | 6.516204 | 49 | 33 | 5.702 | 45.64986 | 12.44986 |
| ColliOrientali | 1/4/2004 | 3.65 | -4.57 | 0 | 6.203704 | 55 | 32 | 6.393 | 45.64986 | 12.44986 |
| ColliOrientali | 1/5/2004 | 3.37 | -5.42 | 0 | 4.050926 | 62 | 28 | 6.082 | 45.64986 | 12.44986 |
| ColliOrientali | 1/6/2004 | 3.21 | -3.16 | 0 | 7.187500 | 60 | 39 | 5.486 | 45.64986 | 12.44986 |
kable(head(bbchSample), caption = "Example BBCH data") %>%
kable_styling(font_size = 10)| Variety | Site | Latitude | Longitude | Date | BBCH |
|---|---|---|---|---|---|
| Carmenere | ColliOrientali | 44 | 11 | 2007-04-01 | 8 |
| Carmenere | ColliOrientali | 44 | 11 | 2008-04-28 | 8 |
| Carmenere | ColliOrientali | 44 | 11 | 2009-04-19 | 8 |
| Carmenere | ColliOrientali | 44 | 11 | 2010-04-21 | 8 |
| Carmenere | ColliOrientali | 44 | 11 | 2011-04-18 | 8 |
| Carmenere | ColliOrientali | 44 | 11 | 2012-04-26 | 8 |
βοΈ Run phenologyCalibration()
calib_result <- phenologyCalibration(
weather_data = colliOrientali,
referenceBBCH = bbchSample,
phenomenalsParameters =phenomenalsParameters,
start_year = 2006,
end_year = 2012,
sites = "ColliOrientali",
varieties = "Carmenere",
iterations = 100,
timestep = "daily"
)
#> π¦οΈ Weather data written for site 'ColliOrientali' β ColliOrientali.csv
#> π’ Initiating phenology calibration...
#> β Be cool β algorithms are thinking really hard right now.
#> π· Good wine takes time, and so does this model!
#>
#> calibration running on Carmenere, ColliOrientali....
#> RMSE (days): 25.6 | β RMSE (days): 13363.6 | β RMSE (days): 15.1 | β RMSE (days): 29.4 | β RMSE (days): 13.4 | β RMSE (days): 32.9 | β RMSE (days): 9.7 | β RMSE (days): 33.7 | β RMSE (days): 42.9 | β RMSE (days): 8.4 | β RMSE (days): 24.0 | β RMSE (days): 33.5 | β RMSE (days): 30.6 | β RMSE (days): 25.9 | β RMSE (days): 8.4 | β RMSE (days): 22.8 | β RMSE (days): 22.0 | β RMSE (days): 9.5 | β RMSE (days): 18.3 | β RMSE (days): 17.7 | β RMSE (days): 15.6 | β RMSE (days): 15.7 | β RMSE (days): 10.3 | β RMSE (days): 12.6 | β RMSE (days): 11.3 | β RMSE (days): 13.8 | β RMSE (days): 9.4 | β RMSE (days): 9.6 | β RMSE (days): 14.2 | β RMSE (days): 9.7 | β RMSE (days): 9.1 | β RMSE (days): 11.0 | β RMSE (days): 12.4 | β RMSE (days): 8.5 | ββ RMSE (days): 13.1 | ββ RMSE (days): 7.8 | ββ RMSE (days): 11.4 | ββ RMSE (days): 7.9 | ββ RMSE (days): 7.6 | ββ RMSE (days): 10.4 | ββ RMSE (days): 7.9 | ββ RMSE (days): 6.8 | ββ RMSE (days): 9.2 | ββ RMSE (days): 7.5 | ββ RMSE (days): 9.4 | ββ RMSE (days): 7.5 | ββ RMSE (days): 8.4 | ββ RMSE (days): 10.0 | ββ RMSE (days): 7.1 | ββ RMSE (days): 7.6 | ββ RMSE (days): 8.4 | ββ RMSE (days): 7.4 | ββ RMSE (days): 9.5 | ββ RMSE (days): 7.1 | ββ RMSE (days): 6.5 | ββ RMSE (days): 7.2 | ββ RMSE (days): 6.3 | ββ RMSE (days): 7.3 | ββ RMSE (days): 9.0 | ββ RMSE (days): 6.4 | ββ RMSE (days): 6.9 | ββ RMSE (days): 8.4 | ββ RMSE (days): 6.8 | ββ RMSE (days): 7.4 | ββ RMSE (days): 7.6 | ββ RMSE (days): 6.4 | ββ RMSE (days): 7.4 | βββ RMSE (days): 6.8 | βββ RMSE (days): 7.4 | βββ RMSE (days): 6.6 | βββ RMSE (days): 7.0
#> | βββ RMSE (days): 6.8 | βββ RMSE (days): 6.9 | βββ RMSE (days): 6.9 | βββ RMSE (days): 6.5 | βββ RMSE (days): 6.9 | βββ RMSE (days): 6.5 | βββ RMSE (days): 7.4 | βββ RMSE (days): 6.4 | βββ RMSE (days): 7.4 | βββ RMSE (days): 6.6 | βββ RMSE (days): 6.9 | βββ RMSE (days): 6.7 | βββ RMSE (days): 7.0 | βββ RMSE (days): 6.4 | βββ RMSE (days): 6.6 | βββ RMSE (days): 6.4 | βββ RMSE (days): 6.7 | βββ RMSE (days): 6.5 | βββ RMSE (days): 6.4 | βββ RMSE (days): 6.3 | βββ RMSE (days): 6.7 | βββ RMSE (days): 6.5 | βββ RMSE (days): 6.4 | βββ RMSE (days): 6.4 | βββ RMSE (days): 6.5 | βββ RMSE (days): 6.4 | βββ RMSE (days): 6.5 | βββ RMSE (days): 6.4 | βββ RMSE (days): 6.8 | ββββ RMSE (days): 6.4 | ββββ RMSE (days): 6.5 | ββββ RMSE (days): 6.5 | ββββ RMSE (days): 6.5 | ββββ RMSE (days): 6.5 | ββββ RMSE (days): 6.4 | ββββ RMSE (days): 6.4 | ββββ RMSE (days): 6.3 | ββββ RMSE (days): 6.4 | ββββ RMSE (days): 6.3 | ββββ RMSE (days): 6.6 | ββββ RMSE (days): 6.5 | ββββ RMSE (days): 6.4 | ββββ RMSE (days): 6.4 | ββββ RMSE (days): 6.5 | ββββ RMSE (days): 6.6 | ββββ RMSE (days): 6.3 | ββββ RMSE (days): 6.5 | ββββ RMSE (days): 6.4 | ββββ RMSE (days): 6.4 | ββββ RMSE (days): 6.6 | ββββ RMSE (days): 6.5 | ββββ RMSE (days): 6.4 | ββββ RMSE (days): 6.4 | ββββ RMSE (days): 6.3 | ββββ RMSE (days): 6.4 | ββββ RMSE (days): 6.3 | ββββ RMSE (days): 6.5 | ββββ RMSE (days): 6.3 | ββββ RMSE (days): 6.5 | ββββ RMSE (days): 6.5 | ββββ RMSE (days): 6.4 | ββββ RMSE (days): 6.3 | ββββ RMSE (days): 6.5 | βββββ RMSE (days): 6.3 | βββββ RMSE (days): 6.4 | βββββ RMSE (days): 6.4 | βββββ RMSE (days): 6.3 | βββββ RMSE (days):
#> 6.4 | βββββ RMSE (days): 6.4 | βββββ RMSE (days): 6.3 | βββββ RMSE (days): 6.3 | βββββ RMSE (days): 6.3 | βββββ RMSE (days): 6.3 | βββββ RMSE (days): 6.5 | βββββ RMSE (days): 6.4 | βββββ RMSE (days): 6.3 | βββββ RMSE (days): 6.3 | βββββ RMSE (days): 6.4 | βββββ RMSE (days): 6.3 | βββββ RMSE (days): 6.2 | βββββ RMSE (days): 6.4 | βββββ RMSE (days): 6.3 | βββββ RMSE (days): 6.3 | βββββ RMSE (days): 6.3 | βββββ RMSE (days): 6.3 | βββββ RMSE (days): 6.3 | βββββ RMSE (days): 6.3 | βββββ RMSE (days): 6.3 | βββββ RMSE (days): 6.4 | βββββ RMSE (days): 6.3 | βββββ RMSE (days): 6.4 | βββββ RMSE (days): 6.3 | βββββ RMSE (days): 6.3 | βββββ RMSE (days): 6.3 | βββββ RMSE (days): 6.3 | βββββ RMSE (days): 6.3 | ββββββ RMSE (days): 6.3 | ββββββ RMSE (days): 6.3 | ββββββ RMSE (days): 6.3 | ββββββ RMSE (days): 6.3 | ββββββ RMSE (days): 6.3 | ββββββ RMSE (days): 6.3 | ββββββ RMSE (days): 6.3 | ββββββ RMSE (days): 6.4 | ββββββ RMSE (days): 6.3 | ββββββ RMSE (days): 6.2 | ββββββ RMSE (days): 6.4 | ββββββ RMSE (days): 6.3 | ββββββ RMSE (days): 6.3 | ββββββ RMSE (days): 6.3 | ββββββ RMSE (days): 6.3 | ββββββ RMSE (days): 6.4 | ββββββ RMSE (days): 6.3 | ββββββ RMSE (days): 6.3 | ββββββ
#> β
Calibration completed successfully
#> π§Ή Cleaning up temporary output files...
#> ποΈ Deleted 1 temporary output files.This will:
- Use the small demo weather and BBCH dataset
- Calibrate parameters only for Carmenere at
ColliOrientali
- Run 100 iterations (feel free to increase for real applications, the higher the iterations, the longer the time to relax a bit)
- Takes around 2-3 minutes with a single site x variety combination
π€ Inspect output object
str(calib_result, max.level = 1)
#> List of 2
#> $ parameters:'data.frame': 202 obs. of 6 variables:
#> $ phenology :'data.frame': 2557 obs. of 13 variables:This helps users see:
- Whatβs inside (parameters, phenology)
- That itβs a named list
π Calibrated parameters
filtered_params <- calib_result$parameters |>
dplyr::filter(site == "colliorientali", variety == "carmenere")
knitr::kable(filtered_params, caption = "Calibrated parameters for Carmenere at ColliOrientali")| site | variety | parameter | value | min | max |
|---|---|---|---|---|---|
| colliorientali | carmenere | bbch08 | 22.833281 | 2 | 30 |
| colliorientali | carmenere | bbch55 | 59.098016 | 41 | 75 |
| colliorientali | carmenere | bbch63 | 83.378396 | 50 | 99 |
| colliorientali | carmenere | bbch65 | 38.103748 | 30 | 60 |
| colliorientali | carmenere | bbch68 | 29.633745 | 1 | 40 |
| colliorientali | carmenere | bbch81 | 57.694229 | 40 | 80 |
| colliorientali | carmenere | bbch85 | 74.271681 | 70 | 80 |
| colliorientali | carmenere | chillThreshold | 7.848592 | 6 | 9 |
| colliorientali | carmenere | chillingRequirement | 122.552312 | 80 | 300 |
| colliorientali | carmenere | cycleLength | 2156.707283 | 2000 | 2800 |
All calibrated parameters are within their defined bounds. π―
However, if any parameter is very close to its minimum or maximum limit, itβs good practice to slightly expand the calibration range and re-run the process. This helps avoid boundary effects and ensures the optimization isnβt artificially constrained.
π Tip: Parameters like
bbch65orbbch85that consistently hit the edge of their range may indicate that your biological assumptions need refining, or that more calibration iterations are needed.
π Phenology curve
This is where you show the time series of predicted BBCH stages over time.
p<-ggplot(calib_result$phenology |>
mutate(Date=as.Date(Date,format='%m/%d/%Y'),
year=year(Date)),aes(x=Date)) +
geom_line(aes(y=BBCHPhase))+
geom_point(aes(y=BBCHRef))+
theme_classic()+
geom_line(aes(y=ChillState),col='blue4')
ggplotly(p)The phenologyCalibration() function successfully fits
thermal and stress-based parameters to reproduce observed BBCH stages.
This calibrated output can now be passed to
runPhenomenals() for trait modeling.