Sensitivity analysis#

import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import matplotlib.dates as mdates
from tqdm.notebook import tqdm

import sys,os
import numpy as np
import pandas as pd
# import matk
import subprocess

from pathlib import Path
from datetime import datetime
from SALib.sample import saltelli, sobol_sequence
import SALib
import logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s: %(message)s')
sys.path.append('../../')
import myfunctions.sensitivityAnalysis as sa
import myfunctions.ATSutils as ATSutils
import myfunctions.utils as utils
import myfunctions.colors as mcolors
import myfunctions.general_plots as gp
def cumsum(df_simu, df_obs):
    """compute cumulative sum for ET"""
    time_ind = df_obs.dropna().index
    df_simu_cumsum = df_obs[["mean_ET [mm/8d]"]].dropna().copy()
    for i in np.arange(len(time_ind))[:]:
        itime = time_ind[i]
        if i == 0:
            btime = df_simu.index[0]
        else:
            btime = time_ind[i-1]
        isum = df_simu.loc[btime:itime, 'total evapotranspiration [m d^-1]'].sum()
        df_simu_cumsum.loc[itime, "mean_ET [mm/8d]"] = isum * 1000 # convert m/8d to mm/8d
        
    return df_simu_cumsum
# work_dir = "../model/runs/"
work_dir = "/global/cscratch1/sd/pshuai/coalcreek/model/runs/"
template_file = work_dir + 'model/inputs/forward_run-obs_error/template.xml'
# template_file = '/global/cscratch1/sd/pshuai/east-taylor/PRISM_ensemble_400/template.xml'

base_dir_name = "ens"
fname_log = "sensitivity.log"
fig_dir = "/global/project/projectdirs/m1800/pin/east-taylor/figure/CoalCreek/sensitivity_analysis/"
data_dir ="../data/"
fname_obs_q = data_dir + "USGS/CoalCreek_outlet_discharge.csv"
fname_modis_et = data_dir + "MODIS/CoalCreek_MODIS_ET-2000_2020.csv"
fname_obs_hourly_q = data_dir + "CoalCreek_outlet_hourly_discharge.csv"
fname_grace = data_dir + "coal_creek-GRACE.csv"
fname_gleam = data_dir + "gleam_coal_creek.csv"

result_dir = "/global/project/projectdirs/m1800/pin/east-taylor/results/"

fname_ensemble_csv = work_dir + "PRISM_ensemble_400/ensemble_para.csv"
fname_modis_snowcover = data_dir + "CoalCreek_snowcover.csv"
fname_daymet_p = data_dir + "CoalCreek_daymet_mean_P-1980_2020.csv"
fname_prism_p = data_dir + "CoalCreek_prism_mean_P-2012-2019.csv"
fname_nldas_p = data_dir + "NLDAS_daily_CoalCreek_mean_1980-2020.csv"

watershed_area = 53159325 # m2

import observation#

obs_q = utils.convertUSGSgauge(fname_obs_q, "discharge", datetime_col="dateTime")

obs_et = pd.read_csv(fname_modis_et, index_col='datetime', parse_dates=True)
t_diff = (obs_et.index[1:] - obs_et.index[0:-1]).total_seconds()/86400 # time diff in days
obs_et_daily = obs_et.iloc[1:, 0]/t_diff # averaging ET over 4,5,8 day period
2022-04-21 14:00:01,220 - root - INFO: insert nan
obs_hourly_q = pd.read_csv(fname_obs_hourly_q, index_col = 0, parse_dates = True)
# obs_semiDaily_q = pd.read_csv(fname_obs_semiDaily_q, index_col = 0, parse_dates = True)
# obs_daily_q = pd.read_csv(fname_obs_daily_q, index_col = 0, parse_dates = True)

obs_hourly_q.index = obs_hourly_q.index.shift(5, freq = 'H') # local (UTC-5) to UTC
obs_hourly_q.rename(columns={'discharge [m^3/d]':'watershed boundary discharge [m^3/d]'}, inplace = True)
# shift the daily average to [12h D-1, 11h D+1]
obs_daily_q = obs_hourly_q.resample('D', base = 0.5).mean()
<ipython-input-14-4a6270d53f1e>:2: FutureWarning: 'base' in .resample() and in Grouper() is deprecated.
The new arguments that you should use are 'offset' or 'origin'.

