Model Output Analysis and Climate Change Impact Analysis#

Now that we have run MATILDA so many times we finally want to have a look at the results. In this notebook we will…

  1. …create custom data frames containing individual output variables from all ensemble members,

  2. plot the ensemble mean of these variables with a 90% confidence interval,

  3. …and create an interactive application to explore the results.

  4. …calculate a set of meterological and hydrological indicators frequently used in climate impact studies,

  5. …plot these climate change indcators in interactive applications,

  6. …and finally create summary figures for our catchment through the 21st century.

Custom dataframes#

First, we read our paths from the config.ini again and use some helper functions to convert our stored MATILDA output back into a dictionary.

Hide code cell source
from tools.helpers import pickle_to_dict, parquet_to_dict,read_yaml
import os
import configparser

# read output directory from config.ini file
config = configparser.ConfigParser()
config.read('config.ini')
dir_output = config['FILE_SETTINGS']['DIR_OUTPUT']
dir_input = config['FILE_SETTINGS']['DIR_INPUT']
settings = read_yaml(os.path.join(dir_output, 'settings.yml'))

# set the file format for storage
compact_files = config.getboolean('CONFIG','COMPACT_FILES')

print("Importing MATILDA scenarios...")

if compact_files:
    # For size:
    matilda_scenarios = parquet_to_dict(f"{dir_output}cmip6/adjusted/matilda_scenarios_parquet")
else:
    # For speed:
    matilda_scenarios = pickle_to_dict(f"{dir_output}cmip6/adjusted/matilda_scenarios.pickle")

print("Done!")
Importing MATILDA scenarios...
Done!

At the moment, the structure of the ensemble output is as follows:

To analyze all projections of a single variable, we need to rearrange the data. The custom_df_matilda() function returns a dataframe with all ensemble members for a given variable and scenario resampled to a desired frequency, e.g. the total annual runoff under SSP 2.

Hide code cell source
from tools.plots import custom_df_matilda
import pandas as pd

# Application example:
print('Total Annual Runoff Projections across Ensemble Members:\n')
matilda_SSP2 = custom_df_matilda(matilda_scenarios, 'SSP2', 'total_runoff', 'YE')

print(matilda_SSP2.head())
Total Annual Runoff Projections across Ensemble Members:

            ACCESS-CM2  ACCESS-ESM1-5  BCC-CSM2-MR       CESM2  CESM2-WACCM  \
TIMESTAMP                                                                     
2000-12-31  748.845248     748.845248   748.845248  748.845248   748.845248   
2001-12-31  730.107694     730.107694   730.107694  730.107694   730.107694   
2002-12-31  721.196245     721.196245   721.196245  721.196245   721.196245   
2003-12-31  607.878225     607.878225   607.878225  607.878225   607.878225   
2004-12-31  668.633480     668.633480   668.633480  668.633480   668.633480   

            CMCC-CM2-SR5   CMCC-ESM2  CNRM-CM6-1  CNRM-ESM2-1     CanESM5  \
TIMESTAMP                                                                   
2000-12-31    748.845248  748.845248  748.845248   748.845248  748.845248   
2001-12-31    730.107694  730.107694  730.107694   730.107694  730.107694   
2002-12-31    721.196245  721.196245  721.196245   721.196245  721.196245   
2003-12-31    607.878225  607.878225  607.878225   607.878225  607.878225   
2004-12-31    668.633480  668.633480  668.633480   668.633480  668.633480   

            ...  KACE-1-0-G  MIROC-ES2L      MIROC6  MPI-ESM1-2-HR  \
TIMESTAMP   ...                                                      
2000-12-31  ...  748.845248  748.845248  748.845248     748.845248   
2001-12-31  ...  730.107694  730.107694  730.107694     730.107694   
2002-12-31  ...  721.196245  721.196245  721.196245     721.196245   
2003-12-31  ...  607.878225  607.878225  607.878225     607.878225   
2004-12-31  ...  668.633480  668.633480  668.633480     668.633480   

            MPI-ESM1-2-LR  MRI-ESM2-0       NESM3  NorESM2-MM     TaiESM1  \
