Skip to content

Commit 6d0bd2c

Browse files
authored
Adds option called get_event_counts to CatalogForecast and adds arguments to plotting functions (#146)
* added verbose option to catalog based tests * added get_event_counts method to CatalogForecast * added plotting arguments for more granular customization * added test for get_event_counts
1 parent aa078b3 commit 6d0bd2c

File tree

4 files changed

+73
-16
lines changed

4 files changed

+73
-16
lines changed

csep/core/catalog_evaluations.py

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
# Third-Party Imports
2+
import time
3+
24
import numpy
35
import scipy.stats
46

@@ -15,7 +17,7 @@
1517
from csep.utils.stats import get_quantiles, cumulative_square_diff
1618

1719

18-
def number_test(forecast, observed_catalog):
20+
def number_test(forecast, observed_catalog, verbose=True):
1921
""" Performs the number test on a catalog-based forecast.
2022
2123
The number test builds an empirical distribution of the event counts for each data. By default, this
@@ -30,7 +32,14 @@ def number_test(forecast, observed_catalog):
3032
evaluation result (:class:`csep.models.EvaluationResult`): evaluation result
3133
"""
3234
event_counts = []
33-
for catalog in forecast:
35+
t0 = time.time()
36+
for i, catalog in enumerate(forecast):
37+
# output status
38+
if verbose:
39+
tens_exp = numpy.floor(numpy.log10(i + 1))
40+
if (i + 1) % 10 ** tens_exp == 0:
41+
t1 = time.time()
42+
print(f'Processed {i + 1} catalogs in {t1 - t0} seconds', flush=True)
3443
event_counts.append(catalog.event_count)
3544
obs_count = observed_catalog.event_count
3645
delta_1, delta_2 = get_quantiles(event_counts, obs_count)
@@ -46,7 +55,7 @@ def number_test(forecast, observed_catalog):
4655
obs_name=observed_catalog.name)
4756
return result
4857

49-
def spatial_test(forecast, observed_catalog):
58+
def spatial_test(forecast, observed_catalog, verbose=True):
5059
""" Performs spatial test for catalog-based forecasts.
5160
5261
@@ -70,7 +79,7 @@ def spatial_test(forecast, observed_catalog):
7079

7180
# compute expected rates for forecast if needed
7281
if forecast.expected_rates is None:
73-
forecast.get_expected_rates()
82+
forecast.get_expected_rates(verbose=verbose)
7483

7584
expected_cond_count = forecast.expected_rates.sum()
7685
forecast_mean_spatial_rates = forecast.expected_rates.spatial_counts()
@@ -81,10 +90,17 @@ def spatial_test(forecast, observed_catalog):
8190
n_obs = numpy.sum(gridded_obs)
8291

8392
# iterate through catalogs in forecast and compute likelihood
84-
for catalog in forecast:
93+
t0 = time.time()
94+
for i, catalog in enumerate(forecast):
8595
gridded_cat = catalog.spatial_counts()
8696
_, lh_norm = _compute_likelihood(gridded_cat, forecast_mean_spatial_rates, expected_cond_count, n_obs)
8797
test_distribution.append(lh_norm)
98+
# output status
99+
if verbose:
100+
tens_exp = numpy.floor(numpy.log10(i + 1))
101+
if (i + 1) % 10 ** tens_exp == 0:
102+
t1 = time.time()
103+
print(f'Processed {i + 1} catalogs in {t1 - t0} seconds', flush=True)
88104

89105
_, obs_lh_norm = _compute_likelihood(gridded_obs, forecast_mean_spatial_rates, expected_cond_count, n_obs)
90106
# if obs_lh is -numpy.inf, recompute but only for indexes where obs and simulated are non-zero
@@ -124,7 +140,7 @@ def spatial_test(forecast, observed_catalog):
124140

125141
return result
126142

127-
def magnitude_test(forecast, observed_catalog):
143+
def magnitude_test(forecast, observed_catalog, verbose=True):
128144
""" Performs magnitude test for catalog-based forecasts """
129145
test_distribution = []
130146

@@ -149,7 +165,7 @@ def magnitude_test(forecast, observed_catalog):
149165

150166
# compute expected rates for forecast if needed
151167
if forecast.expected_rates is None:
152-
forecast.get_expected_rates()
168+
forecast.get_expected_rates(verbose=verbose)
153169

154170
# returns the average events in the magnitude bins
155171
union_histogram = forecast.expected_rates.magnitude_counts()
@@ -160,7 +176,8 @@ def magnitude_test(forecast, observed_catalog):
160176
scaled_union_histogram = union_histogram * union_scale
161177

162178
# compute the test statistic for each catalog
163-
for catalog in forecast:
179+
t0 = time.time()
180+
for i, catalog in enumerate(forecast):
164181
mag_counts = catalog.magnitude_counts()
165182
n_events = numpy.sum(mag_counts)
166183
if n_events == 0:
@@ -172,6 +189,12 @@ def magnitude_test(forecast, observed_catalog):
172189
test_distribution.append(
173190
cumulative_square_diff(numpy.log10(catalog_histogram + 1), numpy.log10(scaled_union_histogram + 1))
174191
)
192+
# output status
193+
if verbose:
194+
tens_exp = numpy.floor(numpy.log10(i + 1))
195+
if (i + 1) % 10 ** tens_exp == 0:
196+
t1 = time.time()
197+
print(f'Processed {i + 1} catalogs in {t1 - t0} seconds', flush=True)
175198

176199
# compute observed statistic
177200
obs_d_statistic = cumulative_square_diff(numpy.log10(obs_histogram + 1), numpy.log10(scaled_union_histogram + 1))
@@ -192,7 +215,7 @@ def magnitude_test(forecast, observed_catalog):
192215

193216
return result
194217

195-
def pseudolikelihood_test(forecast, observed_catalog):
218+
def pseudolikelihood_test(forecast, observed_catalog, verbose=True):
196219
""" Performs the spatial pseudolikelihood test for catalog forecasts.
197220
198221
Performs the spatial pseudolikelihood test as described by Savran et al., 2020. The tests uses a pseudolikelihood
@@ -216,7 +239,7 @@ def pseudolikelihood_test(forecast, observed_catalog):
216239