>>> df.resample(freq="3s", base=2)

becomes:

>>> df.resample(freq="3s", offset="2s")

  obs_daily_q = obs_hourly_q.resample('D', base = 0.5).mean()
df_modis_snowcover = pd.read_csv(fname_modis_snowcover, parse_dates=['datetime'])
df_modis_snowcover.set_index('datetime', inplace= True)
# modis_snowcover['coverage'] = modis_snowcover['snow cover area (km2)']/ modis_snowcover['snow cover area (km2)'].max()

generate parameter sets#

# specify your low, middle, high ranges
my_param_ranges = {
#                     'priestley_taylor_canopy-evaporation' : [0.3, 0.6, 1.2],
#                    'priestley_taylor_surface-potential_evaporation' : [0.3, 0.6, 1.2],
                   'priestley_taylor_alpha-snow' : [0.3, 0.6, 1.2],    
                   'priestley_taylor_alpha-transpiration' : [0.3, 0.6, 1.2],
                   'snowmelt_rate' : [2.74/2, 2.74, 2.74*2],
                   'snowmelt_degree_diff' : [1, 3, 5], # should be positive?
#                     'manning_n' : [-1.70, -1.2, -0.70] # log10
                   }
# permeabilities are done automatically by reading the default from file, then plus/minus 1 order mag
# my_perm_regions = ['s'+str(i+1) for i in range(5)] + ['g' + str(j+1) for j in range(4)]
my_perm_regions = ['s3', 'g1', 'g4']
# my_perm_regions = ['s3', 's4', 'g1', 'g4'] # used this for 400 ensemble runs

my_perm_param_ranges = sa.create_permeability_param_ranges(my_perm_regions, template_file)

my_param_ranges.update(my_perm_param_ranges)

# note bedrock is done specially, as we don't want min/max format,
# key thing is that middle value is the "control"
# my_param_ranges['perm_bedrock'] = [-14, -16, -12]

my_param_ranges
{'priestley_taylor_alpha-snow': [0.3, 0.6, 1.2],
 'priestley_taylor_alpha-transpiration': [0.3, 0.6, 1.2],
 'snowmelt_rate': [1.37, 2.74, 5.48],
 'snowmelt_degree_diff': [1, 3, 5],
 'perm_s3': [-12.709965388637482, -11.709965388637482, -10.709965388637482],
 'perm_g1': [-13.500312917381596, -12.500312917381596, -11.500312917381596],
 'perm_g4': [-14.0, -13.0, -12.0]}

create sobol samples#

sample_matrix, df_sample = sa.generate_sobol_samples(my_param_ranges, 1000)

df_sample
priestley_taylor_alpha-snow priestley_taylor_alpha-transpiration snowmelt_rate snowmelt_degree_diff perm_s3 perm_s4 perm_g1 perm_g4
realization_id
1 0.750000 0.750000 3.425000 3.000000 -11.709965 -11.324222 -12.500313 -13.000000
2 0.975000 0.525000 2.397500 2.000000 -11.209965 -10.824222 -13.000313 -12.500000
3 0.525000 0.975000 4.452500 4.000000 -12.209965 -11.824222 -12.000313 -13.500000
4 0.637500 0.637500 3.938750 4.500000 -11.959965 -12.074222 -12.750313 -12.250000
5 1.087500 1.087500 1.883750 2.500000 -10.959965 -11.074222 -11.750313 -13.250000
... ... ... ... ... ... ... ... ...
996 0.666504 0.443262 4.785635 1.957031 -11.274419 -11.134769 -13.283516 -12.076172
997 1.116504 0.893262 2.730635 3.957031 -12.274419 -12.134769 -12.283516 -13.076172
998 0.891504 0.668262 3.758135 2.957031 -11.774419 -11.634769 -12.783516 -13.576172
999 0.441504 1.118262 1.703135 4.957031 -10.774419 -10.634769 -11.783516 -12.576172
1000 0.497754 0.387012 3.501260 3.707031 -12.149419 -10.509769 -13.408516 -12.201172

1000 rows × 8 columns

generate input files#

Write input.xml based on template.xml. This will not overwrite existing paths.

sa.gen_model_inputs(my_param_ranges, sample_matrix, start_id = 50, num_sample=400, template_file = template_file, 
                    work_dir= work_dir+"PRISM_ensemble_400/", workdir_base=base_dir_name, logfile = fname_log,
                    landcover_list = ['other', 'Evergreen_Forest', 'Shrub_Scrub', 'Deciduous_Forest'])
2022-01-08 15:11:06,492 - root - INFO: Generated 400 ensembles located at: /global/cscratch1/sd/pshuai/east-taylor/PRISM_ensemble_400/

Launch jobs#

launch job natively using sbatch#

Launch job using a sbatch script. Basically write a for loop and submit entire jobs using srun.

Add sleep to be extra safe.

#!/bin/bash -l

#SBATCH -A PROJECT
#SBATCH -N 152
#SBATCH -t 22:00:00
#SBATCH -L SCRATCH
#SBATCH -J am-batch
#SBATCH --qos premium
#SBATCH -C haswell

module use -a /global/project/projectdirs/m3421/ats-new/modulefiles
module load ats/ecoon-land_cover/cori-haswell/intel-6.0.5-mpich-7.7.10/opt

for i in {3..40}
do
    cd /global/cscratch1/sd/pshuai/yakima/ensemble_run-300m-c128/ens$i
    srun -N 4 -n 128 -e job%J.err -o job%J.out ats --xml_file=input.xml sleep 5 &
done

sleep 1

wait

Ensemble runs#

postprocess results#

obs_q = utils.convertUSGSgauge(fname_obs_q, "discharge")

obs_et = pd.read_csv(fname_modis_et, index_col='datetime', parse_dates=True)
t_diff = (obs_et.index[1:] - obs_et.index[0:-1]).total_seconds()/86400 # time diff in days
obs_et_daily = obs_et.iloc[1:, 0]/t_diff # averaging ET over 4,5,8 day period
2022-02-15 11:36:04,469 - root - INFO: insert nan
sub_obs_et = obs_et.loc["2015-10-01":"2019-10-01", ]
obs_hourly_q = pd.read_csv(fname_obs_hourly_q, index_col = 0, parse_dates = True)
# obs_semiDaily_q = pd.read_csv(fname_obs_semiDaily_q, index_col = 0, parse_dates = True)
# obs_daily_q = pd.read_csv(fname_obs_daily_q, index_col = 0, parse_dates = True)

obs_hourly_q.index = obs_hourly_q.index.shift(5, freq = 'H') # local (UTC-5) to UTC
obs_hourly_q.rename(columns={'discharge [m^3/d]':'watershed boundary discharge [m^3/d]'}, inplace = True)
# shift the daily average to [12h D-1, 11h D+1]
obs_daily_q = obs_hourly_q.resample('D', offset = 0.5).mean()
# for var in para_df.columns[0:-1]:
# #     var = "perm_bedrock"
#     parms, index = np.unique(para_df[var], return_index=True)
#     assert(len(parms) == 3)
#     assert( 0 in index) # 0 is the base case
    
# fig, ax = plt.subplots(1,1, figsize=(8,4))

output_variables = ["watershed boundary discharge [m^3/d]", "total evapotranspiration [mm d^-1]",
               'snowmelt [mm d^-1]', 'SWE [m]']
# var = "watershed boundary discharge [m^3/d]"
# for j in range(len(output_variables)):
# output = output_variables[j]

# ax = axes.flatten()[j]
# df_q = obs_q[["Flow [m^3/day]"]].loc["2015-10-01":"2019-10-02", :]
# df_q.columns = ['obs discharge [m^3/d]']
df_q = pd.DataFrame({'datetime':pd.date_range("2015-10-01","2019-10-02")})
df_q.set_index('datetime', inplace=True, drop = True)

