Skip to content

Commit 76844eb

Browse files
committed
Merge branch 'v0.6.1-release-branch' of github.com:SCECcode/pycsep into v0.6.1-release-branch
2 parents 7142fbb + 09e2769 commit 76844eb

File tree

9 files changed

+105
-43
lines changed

9 files changed

+105
-43
lines changed

csep/core/binomial_evaluations.py

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from csep.models import EvaluationResult
66
from csep.core.exceptions import CSEPCatalogException
77

8+
89
def _nbd_number_test_ndarray(fore_cnt, obs_cnt, variance, epsilon=1e-6):
910
""" Computes delta1 and delta2 values from the Negative Binomial (NBD) number test.
1011
@@ -99,7 +100,8 @@ def binary_joint_log_likelihood_ndarray(forecast, catalog):
99100
# Finally, we sum both terms to compute the joint log-likelihood score:
100101
return sum(first_term.data + second_term.data)
101102

102-
103+
104+
103105
def _simulate_catalog(sim_cells, sampling_weights, sim_fore, random_numbers=None):
104106
# Modified this code to generate simulations in a way that every cell gets one earthquake
105107
# Generate uniformly distributed random numbers in [0,1), this
@@ -164,7 +166,7 @@ def _binary_likelihood_test(forecast_data, observed_data, num_simulations=1000,
164166
else:
165167
sim_fore = _simulate_catalog(num_cells_to_simulate, sampling_weights, sim_fore,
166168
random_numbers=random_numbers[idx,:])
167-
169+
168170
# compute joint log-likelihood
169171
current_ll = binary_joint_log_likelihood_ndarray(forecast_data.data, sim_fore)
170172

@@ -208,12 +210,16 @@ def binary_spatial_test(gridded_forecast, observed_catalog, num_simulations=1000
208210
gridded_catalog_data = observed_catalog.spatial_counts()
209211

210212
# simply call likelihood test on catalog data and forecast
211-
qs, obs_ll, simulated_ll = _binary_likelihood_test(gridded_forecast.spatial_counts(), gridded_catalog_data,
212-
num_simulations=num_simulations,
213-
seed=seed,
214-
random_numbers=random_numbers,
215-
use_observed_counts=True,
216-
verbose=verbose, normalize_likelihood=True)
213+
qs, obs_ll, simulated_ll = _binary_likelihood_test(
214+
gridded_forecast.spatial_counts(),
215+
gridded_catalog_data,
216+
num_simulations=num_simulations,
217+
seed=seed,
218+
random_numbers=random_numbers,
219+
use_observed_counts=True,
220+
verbose=verbose,
221+
normalize_likelihood=True
222+
)
217223

218224

219225
# populate result data structure
@@ -261,10 +267,16 @@ def binary_conditional_likelihood_test(gridded_forecast, observed_catalog, num_s
261267
gridded_catalog_data = observed_catalog.spatial_magnitude_counts()
262268

263269
# simply call likelihood test on catalog data and forecast
264-
qs, obs_ll, simulated_ll = _binary_likelihood_test(gridded_forecast.data, gridded_catalog_data,
265-
num_simulations=num_simulations, seed=seed, random_numbers=random_numbers,
266-
use_observed_counts=True,
267-
verbose=verbose, normalize_likelihood=False)
270+
qs, obs_ll, simulated_ll = _binary_likelihood_test(
271+
gridded_forecast.data,
272+
gridded_catalog_data,
273+
num_simulations=num_simulations,
274+
seed=seed,
275+
random_numbers=random_numbers,
276+
use_observed_counts=True,
277+
verbose=verbose,
278+
normalize_likelihood=False
279+
)
268280

269281
# populate result data structure
270282
result = EvaluationResult()

csep/core/catalogs.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -737,16 +737,16 @@ def spatial_magnitude_counts(self, mag_bins=None, tol=0.00001):
737737
""" Return counts of events in space-magnitude region.
738738
739739
We figure out the index of the polygons and create a map that relates the spatial coordinate in the
740-
Cartesian grid with with the polygon in region.
740+
Cartesian grid with the polygon in region.
741741
742742
Args:
743-
mag_bins: magnitude bins (optional). tries to use magnitue bins associated with region
743+
mag_bins (list, numpy.array): magnitude bins (optional), if empty tries to use magnitude bins associated with region
744+
tol (float): tolerance for comparisons within magnitude bins
744745
745746
Returns:
746747
output: unnormalized event count in each bin, 1d ndarray where index corresponds to midpoints
747748
748749
"""
749-
750750
# make sure region is specified with catalog
751751
if self.region is None:
752752
raise CSEPCatalogException("Cannot create binned rates without region information.")
@@ -784,8 +784,8 @@ def get_bvalue(self, mag_bins=None, return_error=True):
784784
If that fails, uses the default magnitude bins provided in constants.
785785
786786
Args:
787-
reterr (bool): returns errors
788787
mag_bins (list or array_like): monotonically increasing set of magnitude bin edges
788+
return_error (bool): returns errors
789789
790790
Returns:
791791
bval (float): b-value
@@ -824,6 +824,10 @@ def p():
824824
else:
825825
return bval
826826

827+
def b_positive(self):
828+
""" Implements the b-positive indicator from Nicholas van der Elst """
829+
pass
830+
827831
def plot(self, ax=None, show=False, extent=None, set_global=False, plot_args=None):
828832
""" Plot catalog according to plate-carree projection
829833
@@ -1028,9 +1032,10 @@ def read_catalog_line(line):
10281032
raise ValueError(
10291033
"catalog_id should be monotonically increasing and events should be ordered by catalog_id")
10301034
# yield final catalog, note: since this is just loading catalogs, it has no idea how many should be there
1031-
yield cls(data=events, catalog_id=prev_id, **kwargs)
1035+
cat = cls(data=events, catalog_id=prev_id, **kwargs)
1036+
yield cat
10321037