TIMESTAMP                                                                   
2000-12-31     748.845248  748.845248  748.845248  748.845248  748.845248   
2001-12-31     730.107694  730.107694  730.107694  730.107694  730.107694   
2002-12-31     721.196245  721.196245  721.196245  721.196245  721.196245   
2003-12-31     607.878225  607.878225  607.878225  607.878225  607.878225   
2004-12-31     668.633480  668.633480  668.633480  668.633480  668.633480   

            UKESM1-0-LL  
TIMESTAMP                
2000-12-31   748.845248  
2001-12-31   730.107694  
2002-12-31   721.196245  
2003-12-31   607.878225  
2004-12-31   668.633480  

[5 rows x 31 columns]

Plot ensemble mean with confidence interval#

Showing curves for every individual ensemble member in one figure gets confusing. A standard way to visualize this is to plot the mean (or median) across the ensemble with a confidence interval. We choose a 95% confidence interval, meaning that based on this sample of 31 climate models, there is a 95% probability that the “true” mean lies within this interval. For that we are using the confidence_interval() function.

© Stefanie Owens @ Medium.com

Hide code cell source
from tools.helpers import confidence_interval

confidence_interval = confidence_interval(matilda_SSP2)
print('\n95% Confidence Intervals for Total Annual Runoff Projections:\n')
print(confidence_interval)
95% Confidence Intervals for Total Annual Runoff Projections:

                  mean    ci_lower    ci_upper
TIMESTAMP                                     
2000-12-31  748.845248  748.845248  748.845248
2001-12-31  730.107694  730.107694  730.107694
2002-12-31  721.196245  721.196245  721.196245
2003-12-31  607.878225  607.878225  607.878225
2004-12-31  668.633480  668.633480  668.633480
...                ...         ...         ...
2096-12-31  563.655730  527.077593  600.233868
2097-12-31  581.046050  552.587005  609.505096
2098-12-31  599.502416  567.452945  631.551887
2099-12-31  586.305365  545.603933  627.006796
2100-12-31  602.892637  569.832498  635.952776

[101 rows x 3 columns]

We are going to use the plotly library again to create interactive plots. For now, let’s plot total discharge over all ensemble members. You can change the variables and resampling frequency in the example at will.

Hide code cell source
from tools.plots import plot_ci_matilda

# Application example
plot_ci_matilda('total_runoff',dic=matilda_scenarios, resample_freq='YE', show=True)

Interactive plotting application#

To make the full dataset more accessible, we can integrate these figures into an interactive application using ploty.Dash. This launches a Dash server that updates the figures as you select variables and frequencies in the dropdown menus. To compare time series, you can align multiple figures in the same application. The demo application aligns four figures showing total runoff, total precipitation, runoff_from_glaciers, and glacier area by default directly in the output cell. If you want to display the complete application in a separate Jupyter tab, set display_mode='tab'.

Hide code cell source
from tools.helpers import adjust_jupyter_config
from dash import Dash
from jupyter_server import serverapp
from tools.plots import matilda_dash

# retrieve server information to find out whether it's running locally or on mybinder.org server
#adjust_jupyter_config()

app1 = Dash(__name__)
matilda_dash(app1,dic=matilda_scenarios, fig_count=4, display_mode='inLine')

port = 8051
if list(serverapp.list_running_servers()) == []:
    app1.run(port=port, jupyter_mode="external")
else:
    app1.run(port=port)
Dash app running on http://127.0.0.1:8051/
_images/dashboard_1_output.gif

Climate Change Impact Analysis#

To highlight the impacts of climate change on our catchment we can calculate a set of indicators frequently used in climate impact studies and visualize them in a Dash board as well. The calculate_indicators() function calculates the following statistics for all ensemble members in annual resolution:

  • Month with minimum/maximum precipitation

  • Timing of Peak Runoff

  • Begin, End, and Length of the melting season

  • Potential and Actual Aridity

  • Total Length of Dry Spells

  • Average Length and Frequency of Low Flow Events

  • Average Length and Frequency of High Flow Events

  • 5th Percentile of Total Runoff

  • 50th Percentile of Total Runoff

  • 95th Percentile of Total Runoff

  • Climatic Water Balance

  • SPI (Standardized Precipitation Index) and SPEI (Standardized Precipitation Evapotranspiration Index) for 1, 3, 6, 12, and 24 months