df_et = obs_et_daily.loc["2015-10-01":"2019-10-02"]
df_et = df_et.to_frame(name = "obs ET [mm/d]")

df_sm = pd.DataFrame({'datetime':pd.date_range("2015-10-01","2019-10-02")})
df_sm.set_index('datetime', inplace=True, drop = True)
df_swe = df_sm.copy()
lst  =[]
for i in tqdm(np.arange(51,450)):
#     idx = para_df.index[i]
    idir = work_dir + "PRISM_ensemble_400/"  + base_dir_name + "." + str(i) + "/"
    try:
        idf = ATSutils.load_waterBalance(idir, WB_filename="water_balance-daily.dat", canopy = True, plot = False)
    except:
        logging.info(f"ensemble: {i} did not start") 
        continue
    
    if idf.index[-1] < datetime(2019,10,1):
        lst.append(i)

    
    for var in output_variables:
        if var == "watershed boundary discharge [m^3/d]":    
            df_q = pd.merge(df_q, idf[[var]], how='outer', left_index=True, right_index=True)
            df_q.rename(columns={var:base_dir_name + "." + str(i)}, inplace = True)
        elif var == "total evapotranspiration [mm d^-1]":
            df_et = pd.merge(df_et, idf[[var]], how='outer', left_index=True, right_index=True)
            df_et.rename(columns={var:base_dir_name + "." + str(i)}, inplace = True)
        elif var == 'snowmelt [mm d^-1]':
            df_sm = pd.merge(df_sm, idf[[var]], how='outer', left_index=True, right_index=True)
            df_sm.rename(columns={var:base_dir_name + "." + str(i)}, inplace = True)
        elif var == 'SWE [m]':
            df_swe = pd.merge(df_swe, idf[[var]], how='outer', left_index=True, right_index=True)
            df_swe.rename(columns={var:base_dir_name + "." + str(i)}, inplace = True)  
        else:
            raise KeyError("variable not implemented.")

if len(lst) > 0:
    logging.info(f"ensembles: {lst} did not finish")            
2022-02-15 11:44:04,292 - root - INFO: ensemble: 447 did not start
2022-02-15 11:44:04,299 - root - INFO: ensemble: 448 did not start
2022-02-15 11:44:04,309 - root - INFO: ensemble: 449 did not start
# use shifted USGS streamflow as obs data
df_q['obs discharge [m^3/d]'] = obs_daily_q.loc['2015-10-01':'2019-10-02', 'watershed boundary discharge [m^3/d]'].values
/tmp/ipykernel_64968/1620480137.py:2: PerformanceWarning: DataFrame is highly fragmented.  This is usually the result of calling `frame.insert` many times, which has poor performance.  Consider using pd.concat instead.  To get a de-fragmented frame, use `newframe = frame.copy()`
  df_q['obs discharge [m^3/d]'] = obs_daily_q.loc['2015-10-01':'2019-10-02', 'watershed boundary discharge [m^3/d]'].values
if len(lst) > 0:
    for i in lst:
        icol = base_dir_name + "." + str(i)
        df = df_q[[icol]].dropna()
        print(i, df.index[-1])

plot ensemble#

# df_q = pd.read_csv(result_dir + "ensemble_run/ens_knl/ensemble_q_all_396.csv", index_col = 0, parse_dates=True)
# df_et = pd.read_csv(result_dir + "ensemble_run/ens_knl/ensemble_ET_all_396.csv", index_col = 0, parse_dates=True)

calculate composite ET#

The 8-day ET (0.1mm/8days or 0.1mm/5days) is the sum of ET during these 8-day time periods (5 days for 361 composite data in 2001, 2002, 2003, 2005, 2006, 2007, 2009, 2010, 6 days for 361 in 2000, 2004, 2008).

see Mu et al., for detailed description.

dates = ["2015-10-01", "2019-10-01"]
# time_ind = df_et.dropna().index
time_ind = obs_et.loc[dates[0]:dates[1], :].index

# df_8d_et = df_et.dropna().copy()
df_8d_et = df_et.loc[time_ind, ].copy()
for i in np.arange(len(time_ind))[:]:
    itime = time_ind[i]
    if i == 0:
        btime = df_et.index[0]
    else:
        btime = time_ind[i-1]
        
    istart = list(df_et.index).index(btime)
    iend = list(df_et.index).index(itime)    
    # `loc` includes both ends, but `iloc` does not include end bound!
    isum = df_et.iloc[istart:iend+1, :].sum(min_count = 1) # return NA is sum() contains NA
    df_8d_et.loc[itime, :] = isum.values
df_8d_et['obs ET [mm/8d]'] = obs_et.loc[time_ind[0] : time_ind[-1], 'mean_ET [mm/8d]'].values
df_8d_et.drop(columns = ['obs ET [mm/d]'], inplace = True)
fig, axes = plt.subplots(2,1, figsize=(8,6), sharex = True)

ax = axes[0]
df_q.plot(ax = ax, color = 'gray', alpha = 0.5)
df_q['obs discharge [m^3/d]'].plot(color = 'k', zorder = 99, ax = ax)
ax.legend('')
utils.custom_legend(['gray', 'k'], ['ensemble simulation', 'USGS'], ax = ax)
ax.set_ylabel("Discharge [m^3/d]")

ax = axes[1]
# df_et.plot(ax = ax, color = 'salmon', alpha = 0.5)
df_8d_et.plot(ax = ax, color = 'salmon', alpha = 0.5)

# ax.plot(obs_et_daily.index, obs_et_daily.values, color = 'k', zorder = 99)
ax.plot(obs_et.index, obs_et['mean_ET [mm/8d]'].values, color = 'k', zorder = 99)

ax.legend('')
utils.custom_legend(['salmon', 'k'], ['ensemble simulation', 'MODIS'], ax = ax)
ax.set_ylabel("ET [mm/8d]")
ax.set_xlim([datetime(2015,10,1), datetime(2019,10,1)])

fig.autofmt_xdate()
fig.tight_layout()
../_images/83187639aee9902af7edd8eb0b97826564d1dd062493f97a1b3252cda16a7ee4.png

save to file#

name = "ensemble_run/prism_400"
df_q.to_csv(result_dir + f"{name}/ensemble_daily_q_all.csv")
df_et.to_csv(result_dir + f"{name}/ensemble_daily_ET_all.csv")
df_sm.to_csv(result_dir + f"{name}/ensemble_daily_snowmelt_all.csv")
df_swe.to_csv(result_dir + f"{name}/ensemble_daily_SWE_all.csv")

df_8d_et.to_csv(result_dir + f"{name}/ensemble_8d_ET_all.csv")
# df_q = pd.read_csv(result_dir + "ensemble_run/ens_knl/ensemble_q_all_396.csv", index_col = 0)

# # use shifted USGS streamflow as obs data
# df_q['obs discharge [m^3/d]'] = obs_daily_q.loc['2015-10-01':'2019-10-02', 'watershed boundary discharge [m^3/d]'].values

# df_q.to_csv(result_dir + "ensemble_run/ens_knl/ensemble_q_all_396.csv")

snow cover data#

# df_q = pd.read_csv(result_dir + "ensemble_run/ens_knl/ensemble_q_all_396.csv", index_col=0)
# df_q.dropna(axis = 1, inplace = True)
df_modis_snowcover = pd.read_csv(fname_modis_snowcover, index_col='datetime')
df_modis_snowcover = df_modis_snowcover[["snow cover pct [-]"]]
df_modis_snowcover = df_modis_snowcover.loc[dates[0] : dates[1]]
df_modis_snowcover.index = pd.to_datetime(df_modis_snowcover.index)
df_snow_cover = pd.DataFrame({'datetime':pd.date_range(dates[0], dates[1])})
df_snow_cover.set_index('datetime', inplace=True, drop = True)

