Skip to content

Commit dbefe96

Browse files
authored
Merge pull request #13 from BritishGeologicalSurvey/fix-open-issue
Fix open issue regarding warnings for criteria without data (Issue #4)
2 parents aae8462 + 02b83cb commit dbefe96

File tree

4 files changed

+84
-73
lines changed

4 files changed

+84
-73
lines changed

pyvolcans/pyvolcans.py

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@
2626
set_weights_from_args,
2727
open_gvp_website,
2828
output_result,
29-
check_for_criteria_without_data,
30-
check_for_perfect_analogues,
29+
warn_on_perfect_analogues,
3130
convert_to_idx,
3231
plot_bar_apriori_analogues,
3332
plot_bar_better_analogues,
@@ -117,7 +116,7 @@ def cli():
117116
# call PyVOLCANS
118117
try:
119118
# main PyVOLCANS result for all volcanoes (and weighting scheme used)
120-
volcans_result, my_volcano_data = \
119+
volcans_result = \
121120
calculate_weighted_analogy_matrix(volcano_input,
122121
weights=new_weights)
123122

@@ -127,19 +126,8 @@ def cli():
127126
volcans_result,
128127
count)
129128

130-
# check for volcanological criteria without data for target volcano
131-
try:
132-
check_for_criteria_without_data(my_volcano_data, volcano_name)
133-
except PyvolcansError as exc:
134-
# do not quit the program in this situation
135-
logging.warning(exc.args[0])
136-
137129
# check for 'too many perfect analogues' (see Tierz et al., 2019)
138-
try:
139-
check_for_perfect_analogues(result=top_analogues)
140-
except PyvolcansError as exc:
141-
# do not quit the program in this situation
142-
logging.warning(exc.args[0])
130+
warn_on_perfect_analogues(result=top_analogues)
143131

144132
# return a formatted PyVOLCANS result
145133
result = output_result(verbose=args.verbose,

pyvolcans/pyvolcans_func.py

Lines changed: 45 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,19 @@
2323
load_eruption_style_analogy,
2424
load_volcano_names)
2525

