Estimating grain yield using PRFT¶
Here we use Photosynthesis Reduction Factor function with different cardinal temperature combinations with and without VPD stress function for the period from heading to maturity (grain filling period).
Authors:
- Azam Lashkari (CIMMYT-China, azam.lashkari@jic.ac.uk)
- Urs Christoph Schulthess (CIMMYT-China, U.Schulthess@cgiar.org)
- Ernesto Giron Echeverry (Independent Researcher, e.giron.e@gmail.com)
Last updated: September 28, 2023
Load libraries¶
import os, sys, gc
import pandas as pd
#import datetime as dt
pd.set_option('display.max_columns', None)
# Import Temperature Response Functions package
import tfunct
from tfunct.data import load_dataset # Function to load existing dataset
print(tfunct.__version__)
tfunct version 1.0.0
Load Phenology, NDVI and Weather example datasets¶
# Load example dataset (Phenology, NDVI and Weather data for each site)
data = load_dataset()
# Display available datasets
print(data.keys()) # ['Pheno', 'NDVI', 'Weather']
print(data['Pheno'].shape, data['NDVI'].shape, data['Weather'].shape)
dict_keys(['Pheno', 'NDVI', 'Weather']) (50, 10) (436, 11) (246702, 14)
Display phenology¶
data['Pheno'].head()
country | location | loc_code | lat | lon | cycle | SowingDateQC | Days_To_Heading | Days_To_Maturity | ObsYield | |
---|---|---|---|---|---|---|---|---|---|---|
0 | Nepal | Bhairahawa | BHR | 27.500 | 83.450 | 2019 | 2018-11-26 | 89 | 122 | 2.96685 |
1 | Pakistan | Faisalabad | FAS | 31.398 | 73.054 | 2014 | 2013-11-23 | 95 | 139 | 4.88205 |
2 | Pakistan | Faisalabad | FAS | 31.398 | 73.054 | 2015 | 2014-12-03 | 77 | 121 | 3.59100 |
3 | Pakistan | Faisalabad | FAS | 31.398 | 73.054 | 2016 | 2015-12-07 | 89 | 133 | 2.86425 |
4 | Pakistan | Faisalabad | FAS | 31.398 | 73.054 | 2017 | 2016-11-21 | 94 | 138 | 3.93300 |
Display NDVI¶
data['NDVI'].head()
country | location | loc_code | lat | lon | cycle | phenotype_date | Year | Month | Day | NDVI | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | Nepal | Bhairahawa | BHR | 27.5 | 83.45 | 2019 | 2019-01-28 | 2019 | 1 | 28 | 0.73 |
1 | Nepal | Bhairahawa | BHR | 27.5 | 83.45 | 2019 | 2019-02-03 | 2019 | 2 | 3 | 0.73 |
2 | Nepal | Bhairahawa | BHR | 27.5 | 83.45 | 2019 | 2019-02-11 | 2019 | 2 | 11 | 0.75 |
3 | Nepal | Bhairahawa | BHR | 27.5 | 83.45 | 2019 | 2019-03-01 | 2019 | 3 | 1 | 0.62 |
4 | Nepal | Bhairahawa | BHR | 27.5 | 83.45 | 2019 | 2019-03-06 | 2019 | 3 | 6 | 0.57 |
Display Weather¶
AgERA5 weather dataset
data['Weather'].head()
location | Year | Month | Day | lat | lon | Precipitation [mm/d] | Relative Humidity max [%] | Relative Humidity min [%] | Shortwave Radiation [MJ/m2/d] | TemperatureMax [C] | TemperatureMin [C] | Wind Speed 2m [m/s] | Vapor Pressure Deficit max [kPa] | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Arizona | 1979 | 1 | 1 | 33.06 | -111.98 | 0.00 | 83.59 | 36.51 | 12.98 | 11.46 | 0.99 | 2.09 | 0.86 |
1 | Arizona | 1979 | 1 | 2 | 33.06 | -111.98 | 0.00 | 47.63 | 32.73 | 11.17 | 10.74 | -1.33 | 2.04 | 0.87 |
2 | Arizona | 1979 | 1 | 3 | 33.06 | -111.98 | 0.00 | 77.74 | 38.90 | 10.13 | 14.33 | 1.55 | 1.16 | 1.00 |
3 | Arizona | 1979 | 1 | 4 | 33.06 | -111.98 | 0.00 | 87.12 | 46.08 | 12.77 | 16.59 | 0.88 | 1.14 | 1.02 |
4 | Arizona | 1979 | 1 | 5 | 33.06 | -111.98 | 3.72 | 86.54 | 64.45 | 9.07 | 17.12 | 4.10 | 1.40 | 0.69 |
Create a model¶
The model contains all of the required functions to analyse the data
%%time
# Load module to create a model
from tfunct.model import Model
# ------------------------
# MODEL CONFIGURATION
# ------------------------
# Define the Path where the output data will be stored
PATH_PRJ = '/Users/ernestogiron/Desktop/TemperatureFunctions/'
RESULTS_PATH = PATH_PRJ + 'results/'
config = {
"PROJECT_PATH": PATH_PRJ,
"RESULTS_PATH": RESULTS_PATH, #'./', # Results will be put in the same folder where the model is running
}
# Parameters used by default
parameters = dict(
RUE = 3,
DRYMATTER = 0.8,
FACTOR_TON_HA = 0.01,
YIELD_FACTOR = 0.8 * 0.01,
TMIN_PERC_FACTOR = 0.25,
NDVI_lowerThreshold = 0.16,
Toptmin = 15,
Topt = 18,
Toptmax = 25,
Tmin = 9,
Tmax = 34,
Lvpd = 1,
Uvpd = 4,
SFvpd_Lthres = 0.2,
SFvpd_Uthres = 1,
)
# create model to estimate grain yield
#model = Model(config, parameters) # Use this if you change any parameter above
model = Model(config)
# Preprocess datasets
model.preprocess_raw_datasets(data)
CPU times: user 356 ms, sys: 30.8 ms, total: 387 ms Wall time: 391 ms
Prepare locations¶
%%time
# Prepare dataset to run all process in parallel using NDVIA GPU if available
sites = model.prepareData()
50it [00:01, 36.35it/s]
CPU times: user 1.33 s, sys: 66 ms, total: 1.4 s Wall time: 1.4 s
#sites[0].inputWPN
sites[0].attributes
{'country': 'Nepal', 'location': 'Bhairahawa', 'loc_code': 'BHR', 'lat': 27.5, 'lon': 83.45, 'cycle': 2019, 'Days_To_Heading': 89, 'Days_To_Maturity': 122, 'ObsYield': 2.96685, 'Sowing_date': '2018-11-26', 'Heading_date': '2019-02-23', 'Maturity_date': '2019-03-28', 'UID': 1, 'ndays_tmn_lt9': 1, 'ndays_tmx_gt34': 0, 'avg_Tdaymax': 24.578, 'avg_NDVI': 0.447, 'avg_iPAR': 0.369}
Photosynthetic Reduction Factor (PRFT)¶
Calculating grain yield using Ritchie's Temperature-based function affecting Photosynthetic Reduction Factor (PRFT)
No stress conditions¶
%%time
PRFT_noStress = model.getYield(sites=sites) #Defaults: tfun='PRFT', sites=None, is_VPDStress=False, expCSV=True
PRFT_noStress.head()
CPU times: user 746 ms, sys: 35.3 ms, total: 781 ms Wall time: 790 ms
UID | country | location | loc_code | cycle | ObsYield | RUE | TminFactor | Topt | SimYield | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | Nepal | Bhairahawa | BHR | 2019 | 2.97 | 3.0 | 0.25 | 18.0 | 2.67 |
1 | 2 | Pakistan | Faisalabad | FAS | 2014 | 4.88 | 3.0 | 0.25 | 18.0 | 4.54 |
2 | 3 | Pakistan | Faisalabad | FAS | 2015 | 3.59 | 3.0 | 0.25 | 18.0 | 3.22 |
3 | 4 | Pakistan | Faisalabad | FAS | 2016 | 2.86 | 3.0 | 0.25 | 18.0 | 3.95 |
4 | 5 | Pakistan | Faisalabad | FAS | 2017 | 3.93 | 3.0 | 0.25 | 18.0 | 4.45 |
Stressed VPD¶
%%time
PRFT_SFvpd = model.getYield(sites=sites, is_VPDStress=True)
PRFT_SFvpd.head()
CPU times: user 135 ms, sys: 4.17 ms, total: 139 ms Wall time: 139 ms
UID | country | location | loc_code | cycle | ObsYield | RUE | TminFactor | Topt | Lvpd | Uvpd | SFvpd_Lthres | SFvpd_Uthres | SimYield | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | Nepal | Bhairahawa | BHR | 2019 | 2.97 | 3.0 | 0.25 | 18.0 | 1.0 | 4.0 | 0.2 | 1.0 | 1.71 |
1 | 2 | Pakistan | Faisalabad | FAS | 2014 | 4.88 | 3.0 | 0.25 | 18.0 | 1.0 | 4.0 | 0.2 | 1.0 | 3.34 |
2 | 3 | Pakistan | Faisalabad | FAS | 2015 | 3.59 | 3.0 | 0.25 | 18.0 | 1.0 | 4.0 | 0.2 | 1.0 | 2.69 |
3 | 4 | Pakistan | Faisalabad | FAS | 2016 | 2.86 | 3.0 | 0.25 | 18.0 | 1.0 | 4.0 | 0.2 | 1.0 | 2.44 |
4 | 5 | Pakistan | Faisalabad | FAS | 2017 | 3.93 | 3.0 | 0.25 | 18.0 | 1.0 | 4.0 | 0.2 | 1.0 | 2.50 |
Display Grain Yield¶
Create a figure to compare simulated grain yield against observed.
from tfunct.util import figures
dirname=os.path.join(config['RESULTS_PATH'], 'PRFT', 'Figures')
figures.chart_compareResults(df_result=PRFT_noStress, fld1="ObsYield", fld2="SimYield", alpha=.75, s=45, xy_lim=2, hue='loc_code',
loc_leg=2, ncol=2, ha='left', va='top',
title='PRFT\nNo streess condition', #xlabel='', ylabel='',
dirname=dirname, fname='PRFT_noStress',
dispScore=True, dispLegend=True, saveFig=True, showFig=True, fmt='jpg')
dirname=os.path.join(config['RESULTS_PATH'], 'PRFT', 'Figures')
figures.chart_compareResults(df_result=PRFT_SFvpd, fld1="ObsYield", fld2="SimYield", alpha=.75, s=45, xy_lim=2, hue='loc_code',
loc_leg=2, ncol=2, ha='left', va='top',
title='PRFT\nVPD streess condition', #xlabel='', ylabel='',
dirname=dirname, fname='PRFT_SFvpd',
dispScore=True, dispLegend=True, saveFig=True, showFig=True, fmt='jpg')
path_to_save_results=os.path.join(config['RESULTS_PATH'], 'PRFT')
figures.plot_corrTempFunct(cmb_noStress=PRFT_noStress, cmb_noStress_filtered=None,
cmb_SFvpd=PRFT_SFvpd, cmb_SFvpd_filtered=None,
functype='PRFT',fld1='ObsYield',fld2='SimYield',hue='location', ncol=6, s=80, alpha=0.95, xy_lim=1,
fonts_axes=10, fonts_titles=12, dispScore=True, errorbar=True, saveFig=True, showFig=True,
path_to_save_results=path_to_save_results, dirname='Figures', fname='Fig_1_errorbar', fmt='pdf')
Combinations¶
To establish the optimum temperature response for grain-filling period, you can run several models using a wide range of cardinal temperatures.
The optimum temperature for photosynthesis depends on the choosen temperature function.
PRFT combinations¶
No stress conditions¶
%%time
functype='PRFT'
isVPDStress=False
df_GYield, data_input, cols = model.setup_dataInput_forCombinations(sites) # Setup input data
# Combinations
RUE = [3.0] #[2.8, 2.9, 3.0, 3.1, 3.2]
Topt = [x for x in range(15, 26)]
TminFactor = [0.25] #[0.0, 0.1, 0.2, 0.25, 0.3, 0.4, 0.5]
# No stress conditions
array_params_to_run, array_results = model.getCombinations(functype=functype, cols=cols, RUE=RUE, Topt=Topt,
TminFactor=TminFactor, isVPDStress=isVPDStress)
cmb_PRFT_noStress = model.getGYield_forCombinations(functype, df_GYield, data_input, array_params_to_run,
isVPDStress, array_results, saveFile=True)
cmb_PRFT_noStress
CPU times: user 274 ms, sys: 13.9 ms, total: 288 ms Wall time: 290 ms
UID | country | location | loc_code | cycle | ObsYield | RUE | TminFactor | Topt | SimYield | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | Nepal | Bhairahawa | BHR | 2019 | 2.97 | 3.0 | 0.25 | 15.0 | 2.36 |
1 | 2 | Pakistan | Faisalabad | FAS | 2014 | 4.88 | 3.0 | 0.25 | 15.0 | 4.16 |
2 | 3 | Pakistan | Faisalabad | FAS | 2015 | 3.59 | 3.0 | 0.25 | 15.0 | 3.02 |
3 | 4 | Pakistan | Faisalabad | FAS | 2016 | 2.86 | 3.0 | 0.25 | 15.0 | 3.34 |
4 | 5 | Pakistan | Faisalabad | FAS | 2017 | 3.93 | 3.0 | 0.25 | 15.0 | 3.89 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
545 | 46 | India | Pusa | PUS-LS | 2018 | 1.44 | 3.0 | 0.25 | 25.0 | 2.28 |
546 | 47 | India | Pusa | PUS-LS | 2019 | 3.10 | 3.0 | 0.25 | 25.0 | 4.75 |
547 | 48 | India | Ludhiana | LDH-NS | 2022 | 6.09 | 3.0 | 0.25 | 25.0 | 5.07 |
548 | 49 | India | Ludhiana | LDH-LS | 2022 | 4.77 | 3.0 | 0.25 | 25.0 | 3.99 |
549 | 50 | Spain | Spain | SPN | 2018 | 8.35 | 3.0 | 0.25 | 25.0 | 7.08 |
550 rows × 10 columns
No stress Metrics¶
%%time
m_PRFT_noStress = model.getCombinations_Metrics(functype, isVPDStress, df_GYield,
array_params_to_run, array_results, saveFile=True) #, fmt='parquet')
m_PRFT_noStress
100%|██████████████████████████████████████████████████████████████████████████████████████████████| 11/11 [00:30<00:00, 2.79s/it]
CPU times: user 28.3 s, sys: 2.11 s, total: 30.4 s Wall time: 30.7 s
RUE | TminFactor | Topt | MAE | MSE | RMSE | RMSRE | MAPE | pvalue | R2 | EF | intercept | slope | Cb | CCC | Accuracy | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 3.0 | 0.25 | 15.0 | 0.752 | 0.923 | 0.961 | 20.672 | 17.186 | 4.414394e-15 | 0.726 | 0.641 | 0.4338 | 0.8093 | 0.96 | 0.84 | 82.81 |
1 | 3.0 | 0.25 | 16.0 | 0.692 | 0.759 | 0.871 | 20.055 | 16.308 | 1.964853e-15 | 0.735 | 0.705 | 0.5507 | 0.8355 | 0.99 | 0.87 | 83.69 |
2 | 3.0 | 0.25 | 17.0 | 0.685 | 0.728 | 0.853 | 21.322 | 16.539 | 1.660386e-15 | 0.737 | 0.717 | 0.6656 | 0.8561 | 1.00 | 0.88 | 83.46 |
3 | 3.0 | 0.25 | 18.0 | 0.736 | 0.787 | 0.887 | 23.654 | 18.217 | 2.118803e-15 | 0.734 | 0.694 | 0.7716 | 0.8723 | 0.99 | 0.87 | 81.78 |
4 | 3.0 | 0.25 | 19.0 | 0.788 | 0.905 | 0.951 | 26.438 | 19.954 | 3.803797e-15 | 0.727 | 0.648 | 0.8701 | 0.8844 | 0.98 | 0.85 | 80.05 |
5 | 3.0 | 0.25 | 20.0 | 0.853 | 1.056 | 1.028 | 29.297 | 21.957 | 9.429942e-15 | 0.717 | 0.590 | 0.9627 | 0.8917 | 0.96 | 0.83 | 78.04 |
6 | 3.0 | 0.25 | 21.0 | 0.912 | 1.213 | 1.101 | 31.889 | 23.732 | 2.729854e-14 | 0.704 | 0.529 | 1.0464 | 0.8948 | 0.94 | 0.81 | 76.27 |
7 | 3.0 | 0.25 | 22.0 | 0.958 | 1.358 | 1.165 | 34.180 | 25.195 | 8.858484e-14 | 0.689 | 0.472 | 1.1232 | 0.8934 | 0.93 | 0.79 | 74.81 |
8 | 3.0 | 0.25 | 23.0 | 0.995 | 1.481 | 1.217 | 36.113 | 26.381 | 3.282640e-13 | 0.672 | 0.424 | 1.1967 | 0.8870 | 0.92 | 0.77 | 73.62 |
9 | 3.0 | 0.25 | 24.0 | 1.018 | 1.575 | 1.255 | 37.669 | 27.214 | 1.359359e-12 | 0.652 | 0.388 | 1.2627 | 0.8761 | 0.92 | 0.76 | 72.79 |
10 | 3.0 | 0.25 | 25.0 | 1.032 | 1.632 | 1.277 | 38.734 | 27.753 | 5.840609e-12 | 0.631 | 0.366 | 1.3177 | 0.8613 | 0.92 | 0.75 | 72.25 |
stressed Vapor pressure deficit (VPD)¶
%%time
functype='PRFT'
isVPDStress=True
df_GYield, data_input, cols = model.setup_dataInput_forCombinations(sites) # Setup input data
# Combinations
RUE = [3.0]
Topt = [x for x in range(15, 26)]
TminFactor = [0.25] #[0.0, 0.1, 0.2, 0.25, 0.3, 0.4, 0.5]
Lvpd = [0.5, 1, 1.5, 2, 2.5, 3, 3.5]
Uvpd = [1, 1.5, 2, 2.5, 3, 3.5, 4]
SFvpd_Lthres = [0.2, 0.4, 0.6, 0.8]
SFvpd_Uthres = [1]
# No stress conditions
array_params_to_run, array_results = model.getCombinations(functype=functype, cols=cols, RUE=RUE, Topt=Topt, TminFactor=TminFactor,
Lvpd=Lvpd, Uvpd=Uvpd, SFvpd_Lthres=SFvpd_Lthres, SFvpd_Uthres=SFvpd_Uthres,
isVPDStress=isVPDStress)
cmb_PRFT_SFvpd = model.getGYield_forCombinations(functype, df_GYield, data_input, array_params_to_run,
isVPDStress, array_results, saveFile=True)
cmb_PRFT_SFvpd
CPU times: user 952 ms, sys: 33.9 ms, total: 986 ms Wall time: 995 ms
UID | country | location | loc_code | cycle | ObsYield | RUE | TminFactor | Topt | Lvpd | Uvpd | SFvpd_Lthres | SFvpd_Uthres | SimYield | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | Nepal | Bhairahawa | BHR | 2019 | 2.97 | 3.0 | 0.25 | 15.0 | 0.5 | 1.0 | 0.2 | 1.0 | 0.54 |
1 | 2 | Pakistan | Faisalabad | FAS | 2014 | 4.88 | 3.0 | 0.25 | 15.0 | 0.5 | 1.0 | 0.2 | 1.0 | 0.95 |
2 | 3 | Pakistan | Faisalabad | FAS | 2015 | 3.59 | 3.0 | 0.25 | 15.0 | 0.5 | 1.0 | 0.2 | 1.0 | 1.19 |
3 | 4 | Pakistan | Faisalabad | FAS | 2016 | 2.86 | 3.0 | 0.25 | 15.0 | 0.5 | 1.0 | 0.2 | 1.0 | 0.94 |
4 | 5 | Pakistan | Faisalabad | FAS | 2017 | 3.93 | 3.0 | 0.25 | 15.0 | 0.5 | 1.0 | 0.2 | 1.0 | 0.78 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
61595 | 46 | India | Pusa | PUS-LS | 2018 | 1.44 | 3.0 | 0.25 | 25.0 | 3.5 | 4.0 | 0.8 | 1.0 | 1.26 |
61596 | 47 | India | Pusa | PUS-LS | 2019 | 3.10 | 3.0 | 0.25 | 25.0 | 3.5 | 4.0 | 0.8 | 1.0 | 3.44 |
61597 | 48 | India | Ludhiana | LDH-NS | 2022 | 6.09 | 3.0 | 0.25 | 25.0 | 3.5 | 4.0 | 0.8 | 1.0 | 4.76 |
61598 | 49 | India | Ludhiana | LDH-LS | 2022 | 4.77 | 3.0 | 0.25 | 25.0 | 3.5 | 4.0 | 0.8 | 1.0 | 3.31 |
61599 | 50 | Spain | Spain | SPN | 2018 | 8.35 | 3.0 | 0.25 | 25.0 | 3.5 | 4.0 | 0.8 | 1.0 | 6.90 |
61600 rows × 14 columns
%%time
m_PRFT_SFvpd = model.getCombinations_Metrics(functype, isVPDStress, df_GYield,
array_params_to_run, array_results, saveFile=True)
m_PRFT_SFvpd
100%|████████████████████████████████████████████████████████████████████████████████████████| 1232/1232 [00:00<00:00, 1372.88it/s]
CPU times: user 744 ms, sys: 1.02 s, total: 1.76 s Wall time: 910 ms
RUE | TminFactor | Topt | Lvpd | Uvpd | SFvpd_Lthres | SFvpd_Uthres | MAE | MSE | RMSE | RMSRE | MAPE | pvalue | R2 | EF | intercept | slope | Cb | CCC | Accuracy | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 3.0 | 0.25 | 15.0 | 0.5 | 1.0 | 0.2 | 1.0 | 3.522 | 14.317 | 3.784 | 77.338 | 76.465 | 5.004774e-05 | 0.293 | -4.566 | 0.1838 | 0.1904 | 0.12 | 0.07 | 23.53 |
1 | 3.0 | 0.25 | 15.0 | 0.5 | 1.0 | 0.4 | 1.0 | 2.748 | 8.912 | 2.985 | 60.492 | 59.236 | 9.871293e-10 | 0.544 | -2.464 | 0.2445 | 0.3462 | 0.22 | 0.17 | 40.76 |
2 | 3.0 | 0.25 | 15.0 | 0.5 | 1.0 | 0.6 | 1.0 | 1.976 | 4.875 | 2.208 | 44.225 | 42.019 | 1.148104e-12 | 0.655 | -0.895 | 0.3118 | 0.5008 | 0.42 | 0.35 | 57.98 |
3 | 3.0 | 0.25 | 15.0 | 0.5 | 1.0 | 0.8 | 1.0 | 1.237 | 2.213 | 1.488 | 29.692 | 25.959 | 3.544116e-14 | 0.701 | 0.140 | 0.3757 | 0.6561 | 0.72 | 0.62 | 74.04 |
4 | 3.0 | 0.25 | 15.0 | 0.5 | 1.5 | 0.2 | 1.0 | 3.142 | 11.904 | 3.450 | 71.356 | 68.675 | 4.596118e-04 | 0.228 | -3.627 | 0.0768 | 0.2972 | 0.24 | 0.12 | 31.33 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
1227 | 3.0 | 0.25 | 25.0 | 3.0 | 4.0 | 0.8 | 1.0 | 0.756 | 0.927 | 0.963 | 26.268 | 19.119 | 5.743903e-13 | 0.665 | 0.640 | 0.9409 | 0.7550 | 0.99 | 0.82 | 80.88 |
1228 | 3.0 | 0.25 | 25.0 | 3.5 | 4.0 | 0.2 | 1.0 | 0.822 | 1.185 | 1.088 | 27.784 | 20.216 | 1.095611e-10 | 0.584 | 0.539 | 1.0992 | 0.7310 | 1.00 | 0.78 | 79.78 |
1229 | 3.0 | 0.25 | 25.0 | 3.5 | 4.0 | 0.4 | 1.0 | 0.759 | 0.959 | 0.979 | 26.979 | 19.394 | 2.090076e-12 | 0.646 | 0.627 | 1.0919 | 0.7566 | 1.00 | 0.82 | 80.61 |
1230 | 3.0 | 0.25 | 25.0 | 3.5 | 4.0 | 0.6 | 1.0 | 0.710 | 0.863 | 0.929 | 27.223 | 18.864 | 1.524712e-13 | 0.682 | 0.665 | 1.0818 | 0.7830 | 1.00 | 0.84 | 81.14 |
1231 | 3.0 | 0.25 | 25.0 | 3.5 | 4.0 | 0.8 | 1.0 | 0.747 | 0.895 | 0.946 | 28.420 | 19.816 | 9.480468e-14 | 0.689 | 0.652 | 1.0718 | 0.8093 | 0.99 | 0.84 | 80.18 |
1232 rows × 20 columns
Display grain yield comparison with and without VPD stress¶
path_to_save_results=os.path.join(config['RESULTS_PATH'], 'PRFT')
figures.plot_corrTempFunct(cmb_noStress=cmb_PRFT_noStress, cmb_noStress_filtered=cmb_PRFT_noStress,
cmb_SFvpd=cmb_PRFT_SFvpd, cmb_SFvpd_filtered=cmb_PRFT_SFvpd,
functype='PRFT',fld1='ObsYield',fld2='SimYield',hue='location', ncol=6, s=20, alpha=0.65, xy_lim=1,
fonts_axes=10, fonts_titles=12, dispScore=True, errorbar=False, saveFig=True, showFig=True,
path_to_save_results=path_to_save_results, dirname='Figures', fname='Fig_2_nofilters', fmt='jpg')
path_to_save_results=os.path.join(config['RESULTS_PATH'], 'PRFT')
figures.plot_corrTempFunct(cmb_noStress=cmb_PRFT_noStress, cmb_noStress_filtered=cmb_PRFT_noStress,
cmb_SFvpd=cmb_PRFT_SFvpd, cmb_SFvpd_filtered=cmb_PRFT_SFvpd,
functype='PRFT',fld1='ObsYield',fld2='SimYield',hue='location', ncol=6, s=80, alpha=0.95, xy_lim=1,
fonts_axes=10, fonts_titles=12, dispScore=True, errorbar=True, saveFig=True, showFig=True,
path_to_save_results=path_to_save_results, dirname='Figures', fname='Fig_2_nofilters_errorbar', fmt='jpg')
Select the best results using filters¶
After running several models you can filter results to see which one look better.
The following table is an example of one filter used to evaluate the best models, it only use three (3) evaluation metrics (Cb, CCC, and RMSRE) to select the top ones.
Temperature Function | Cb | CCC | RMSRE | |
---|---|---|---|---|
PRFT | 0.98 | > 0.8 | ||
PRFT vpd stress | 0.98 | > 0.8 | < 22 |
# Load module to filter combinations in an easy way
from tfunct.util import filterSimulations
filter PRFT manually¶
m_PRFT_noStress[(m_PRFT_noStress['Cb']>0.98) & (m_PRFT_noStress['CCC']>0.80) ]
RUE | TminFactor | Topt | MAE | MSE | RMSE | RMSRE | MAPE | pvalue | R2 | EF | intercept | slope | Cb | CCC | Accuracy | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | 3.0 | 0.25 | 16.0 | 0.692 | 0.759 | 0.871 | 20.055 | 16.308 | 1.964853e-15 | 0.735 | 0.705 | 0.5507 | 0.8355 | 0.99 | 0.87 | 83.69 |
2 | 3.0 | 0.25 | 17.0 | 0.685 | 0.728 | 0.853 | 21.322 | 16.539 | 1.660386e-15 | 0.737 | 0.717 | 0.6656 | 0.8561 | 1.00 | 0.88 | 83.46 |
3 | 3.0 | 0.25 | 18.0 | 0.736 | 0.787 | 0.887 | 23.654 | 18.217 | 2.118803e-15 | 0.734 | 0.694 | 0.7716 | 0.8723 | 0.99 | 0.87 | 81.78 |
m_PRFT_SFvpd[(m_PRFT_SFvpd['Cb']>0.98) & (m_PRFT_SFvpd['CCC']>0.80) & (m_PRFT_SFvpd['RMSRE']<22)]
RUE | TminFactor | Topt | Lvpd | Uvpd | SFvpd_Lthres | SFvpd_Uthres | MAE | MSE | RMSE | RMSRE | MAPE | pvalue | R2 | EF | intercept | slope | Cb | CCC | Accuracy | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
551 | 3.0 | 0.25 | 19.0 | 3.0 | 3.5 | 0.8 | 1.0 | 0.689 | 0.755 | 0.869 | 21.910 | 16.914 | 5.709861e-15 | 0.723 | 0.706 | 0.7573 | 0.8061 | 1.00 | 0.86 | 83.09 |
651 | 3.0 | 0.25 | 20.0 | 2.5 | 3.0 | 0.8 | 1.0 | 0.674 | 0.758 | 0.871 | 21.581 | 16.520 | 3.880880e-15 | 0.727 | 0.705 | 0.7142 | 0.8024 | 0.99 | 0.86 | 83.48 |
859 | 3.0 | 0.25 | 22.0 | 2.0 | 2.5 | 0.8 | 1.0 | 0.646 | 0.732 | 0.856 | 20.753 | 15.672 | 1.853433e-15 | 0.735 | 0.715 | 0.7631 | 0.7889 | 0.99 | 0.86 | 84.33 |
971 | 3.0 | 0.25 | 23.0 | 2.0 | 2.5 | 0.8 | 1.0 | 0.661 | 0.740 | 0.860 | 21.571 | 16.249 | 3.514474e-15 | 0.728 | 0.712 | 0.8163 | 0.7835 | 0.99 | 0.86 | 83.75 |
No stress conditions¶
%%time
# path of the combinations and metrics files
c_PRFT = os.path.join(config['RESULTS_PATH'], "PRFT", "PRFT_noStress", "combinations_Yield_PRFT_noStress.parquet")
m_PRFT = os.path.join(config['RESULTS_PATH'], "PRFT", "PRFT_noStress", "metrics_Yield_PRFT_noStress.parquet")
# Filters
cmb_filters = dict(
RUE = ('RUE', '=', 3.0 ),
TminFactor = ('TminFactor', '=', 0.25 )
)
met_filters = dict(
Cb = ('>', 0.98),
CCC = ('>', 0.80)
)
outputPath = os.path.join(config['RESULTS_PATH'], "PRFT")
# Use pdFormat=True to display filtered table. Use when the results are small to avoid freeze you computer session
# Use fmt='parquet' and avoid fmt='csv' when the results are quite large.
# Use dispFig=True if the filtered table is small.
filtered_c_PRFT = filterSimulations(functype='PRFT', VPDstress=False, cmb=c_PRFT, met=m_PRFT,
cmb_filters=cmb_filters, met_filters=met_filters, pdFormat=True,
dispFig=True, saveFig=True, figname='Fig_topComb_avgYield', figfmt='pdf',
saveResults=True, outputPath=outputPath, fmt='parquet')
filtered_c_PRFT
Loading combinations... Loading metrics... 11 combinations found 3 combinations found after applied filter Filtered data set saved at /Users/ernestogiron/Desktop/TemperatureFunctions/results/PRFT/PRFT_filtered/filtered_metrics_combinations_Yield_PRFT_noStress_20231001.parquet CPU times: user 1.17 s, sys: 130 ms, total: 1.3 s Wall time: 677 ms
UID | country | location | loc_code | cycle | ObsYield | RUE | TminFactor | Topt | SimYield | MAE | MSE | RMSE | RMSRE | MAPE | pvalue | R2 | EF | intercept | slope | Cb | CCC | Accuracy | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | Nepal | Bhairahawa | BHR | 2019 | 2.97 | 3.0 | 0.25 | 16.0 | 2.48 | 0.692 | 0.759 | 0.871 | 20.055 | 16.308 | 1.964853e-15 | 0.735 | 0.705 | 0.5507 | 0.8355 | 0.99 | 0.87 | 83.69 |
1 | 2 | Pakistan | Faisalabad | FAS | 2014 | 4.88 | 3.0 | 0.25 | 16.0 | 4.31 | 0.692 | 0.759 | 0.871 | 20.055 | 16.308 | 1.964853e-15 | 0.735 | 0.705 | 0.5507 | 0.8355 | 0.99 | 0.87 | 83.69 |
2 | 3 | Pakistan | Faisalabad | FAS | 2015 | 3.59 | 3.0 | 0.25 | 16.0 | 3.10 | 0.692 | 0.759 | 0.871 | 20.055 | 16.308 | 1.964853e-15 | 0.735 | 0.705 | 0.5507 | 0.8355 | 0.99 | 0.87 | 83.69 |
3 | 4 | Pakistan | Faisalabad | FAS | 2016 | 2.86 | 3.0 | 0.25 | 16.0 | 3.57 | 0.692 | 0.759 | 0.871 | 20.055 | 16.308 | 1.964853e-15 | 0.735 | 0.705 | 0.5507 | 0.8355 | 0.99 | 0.87 | 83.69 |
4 | 5 | Pakistan | Faisalabad | FAS | 2017 | 3.93 | 3.0 | 0.25 | 16.0 | 4.10 | 0.692 | 0.759 | 0.871 | 20.055 | 16.308 | 1.964853e-15 | 0.735 | 0.705 | 0.5507 | 0.8355 | 0.99 | 0.87 | 83.69 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
145 | 46 | India | Pusa | PUS-LS | 2018 | 1.44 | 3.0 | 0.25 | 18.0 | 1.64 | 0.736 | 0.787 | 0.887 | 23.654 | 18.217 | 2.118803e-15 | 0.734 | 0.694 | 0.7716 | 0.8723 | 0.99 | 0.87 | 81.78 |
146 | 47 | India | Pusa | PUS-LS | 2019 | 3.10 | 3.0 | 0.25 | 18.0 | 3.45 | 0.736 | 0.787 | 0.887 | 23.654 | 18.217 | 2.118803e-15 | 0.734 | 0.694 | 0.7716 | 0.8723 | 0.99 | 0.87 | 81.78 |
147 | 48 | India | Ludhiana | LDH-NS | 2022 | 6.09 | 3.0 | 0.25 | 18.0 | 4.48 | 0.736 | 0.787 | 0.887 | 23.654 | 18.217 | 2.118803e-15 | 0.734 | 0.694 | 0.7716 | 0.8723 | 0.99 | 0.87 | 81.78 |
148 | 49 | India | Ludhiana | LDH-LS | 2022 | 4.77 | 3.0 | 0.25 | 18.0 | 2.90 | 0.736 | 0.787 | 0.887 | 23.654 | 18.217 | 2.118803e-15 | 0.734 | 0.694 | 0.7716 | 0.8723 | 0.99 | 0.87 | 81.78 |
149 | 50 | Spain | Spain | SPN | 2018 | 8.35 | 3.0 | 0.25 | 18.0 | 7.32 | 0.736 | 0.787 | 0.887 | 23.654 | 18.217 | 2.118803e-15 | 0.734 | 0.694 | 0.7716 | 0.8723 | 0.99 | 0.87 | 81.78 |
150 rows × 23 columns
VPD stress conditions¶
%%time
# Path of the combinations and metrics files
c_PRFT_SFvpd = os.path.join(config['RESULTS_PATH'], "PRFT", "PRFT_SFvpd", "combinations_Yield_PRFT_SFvpd.parquet")
m_PRFT_SFvpd = os.path.join(config['RESULTS_PATH'], "PRFT", "PRFT_SFvpd", "metrics_Yield_PRFT_SFvpd.parquet")
cmb_filters = dict(
RUE = ('RUE', '=', 3.0 ),
TminFactor = ('TminFactor', '=', 0.25 )
)
met_filters = dict(
Cb = ('>', 0.98),
CCC = ('>', 0.80)
)
outputPath = os.path.join(config['RESULTS_PATH'], "PRFT")
# Use pdFormat=True to display filtered table. Use when the results are small to avoid freeze you computer session
# Use fmt='parquet' and avoid fmt='csv' when the results are quite large.
# Use dispFig=True if the filtered table is small.
filtered_c_PRFT_SFvpd = filterSimulations(functype='PRFT', VPDstress=True, cmb=c_PRFT_SFvpd, met=m_PRFT_SFvpd,
cmb_filters=cmb_filters, met_filters=met_filters, pdFormat=True,
dispFig=True, saveFig=True, figname='Fig_topComb_avgYield', figfmt='pdf',
saveResults=True, outputPath=outputPath, fmt='parquet')
filtered_c_PRFT_SFvpd
Loading combinations... Loading metrics... 1232 combinations found 51 combinations found after applied filter Filtered data set saved at /Users/ernestogiron/Desktop/TemperatureFunctions/results/PRFT/PRFT_filtered/filtered_metrics_combinations_Yield_PRFT_SFvpd_20231001.parquet CPU times: user 1.25 s, sys: 151 ms, total: 1.4 s Wall time: 784 ms
UID | country | location | loc_code | cycle | ObsYield | RUE | TminFactor | Topt | Lvpd | Uvpd | SFvpd_Lthres | SFvpd_Uthres | SimYield | MAE | MSE | RMSE | RMSRE | MAPE | pvalue | R2 | EF | intercept | slope | Cb | CCC | Accuracy | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | Nepal | Bhairahawa | BHR | 2019 | 2.97 | 3.0 | 0.25 | 18.0 | 3.5 | 4.0 | 0.6 | 1.0 | 2.47 | 0.778 | 0.954 | 0.977 | 23.351 | 18.737 | 2.337009e-13 | 0.677 | 0.629 | 0.6547 | 0.8021 | 0.99 | 0.83 | 81.26 |
1 | 2 | Pakistan | Faisalabad | FAS | 2014 | 4.88 | 3.0 | 0.25 | 18.0 | 3.5 | 4.0 | 0.6 | 1.0 | 4.48 | 0.778 | 0.954 | 0.977 | 23.351 | 18.737 | 2.337009e-13 | 0.677 | 0.629 | 0.6547 | 0.8021 | 0.99 | 0.83 | 81.26 |
2 | 3 | Pakistan | Faisalabad | FAS | 2015 | 3.59 | 3.0 | 0.25 | 18.0 | 3.5 | 4.0 | 0.6 | 1.0 | 3.22 | 0.778 | 0.954 | 0.977 | 23.351 | 18.737 | 2.337009e-13 | 0.677 | 0.629 | 0.6547 | 0.8021 | 0.99 | 0.83 | 81.26 |
3 | 4 | Pakistan | Faisalabad | FAS | 2016 | 2.86 | 3.0 | 0.25 | 18.0 | 3.5 | 4.0 | 0.6 | 1.0 | 3.58 | 0.778 | 0.954 | 0.977 | 23.351 | 18.737 | 2.337009e-13 | 0.677 | 0.629 | 0.6547 | 0.8021 | 0.99 | 0.83 | 81.26 |
4 | 5 | Pakistan | Faisalabad | FAS | 2017 | 3.93 | 3.0 | 0.25 | 18.0 | 3.5 | 4.0 | 0.6 | 1.0 | 4.01 | 0.778 | 0.954 | 0.977 | 23.351 | 18.737 | 2.337009e-13 | 0.677 | 0.629 | 0.6547 | 0.8021 | 0.99 | 0.83 | 81.26 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
2545 | 46 | India | Pusa | PUS-LS | 2018 | 1.44 | 3.0 | 0.25 | 25.0 | 3.5 | 4.0 | 0.8 | 1.0 | 1.26 | 0.747 | 0.895 | 0.946 | 28.420 | 19.816 | 9.480468e-14 | 0.689 | 0.652 | 1.0718 | 0.8093 | 0.99 | 0.84 | 80.18 |
2546 | 47 | India | Pusa | PUS-LS | 2019 | 3.10 | 3.0 | 0.25 | 25.0 | 3.5 | 4.0 | 0.8 | 1.0 | 3.44 | 0.747 | 0.895 | 0.946 | 28.420 | 19.816 | 9.480468e-14 | 0.689 | 0.652 | 1.0718 | 0.8093 | 0.99 | 0.84 | 80.18 |
2547 | 48 | India | Ludhiana | LDH-NS | 2022 | 6.09 | 3.0 | 0.25 | 25.0 | 3.5 | 4.0 | 0.8 | 1.0 | 4.76 | 0.747 | 0.895 | 0.946 | 28.420 | 19.816 | 9.480468e-14 | 0.689 | 0.652 | 1.0718 | 0.8093 | 0.99 | 0.84 | 80.18 |
2548 | 49 | India | Ludhiana | LDH-LS | 2022 | 4.77 | 3.0 | 0.25 | 25.0 | 3.5 | 4.0 | 0.8 | 1.0 | 3.31 | 0.747 | 0.895 | 0.946 | 28.420 | 19.816 | 9.480468e-14 | 0.689 | 0.652 | 1.0718 | 0.8093 | 0.99 | 0.84 | 80.18 |
2549 | 50 | Spain | Spain | SPN | 2018 | 8.35 | 3.0 | 0.25 | 25.0 | 3.5 | 4.0 | 0.8 | 1.0 | 6.90 | 0.747 | 0.895 | 0.946 | 28.420 | 19.816 | 9.480468e-14 | 0.689 | 0.652 | 1.0718 | 0.8093 | 0.99 | 0.84 | 80.18 |
2550 rows × 27 columns
path_to_save_results=os.path.join(config['RESULTS_PATH'],'PRFT', 'PRFT_filtered')
figures.plot_corrTempFunct(cmb_noStress=filtered_c_PRFT, cmb_noStress_filtered=filtered_c_PRFT,
cmb_SFvpd=filtered_c_PRFT_SFvpd, cmb_SFvpd_filtered=filtered_c_PRFT_SFvpd,
functype='PRFT',fld1='ObsYield',fld2='SimYield',hue='location', ncol=6, s=40, alpha=0.95, xy_lim=1,
fonts_axes=10, fonts_titles=12, dispScore=True, errorbar=False, saveFig=True, showFig=True,
path_to_save_results=path_to_save_results, dirname='Figures', fname='Fig_2_filtered', fmt='jpg')
path_to_save_results=os.path.join(config['RESULTS_PATH'], 'PRFT', 'PRFT_filtered')
figures.plot_corrTempFunct(cmb_noStress=filtered_c_PRFT, cmb_noStress_filtered=filtered_c_PRFT,
cmb_SFvpd=filtered_c_PRFT_SFvpd, cmb_SFvpd_filtered=filtered_c_PRFT_SFvpd,
functype='PRFT',fld1='ObsYield',fld2='SimYield',hue='location', ncol=6, s=80, alpha=0.95, xy_lim=1,
fonts_axes=10, fonts_titles=12, dispScore=True, errorbar=True, saveFig=True, showFig=True,
path_to_save_results=path_to_save_results, dirname='Figures', fname='Fig_2_filtered_errorbar', fmt='jpg')
Comparison of the best combinations¶
No Stress condition¶
# Prepare data as a input for create comparison figures by site and/or best combinations
path_to_save_results=os.path.join(config['RESULTS_PATH'], 'PRFT', 'PRFT_filtered')
df_TDays_vs_TempResponse, df_TDays_vs_TempResponse_mean_allSites = \
figures.createFigure_Type_I_PRFT(sites, cmb=filtered_c_PRFT, roundVal=3, maxTDay=40, saveTable=True,
path_to_save_results=path_to_save_results, fmt='parquet')
df_TDays_vs_TempResponse.head()
100%|██████████████████████████████████████████████████████████████████████████████████████████████| 50/50 [00:01<00:00, 40.26it/s]
Calculating average value for all simulations...
100%|████████████████████████████████████████████████████████████████████████████████████████████████| 3/3 [00:01<00:00, 2.88it/s]
TDay | TempResponse | UID | country | location | loc_code | cycle | RUE | TminFactor | Topt | ObsYield | SimYield | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0.0 | 0.360 | 1 | Nepal | Bhairahawa | BHR | 2019 | 3.0 | 0.25 | 16.0 | 2.97 | 2.576667 |
1 | 0.1 | 0.368 | 1 | Nepal | Bhairahawa | BHR | 2019 | 3.0 | 0.25 | 16.0 | 2.97 | 2.576667 |
2 | 0.2 | 0.376 | 1 | Nepal | Bhairahawa | BHR | 2019 | 3.0 | 0.25 | 16.0 | 2.97 | 2.576667 |
3 | 0.3 | 0.384 | 1 | Nepal | Bhairahawa | BHR | 2019 | 3.0 | 0.25 | 16.0 | 2.97 | 2.576667 |
4 | 0.4 | 0.392 | 1 | Nepal | Bhairahawa | BHR | 2019 | 3.0 | 0.25 | 16.0 | 2.97 | 2.576667 |
#cmb = [cmb_PRFT_noStress['UID']==uid].reset_index(drop=True)
figures.diplay_Figure_Type_I_PRFT_InOneFig(cmb=filtered_c_PRFT, fnct='PRFT',
df_tdays=df_TDays_vs_TempResponse,
df_tdays_mean=df_TDays_vs_TempResponse_mean_allSites,
saveFig=True,showFig=True, fmt='jpg',
path_to_save_results=path_to_save_results)
100%|██████████████████████████████████████████████████████████████████████████████████████████████| 50/50 [00:24<00:00, 2.01it/s] 100%|████████████████████████████████████████████████████████████████████████████████████████████████| 3/3 [00:00<00:00, 6.12it/s]
%%time
#cmb = [cmb_PRFT_noStress['UID']==uid].reset_index(drop=True)
figures.display_FigTDay_vs_TempResponseBySiteYrs(cmb=filtered_c_PRFT, df_tdays=df_TDays_vs_TempResponse,
roundVal=2, maxTDay=40, fnct='PRFT',
saveFig=True, showFig=True, fmt='jpg', cols=5,
path_to_save_results=path_to_save_results)
100%|██████████████████████████████████████████████████████████████████████████████████████████████| 10/10 [00:43<00:00, 4.38s/it]
CPU times: user 51.3 s, sys: 1.25 s, total: 52.5 s Wall time: 52.8 s
uid = 1
df0 = filtered_c_PRFT[filtered_c_PRFT['UID']==uid].reset_index(drop=True)
figures.diplay_Figure_Type_I_PRFT_InOneFig(cmb=df0, fnct='PRFT',
df_tdays=df_TDays_vs_TempResponse,
df_tdays_mean=df_TDays_vs_TempResponse_mean_allSites,
saveFig=True,showFig=True, fmt='jpg',
path_to_save_results=path_to_save_results) #fname = "Figure_PRFT_SiteYrs_Comparison"
100%|████████████████████████████████████████████████████████████████████████████████████████████████| 3/3 [00:00<00:00, 4.62it/s]
Conclusion¶
Congratulations! You have run a simulation using a prebuilt dataset and the Temperature Functions API.
Type 1- one cardinal temperature¶
Model Parameters | No stress | VPD stress |
---|---|---|
RUE | 3.0 | 3.0 |
TminFactor | 0.25 | 0.25 |
Topt | 16-18 °C | 18-25 °C |
VPDL | 2-3.5 kPa | |
VPDU | 2.5-4.0 kPa | |
SFvpdL | 0.4-08 | |
SFvpdU | 1 |
print(filtered_c_PRFT['Topt'].unique())
print(filtered_c_PRFT_SFvpd['Topt'].sort_values().unique())
print(filtered_c_PRFT_SFvpd['Lvpd'].sort_values().unique())
print(filtered_c_PRFT_SFvpd['Uvpd'].sort_values().unique())
print(filtered_c_PRFT_SFvpd['SFvpd_Lthres'].sort_values().unique())
print(filtered_c_PRFT_SFvpd['SFvpd_Uthres'].sort_values().unique())
[16. 17. 18.] [18. 19. 20. 21. 22. 23. 24. 25.] [2. 2.5 3. 3.5] [2.5 3. 3.5 4. ] [0.4 0.6 0.8] [1.]