# i = 1
for i in tqdm(df_q.columns[:-1]):
    
    # if i not in ['ens.7']:
    idir = work_dir + "PRISM_ensemble_400/"  + i + "/"
    df = ATSutils.get_snow_cover(idir)

    df_snow_cover = pd.merge(df_snow_cover, df[["snow_cover_pct [-]"]], how='outer', left_index=True, right_index=True)
    df_snow_cover.rename(columns={"snow_cover_pct [-]":i}, inplace = True)
df_snow_cover
ens.51 ens.52 ens.53 ens.54 ens.55 ens.56 ens.57 ens.58 ens.59 ens.60 ... ens.437 ens.438 ens.439 ens.440 ens.441 ens.442 ens.443 ens.444 ens.445 ens.446
datetime
2015-10-01 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
2015-10-02 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
2015-10-03 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
2015-10-04 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
2015-10-05 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
2019-09-27 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
2019-09-28 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
2019-09-29 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
2019-09-30 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
2019-10-01 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN

1462 rows × 396 columns

df_snow_cover = pd.merge(df_modis_snowcover[["snow cover pct [-]"]], df_snow_cover, how='outer', left_index=True, right_index=True)
df_snow_cover.rename(columns={'snow cover pct [-]':'MODIS snowcover [-]'}, inplace = True)
df_snow_cover.to_csv(result_dir + f"{name}/ensemble_daily_snowcover_all.csv")
fig, ax = plt.subplots(1,1, figsize=(8,4))

df_snow_cover.plot(ax=ax,color = 'gray', kind='line', style='o', alpha = 0.5)
# df_snow_cover[['MODIS snowcover [-]']].plot(ax=ax, kind = 'scatter', color = 'k')
ax.plot(df_snow_cover.index, df_snow_cover['MODIS snowcover [-]'], '*--k')

ax.legend('')
utils.custom_legend(['gray', 'k'], ['ensemble', 'MODIS'], ax = ax, loc = 'best')
ax.set_ylabel("Snowcover percentage [-]")
Text(0, 0.5, 'Snowcover percentage [-]')
../_images/afcdacdcc7523e53bca855fabb3cb9a721e7fdd90591d8a16dbb472ed42d385f.png

Merge with MODIS 8-day maximum snowcover. Here we recalculate the 8-day maximum snowcover from ATS using snow cover data during 8-day period.

# df_snow_cover = pd.read_csv(result_dir + "ensemble_run-400/ensemble_daily_snowcover_all.csv", index_col='datetime', parse_dates=['datetime'])
time_ind = df_modis_snowcover.index

df_8d_snowcover = df_snow_cover.loc[time_ind].copy()
# df_8d_snowcover.pop('MODIS snowcover [-]')
for i in np.arange(len(time_ind))[:]:
    itime = time_ind[i]
    if i == 0:
        btime = df_snow_cover.index[0]
    else:
        btime = time_ind[i-1]
        
    istart = list(df_snow_cover.index).index(btime)
    iend = list(df_snow_cover.index).index(itime)
    # `loc` includes both ends, but `iloc` does not include end bound!
    imax = df_snow_cover.iloc[istart+1:iend+1, :].max(axis = 0) # return NA is sum() contains NA
    df_8d_snowcover.loc[itime, :] = imax.values
df_8d_snowcover['MODIS snowcover [-]'] = df_modis_snowcover['snow cover pct [-]'].values
# df_8d_et.drop(columns = ['obs ET [mm/d]'], inplace = True)
fig, ax = plt.subplots(1,1, figsize=(8,4))

df_8d_snowcover.plot( ax=ax,color = 'gray', alpha = 0.5, marker='.', linestyle='none')
ax.plot(df_8d_snowcover.index, df_8d_snowcover['MODIS snowcover [-]'], '*-k')

ax.legend('')
utils.custom_legend(['gray', 'k'], ['ensemble', 'MODIS'], ax = ax, loc = 'best')
ax.set_ylabel("Snowcover percentage [-]")
Text(0, 0.5, 'Snowcover percentage [-]')
../_images/cd41eadf3991c7f4e1672deb184bf27b25a1fd93f4ac45504d690c54025b237e.png
df_8d_snowcover.to_csv(result_dir + f"{name}/ensemble_8d_snowcover_all.csv")