26+
27+
# Define custom message formatter for warnings
28+
def _format_pyvolcans_warning(message, *args, **kwargs):
29+
"""
30+
Format warnings into PyVOLCANS style, discarding all but the
31+
message.
32+
"""
33+
return f"\nUserWarning (PyVOLCANS): {message}\n"
34+
35+
36+
# Replace built-in warning format function
37+
warnings.formatwarning = _format_pyvolcans_warning
38+
2639
# fuzzywuzzy would like to use a sequence matcher provided by the
2740
# Python-Levenshtein package, but this has dependencies that require
2841
# compilation. When it is not installed, it uses the matcher provided
@@ -364,19 +377,13 @@ def calculate_weighted_analogy_matrix(my_volcano, weights,
364377
weights (or weighting scheme) that is chosen by the user for each
365378
particular run of PyVOLCANS. A different weighting scheme can generate
366379
an entirely different set of total analogy values.
367-
368-
my_volcano_data_dictionary : dict
369-
Dictionary containing information on whether there is volcanological
370-
data available (dict_value = 1), or there is not (dict_value = 0); for
371-
each of the volcanological criteria used by PyVOLCANS, considering the
372-
specific target volcano chosen by the user to run the program.
373380
"""
374381

375382
# get the index for my_volcano
376383
volcano_idx = convert_to_idx(my_volcano)
377384

378385
# check for volcanological criteria without data for the target volcano
379-
my_volcano_data_dictionary = {} # empty dictionary
386+
my_volcano_data_dictionary = {}
380387
# NB. If the single-criterion analogy of the target volcano with itself is
381388
# equal to zero, then there is no data available for that particular
382389
# volcanological criterion
@@ -389,6 +396,11 @@ def calculate_weighted_analogy_matrix(my_volcano, weights,
389396
my_volcano_data_dictionary[criterion] = \
390397
my_volcano_single_analogies[volcano_idx]
391398

399+
# check for volcanological criteria without data for target volcano
400+
warn_on_criteria_without_data(my_volcano_data_dictionary,
401+
my_volcano,
402+
weights)
403+
392404
# calculate single-criterion analogy matrices for specific weighting scheme
393405
weighted_tectonic_analogy = \
394406
weights['tectonic_setting'] * analogies['tectonic_setting']
@@ -426,7 +438,7 @@ def calculate_weighted_analogy_matrix(my_volcano, weights,
426438
volcans_result['ASt'] = \
427439
weighted_eruption_style_analogy[volcano_idx, ]
428440

429-
return volcans_result, my_volcano_data_dictionary
441+
return volcans_result
430442

431443

432444
def get_analogies(my_volcano, volcans_result, count=10):
@@ -481,10 +493,10 @@ def get_analogies(my_volcano, volcans_result, count=10):
481493
return filtered_result, volcano_name
482494

483495

484-
def check_for_perfect_analogues(result):
496+
def warn_on_perfect_analogues(result):
485497
"""
486498
Assesses whether all the calculated top analogue volcanoes share the same
487-
value of total analogy, and raises a PyvolcansError exception if that is
499+
value of total analogy, and raises UserWarning if that is
488500
the case.
489501
490502
Parameters
@@ -493,36 +505,23 @@ def check_for_perfect_analogues(result):
493505
Sub-set of results from the Pandas dataframe volcans_result,
494506
thus only including the data for the top analogue volcanoes
495507
to the target volcano.
496-
497-
Raises
498-
-------
499-
PyvolcansError
500-
If all the top analogue volcanoes have the same value of total analogy.
501-
This observation may be related to issues with the data available for
502-
the target volcano (and/or analogue volcanoes), but can also be
503-
indicative of the fact that the weighting scheme selected is too
504-
simplified, or not informative enough (e.g. a single-criterion search
505-
of volcanoes on subduction zones under continental crust will yield
506-
hundreds of volcanoes that share that same characteristic. Please see
507-
Tierz et al., 2019, for more details)
508508
"""
509509

510510
maximum_analogy = result['total_analogy'].iloc[0]
511511
if result['total_analogy'].eq(maximum_analogy).all():
512-
msg = ("WARNING!!! "
513-
"All top analogue volcanoes have the same value "
512+
msg = ("All top analogue volcanoes have the same value "
514513
"of total analogy. Please be aware of possible "
515514
"data deficiencies and/or the use of a simplified "
516515
"weighting scheme (see Tierz et al., 2019, for more "
517-
"details).\n")
518-
raise PyvolcansError(msg)
516+
"details).")
517+
warnings.warn(msg)
519518

520519

521-
def check_for_criteria_without_data(my_volcano_data, my_volcano_name):
520+
def warn_on_criteria_without_data(my_volcano_data, my_volcano_name, weights):
522521
"""
523522
Assesses whether some volcanological criteria do not have any data for the
524-
specific target volcano chosen by the user, raising a PyvolcansError
525-
exception if this is the case, informing the user which are these criteria.
523+
specific target volcano chosen by the user, raising a UserWarning
524+
if this is the case, informing the user which are these criteria.
526525
527526
Parameters
528527
----------
@@ -533,33 +532,25 @@ def check_for_criteria_without_data(my_volcano_data, my_volcano_name):
533532
data available (dict_value = 1), or there is not (dict_value = 0); for
534533
each of the volcanological criteria used by PyVOLCANS, considering the
535534
specific target volcano chosen by the user to run the program.
536-
537-
Raises
538-
-------
539-
PyvolcansError
540-
If one or more volcanological criteria have no data available for the
541-
selected target volcano.
535+
weights : dict
536+
Set of weights (weighting scheme) selected by the user to run PyVOLCANS
542537
"""
543-
544-
# based on:
545-
# https://thispointer.com/python-how-to-find-keys-by-value-in-dictionary
538+
my_flag_list = ['-Ts', '-G', '-M', '-Sz', '-St']
546539
my_list_keys = list()
547-
my_list_items = my_volcano_data.items()
548-
for item in my_list_items:
549-
if item[1] == 0:
550-
my_list_keys.append(item[0])
540+
for (key, value), flag in zip(my_volcano_data.items(), my_flag_list):
541+
if value == 0 and weights[key] > 0:
542+
my_list_keys.append(f'{key} ({flag})')
551543

552544
# check whether the list is not empty (in other words, there are some
553545
# volcanological criteria without data)
554546
if my_list_keys:
555547
nodata_criteria_text = ', '.join(my_list_keys)
556-
msg = ("WARNING!!! "
557-
"The following volcanological criteria do not have "
548+
msg = ("The following selected criteria do not have "
558549
"any data available for the selected target volcano "
559-
f"({my_volcano_name}): {nodata_criteria_text}. Please "
550+
f"({my_volcano_name}) --> {nodata_criteria_text}. Please "
560551
"consider excluding these criteria from your weighting scheme "
561552
"(i.e. setting their weights to zero).")
562-
raise PyvolcansError(msg)
553+
warnings.warn(msg)
563554

564555

565556
def open_gvp_website(top_analogue_vnum):
@@ -733,22 +724,22 @@ def plot_bar_apriori_analogues(my_volcano_name, my_volcano_vnum,
733724
# slice volcans_result to derive a data frame with the a priori analogues
734725
all_my_apriori_analogies = \
735726
volcans_result.loc[my_apriori_volcano_idx,
736-
['name','ATs','AG','AM','ASz','ASt']]
727+
['name', 'ATs', 'AG', 'AM', 'ASz', 'ASt']]
737728

738729
# plot single- and total-analogy values for all a priori analogues
739730
my_apriori_analogues_plot = \
740731
all_my_apriori_analogies.plot.bar(x="name",
741-
y=["ATs","AG","AM","ASz","ASt"],
732+
y=["ATs", "AG", "AM", "ASz", "ASt"],
742733
stacked=True)
743734

744735
fig1 = plt.gcf()
745-
my_apriori_analogues_plot.set_ylim([0,1])
736+
my_apriori_analogues_plot.set_ylim([0, 1])
746737
plt.title(f"A priori analogues: {my_volcano_name} ({my_volcano_vnum})",
747738
y=1.15, pad=5)
748739
plt.xlabel(None)
749740
plt.ylabel('Total Analogy')
750741
plt.legend(bbox_to_anchor=(0.9, 1.16), ncol=5)
751-
plt.tight_layout() # ensuring labels/titles are displayed properly
742+
plt.tight_layout() # ensuring labels/titles are displayed properly
752743

753744
if save_figure:
754745
fig1.savefig(
@@ -758,6 +749,7 @@ def plot_bar_apriori_analogues(my_volcano_name, my_volcano_vnum,
758749

759750
return all_my_apriori_analogies
760751

752+
761753
def plot_bar_better_analogues(my_volcano_name, my_volcano_vnum,
762754
better_analogues, criteria_weights_text,
763755
save_figure=None):
@@ -807,15 +799,13 @@ def plot_bar_better_analogues(my_volcano_name, my_volcano_vnum,
807799
y="percentage_better",
808800
legend=False,
809801
title=f"Better analogues: {my_volcano_name} ({my_volcano_vnum})",
810-
ylim=[0,50])
802+
ylim=[0, 50])
811803
plt.xlabel(None)
812804
plt.ylabel('Percentage of better analogues')
813805
plt.tight_layout()
814806
if save_figure:
815-
plt.savefig(
816-
(f"{my_volcano_name}_better_analogues_"
817-
f"{criteria_weights_text}.png"),
818-
dpi=600)
807+
filename = (f"{my_volcano_name}_better_analogues_{criteria_weights_text}.png")
808+
plt.savefig(filename, dpi=600)
819809

820810
return df_better_analogues
821811

test/test_pyvolcans.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ def mock_weights():
137137

138138

139139
def test_plot_bar_apriori_analogues(mock_weights, mock_analogies):
140-
pandas_df, _ = calculate_weighted_analogy_matrix('West Eifel Volcanic Field',
140+
pandas_df = calculate_weighted_analogy_matrix('West Eifel Volcanic Field',
141141
mock_weights,
142142
mock_analogies)
143143
df_bar = plot_bar_apriori_analogues('West Eifel Volcanic Field',
@@ -152,7 +152,7 @@ def test_plot_bar_apriori_analogues(mock_weights, mock_analogies):
152152

153153

154154
def test_plot_bar_better_analogues(mock_weights, mock_analogies):
155-
pandas_df, _ = calculate_weighted_analogy_matrix('West Eifel Volcanic Field',
155+
pandas_df = calculate_weighted_analogy_matrix('West Eifel Volcanic Field',
156156
mock_weights,
157157
mock_analogies)
158158
_, better_analogues = \
@@ -196,7 +196,7 @@ def test_plot_bar_better_analogues(mock_weights, mock_analogies):
196196
'eruption_size': 0.25,
197197
'eruption_style': 0}, 11110)])
198198
def test_combined_analogy_matrix_no_tectonic(weights, expected, mock_analogies):
199-
pandas_df, _ = calculate_weighted_analogy_matrix(
199+
pandas_df = calculate_weighted_analogy_matrix(
200200
'West Eifel Volcanic Field', weights, mock_analogies)
201201
matrix = pandas_df.loc[get_volcano_idx_from_name(
202202
'West Eifel Volcanic Field'), 'total_analogy']

test/test_pyvolcans_func.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import pytest
2+
3+
from pyvolcans import pyvolcans_func
4+
5+
6+
@pytest.mark.parametrize('volcano, weights, expected', [
7+
('Korath Range',
8+
{'tectonic_setting': 0.2,
9+
'geochemistry': 0.2,
10+
'morphology': 0.2,
11+
'eruption_size': 0.2,
12+
'eruption_style': 0.2},
13+
r'.* morphology .* eruption_size .* eruption_style'),
14+
('Korath Range',
15+
{'tectonic_setting': 0.2,
16+
'geochemistry': 0.4,
17+
'morphology': 0,
18+
'eruption_size': 0.2,
19+
'eruption_style': 0.2},
20+
r'.* eruption_size .* eruption_style'),
21+
('Korath Range',
22+
{'tectonic_setting': 0.2,
23+
'geochemistry': 0.2,
24+
'morphology': 0,
25+
'eruption_size': 0,
26+
'eruption_style': 0.6},
27+
r'.* eruption_style'),
28+
])
29+
def test_calculate_weighted_analogy_matrix_warnings(volcano, weights, expected):
30+
"""Test that warnings include correct missing criteria,
31+
based on regular expression match."""
32+
with pytest.warns(UserWarning, match=expected):
33+
pyvolcans_func.calculate_weighted_analogy_matrix(volcano, weights)

0 commit comments

Comments
 (0)