-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #43 from DamienIrving/master
Add moments test
- Loading branch information
Showing
3 changed files
with
240 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,217 @@ | ||
"""Functions and command line program for moments testing.""" | ||
|
||
import argparse | ||
import logging | ||
|
||
import numpy as np | ||
import matplotlib.pyplot as plt | ||
import scipy | ||
|
||
from . import fileio | ||
|
||
|
||
logging.basicConfig(level=logging.INFO) | ||
|
||
|
||
def calc_ci(data): | ||
"""Calculate the 95% confidence interval""" | ||
|
||
lower_ci = np.percentile(np.array(data), 2.5, axis=0) | ||
upper_ci = np.percentile(np.array(data), 97.5, axis=0) | ||
|
||
return lower_ci, upper_ci | ||
|
||
|
||
def create_plot( | ||
fcst_file, | ||
obs_file, | ||
var, | ||
outfile=None, | ||
min_lead=None, | ||
ensemble_dim="ensemble", | ||
init_dim="init_date", | ||
lead_dim="lead_time", | ||
): | ||
"""Create a stability assessment plot. | ||
Parameters | ||
---------- | ||
fcst_file : str | ||
Forecast file containing metric of interest | ||
obs_file : str | ||
Observations file containing metric of interest | ||
var : str | ||
Variable name (in fcst_file) | ||
outfile : str, default None | ||
Path for output image file | ||
min_lead : int, optional | ||
Minimum lead time | ||
ensemble_dim : str, default ensemble | ||
Name of ensemble member dimension | ||
init_dim : str, default init_date | ||
Name of initial date dimension | ||
lead_dim : str, default lead_time | ||
Name of lead time dimension | ||
""" | ||
|
||
ds_obs = fileio.open_dataset(obs_file) | ||
da_obs = ds_obs[var].dropna("time") | ||
sample_size = len(da_obs) | ||
mean_obs = float(np.mean(da_obs)) | ||
std_obs = float(np.std(da_obs)) | ||
skew_obs = float(scipy.stats.skew(da_obs)) | ||
kurtosis_obs = float(scipy.stats.kurtosis(da_obs)) | ||
|
||
ds_fcst = fileio.open_dataset(fcst_file) | ||
da_fcst = ds_fcst[var] | ||
if min_lead is not None: | ||
da_fcst = da_fcst.where(ds_fcst[lead_dim] >= min_lead) | ||
dims = [ensemble_dim, init_dim, lead_dim] | ||
da_fcst_stacked = da_fcst.dropna(lead_dim).stack({"sample": dims}) | ||
|
||
mean_values = [] | ||
std_values = [] | ||
skew_values = [] | ||
kurtosis_values = [] | ||
for i in range(1000): | ||
random_sample = np.random.choice(da_fcst_stacked, sample_size) | ||
mean = float(np.mean(random_sample)) | ||
std = float(np.std(random_sample)) | ||
skew = float(scipy.stats.skew(random_sample)) | ||
kurtosis = float(scipy.stats.kurtosis(random_sample)) | ||
mean_values.append(mean) | ||
std_values.append(std) | ||
skew_values.append(skew) | ||
kurtosis_values.append(kurtosis) | ||
|
||
mean_lower_ci, mean_upper_ci = calc_ci(mean_values) | ||
std_lower_ci, std_upper_ci = calc_ci(std_values) | ||
skew_lower_ci, skew_upper_ci = calc_ci(skew_values) | ||
kurtosis_lower_ci, kurtosis_upper_ci = calc_ci(kurtosis_values) | ||
|
||
fig = plt.figure(figsize=[15, 12]) | ||
ax1 = fig.add_subplot(221) | ||
ax2 = fig.add_subplot(222) | ||
ax3 = fig.add_subplot(223) | ||
ax4 = fig.add_subplot(224) | ||
|
||
ax1.hist(mean_values, rwidth=0.8, color="0.5") | ||
ax1.set_title("(a) mean") | ||
ax1.axvline(mean_lower_ci, color="0.2", linestyle="--") | ||
ax1.axvline(mean_upper_ci, color="0.2", linestyle="--") | ||
ax1.axvline(mean_obs) | ||
ax1.set_ylabel("count") | ||
|
||
ax2.hist(std_values, rwidth=0.8, color="0.5") | ||
ax2.set_title("(b) standard deviation") | ||
ax2.axvline(std_lower_ci, color="0.2", linestyle="--") | ||
ax2.axvline(std_upper_ci, color="0.2", linestyle="--") | ||
ax2.axvline(std_obs) | ||
ax2.set_ylabel("count") | ||
|
||
ax3.hist(skew_values, rwidth=0.8, color="0.5") | ||
ax3.set_title("(c) skewness") | ||
ax3.set_ylabel("count") | ||
ax3.axvline(skew_lower_ci, color="0.2", linestyle="--") | ||
ax3.axvline(skew_upper_ci, color="0.2", linestyle="--") | ||
ax3.axvline(skew_obs) | ||
|
||
ax4.hist(kurtosis_values, rwidth=0.8, color="0.5") | ||
ax4.set_title("(d) kurtosis") | ||
ax4.set_ylabel("count") | ||
ax4.axvline(kurtosis_lower_ci, color="0.2", linestyle="--") | ||
ax4.axvline(kurtosis_upper_ci, color="0.2", linestyle="--") | ||
ax4.axvline(kurtosis_obs) | ||
|
||
mean_text = f"Obs = {mean_obs}, Model 95% CI ={mean_lower_ci} to {mean_upper_ci}" | ||
std_text = f"Obs = {std_obs}, Model 95% CI ={std_lower_ci} to {std_upper_ci}" | ||
skew_text = f"Obs = {skew_obs}, Model 95% CI ={skew_lower_ci} to {skew_upper_ci}" | ||
kurtosis_text = f"Obs = {kurtosis_obs}, Model 95% CI ={kurtosis_lower_ci} to {kurtosis_upper_ci}" | ||
logging.info(f"Mean: {mean_text}") | ||
logging.info(f"Standard deviation: {std_text}") | ||
logging.info(f"Skewness: {skew_text}") | ||
logging.info(f"Kurtosis: {kurtosis_text}") | ||
|
||
if outfile: | ||
infile_logs = { | ||
fcst_file: ds_fcst.attrs["history"], | ||
obs_file: ds_obs.attrs["history"], | ||
} | ||
command_history = fileio.get_new_log(infile_logs=infile_logs) | ||
metadata = { | ||
"mean": mean_text, | ||
"standard deviation": std_text, | ||
"skewness": skew_text, | ||
"kurtosis": kurtosis_text, | ||
"history": command_history, | ||
} | ||
plt.savefig( | ||
outfile, | ||
bbox_inches="tight", | ||
facecolor="white", | ||
dpi=200, | ||
metadata=metadata, | ||
) | ||
else: | ||
plt.show() | ||
|
||
|
||
def _parse_command_line(): | ||
"""Parse the command line for input agruments""" | ||
|
||
parser = argparse.ArgumentParser( | ||
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter | ||
) | ||
parser.add_argument("fcst_file", type=str, help="Forecast file") | ||
parser.add_argument("obs_file", type=str, help="Observations file") | ||
parser.add_argument("var", type=str, help="Variable name") | ||
|
||
parser.add_argument("--outfile", type=str, default=None, help="Output file name") | ||
parser.add_argument( | ||
"--ensemble_dim", | ||
type=str, | ||
default="ensemble", | ||
help="Name of ensemble member dimension", | ||
) | ||
parser.add_argument( | ||
"--init_dim", | ||
type=str, | ||
default="init_date", | ||
help="Name of initial date dimension", | ||
) | ||
parser.add_argument( | ||
"--lead_dim", | ||
type=str, | ||
default="lead_time", | ||
help="Name of lead time dimension", | ||
) | ||
parser.add_argument( | ||
"--min_lead", | ||
type=int, | ||
default=None, | ||
help="Minimum lead time", | ||
) | ||
args = parser.parse_args() | ||
|
||
return args | ||
|
||
|
||
def _main(): | ||
"""Run the command line program.""" | ||
|
||
args = _parse_command_line() | ||
|
||
create_plot( | ||
args.fcst_file, | ||
args.obs_file, | ||
args.var, | ||
outfile=args.outfile, | ||
min_lead=args.min_lead, | ||
ensemble_dim=args.ensemble_dim, | ||
init_dim=args.init_dim, | ||
lead_dim=args.lead_dim, | ||
) | ||
|
||
|
||
if __name__ == "__main__": | ||
_main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters