• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nci / scores / 9866077057

10 Jul 2024 12:24AM CUT coverage: 100.0%. Remained the same
9866077057

Pull #600

github

web-flow
Merge 7a40baa8a into 7093294bd
Pull Request #600: (DRAFT PR) Draft release notes for version 1.0.0

348 of 348 branches covered (100.0%)

Branch coverage included in aggregate %.

1706 of 1706 relevant lines covered (100.0%)

4.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

100.0
/src/scores/continuous/consistent_impl.py
1
"""
2
Implementation of scoring functions that are consistent for
3
single-valued forecasts targeting quantiles, expectiles or Huber functionals.
4
"""
5

6
from typing import Callable, Optional
4✔
7

8
import xarray as xr
4✔
9

10
from scores.functions import apply_weights
4✔
11
from scores.typing import FlexibleDimensionTypes
4✔
12
from scores.utils import gather_dimensions
4✔
13

14

15
def consistent_expectile_score(
4✔
16
    fcst: xr.DataArray,
17
    obs: xr.DataArray,
18
    alpha: float,
19
    phi: Callable[[xr.DataArray], xr.DataArray],
20
    phi_prime: Callable[[xr.DataArray], xr.DataArray],
21
    *,  # Force keywords arguments to be keyword-only
22
    reduce_dims: Optional[FlexibleDimensionTypes] = None,
23
    preserve_dims: Optional[FlexibleDimensionTypes] = None,
24
    weights: Optional[xr.DataArray] = None,
25
) -> xr.DataArray:
26
    """
27
    Calculates the score using a scoring function that is consistent for the
28
    alpha-expectile functional, based on a supplied convex function phi.
29
    See Geniting (2011), or Equation (10) from Taggart (2022).
30

31
    .. math::
32

33
        S(x, y) =
34
        \\begin{cases}
35
        (1 - \\alpha)(\\phi(y) - \\phi(x) - \\phi'(x)(y-x)), & y < x \\\\
36
        \\alpha(\\phi(y) - \\phi(x) - \\phi'(x)(y-x)), & x \\leq y
37
        \\end{cases}
38

39
    where
40
        - :math:`x` is the forecast
41
        - :math:`y` is the observation
42
        - :math:`\\alpha` is the expectile level
43
        - :math:`\\phi` is a convex function of a single variable
44
        - :math:`\\phi'` is the subderivative of :math:`\\phi`
45
        - :math:`S(x,y)` is the score.
46

47
    Note that if :math:`\\phi` is differentiable then :math:`\\phi'` is its derivative.
48

49
    The score is negatively oriented with :math:`0 \\leq S(x,y) < \\infty`.
50

51
    We strongly recommend that you work through the tutorial 
52
    https://scores.readthedocs.io/en/stable/tutorials/Consistent_Scores.html to help you 
53
    understand how to use the consistent scoring functions in ``scores``.
54

55
    Args:
56
        fcst: array of forecast values.
57
        obs: array of corresponding observation values.
58
        alpha: expectile level. Must be strictly between 0 and 1.
59
        phi: a convex function on the real numbers, accepting a single array like argument.
60
        phi_prime: a subderivative of `phi`, accepting a single array like argument.
61
        reduce_dims: Optionally specify which dimensions to reduce when
62
            calculating the consistent expectile score. All other dimensions will be preserved. As a
63
            special case, 'all' will allow all dimensions to be reduced. Only one
64
            of `reduce_dims` and `preserve_dims` can be supplied. The default behaviour
65
            if neither are supplied is to reduce all dims.
66
        preserve_dims: Optionally specify which dimensions to preserve when calculating
67
            the consistent quantile score. All other dimensions will be reduced. As a special case, 'all'
68
            will allow all dimensions to be preserved. In this case, the result will be in
69
            the same shape/dimensionality as the forecast, and the errors will be the consistent quantile
70
            score at each point (i.e. single-value comparison against observed), and the
71
            forecast and observed dimensions must match precisely. Only one of `reduce_dims`
72
            and `preserve_dims` can be supplied. The default behaviour if neither are supplied
73
            is to reduce all dims.
74
        weights: Optionally provide an array for weighted averaging (e.g. by area, by latitude,
75
            by population, custom)
76

77
    Returns:
78
        array of (mean) scores that is consistent for alpha-expectile functional,
79
        with the dimensions specified by `dims`. If `dims` is `None`, the returned DataArray will have
80
        only one entry, the overall mean score.
81

82
    Raises:
83
        ValueError: if `alpha` is not strictly between 0 and 1.
84

85
    References:
86
        -   Gneiting, T. (2011). Making and Evaluating Point Forecasts. Journal of the 
87
            American Statistical Association, 106(494), 746–762. 
88
            https://doi.org/10.1198/jasa.2011.r10138
89
        -   Taggart, R. (2022). Evaluation of point forecasts for extreme events using 
90
            consistent scoring functions. Quarterly Journal of the Royal Meteorological 
91
            Society, 148(742), 306–320. https://doi.org/10.1002/qj.4206
92

93
    """
94
    check_alpha(alpha)
4✔
95

