Skip to content

Commit

Permalink
feature 926 TCMPRPlotter improvements (#959)
Browse files Browse the repository at this point in the history
  • Loading branch information
georgemccabe authored Jun 18, 2021
1 parent 319306a commit c10bb60
Show file tree
Hide file tree
Showing 9 changed files with 705 additions and 419 deletions.
37 changes: 35 additions & 2 deletions docs/Users_Guide/glossary.rst
Original file line number Diff line number Diff line change
Expand Up @@ -873,7 +873,32 @@ METplus Configuration Glossary
.. warning:: **DEPRECATED:** Please use :term:`TCMPR_PLOTTER_DEP_VARS` instead.

TCMPR_PLOTTER_DEP_VARS
Corresponds to the optional flag -dep in the plot_TCMPR.R script, which is wrapped by TCMPRPlotter. The value to this flag is a comma-separated list (no whitespace) of dependent variable columns to plot ( e.g. AMSLP-BMSLP, AMAX_WIND-BMAX_WIND, TK_ERR). If this is undefined, then the default plot for TK_ERR (track error) is generated. Note, if you want the track error plot generated, in addition to other plots, then you need to explicitly list this with the other variables. Please refer to the `MET User's Guide <https://dtcenter.org/community-code/model-evaluation-tools-met/documentation>`_ for more details.
Corresponds to the optional flag -dep in the plot_TCMPR.R script, which is
wrapped by TCMPRPlotter. The value to this flag is a comma-separated list
(no whitespace) of dependent variable columns to plot ( e.g. AMSLP-BMSLP,
AMAX_WIND-BMAX_WIND, TK_ERR). If this is undefined, then the default plot
for TK_ERR (track error) is generated. The values in this list are looped
over to run once for each and can be referenced in other variables using
the {dep} tag. Note, if you want the track error
plot generated, in addition to other plots, then you need to explicitly
list this with the other variables. Please refer to the
`MET User's Guide <https://dtcenter.org/community-code/model-evaluation-tools-met/documentation>`_
for more details.

| *Used by:* TCMPRPlotter
TCMPR_PLOTTER_DEP_LABELS
List of strings that correspond to the values in
:term:`TCMPR_PLOTTER_DEP_VARS` that can be referenced in other variables
to set the plot title, axis labels, etc. with the {dep_label} tag.

| *Used by:* TCMPRPlotter

TCMPR_PLOTTER_PLOT_LABELS
List of strings that correspond to the values in
:term:`TCMPR_PLOTTER_PLOT_TYPES` that can be referenced in other variables
to set the plot title, axis labels, etc. with the {plot_label} tag.

| *Used by:* TCMPRPlotter
Expand Down Expand Up @@ -3327,7 +3352,15 @@ METplus Configuration Glossary
.. warning:: **DEPRECATED:** Please use :term:`TCMPR_PLOTTER_PLOT_TYPES` instead.

TCMPR_PLOTTER_PLOT_TYPES
Specify what plot types are desired for the TC Matched Pairs plotting tool. By default, a boxplot is generated if this is undefined in the configuration file. If other plots are requested and a boxplot is also desired, you must explicitly listboxplot in your list of plot types. Supported plot types: BOXPLOT, POINT, MEAN, MEDIAN, RELPERF (relative performance), RANK (time series of ranks for the first model), SCATTER, SKILL_MN (mean skill scores) and SKILL_MD (median skill scores).
Specify what plot types are desired for the TC Matched Pairs plotting
tool. By default, a boxplot is generated if this is undefined in the
configuration file. If other plots are requested and a boxplot is also
desired, you must explicitly list boxplot in your list of plot types.
Supported plot types: BOXPLOT, POINT, MEAN, MEDIAN, RELPERF
(relative performance), RANK (time series of ranks for the first model),
SCATTER, SKILL_MN (mean skill scores) and SKILL_MD (median skill scores).
The values in this list are looped over to run once for each and can be
referenced in other variables using the {plot} tag.

| *Used by:* TCMPRPlotter
Expand Down
2 changes: 2 additions & 0 deletions docs/Users_Guide/wrappers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5299,6 +5299,8 @@ METplus Configuration
| :term:`TCMPR_PLOTTER_FOOTNOTE_FLAG`
| :term:`TCMPR_PLOTTER_PLOT_CONFIG_OPTS`
| :term:`TCMPR_PLOTTER_SAVE_DATA`
| :term:`TCMPR_PLOTTER_DEP_LABELS`
| :term:`TCMPR_PLOTTER_PLOT_LABELS`
|
The following are TCMPR flags, if set to 'no', then don't set flag, if
Expand Down
323 changes: 323 additions & 0 deletions internal_tests/pytests/tcmpr_plotter/test_tcmpr_plotter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,323 @@
#!/usr/bin/env python3

import os
import sys
import re
import pytest
from datetime import datetime

import produtil

from metplus.wrappers.tcmpr_plotter_wrapper import TCMPRPlotterWrapper

EXPECTED_INPUT_FILES = [
'another_fake_filter_20141214_00.tcst',
'empty_filter.tcst',
'fake_filter_20141214_00.tcst',
]
TIME_FMT = '%Y%m%d%H'
RUN_TIME = '20141214'

def set_minimum_config_settings(config):
# set config variables to prevent command from running and bypass check
# if input files actually exist
config.set('config', 'DO_NOT_RUN_EXE', True)

# set process and time config variables
config.set('config', 'PROCESS_LIST', 'TCMPRPlotter')
config.set('config', 'LOOP_BY', 'INIT')
config.set('config', 'INIT_TIME_FMT', TIME_FMT)
config.set('config', 'INIT_BEG', RUN_TIME)
config.set('config', 'INIT_END', RUN_TIME)
config.set('config', 'INIT_INCREMENT', '12H')
config.set('config', 'LOOP_ORDER', 'processes')
config.set('config', 'TCMPR_PLOTTER_CONFIG_FILE',
('{PARM_BASE}/use_cases/met_tool_wrapper/'
'TCMPRPlotter/TCMPRPlotterConfig_Customize'))
config.set('config', 'TCMPR_PLOTTER_PLOT_OUTPUT_DIR',
'{OUTPUT_BASE}/TCMPRPlotter/tcmpr_plots')

@pytest.mark.parametrize(
'config_overrides,expected_loop_args', [
# 0: no loop args
({},
{'dep': [('', '')], 'plot': [('', '')]}),
# 1: 1 dep arg
({'TCMPR_PLOTTER_DEP_VARS': 'item1'},
{'dep': [('item1', 'item1')], 'plot': [('', '')]}),
# 2: 2 dep args
({'TCMPR_PLOTTER_DEP_VARS': 'item1, item2'},
{'dep': [('item1', 'item1'), ('item2', 'item2')],
'plot': [('', '')]}),
# 3: 1 dep arg, 1 dep label
({'TCMPR_PLOTTER_DEP_VARS': 'item1',
'TCMPR_PLOTTER_DEP_LABELS': 'Label 1'},
{'dep': [('item1', 'Label 1')], 'plot': [('', '')]}),
# 4: 2 dep args, 2 dep labels
({'TCMPR_PLOTTER_DEP_VARS': 'item1, item2',
'TCMPR_PLOTTER_DEP_LABELS': 'Label 1, Label 2'},
{'dep': [('item1', 'Label 1'), ('item2', 'Label 2')],
'plot': [('', '')]}),
# 5: 2 dep args, 1 dep label (error)
({'TCMPR_PLOTTER_DEP_VARS': 'item1, item2',
'TCMPR_PLOTTER_DEP_LABELS': 'Label 1'},
None),
# 6: 1 dep arg, 2 dep labels (error)
({'TCMPR_PLOTTER_DEP_VARS': 'item1',
'TCMPR_PLOTTER_DEP_LABELS': 'Label 1, Label 2'},
None),
# 7: 1 plot arg
({'TCMPR_PLOTTER_PLOT_TYPES': 'item1'},
{'plot': [('item1', 'item1')], 'dep': [('', '')]}),
# 8: 2 plot args
({'TCMPR_PLOTTER_PLOT_TYPES': 'item1, item2'},
{'plot': [('item1', 'item1'), ('item2', 'item2')],
'dep': [('', '')]}),
# 9: 1 plot arg, 1 plot label
({'TCMPR_PLOTTER_PLOT_TYPES': 'item1',
'TCMPR_PLOTTER_PLOT_LABELS': 'Label 1'},
{'plot': [('item1', 'Label 1')], 'dep': [('', '')]}),
# 10: 2 plot args, 2 plot labels
({'TCMPR_PLOTTER_PLOT_TYPES': 'item1, item2',
'TCMPR_PLOTTER_PLOT_LABELS': 'Label 1, Label 2'},
{'plot': [('item1', 'Label 1'), ('item2', 'Label 2')],
'dep': [('', '')]}),
# 11: 2 plot args, 1 plot label (error)
({'TCMPR_PLOTTER_PLOT_TYPES': 'item1, item2',
'TCMPR_PLOTTER_PLOT_LABELS': 'Label 1'},
None),
# 12: 1 plot arg, 2 plot labels (error)
({'TCMPR_PLOTTER_PLOT_TYPES': 'item1',
'TCMPR_PLOTTER_PLOT_LABELS': 'Label 1, Label 2'},
None),
# 13: 2 dep args, 2 plot args, 2 plot labels
({'TCMPR_PLOTTER_DEP_VARS': 'item1, item2',
'TCMPR_PLOTTER_PLOT_TYPES': 'pitem1, pitem2',
'TCMPR_PLOTTER_PLOT_LABELS': 'P Label 1, P Label 2'
},
{'dep': [('item1', 'item1'), ('item2', 'item2')],
'plot': [('pitem1', 'P Label 1'), ('pitem2', 'P Label 2')]}),
]
)
def test_read_loop_info(metplus_config, config_overrides, expected_loop_args):
config = metplus_config()

set_minimum_config_settings(config)

# set config variable overrides
for key, value in config_overrides.items():
config.set('config', key, value)

wrapper = TCMPRPlotterWrapper(config)
assert wrapper.read_loop_info() == expected_loop_args

@pytest.mark.parametrize(
'config_overrides,expected_strings', [
# 0: no optional arguments
({}, ['']),
# 1: 1 dep
({'TCMPR_PLOTTER_DEP_VARS': 'ditem1'}, ['-dep ditem1']),
# 2: 2 dep
({'TCMPR_PLOTTER_DEP_VARS': 'ditem1, ditem2'}, ['-dep ditem1',
'-dep ditem2']),
# 3: 1 plot
({'TCMPR_PLOTTER_PLOT_TYPES': 'pitem1'}, ['-plot pitem1']),
# 4: 2 plots
({'TCMPR_PLOTTER_PLOT_TYPES': 'pitem1, pitem2'}, ['-plot pitem1',
'-plot pitem2']),
# 5: 1 dep, 1 plot
({'TCMPR_PLOTTER_DEP_VARS': 'ditem1',
'TCMPR_PLOTTER_PLOT_TYPES': 'pitem1'}, ['-dep ditem1 -plot pitem1']),
# 6: 2 deps, 1 plot
({'TCMPR_PLOTTER_DEP_VARS': 'ditem1, ditem2',
'TCMPR_PLOTTER_PLOT_TYPES': 'pitem1'}, ['-dep ditem1 -plot pitem1',
'-dep ditem2 -plot pitem1']),
# 7: 1 dep, 2 plots
({'TCMPR_PLOTTER_DEP_VARS': 'ditem1',
'TCMPR_PLOTTER_PLOT_TYPES': 'pitem1, pitem2'},
['-dep ditem1 -plot pitem1',
'-dep ditem1 -plot pitem2']),
# 8: 2 deps, 2 plots
({'TCMPR_PLOTTER_DEP_VARS': 'ditem1, ditem2',
'TCMPR_PLOTTER_PLOT_TYPES': 'pitem1, pitem2'},
['-dep ditem1 -plot pitem1',
'-dep ditem1 -plot pitem2',
'-dep ditem2 -plot pitem1',
'-dep ditem2 -plot pitem2'
]),
# 9: 1 dep, 1 dep label, use label in ylab
({'TCMPR_PLOTTER_DEP_VARS': 'ditem1',
'TCMPR_PLOTTER_DEP_LABELS': 'D Label 1',
'TCMPR_PLOTTER_YLAB': '{dep_label}A'},
['-ylab "D Label 1A" -dep ditem1']),
# 10: 1 dep/label, 1 plot/label use both labels in title
({'TCMPR_PLOTTER_DEP_VARS': 'ditem1',
'TCMPR_PLOTTER_DEP_LABELS': 'D Label 1',
'TCMPR_PLOTTER_PLOT_TYPES': 'pitem1',
'TCMPR_PLOTTER_PLOT_LABELS': 'P Label 1',
'TCMPR_PLOTTER_TITLE': '{dep_label}A {plot_label}B'},
['-title "D Label 1A P Label 1B" -dep ditem1 -plot pitem1']),
# 10: 1 dep/label, 1 plot/label use labels in title, value in prefix
({'TCMPR_PLOTTER_DEP_VARS': 'ditem1',
'TCMPR_PLOTTER_DEP_LABELS': 'D Label 1',
'TCMPR_PLOTTER_PLOT_TYPES': 'pitem1',
'TCMPR_PLOTTER_PLOT_LABELS': 'P Label 1',
'TCMPR_PLOTTER_TITLE': '{dep_label}A {plot_label}B',
'TCMPR_PLOTTER_PREFIX': 'Eta_{dep}_{plot}'},
[('-prefix "Eta_ditem1_pitem1" -title "D Label 1A P Label 1B" '
'-dep ditem1 -plot pitem1')]),
# 11: 1 dep/label, 1 plot/label use labels in title/prefix
# spaces change to underscores in prefix
({'TCMPR_PLOTTER_DEP_VARS': 'ditem1',
'TCMPR_PLOTTER_DEP_LABELS': 'D Label 1',
'TCMPR_PLOTTER_PLOT_TYPES': 'pitem1',
'TCMPR_PLOTTER_PLOT_LABELS': 'P Label 1',
'TCMPR_PLOTTER_TITLE': '{dep_label}A {plot_label}B',
'TCMPR_PLOTTER_PREFIX': 'Eta_{dep_label}_{plot_label}'},
[('-prefix "Eta_D_Label_1_P_Label_1" -title "D Label 1A P Label 1B" '
'-dep ditem1 -plot pitem1')]),
]
)

def test_tcmpr_plotter_loop(metplus_config, config_overrides,
expected_strings):
config = metplus_config()

set_minimum_config_settings(config)

# set config variable overrides
for key, value in config_overrides.items():
config.set('config', key, value)

test_data = os.path.join(config.getdir('METPLUS_BASE'),
'internal_tests',
'data',
'stat_data')

config.set('config', 'TCMPR_PLOTTER_TCMPR_DATA_DIR', test_data)

wrapper = TCMPRPlotterWrapper(config)
assert wrapper.isOK

app_path = wrapper.c_dict.get('TCMPR_SCRIPT')
config_file = wrapper.c_dict.get('CONFIG_FILE')
out_dir = wrapper.c_dict.get('OUTPUT_DIR')

input_files = []
for input_file in EXPECTED_INPUT_FILES:
input_files.append(os.path.join(test_data, input_file))

expected_cmds = []
for expected_string in expected_strings:
# add a space before value if expected string has a value
if expected_string:
expected_string = f' {expected_string}'

expected_cmds.append(f"Rscript {app_path} -config {config_file}"
f"{expected_string}"
f" -lookin {' '.join(input_files)}"
f" -outdir {out_dir}")

all_cmds = wrapper.run_all_times()
print(f"ALL COMMANDS: {all_cmds}")

for (actual_cmd, _), expected_cmd in zip(all_cmds, expected_cmds):
# ensure commands are generated as expected
assert actual_cmd == expected_cmd


@pytest.mark.parametrize(
'config_overrides,expected_string', [
# no optional arguments
({}, ''),
# strings
({'TCMPR_PLOTTER_PREFIX': 'my_string'}, '-prefix "my_string"'),
({'TCMPR_PLOTTER_XLIM': 'my_string'}, '-xlim my_string'),
({'TCMPR_PLOTTER_YLIM': 'my_string'}, '-ylim my_string'),
({'TCMPR_PLOTTER_FILTERED_TCST_DATA_FILE': 'my_string'},
'-tcst my_string'),
({'TCMPR_PLOTTER_SCATTER_X': 'my_string'}, '-scatter_x my_string'),
({'TCMPR_PLOTTER_SCATTER_Y': 'my_string'}, '-scatter_y my_string'),
({'TCMPR_PLOTTER_SKILL_REF': 'my_string'}, '-skill_ref my_string'),
({'TCMPR_PLOTTER_SERIES': 'my_string'}, '-series my_string'),
({'TCMPR_PLOTTER_SERIES_CI': 'my_string'}, '-series_ci my_string'),
({'TCMPR_PLOTTER_LEAD': 'my_string'}, '-lead my_string'),
({'TCMPR_PLOTTER_RP_DIFF': 'my_string'}, '-rp_diff my_string'),
({'TCMPR_PLOTTER_DEMO_YR': 'my_string'}, '-demo_yr my_string'),
({'TCMPR_PLOTTER_HFIP_BSLN': 'my_string'}, '-hfip_bsln my_string'),
({'TCMPR_PLOTTER_PLOT_CONFIG_OPTS': 'my_string'},
'-plot_config my_string'),
({'TCMPR_PLOTTER_SAVE_DATA': 'my_string'}, '-save_data my_string'),
# booleans True
({'TCMPR_PLOTTER_FOOTNOTE_FLAG': 'True'}, '-footnote_flag'),
({'TCMPR_PLOTTER_NO_EE': 'True'}, '-no_ee'),
({'TCMPR_PLOTTER_NO_LOG': 'True'}, '-no_log'),
({'TCMPR_PLOTTER_SAVE': 'True'}, '-save'),
# booleans False
({'TCMPR_PLOTTER_FOOTNOTE_FLAG': 'False'}, ''),
({'TCMPR_PLOTTER_NO_EE': 'False'}, ''),
({'TCMPR_PLOTTER_NO_LOG': 'False'}, ''),
({'TCMPR_PLOTTER_SAVE': 'False'}, ''),
# strings add quotes
({'TCMPR_PLOTTER_TITLE': 'my_string'}, '-title "my_string"'),
({'TCMPR_PLOTTER_SUBTITLE': 'my_string'}, '-subtitle "my_string"'),
({'TCMPR_PLOTTER_XLAB': 'my_string'}, '-xlab "my_string"'),
({'TCMPR_PLOTTER_YLAB': 'my_string'}, '-ylab "my_string"'),
({'TCMPR_PLOTTER_FILTER': '-amodel GFSO,EMX,CMC'},
'-filter "-amodel GFSO,EMX,CMC"'),
({'TCMPR_PLOTTER_LEGEND': 'my_string'}, '-legend "my_string"'),
# looped arguments (single item)
({'TCMPR_PLOTTER_DEP_VARS': 'item1'}, '-dep item1'),
({'TCMPR_PLOTTER_PLOT_TYPES': 'item1'}, '-plot item1'),
]
)

def test_tcmpr_plotter(metplus_config, config_overrides, expected_string):
# add a space before value if expected string has a value
if expected_string:
expected_string = f' {expected_string}'

for single_file in [True, False]:
config = metplus_config()

set_minimum_config_settings(config)

# set config variable overrides
for key, value in config_overrides.items():
config.set('config', key, value)

test_data = os.path.join(config.getdir('METPLUS_BASE'),
'internal_tests',
'data',
'stat_data')
if single_file:
test_data = os.path.join(test_data, 'fake_filter_20141214_00.tcst')

config.set('config', 'TCMPR_PLOTTER_TCMPR_DATA_DIR', test_data)

wrapper = TCMPRPlotterWrapper(config)
assert wrapper.isOK

app_path = wrapper.c_dict.get('TCMPR_SCRIPT')
config_file = wrapper.c_dict.get('CONFIG_FILE')
out_dir = wrapper.c_dict.get('OUTPUT_DIR')

input_files = []
if single_file:
input_files.append(test_data)
else:
for input_file in EXPECTED_INPUT_FILES:
input_files.append(os.path.join(test_data, input_file))

expected_cmds = [(f"Rscript {app_path} -config {config_file}"
f"{expected_string}"
f" -lookin {' '.join(input_files)}"
f" -outdir {out_dir}"),
]

all_cmds = wrapper.run_all_times()
print(f"ALL COMMANDS: {all_cmds}")

for (actual_cmd, _), expected_cmd in zip(all_cmds, expected_cmds):
# ensure commands are generated as expected
assert actual_cmd == expected_cmd
Loading

0 comments on commit c10bb60

Please sign in to comment.