Skip to content

Commit f386d32

Browse files
authored
Merge pull request #263 from pabloitu/260-plot-refactoring
Refactored Plot Module, updating plotting functions and changed API to `csep.plots`
2 parents 1c81958 + 3ba5a38 commit f386d32

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+49398
-1792
lines changed

.coveragerc

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ omit =
55
tests/*
66
docs/*
77
examples/*
8-
csep/utils/plots.py
98
csep/utils/constants.py
109
csep/utils/datasets.py
1110
csep/utils/documents.py

csep/__init__.py

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,17 @@
33
import time
44

55
from csep._version import __version__
6-
7-
from csep.core import forecasts
6+
from csep.core import catalog_evaluations
87
from csep.core import catalogs
8+
from csep.core import forecasts
99
from csep.core import poisson_evaluations
10-
from csep.core import catalog_evaluations
1110
from csep.core import regions
11+
from csep.core.exceptions import CSEPCatalogException
12+
from csep.core.forecasts import GriddedForecast, CatalogForecast
1213
from csep.core.repositories import (
1314
load_json,
1415
write_json
1516
)
16-
17-
from csep.core.exceptions import CSEPCatalogException
18-
19-
from csep.utils import datasets
20-
from csep.utils import readers
21-
22-
from csep.core.forecasts import GriddedForecast, CatalogForecast
2317
from csep.models import (
2418
EvaluationResult,
2519
CatalogNumberTestResult,
@@ -28,7 +22,8 @@
2822
CatalogPseudolikelihoodTestResult,
2923
CalibrationTestResult
3024
)
31-
25+
from csep.utils import datasets
26+
from csep.utils import readers
3227
from csep.utils.time_utils import (
3328
utc_now_datetime,
3429
strptime_to_utc_datetime,
@@ -333,7 +328,7 @@ def query_gns(start_time, end_time, min_magnitude=2.950,
333328
verbose (bool): print catalog summary statistics
334329
335330
Returns:
336-
:class:`csep.core.catalogs.CSEPCatalog
331+
:class:`csep.core.catalogs.CSEPCatalog`
337332
"""
338333

339334
# Timezone should be in UTC
@@ -366,7 +361,7 @@ def query_gcmt(start_time, end_time, min_magnitude=5.0,
366361
max_depth=None,
367362
catalog_id=None,
368363
min_latitude=None, max_latitude=None,
369-
min_longitude=None, max_longitude=None):
364+
min_longitude=None, max_longitude=None, verbose=True):
370365

371366
eventlist = readers._query_gcmt(start_time=start_time,
372367
end_time=end_time,
@@ -381,6 +376,17 @@ def query_gcmt(start_time, end_time, min_magnitude=5.0,
381376
name='gCMT',
382377
catalog_id=catalog_id,
383378
date_accessed=utc_now_datetime())
379+
380+
if verbose:
381+
print("Downloaded catalog from GCMT with following parameters")
382+
print("Start Date: {}\nEnd Date: {}".format(str(catalog.start_time),
383+
str(catalog.end_time)))
384+
print("Min Latitude: {} and Max Latitude: {}".format(catalog.min_latitude,
385+
catalog.max_latitude))
386+
print("Min Longitude: {} and Max Longitude: {}".format(catalog.min_longitude,
387+
catalog.max_longitude))
388+
print("Min Magnitude: {}".format(catalog.min_magnitude))
389+
print(f"Found {catalog.event_count} events in the gns catalog.")
384390
return catalog
385391

386392

1.53 MB
Binary file not shown.

csep/core/binomial_evaluations.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def negative_binomial_number_test(gridded_forecast, observed_catalog, variance):
6565
delta1, delta2 = _nbd_number_test_ndarray(fore_cnt, obs_cnt, variance, epsilon=epsilon)
6666

6767
# store results
68-
result.test_distribution = ('negative_binomial', fore_cnt)
68+
result.test_distribution = ('negative_binomial', fore_cnt, variance)
6969
result.name = 'NBD N-Test'
7070
result.observed_statistic = obs_cnt
7171
result.quantile = (delta1, delta2)
@@ -126,7 +126,7 @@ def _simulate_catalog(sim_cells, sampling_weights, sim_fore, random_numbers=None
126126

127127

128128
def _binary_likelihood_test(forecast_data, observed_data, num_simulations=1000, random_numbers=None,
129-
seed=None, use_observed_counts=True, verbose=True, normalize_likelihood=False):
129+
seed=None, use_observed_counts=True, verbose=False, normalize_likelihood=False):
130130
""" Computes binary conditional-likelihood test from CSEP using an efficient simulation based approach.
131131
132132
Args:

csep/core/brier_evaluations.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def _simulate_catalog(sim_cells, sampling_weights, random_numbers=None):
7171

7272

7373
def _brier_score_test(forecast_data, observed_data, num_simulations=1000,
74-
random_numbers=None, seed=None, verbose=True):
74+
random_numbers=None, seed=None, verbose=False):
7575
""" Computes the Brier consistency test conditional on the total observed
7676
number of events
7777

csep/core/catalog_evaluations.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from csep.core.forecasts import CatalogForecast
2424

2525

26-
def number_test(forecast, observed_catalog, verbose=True):
26+
def number_test(forecast, observed_catalog, verbose=False):
2727
""" Performs the number test on a catalog-based forecast.
2828
2929
The number test builds an empirical distribution of the event counts for each data. By default, this
@@ -62,7 +62,7 @@ def number_test(forecast, observed_catalog, verbose=True):
6262
return result
6363

6464

65-
def spatial_test(forecast, observed_catalog, verbose=True):
65+
def spatial_test(forecast, observed_catalog, verbose=False):
6666
""" Performs spatial test for catalog-based forecasts.
6767
6868
@@ -136,7 +136,7 @@ def spatial_test(forecast, observed_catalog, verbose=True):
136136
delta_1, delta_2 = get_quantiles(test_distribution_spatial_1d, obs_lh_norm)
137137

138138
result = CatalogSpatialTestResult(test_distribution=test_distribution_spatial_1d,
139-
name='S-Test',
139+
name='Catalog S-Test',
140140
observed_statistic=obs_lh_norm,
141141
quantile=(delta_1, delta_2),
142142
status=message,
@@ -148,7 +148,7 @@ def spatial_test(forecast, observed_catalog, verbose=True):
148148
return result
149149

150150

151-
def magnitude_test(forecast, observed_catalog, verbose=True):
151+
def magnitude_test(forecast, observed_catalog, verbose=False):
152152
""" Performs magnitude test for catalog-based forecasts """
153153
test_distribution = []
154154

@@ -160,7 +160,7 @@ def magnitude_test(forecast, observed_catalog, verbose=True):
160160
print("Cannot perform magnitude test when observed event count is zero.")
161161
# prepare result
162162
result = CatalogMagnitudeTestResult(test_distribution=test_distribution,
163-
name='M-Test',
163+
name='Catalog M-Test',
164164
observed_statistic=None,
165165
quantile=(None, None),
166166
status='not-valid',
@@ -212,7 +212,7 @@ def magnitude_test(forecast, observed_catalog, verbose=True):
212212

213213
# prepare result
214214
result = CatalogMagnitudeTestResult(test_distribution=test_distribution,
215-
name='M-Test',
215+
name='Catalog M-Test',
216216
observed_statistic=obs_d_statistic,
217217
quantile=(delta_1, delta_2),
218218
status='normal',
@@ -224,7 +224,7 @@ def magnitude_test(forecast, observed_catalog, verbose=True):
224224
return result
225225

226226

227-
def pseudolikelihood_test(forecast, observed_catalog, verbose=True):
227+
def pseudolikelihood_test(forecast, observed_catalog, verbose=False):
228228
""" Performs the spatial pseudolikelihood test for catalog forecasts.
229229
230230
Performs the spatial pseudolikelihood test as described by Savran et al., 2020. The tests uses a pseudolikelihood
@@ -307,7 +307,7 @@ def pseudolikelihood_test(forecast, observed_catalog, verbose=True):
307307
# prepare evaluation result
308308
result = CatalogPseudolikelihoodTestResult(
309309
test_distribution=test_distribution_1d,
310-
name='PL-Test',
310+
name='Catalog PL-Test',
311311
observed_statistic=obs_plh,
312312
quantile=(delta_1, delta_2),
313313
status=message,

csep/core/catalogs.py

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from csep.utils.log import LoggingMixin
2424
from csep.utils.readers import csep_ascii
2525
from csep.utils.file import get_file_extension
26-
from csep.utils.plots import plot_catalog
26+
from csep.plots import plot_catalog, plot_magnitude_versus_time
2727

2828

2929
class AbstractBaseCatalog(LoggingMixin):
@@ -840,48 +840,59 @@ def b_positive(self):
840840
""" Implements the b-positive indicator from Nicholas van der Elst """
841841
pass
842842

843-
def plot(self, ax=None, show=False, extent=None, set_global=False, plot_args=None):
844-
""" Plot catalog according to plate-carree projection
843+
def plot(self, ax=None, show=False, extent=None, set_global=False, **kwargs):
844+
""" Plots the catalog epicenters.
845+
846+
See :func:`csep.utils.plots.plot_catalog` for a description of keyword arguments.
845847
846848
Args:
847-
ax (`matplotlib.pyplot.axes`): Previous axes onto which catalog can be drawn
848-
show (bool): if true, show the figure. this call is blocking.
849+
ax (matplotlib.pyplot.axes): Previous axes onto which catalog can be drawn
850+
show (bool): If True, shows the figure.
849851
extent (list): Force an extent [lon_min, lon_max, lat_min, lat_max]
850-
plot_args (optional/dict): dictionary containing plotting arguments for making figures
852+
set_global (bool): Whether to plot using a global projection
853+
**kwargs (dict): Keyword arguments passed to
854+
:func:`csep.utils.plots.plot_catalog`
851855
852856
Returns:
853857
axes: matplotlib.Axes.axes
854858
"""
855859

856860
# no mutable function arguments
857-
plot_args_default = {
858-
'basemap': 'ESRI_terrain',
859-
'markersize': 2,
860-
'markercolor': 'red',
861-
'alpha': 0.3,
862-
'mag_scale': 7,
863-
'legend': True,
864-
'grid_labels': True,
865-
'legend_loc': 3,
866-
'figsize': (8, 8),
867-
'title': self.name,
868-
'mag_ticks': False
861+
plot_args = {
862+
'basemap': kwargs.pop('basemap', 'ESRI_terrain') if ax is None else None
869863
}
870864

871865
# Plot the region border (if it exists) by default
872866
try:
873867
# This will throw error if catalog does not have region
874868
_ = self.region.num_nodes
875-
plot_args_default['region_border'] = True
869+
plot_args['region_border'] = True
876870
except AttributeError:
877871
pass
878872

879-
plot_args = plot_args or {}
880-
plot_args_default.update(plot_args)
873+
plot_args.update(kwargs.get('plot_args', {}))
874+
plot_args.update(kwargs)
881875

882876
# this call requires internet connection and basemap
883-
ax = plot_catalog(self, ax=ax,show=show, extent=extent,
884-
set_global=set_global, plot_args=plot_args_default)
877+
ax = plot_catalog(self, ax=ax, show=show, extent=extent,
878+
set_global=set_global, **plot_args)
879+
return ax
880+
881+
def plot_magnitude_versus_time(self, ax=None, show=False, **kwargs):
882+
""" Plot the magnitude-time series of a catalog. See
883+
https://docs.cseptesting.org/reference/generated/csep.utils.plots.plot_magnitude_versus_time.html for
884+
a description of keyword arguments.
885+
886+
Args:
887+
ax (`matplotlib.pyplot.axes`): Previous axes onto which catalog can be drawn
888+
show (bool): if true, show the figure. this call is blocking.
889+
890+
Returns:
891+
axes: matplotlib.Axes.axes
892+
"""
893+
894+
ax = plot_magnitude_versus_time(self, ax=ax, show=show, **kwargs)
895+
885896
return ax
886897

887898

csep/core/forecasts.py

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from csep.utils.time_utils import decimal_year, datetime_to_utc_epoch
1414
from csep.core.catalogs import AbstractBaseCatalog
1515
from csep.utils.constants import SECONDS_PER_ASTRONOMICAL_YEAR
16-
from csep.utils.plots import plot_spatial_dataset
16+
from csep.plots import plot_gridded_dataset
1717

1818

1919
# idea: should this be a SpatialDataSet and the class below SpaceMagnitudeDataSet, bc of functions like
@@ -432,17 +432,27 @@ def load_ascii(cls, ascii_fname, start_date=None, end_date=None, name=None, swap
432432
gds = cls(start_date, end_date, magnitudes=mws, name=name, region=region, data=rates)
433433
return gds
434434

435-
def plot(self, ax=None, show=False, log=True, extent=None, set_global=False, plot_args=None):
436-
""" Plot gridded forecast according to plate-carree projection
435+
def plot(self, ax=None, show=False, log=True, extent=None, set_global=False, plot_args=None,
436+
**kwargs):
437+
""" Plot the spatial rate of the forecast
438+
439+
See :func:`csep.utils.plots.plot_gridded_dataset` for a detailed description of the
440+
keyword arguments.
437441
438442
Args:
439-
show (bool): if true, show the figure. this call is blocking.
440-
plot_args (optional/dict): dictionary containing plotting arguments for making figures
443+
ax (`matplotlib.pyplot.axes`): Previous axes onto which catalog can be drawn
444+
show (bool): If True, shows the figure.
445+
log (bool): If True, plots the base-10 logarithm of the spatial rates
446+
extent (list): Force an extent [lon_min, lon_max, lat_min, lat_max]
447+
set_global (bool): Whether to plot using a global projection
448+
**kwargs (dict): Keyword arguments passed to
449+
:func:`csep.utils.plots.plot_gridded_dataset`
441450
442451
Returns:
443452
axes: matplotlib.Axes.axes
444453
"""
445-
# no mutable function arguments
454+
455+
446456
if self.start_time is None or self.end_time is None:
447457
time = 'forecast period'
448458
else:
@@ -451,19 +461,24 @@ def plot(self, ax=None, show=False, log=True, extent=None, set_global=False, plo
451461
time = f'{round(end-start,3)} years'
452462

453463
plot_args = plot_args or {}
454-
plot_args.setdefault('figsize', (10, 10))
455-
plot_args.setdefault('title', self.name)
456-
464+
plot_args.update({
465+
'basemap': kwargs.pop('basemap', 'ESRI_terrain') if ax is None else None,
466+
'title': kwargs.pop('title', None) or self.name,
467+
'figsize': kwargs.pop('figsize', None) or (8, 8),
468+
'plot_region': True
469+
})
470+
plot_args.update(**kwargs)
457471
# this call requires internet connection and basemap
458472
if log:
459473
plot_args.setdefault('clabel', f'log10 M{self.min_magnitude}+ rate per cell per {time}')
460474
with numpy.errstate(divide='ignore'):
461-
ax = plot_spatial_dataset(numpy.log10(self.spatial_counts(cartesian=True)), self.region, ax=ax,
462-
show=show, extent=extent, set_global=set_global, plot_args=plot_args)
475+
ax = plot_gridded_dataset(numpy.log10(self.spatial_counts(cartesian=True)), self.region, ax=ax,
476+
show=show, extent=extent, set_global=set_global,
477+
**plot_args)
463478
else:
464479
plot_args.setdefault('clabel', f'M{self.min_magnitude}+ rate per cell per {time}')
465-
ax = plot_spatial_dataset(self.spatial_counts(cartesian=True), self.region, ax=ax,show=show, extent=extent,
466-
set_global=set_global, plot_args=plot_args)
480+
ax = plot_gridded_dataset(self.spatial_counts(cartesian=True), self.region, ax=ax, show=show, extent=extent,
481+
set_global=set_global, **plot_args)
467482
return ax
468483

469484

@@ -654,7 +669,7 @@ def magnitude_counts(self):
654669
self.get_expected_rates()
655670
return self.expected_rates.magnitude_counts()
656671

657-
def get_event_counts(self, verbose=True):
672+
def get_event_counts(self, verbose=False):
658673
""" Returns a numpy array containing the number of event counts for each catalog.
659674
660675
Note: This function can take a while to compute if called without already iterating through a forecast that
@@ -715,7 +730,7 @@ def get_expected_rates(self, verbose=False):
715730
magnitudes=self.magnitudes, name=self.name)
716731
return self.expected_rates
717732

718-
def plot(self, plot_args = None, verbose=True, **kwargs):
733+
def plot(self, plot_args = None, verbose=False, **kwargs):
719734
plot_args = plot_args or {}
720735
if self.expected_rates is None:
721736
self.get_expected_rates(verbose=verbose)

csep/core/poisson_evaluations.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def paired_t_test(forecast, benchmark_forecast, observed_catalog,
4747
result.name = 'Paired T-Test'
4848
result.test_distribution = (out['ig_lower'], out['ig_upper'])
4949
result.observed_statistic = out['information_gain']
50-
result.quantile = (out['t_statistic'], out['t_critical'])
50+
result.quantile = (out['t_statistic'], out['t_critical'], alpha)
5151
result.sim_name = (forecast.name, benchmark_forecast.name)
5252
result.obs_name = observed_catalog.name
5353
result.status = 'normal'
@@ -601,7 +601,7 @@ def _simulate_catalog(num_events, sampling_weights, sim_fore,
601601

602602
def _poisson_likelihood_test(forecast_data, observed_data,
603603
num_simulations=1000, random_numbers=None,
604-
seed=None, use_observed_counts=True, verbose=True,
604+
seed=None, use_observed_counts=True, verbose=False,
605605
normalize_likelihood=False):
606606
"""
607607
Computes the likelihood-test from CSEP using an efficient simulation based approach.

csep/core/regions.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -658,7 +658,11 @@ def get_cartesian(self, data):
658658
"""Returns 2d ndrray representation of the data set, corresponding to the bounding box.
659659
660660
Args:
661-
data:
661+
data: An array of values, whose indices corresponds to each cell of the region
662+
663+
Returns:
664+
A 2D array of values, corresponding to the original data projected onto the region.
665+
Values outside the region polygon are represented as np.nan
662666
"""
663667
assert len(data) == len(self.polygons)
664668
results = numpy.zeros(self.bbox_mask.shape[:2])

0 commit comments

Comments
 (0)