Skip to content

Commit a50d1e3

Browse files
committed
ft: finalized brier score implementation as well as brier consistency test. It is implemented only using a Binary calculation for spatial-magnitude bins.
tests: Implemented brier score tests.
1 parent 5fb55ca commit a50d1e3

File tree

3 files changed

+103
-30
lines changed

3 files changed

+103
-30
lines changed

csep/core/brier_evaluations.py

+43-13
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,22 @@
77

88

99
def _brier_score_ndarray(forecast, observations):
10-
""" Computes the brier score
10+
""" Computes the brier (binary) score for spatial-magnitude cells
11+
using the formula:
12+
13+
Q(Lambda, Sigma) = 1/N sum_{i=1}^N (Lambda_i - Ind(Sigma_i > 0 ))^2
14+
15+
where Lambda is the forecast array, Sigma is the observed catalog, N the
16+
number of spatial-magnitude cells and Ind is the indicator function, which
17+
is 1 if Sigma_i > 0 and 0 otherwise.
18+
19+
Args:
20+
forecast: 2d array of forecasted rates
21+
observations: 2d array of observed counts
22+
Returns
23+
brier: float, brier score
1124
"""
25+
1226
prob_success = 1 - poisson.cdf(0, forecast)
1327
brier_cell = np.square(prob_success.ravel() - (observations.ravel() > 0))
1428
brier = -2 * brier_cell.sum()
@@ -19,6 +33,18 @@ def _brier_score_ndarray(forecast, observations):
1933

2034

2135
def _simulate_catalog(sim_cells, sampling_weights, random_numbers=None):
36+
"""
37+
Simulates a catalog by sampling from the sampling_weights array.
38+
Identical to binomial_evaluations._simulate_catalog
39+
40+
Args:
41+
sim_cells:
42+
sampling_weights:
43+
random_numbers:
44+
45+
Returns:
46+
47+
"""
2248
sim_fore = numpy.zeros(sampling_weights.shape)
2349

2450
if random_numbers is None:
@@ -46,10 +72,19 @@ def _simulate_catalog(sim_cells, sampling_weights, random_numbers=None):
4672

4773
def _brier_score_test(forecast_data, observed_data, num_simulations=1000,
4874
random_numbers=None, seed=None, verbose=True):
49-
""" Computes binary conditional-likelihood test from CSEP using an
50-
efficient simulation based approach.
75+
""" Computes the Brier consistency test conditional on the total observed
76+
number of events
5177
5278
Args:
79+
forecast_data: 2d array of forecasted rates for spatial_magnitude cells
80+
observed_data: 2d array of a catalog resampled to spatial_magnitude
81+
cells
82+
num_simulations: number of synthetic catalog simulations
83+
random_numbers: numpy array of random numbers to use for simulation
84+
seed: seed for random number generator
85+
verbose: print status updates
86+
87+
5388
5489
"""
5590
# Array-masking that avoids log singularities:
@@ -58,8 +93,6 @@ def _brier_score_test(forecast_data, observed_data, num_simulations=1000,
5893
# set seed for the likelihood test
5994
if seed is not None:
6095
numpy.random.seed(seed)
61-
import time
62-
start = time.process_time()
6396

6497
# used to determine where simulated earthquake should
6598
# be placed, by definition of cumsum these are sorted
@@ -93,7 +126,6 @@ def _brier_score_test(forecast_data, observed_data, num_simulations=1000,
93126
print(f'... {idx + 1} catalogs simulated.')
94127

95128
obs_brier = _brier_score_ndarray(forecast_data.data, observed_data)
96-
97129
# quantile score
98130
qs = numpy.sum(simulated_brier <= obs_brier) / num_simulations
99131

@@ -107,13 +139,11 @@ def brier_score_test(gridded_forecast,
107139
seed=None,
108140
random_numbers=None,
109141
verbose=False):
110-
""" Performs the Brier conditional test on Gridded Forecast using an
111-
Observed Catalog.
112-
Normalizes the forecast so the forecasted rate are consistent with the
113-
observations. This modification
114-
eliminates the strong impact differences in the number distribution
115-
have on the forecasted rates.
116-
142+
"""
143+
Performs the Brier conditional test on a Gridded Forecast using an
144+
Observed Catalog. Normalizes the forecast so the forecasted rate are
145+
consistent with the observations. This modification eliminates the strong
146+
impact differences in the number distribution have on the forecasted rates.
117147
"""
118148

119149
# grid catalog onto spatial grid

tests/test_brier.py

-16
This file was deleted.

tests/test_evaluations.py

+60-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import os
22
import numpy
3+
import numpy as np
4+
import scipy
35
import unittest
46

57
import csep.core.poisson_evaluations as poisson
68
import csep.core.binomial_evaluations as binary
7-
9+
import csep.core.brier_evaluations as brier
810
def get_datadir():
911
root_dir = os.path.dirname(os.path.abspath(__file__))
1012
data_dir = os.path.join(root_dir, 'artifacts', 'Comcat')
@@ -115,5 +117,62 @@ def test_binomial_likelihood(self):
115117
numpy.testing.assert_allclose(simulated_ll[0], -7.921741654647629)
116118

117119

120+
class TestPoissonBrier(unittest.TestCase):
121+
122+
def __init__(self, *args, **kwargs):
123+
super().__init__(*args, **kwargs)
124+
self.seed = 1
125+
self.forecast_data = numpy.array([[0.3, 0.2, 0.1], [0.2, 0.1, 0.1]])
126+
self.observed_data = numpy.array([[0, 1, 2], [1, 1, 0]])
127+
128+
def test_brier_score_calculation(self):
129+
130+
# 1 bin
131+
rate = 1
132+
prob = 1 - scipy.stats.poisson.pmf(0, rate)
133+
brier_score_hit = -2 * (prob - 1)**2
134+
brier_score = brier._brier_score_ndarray(numpy.array([[rate]]),
135+
numpy.array([[1]]))
136+
numpy.testing.assert_allclose(brier_score_hit, brier_score)
137+
138+
brier_score_nohit = -2 * prob**2
139+
brier_score = brier._brier_score_ndarray(numpy.array([[rate]]),
140+
numpy.array([[0]]))
141+
numpy.testing.assert_allclose(brier_score_nohit, brier_score)
142+
# 2 bins
143+
rate = np.array([1, 0.5])
144+
hits = np.array([0, 1])
145+
prob = 1 - scipy.stats.poisson.pmf(0, rate)
146+
brier_score_2bins = (-2 * (prob - hits)**2).sum() / len(rate)
147+
brier_score = brier._brier_score_ndarray(np.array(rate),
148+
np.array([hits]))
149+
numpy.testing.assert_allclose(brier_score_2bins, brier_score)
150+
151+
hits = np.array([0, 0])
152+
brier_score_2bins = (-2 * (prob - hits)**2).sum() / len(rate)
153+
brier_score = brier._brier_score_ndarray(np.array(rate),
154+
np.array([hits]))
155+
numpy.testing.assert_allclose(brier_score_2bins, brier_score)
156+
157+
def test_brier_test(self):
158+
159+
expected_sim = numpy.array([1, 1, 1, 1, 0, 0])
160+
qs, obs_brier, simulated_brier = brier._brier_score_test(
161+
self.forecast_data,
162+
self.observed_data,
163+
num_simulations=1,
164+
seed=self.seed,
165+
verbose=True)
166+
167+
probs = 1 - scipy.stats.poisson.pmf(0, self.forecast_data.ravel())
168+
sim_brier_analytic = ((-2 * (probs - expected_sim)**2).sum() /
169+
len(probs))
170+
numpy.testing.assert_allclose(simulated_brier[0], sim_brier_analytic)
171+
obs_brier_analytic = (
172+
(-2 * (probs - self.observed_data.ravel().astype(bool))**2).sum() /
173+
len(probs))
174+
numpy.testing.assert_allclose(obs_brier, obs_brier_analytic)
175+
176+
118177
if __name__ == '__main__':
119178
unittest.main()

0 commit comments

Comments
 (0)