For details on these metrics check the source code.

Hide code cell source
import pandas as pd
from tools.helpers import dict_to_pickle, dict_to_parquet
from tools.indicators import calculate_indicators

print("Calculating Climate Change Indicators...")
matilda_indicators = calculate_indicators(matilda_scenarios)
print("Writing Indicators To File...")

if compact_files:
    dict_to_parquet(matilda_indicators, f"{dir_output}cmip6/adjusted/matilda_indicators_parquet")
else:
    dict_to_pickle(matilda_indicators, f"{dir_output}cmip6/adjusted/matilda_indicators_pickle")
Calculating Climate Change Indicators...
SSP2: 100%|██████████| 31/31 [00:10<00:00,  3.07it/s]
SSP5: 100%|██████████| 31/31 [00:09<00:00,  3.13it/s]
Writing Indicators To File...

Now, we create another interactive application to visualize the calculated indicators.

Hide code cell source
from tools.plots import matilda_indicators_dash
from dash import Dash
from jupyter_server import serverapp


app2 = Dash(__name__)
matilda_indicators_dash(app2, matilda_indicators)

port = 8052
if list(serverapp.list_running_servers()) == []:
    app2.run(port=port, jupyter_mode="external")
else:
    app2.run(port=port)
Dash app running on http://127.0.0.1:8052/
_images/dashboard_2_output.gif

Matilda Summary#

While interactive applications are great to explore, they require a lot of data and a running server. Therefore, we create two summary figures to illustrate the most important results in a compact way.

The first figure shows the forcing data, the glacier area and all components of the water balance over the course of the 21st century.

Hide code cell source
from tools.plots import MatildaSummary

summary = MatildaSummary(dir_input, dir_output, settings)
summary.load_data()

summary.plot_summary(save_path=f"{dir_output}/figures/summary_ensemble.png");
total_runoff extracted for SSP2
total_runoff extracted for SSP5
actual_evaporation extracted for SSP2
actual_evaporation extracted for SSP5
total_precipitation extracted for SSP2
total_precipitation extracted for SSP5
glacier_area extracted for SSP2
glacier_area extracted for SSP5
snow_melt_on_glaciers extracted for SSP2
snow_melt_on_glaciers extracted for SSP5
ice_melt_on_glaciers extracted for SSP2
ice_melt_on_glaciers extracted for SSP5
melt_off_glaciers extracted for SSP2
melt_off_glaciers extracted for SSP5
Creating MATILDA summary plot...
Plotting glacierized area...
Plotting snow & ice melt...
Plotting runoff & precipitation...
Plotting temperature...
Creating legends...
Final formatting...
Figure saved to output//figures/summary_ensemble.png
>> DONE! <<
_images/3c8b96c979004c0c19613ca698076e02400c40864c59af392ad8a9a7f4a60885.png

The second figure summarizes the ensemble means of the key variables in two-dimensional grids. This allows to easily identify changes in the seasonal cycle over the years.

Hide code cell source
from tools.plots import plot_annual_cycles

plot_annual_cycles(matilda_scenarios, save_path=f"{dir_output}/figures/summary_gridplots.png")
total_runoff extracted for SSP2
total_runoff extracted for SSP5
snow_melt_on_glaciers extracted for SSP2
snow_melt_on_glaciers extracted for SSP5
ice_melt_on_glaciers extracted for SSP2
ice_melt_on_glaciers extracted for SSP5
melt_off_glaciers extracted for SSP2
melt_off_glaciers extracted for SSP5
actual_evaporation extracted for SSP2
actual_evaporation extracted for SSP5
Figure saved to output//figures/summary_gridplots.png
_images/626842a93a200f7ea055e304cb8013f5781c5d2f892c62bd7467eac62d91e70c.png

Finish line#

Congratulations, you made it till the end! We reward you with a virtual swim in the beautiful Issyk-Kul!

_images/issykul_swim.JPG

© Phillip Schuster

You can now explore your results, go back to refine your calibration or close this book for good.

Thanks for sticking with us and please get in touch if you like! Cheers!