96
    reduce_dims = gather_dimensions(fcst.dims, obs.dims, reduce_dims=reduce_dims, preserve_dims=preserve_dims)
4✔
97

98
    score_overfcst = (1 - alpha) * (phi(obs) - phi(fcst) - phi_prime(fcst) * (obs - fcst))
4✔
99
    score_underfcst = alpha * (phi(obs) - phi(fcst) - phi_prime(fcst) * (obs - fcst))
4✔
100
    result = score_overfcst.where(obs < fcst, score_underfcst)
4✔
101
    result = apply_weights(result, weights=weights)
4✔
102
    result = result.mean(dim=reduce_dims)
4✔
103

104
    return result
4✔
105

106

107
def consistent_huber_score(
4✔
108
    fcst: xr.DataArray,
109
    obs: xr.DataArray,
110
    huber_param: float,
111
    phi: Callable[[xr.DataArray], xr.DataArray],
112
    phi_prime: Callable[[xr.DataArray], xr.DataArray],
113
    *,  # Force keywords arguments to be keyword-only
114
    reduce_dims: Optional[FlexibleDimensionTypes] = None,
115
    preserve_dims: Optional[FlexibleDimensionTypes] = None,
116
    weights: Optional[xr.DataArray] = None,
117
) -> xr.DataArray:
118
    """
119
    Calculates the score that is consistent for the Huber mean functional with tuning
120
    parameter `tuning_param`, based on convex function phi. See Taggart (2022a), or
121
    Equation (11) from Taggart (2022b). See Taggart (2022b), end of Section 3.4, for the
122
    standard formula.
123

124
    .. math::
125
        S(x,y) = \\frac{1}{2}(\\phi(y)-\\phi(\\kappa_v (x-y) + y) + \\kappa_v (x-y)\\phi'(x))
126

127
    where
128
        - :math:`\\kappa_v(x) = \\mathrm{max}(-v, \\mathrm{min}(x, v))` is the "capping function"
129
        - :math:`v` is the Huber parameter
130
        - :math:`x` is the forecast
131
        - :math:`y` is the observation
132
        - :math:`\\phi` is a convex function of a single variable
133
        - :math:`\\phi'` is the subderivative of :math:`\\phi`
134
        - :math:`S(x,y)` is the score.
135

136
    The score is negatively oriented with :math:`0 \\leq S(x,y) < \\infty`.
137

138
    We strongly recommend that you work through the tutorial
139
    https://scores.readthedocs.io/en/stable/tutorials/Consistent_Scores.html to help you
140
    understand how to use the consistent scoring functions in ``scores``.
141

142
    Args:
143
        fcst: array of forecast values.
144
        obs: array of corresponding observation values.
145
        huber_param: Huber mean tuning parameter. This corresponds to the transition point between
146
            linear and quadratic loss for Huber loss. Must be positive.
147
        phi: a convex function on the real numbers, accepting a single array like argument.
148
        phi_prime: a subderivative of `phi`, accepting a single array like argument.
149
        reduce_dims: Optionally specify which dimensions to reduce when
150
            calculating the consistent Huber score. All other dimensions will be preserved. As a
151
            special case, 'all' will allow all dimensions to be reduced. Only one
152
            of `reduce_dims` and `preserve_dims` can be supplied. The default behaviour
153
            if neither are supplied is to reduce all dims.
154
        preserve_dims: Optionally specify which dimensions to preserve when calculating
155
            the consistent Huber score. All other dimensions will be reduced. As a special case, 'all'
156
            will allow all dimensions to be preserved. In this case, the result will be in
157
            the same shape/dimensionality as the forecast, and the errors will be the consistent Huber
158
            score at each point (i.e. single-value comparison against observed), and the
159
            forecast and observed dimensions must match precisely. Only one of `reduce_dims`
160
            and `preserve_dims` can be supplied. The default behaviour if neither are supplied
161
            is to reduce all dims.
162
        weights: Optionally provide an array for weighted averaging (e.g. by area, by latitude,
163
            by population, custom)
164

165
    Returns:
166
        array of (mean) scores that is consistent for Huber mean functional,
167
        with the dimensions specified by `dims`. If `dims` is `None`, the returned DataArray will have
168
        only one entry, the overall mean score.
169

170
    Raises:
171
       ValueError: if `huber_param <= 0`.
172

173
    References:
174
        -   Taggart, R. J. (2022a). Point forecasting and forecast evaluation with
175
            generalized Huber loss. Electronic Journal of Statistics, 16(1), 201-231.
176
            https://doi.org/10.1214/21-ejs1957
177
        -   Taggart, R. (2022b). Evaluation of point forecasts for extreme events using
178
            consistent scoring functions. Quarterly Journal of the Royal Meteorological
179
            Society, 148(742), 306–320. https://doi.org/10.1002/qj.4206
180
    """
181
    check_huber_param(huber_param)
4✔
182
    reduce_dims = gather_dimensions(fcst.dims, obs.dims, reduce_dims=reduce_dims, preserve_dims=preserve_dims)
4✔
183

184
    kappa = (fcst - obs).clip(min=-huber_param, max=huber_param)