1033-
if os.path.isdir(filename):
1038+
elif os.path.isdir(filename):
10341039
raise NotImplementedError("reading from directory or batched files not implemented yet!")
10351040

10361041
@classmethod

csep/core/forecasts.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -649,7 +649,7 @@ def magnitude_counts(self):
649649
self.get_expected_rates()
650650
return self.expected_rates.magnitude_counts()
651651

652-
def get_event_counts(self):
652+
def get_event_counts(self, verbose=True):
653653
""" Returns a numpy array containing the number of event counts for each catalog.
654654
655655
Note: This function can take a while to compute if called without already iterating through a forecast that
@@ -661,7 +661,13 @@ def get_event_counts(self):
661661
"""
662662
if len(self._event_counts) == 0:
663663
# event counts is filled while iterating over the catalog
664-
for _ in self:
664+
t0 = time.time()
665+
for i, _ in enumerate(self):
666+
if verbose:
667+
tens_exp = numpy.floor(numpy.log10(i + 1))
668+
if (i + 1) % 10 ** tens_exp == 0:
669+
t1 = time.time()
670+
print(f'Processed {i + 1} catalogs in {t1 - t0:.2f} seconds', flush=True)
665671
pass
666672
return numpy.array(self._event_counts)
667673

@@ -697,7 +703,7 @@ def get_expected_rates(self, verbose=False):
697703
tens_exp = numpy.floor(numpy.log10(i + 1))
698704
if (i + 1) % 10 ** tens_exp == 0:
699705
t1 = time.time()
700-
print(f'Processed {i + 1} catalogs in {t1 - t0} seconds', flush=True)
706+
print(f'Processed {i + 1} catalogs in {t1 - t0:.3f} seconds', flush=True)
701707
# after we iterate through the catalogs, we know self.n_cat
702708
data = data / self.n_cat
703709
self.expected_rates = GriddedForecast(self.start_time, self.end_time, data=data, region=self.region,

csep/utils/plots.py

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1557,7 +1557,7 @@ def plot_poisson_consistency_test(eval_results, normalize=False, one_sided_lower
15571557
figsize= plot_args.get('figsize', None)
15581558
title = plot_args.get('title', results[0].name)
15591559
title_fontsize = plot_args.get('title_fontsize', None)
1560-
xlabel = plot_args.get('xlabel', 'X')
1560+
xlabel = plot_args.get('xlabel', '')
15611561
xlabel_fontsize = plot_args.get('xlabel_fontsize', None)
15621562
xticks_fontsize = plot_args.get('xticks_fontsize', None)
15631563
ylabel_fontsize = plot_args.get('ylabel_fontsize', None)
@@ -1567,6 +1567,7 @@ def plot_poisson_consistency_test(eval_results, normalize=False, one_sided_lower
15671567
hbars = plot_args.get('hbars', True)
15681568
tight_layout = plot_args.get('tight_layout', True)
15691569
percentile = plot_args.get('percentile', 95)
1570+
plot_mean = plot_args.get('mean', False)
15701571

15711572
if axes is None:
15721573
fig, ax = pyplot.subplots(figsize=figsize)
@@ -1580,6 +1581,7 @@ def plot_poisson_consistency_test(eval_results, normalize=False, one_sided_lower
15801581
if res.test_distribution[0] == 'poisson':
15811582
plow = scipy.stats.poisson.ppf((1 - percentile/100.)/2., res.test_distribution[1])
15821583
phigh = scipy.stats.poisson.ppf(1 - (1 - percentile/100.)/2., res.test_distribution[1])
1584+
mean = res.test_distribution[1]
15831585
observed_statistic = res.observed_statistic
15841586
# empirical distributions
15851587
else:
@@ -1596,12 +1598,14 @@ def plot_poisson_consistency_test(eval_results, normalize=False, one_sided_lower
15961598
else:
15971599
plow = numpy.percentile(test_distribution, (100 - percentile)/2.)
15981600
phigh = numpy.percentile(test_distribution, 100 - (100 - percentile)/2.)
1601+
mean = numpy.mean(res.test_distribution)
15991602

16001603
if not numpy.isinf(observed_statistic): # Check if test result does not diverges
1601-
low = observed_statistic - plow
1602-
high = phigh - observed_statistic
1603-
ax.errorbar(observed_statistic, index, xerr=numpy.array([[low, high]]).T,
1604-
fmt=_get_marker_style(observed_statistic, (plow, phigh), one_sided_lower),
1604+
percentile_lims = numpy.array([[mean - plow, phigh - mean]]).T
1605+
ax.plot(observed_statistic, index,
1606+
_get_marker_style(observed_statistic, (plow, phigh), one_sided_lower))
1607+
ax.errorbar(mean, index, xerr=percentile_lims,
1608+
fmt='ko'*plot_mean,
16051609
capsize=capsize, linewidth=linewidth, ecolor=color)
16061610
# determine the limits to use
16071611
xlims.append((plow, phigh, observed_statistic))
@@ -1887,7 +1891,7 @@ def add_labels_for_publication(figure, style='bssa', labelsize=16):
18871891
return
18881892

18891893

1890-
def plot_consistency_test(eval_results, normalize=False, one_sided_lower=True, plot_args=None, variance=None):
1894+
def plot_consistency_test(eval_results, normalize=False, axes=None, one_sided_lower=False, variance=None, plot_args=None, show=False):
18911895
""" Plots results from CSEP1 tests following the CSEP1 convention.
18921896
18931897
Note: All of the evaluations should be from the same type of evaluation, otherwise the results will not be
@@ -1927,8 +1931,10 @@ def plot_consistency_test(eval_results, normalize=False, one_sided_lower=True, p
19271931
# Parse plot arguments. More can be added here
19281932
if plot_args is None:
19291933
plot_args = {}
1930-
figsize= plot_args.get('figsize', (7,8))
1931-
xlabel = plot_args.get('xlabel', 'X')
1934+
figsize= plot_args.get('figsize', None)
1935+
title = plot_args.get('title', results[0].name)
1936+
title_fontsize = plot_args.get('title_fontsize', None)
1937+
xlabel = plot_args.get('xlabel', '')
19321938
xlabel_fontsize = plot_args.get('xlabel_fontsize', None)
19331939
xticks_fontsize = plot_args.get('xticks_fontsize', None)
19341940
ylabel_fontsize = plot_args.get('ylabel_fontsize', None)
@@ -1938,15 +1944,22 @@ def plot_consistency_test(eval_results, normalize=False, one_sided_lower=True, p
19381944
hbars = plot_args.get('hbars', True)
19391945
tight_layout = plot_args.get('tight_layout', True)
19401946
percentile = plot_args.get('percentile', 95)
1947+
plot_mean = plot_args.get('mean', False)
1948+
1949+
if axes is None:
1950+
fig, ax = pyplot.subplots(figsize=figsize)
1951+
else:
1952+
ax = axes
1953+
fig = ax.get_figure()
19411954

1942-
fig, ax = pyplot.subplots(figsize=figsize)
19431955
xlims = []
19441956

19451957
for index, res in enumerate(results):
19461958
# handle analytical distributions first, they are all in the form ['name', parameters].
19471959
if res.test_distribution[0] == 'poisson':
19481960
plow = scipy.stats.poisson.ppf((1 - percentile/100.)/2., res.test_distribution[1])
19491961
phigh = scipy.stats.poisson.ppf(1 - (1 - percentile/100.)/2., res.test_distribution[1])
1962+
mean = res.test_distribution[1]
19501963
observed_statistic = res.observed_statistic
19511964

19521965
elif res.test_distribution[0] == 'negative_binomial':
@@ -1973,13 +1986,15 @@ def plot_consistency_test(eval_results, normalize=False, one_sided_lower=True, p
19731986
else:
19741987
plow = numpy.percentile(test_distribution, 2.5)
19751988
phigh = numpy.percentile(test_distribution, 97.5)
1989+
mean = numpy.mean(res.test_distribution)
19761990

19771991
if not numpy.isinf(observed_statistic): # Check if test result does not diverges
1978-
low = observed_statistic - plow
1979-
high = phigh - observed_statistic
1980-
ax.errorbar(observed_statistic, index, xerr=numpy.array([[low, high]]).T,
1981-
fmt=_get_marker_style(observed_statistic, (plow, phigh), one_sided_lower),
1982-
capsize=4, linewidth=linewidth, ecolor=color, markersize = 10, zorder=1)
1992+
percentile_lims = numpy.array([[mean - plow, phigh - mean]]).T
1993+
ax.plot(observed_statistic, index,
1994+
_get_marker_style(observed_statistic, (plow, phigh), one_sided_lower))
1995+
ax.errorbar(mean, index, xerr=percentile_lims,
1996+
fmt='ko'*plot_mean,
1997+
capsize=capsize, linewidth=linewidth, ecolor=color)
19831998
# determine the limits to use
19841999
xlims.append((plow, phigh, observed_statistic))
19852000
# we want to only extent the distribution where it falls outside of it in the acceptable tail
@@ -2001,18 +2016,23 @@ def plot_consistency_test(eval_results, normalize=False, one_sided_lower=True, p
20012016
except ValueError:
20022017
raise ValueError('All EvaluationResults have infinite observed_statistics')
20032018
ax.set_yticks(numpy.arange(len(results)))
2004-
ax.set_yticklabels([res.sim_name for res in results], fontsize=14)
2019+
ax.set_yticklabels([res.sim_name for res in results], fontsize=ylabel_fontsize)
20052020
ax.set_ylim([-0.5, len(results)-0.5])
20062021
if hbars:
20072022
yTickPos = ax.get_yticks()
20082023
if len(yTickPos) >= 2:
20092024
ax.barh(yTickPos, numpy.array([99999] * len(yTickPos)), left=-10000,
20102025
height=(yTickPos[1] - yTickPos[0]), color=['w', 'gray'], alpha=0.2, zorder=0)
2011-
ax.set_xlabel(xlabel, fontsize=14)
2012-
ax.tick_params(axis='x', labelsize=13)
2026+
ax.set_title(title, fontsize=title_fontsize)
2027+
ax.set_xlabel(xlabel, fontsize=xlabel_fontsize)
2028+
ax.tick_params(axis='x', labelsize=xticks_fontsize)
20132029
if tight_layout:
20142030
ax.figure.tight_layout()
20152031
fig.tight_layout()
2032+
2033+
if show:
2034+
pyplot.show()
2035+
20162036
return ax
20172037

20182038

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
numpy
22
scipy
33
pandas
4-
matplotlib<=3.5.3
4+
matplotlib
55
cartopy
66
obspy
77
pyproj

requirements.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ dependencies:
77
- numpy
88
- pandas
99
- scipy
10-
- matplotlib<=3.5.3
10+
- matplotlib
1111
- pyproj
1212
- obspy
1313
- python-dateutil

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def get_version():
3030
'numpy',
3131
'scipy',
3232
'pandas',
33-
'matplotlib<=3.5.3',
33+
'matplotlib',
3434
'cartopy',
3535
'obspy',
3636
'pyproj',

tests/test_evaluations.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ def get_datadir():
1111
data_dir = os.path.join(root_dir, 'artifacts', 'Comcat')
1212
return data_dir
1313

14+
1415
class TestPoissonLikelihood(unittest.TestCase):
1516

1617
def __init__(self, *args, **kwargs):
@@ -85,5 +86,6 @@ def test_joint_likelihood_calculation(self):
8586

8687
numpy.testing.assert_allclose(bill, -6.7197988064)
8788

89+
8890
if __name__ == '__main__':
89-
unittest.main()
91+
unittest.main()

tests/test_forecast.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,24 @@
1-
import os, unittest
1+
import os
2+
import unittest
23
import numpy
34
from csep import load_catalog_forecast
45

6+
57
def get_test_catalog_root():
68
root_dir = os.path.dirname(os.path.abspath(__file__))
79
data_dir = os.path.join(root_dir, 'artifacts', 'test_ascii_catalogs')
810
return data_dir
911

12+
1013
class TestCatalogForecastCreation(unittest.TestCase):
1114

15+
def test_all_present(self):
16+
fname = os.path.join(get_test_catalog_root(), 'all_present.csv')
17+
test_fore = load_catalog_forecast(fname)
18+
total_event_count = numpy.array([cat.event_count for cat in test_fore]).sum()
19+
self.assertEqual(10, test_fore.n_cat)
20+
self.assertEqual(10, total_event_count)
21+
1222
def test_ascii_load_all_empty(self):
1323
fname = os.path.join(get_test_catalog_root(), 'all_empty.csv')
1424
test_fore = load_catalog_forecast(fname)
@@ -56,5 +66,12 @@ def test_get_event_counts(self):
5666
test_fore = load_catalog_forecast(fname)
5767
numpy.testing.assert_array_equal(numpy.ones(10), test_fore.get_event_counts())
5868

69+
def test_multiple_iterations(self):
70+
fname = os.path.join(get_test_catalog_root(), 'all_present.csv')
71+
test_fore = load_catalog_forecast(fname)
72+
ec1 = [cat.event_count for cat in test_fore]
73+
ec2 = [cat.event_count for cat in test_fore]
74+
numpy.testing.assert_array_equal(ec1, ec2)
75+
5976
if __name__ == '__main__':
6077
unittest.main()

0 commit comments

Comments
 (0)