217240
# compute expected rates for forecast if needed
218241
if forecast.expected_rates is None:
219-
_ = forecast.get_expected_rates()
242+
_ = forecast.get_expected_rates(verbose=verbose)
220243

221244
expected_cond_count = forecast.expected_rates.sum()
222245
forecast_mean_spatial_rates = forecast.expected_rates.spatial_counts()
@@ -226,10 +249,17 @@ def pseudolikelihood_test(forecast, observed_catalog):
226249
gridded_obs = observed_catalog.spatial_counts()
227250
n_obs = numpy.sum(gridded_obs)
228251

229-
for catalog in forecast:
252+
t0 = time.time()
253+
for i, catalog in enumerate(forecast):
230254
gridded_cat = catalog.spatial_counts()
231255
plh, _ = _compute_likelihood(gridded_cat, forecast_mean_spatial_rates, expected_cond_count, n_obs)
232256
test_distribution.append(plh)
257+
# output status
258+
if verbose:
259+
tens_exp = numpy.floor(numpy.log10(i + 1))
260+
if (i + 1) % 10 ** tens_exp == 0:
261+
t1 = time.time()
262+
print(f'Processed {i + 1} catalogs in {t1 - t0} seconds', flush=True)
233263

234264
obs_plh, _ = _compute_likelihood(gridded_obs, forecast_mean_spatial_rates, expected_cond_count, n_obs)
235265
# if obs_lh is -numpy.inf, recompute but only for indexes where obs and simulated are non-zero

csep/core/forecasts.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,8 @@ def __init__(self, filename=None, catalogs=None, name=None,
535535
# should be a MarkedGriddedDataSet
536536
self.expected_rates = expected_rates
537537

538+
self._event_counts = []
539+
538540
# defines the space, time, and magnitude region of the forecasts
539541
self.region = region
540542

@@ -606,6 +608,8 @@ def __next__(self):
606608
if self.filter_spatial:
607609
catalog = catalog.filter_spatial(self.region)
608610

611+
self._event_counts.append(catalog.event_count)
612+
609613
if is_generator and self.store:
610614
self._catalogs.append(catalog)
611615

@@ -647,6 +651,22 @@ def magnitude_counts(self):
647651
else:
648652
return None
649653

654+
def get_event_counts(self):
655+
""" Returns a numpy array containing the number of event counts for each catalog.
656+
657+
Note: This function can take a while to compute if called without already iterating through a forecast that
658+
is being stored on disk. This should only happen to large forecasts that have been initialized with
659+
store = False. This should only happen on the first iteration of the catalog.
660+
661+
Returns:
662+
(numpy.array): event counts with size equal of catalogs in forecast
663+
"""
664+
if len(self._event_counts) == 0:
665+
# event counts is filled while iterating over the catalog
666+
for _ in self:
667+
pass
668+
return numpy.array(self._event_counts)
669+
650670
def get_expected_rates(self, verbose=False):
651671
""" Compute the expected rates in space-magnitude bins
652672

csep/utils/plots.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -696,6 +696,7 @@ def plot_catalog(catalog, ax=None, show=False, extent=None, set_global=False, pl
696696
# scatter properties
697697
markersize = plot_args.get('markersize', 2)
698698
markercolor = plot_args.get('markercolor', 'blue')
699+
markeredgecolor = plot_args.get('markeredgecolor', 'black')
699700
alpha = plot_args.get('alpha', 1)
700701
mag_scale = plot_args.get('mag_scale', 1)
701702
legend = plot_args.get('legend', False)
@@ -771,6 +772,7 @@ def size_map(markersize, values, scale):
771772
s=size_map(markersize, catalog.get_magnitudes(), mag_scale),
772773
transform=cartopy.crs.PlateCarree(),
773774
color=markercolor,
775+
edgecolors=markeredgecolor,
774776
alpha=alpha)
775777

776778
# Legend
@@ -1345,6 +1347,9 @@ def plot_comparison_test(results_t, results_w=None, axes=None, plot_args=None):
13451347
linewidth = plot_args.get('linewidth', 1)
13461348
markersize = plot_args.get('markersize', 2)
13471349
percentile = plot_args.get('percentile', 95)
1350+
xticklabels_rotation = plot_args.get('xticklabels_rotation', 90)
1351+
xlabel_fontsize = plot_args.get('xlabel_fontsize', 12)
1352+
ylabel_fontsize = plot_args.get('ylabel_fontsize', 12)
13481353

13491354
if axes is None:
13501355
fig, ax = pyplot.subplots(figsize=figsize)
@@ -1377,10 +1382,10 @@ def plot_comparison_test(results_t, results_w=None, axes=None, plot_args=None):
13771382
facecolor = 'white'
13781383
ax.plot(index, result_t.observed_statistic, marker='o', markerfacecolor=facecolor, markeredgecolor=color, markersize=markersize)
13791384

1380-
ax.set_xticklabels([res.sim_name[0] for res in results_t], rotation=90)
1385+
ax.set_xticklabels([res.sim_name[0] for res in results_t], rotation=xticklabels_rotation)
13811386
ax.set_xticks(numpy.arange(len(results_t)))
1382-
ax.set_xlabel(xlabel)
1383-
ax.set_ylabel(ylabel)
1387+
ax.set_xlabel(xlabel, fontsize=xlabel_fontsize)
1388+
ax.set_ylabel(ylabel, fontsize=ylabel_fontsize)
13841389
ax.set_title(title)
13851390
ax.yaxis.grid()
13861391
xTickPos = ax.get_xticks()

tests/test_forecast.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,10 @@ def test_ascii_some_missing_verbose(self):
5151
self.assertEqual(10, test_fore.n_cat)
5252
numpy.testing.assert_array_equal([cat.catalog_id for cat in test_fore], numpy.arange(10))
5353

54-
def test_ascii_all_present(self):
55-
pass
54+
def test_get_event_counts(self):
55+
fname = os.path.join(get_test_catalog_root(), 'all_present.csv')
56+
test_fore = load_catalog_forecast(fname)
57+
numpy.testing.assert_array_equal(numpy.ones(10), test_fore.get_event_counts())
5658

5759
if __name__ == '__main__':
5860
unittest.main()

0 commit comments

Comments
 (0)