4✔
185
    result = 0.5 * (phi(obs) - phi(kappa + obs) + kappa * phi_prime(fcst))
4✔
186
    result = apply_weights(result, weights=weights)
4✔
187
    result = result.mean(dim=reduce_dims)
4✔
188

189
    return result
4✔
190

191

192
def consistent_quantile_score(
4✔
193
    fcst: xr.DataArray,
194
    obs: xr.DataArray,
195
    alpha: float,
196
    g: Callable[[xr.DataArray], xr.DataArray],
197
    *,  # Force keywords arguments to be keyword-only
198
    reduce_dims: Optional[FlexibleDimensionTypes] = None,
199
    preserve_dims: Optional[FlexibleDimensionTypes] = None,
200
    weights: Optional[xr.DataArray] = None,
201
) -> xr.DataArray:
202
    """
203
    Calculates the score that is consistent for the alpha-quantile functional, based on 
204
    nondecreasing function g. See Gneiting (2011), or Equation (8) from Taggart (2022).
205

206
    .. math::
207

208
        S(x, y) =
209
        \\begin{cases}
210
        (1 - \\alpha)(g(x) - g(y)), & y < x \\\\
211
        \\alpha(g(y) - g(x)), & x \\leq y
212
        \\end{cases}
213

214
    where
215
        - :math:`x` is the forecast
216
        - :math:`y` is the observation
217
        - :math:`\\alpha` is the quantile level
218
        - :math:`g` is a nondecreasing function of a single variable
219
        - :math:`S(x,y)` is the score.
220

221
    The score is negatively oriented with :math:`0 \\leq S(x,y) < \\infty`.
222

223
    We strongly recommend that you work through the tutorial 
224
    https://scores.readthedocs.io/en/stable/tutorials/Consistent_Scores.html to help you 
225
    understand how to use the consistent scoring functions in ``scores``.
226

227
    Args:
228
        fcst: array of forecast values.
229
        obs: array of corresponding observation values.
230
        alpha: quantile level. Must be strictly between 0 and 1.
231
        g: nondecreasing function on the real numbers, accepting a single array like argument.
232
        reduce_dims: Optionally specify which dimensions to reduce when
233
            calculating the consistent quantile score. All other dimensions will be preserved. As a
234
            special case, 'all' will allow all dimensions to be reduced. Only one
235
            of `reduce_dims` and `preserve_dims` can be supplied. The default behaviour
236
            if neither are supplied is to reduce all dims.
237
        preserve_dims: Optionally specify which dimensions to preserve when calculating
238
            the consistent quantile score. All other dimensions will be reduced. As a special case, 'all'
239
            will allow all dimensions to be preserved. In this case, the result will be in
240
            the same shape/dimensionality as the forecast, and the errors will be the consistent quantile
241
            score at each point (i.e. single-value comparison against observed), and the
242
            forecast and observed dimensions must match precisely. Only one of `reduce_dims`
243
            and `preserve_dims` can be supplied. The default behaviour if neither are supplied
244
            is to reduce all dims.
245
        weights: Optionally provide an array for weighted averaging (e.g. by area, by latitude,
246
            by population, custom)
247

248
    Returns:
249
        array of (mean) scores that are consistent for alpha-quantile functional,
250
        with the dimensions specified by `dims`. If `dims` is `None`, the returned DataArray will have
251
        only one entry, the overall mean score.
252

253
    Raises:
254
        ValueError: if `alpha` is not strictly between 0 and 1.
255

256
    References:
257
        -   Gneiting, T. (2011). Making and Evaluating Point Forecasts. Journal of the 
258
            American Statistical Association, 106(494), 746–762. 
259
            https://doi.org/10.1198/jasa.2011.r10138
260
        -   Taggart, R. (2022). Evaluation of point forecasts for extreme events using 
261
            consistent scoring functions. Quarterly Journal of the Royal Meteorological 
262
            Society, 148(742), 306–320. https://doi.org/10.1002/qj.4206
263
    """
264
    check_alpha(alpha)
4✔
265
    reduce_dims = gather_dimensions(fcst.dims, obs.dims, reduce_dims=reduce_dims, preserve_dims=preserve_dims)
4✔
266

267
    score_overfcst = (1 - alpha) * (g(fcst) - g(obs))
4✔
268
    score_underfcst = -alpha * (g(fcst) - g(obs))
4✔
269
    result = score_overfcst.where(obs < fcst, score_underfcst)
4✔
270
    result = apply_weights(result, weights=weights)
4✔
271
    result = result.mean(dim=reduce_dims)
4✔
272

273
    return result
4✔
274

275

276
def check_alpha(alpha: float) -> None:
4✔
277
    """Raises if quantile or expectile level `alpha` not in the open interval (0,1)."""
278
    if alpha <= 0 or alpha >= 1:
4✔
279
        raise ValueError("`alpha` must be strictly between 0 and 1")
4✔
280

281

282
def check_huber_param(huber_param: float) -> None:
4✔
283
    """Raises if `huber_param` is not positive."""
284
    if huber_param <= 0:
4✔
285
        raise ValueError("`huber_param` must be positive")
4✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc