Skip to content

Commit a2176b5

Browse files
authored
Merge pull request #307 from raybellwaves/add-linslope
2 parents 3e4c49d + 6a76bdc commit a2176b5

17 files changed

+183
-26
lines changed

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Features
1212
observations are probability distributions ``p`` or cumulative
1313
distributionss ``c``. See :py:func:`~xskillscore.rps` docstrings and doctests for
1414
examples. (:pr:`300`) `Aaron Spring`_
15+
- Added slope of linear fit :py:func:`~xskillscore.linslope`. `Ray Bell`_
1516

1617
Internal Changes
1718
~~~~~~~~~~~~~~~~

ci/docs_notebooks.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,6 @@ dependencies:
2525
- sphinx_rtd_theme
2626
- pip
2727
- pip:
28-
- sphinx_autosummary_accessors
29-
# Install latest version of xskillscore.
30-
- -e ..
28+
- sphinx_autosummary_accessors
29+
# Install latest version of xskillscore.
30+
- -e ..

ci/minimum-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,4 @@ dependencies:
2323
- pytest-xdist
2424
- pip
2525
- pip:
26-
- -e ..
26+
- -e ..

docs/source/api.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Correlation Metrics
2424
spearman_r_eff_p_value
2525
effective_sample_size
2626
r2
27+
linslope
2728

2829
Distance Metrics
2930
~~~~~~~~~~~~~~~~
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
xskillscore.linslope
2+
====================
3+
4+
.. currentmodule:: xskillscore
5+
6+
.. autofunction:: linslope

docs/source/quick-start.ipynb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
"* Spearman Correlation p value (`spearman_r_p_value`)\n",
6767
"* Spearman Correlation effective p value (`spearman_r_eff_p_value`)\n",
6868
"* Effective Sample Size (`effective_sample_size`)\n",
69+
"* Slope of Linear Fit (`linslope`)\n",
6970
"* Coefficient of Determination (`r2`)\n",
7071
"\n",
7172
"### Distance-Based\n",

xskillscore/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from .core.contingency import Contingency
88
from .core.deterministic import (
99
effective_sample_size,
10+
linslope,
1011
mae,
1112
mape,
1213
me,

xskillscore/core/accessor.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from .deterministic import (
44
effective_sample_size,
5+
linslope,
56
mae,
67
mape,
78
me,
@@ -46,6 +47,11 @@ def _in_ds(self, x):
4647
else:
4748
return self._obj[x]
4849

50+
def linslope(self, a, b, *args, **kwargs):
51+
a = self._in_ds(a)
52+
b = self._in_ds(b)
53+
return linslope(a, b, *args, **kwargs)
54+
4955
def pearson_r(self, a, b, *args, **kwargs):
5056
a = self._in_ds(a)
5157
b = self._in_ds(b)

xskillscore/core/deterministic.py

Lines changed: 80 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from .np_deterministic import (
66
_effective_sample_size,
7+
_linslope,
78
_mae,
89
_mape,
910
_me,
@@ -27,21 +28,22 @@
2728
)
2829

2930
__all__ = [
31+
"effective_sample_size",
32+
"linslope",
33+
"mae",
34+
"mape",
35+
"me",
36+
"median_absolute_error",
37+
"mse",
3038
"pearson_r",
31-
"pearson_r_p_value",
3239
"pearson_r_eff_p_value",
33-
"me",
40+
"pearson_r_p_value",
41+
"r2",
3442
"rmse",
35-
"mse",
36-
"mae",
37-
"median_absolute_error",
3843
"smape",
39-
"mape",
4044
"spearman_r",
41-
"spearman_r_p_value",
4245
"spearman_r_eff_p_value",
43-
"effective_sample_size",
44-
"r2",
46+
"spearman_r_p_value",
4547
]
4648

4749

@@ -71,6 +73,75 @@ def _determine_input_core_dims(dim, weights):
7173
return input_core_dims
7274

7375

76+
def linslope(a, b, dim=None, weights=None, skipna=False, keep_attrs=False):
77+
"""Slope of linear fit.
78+
79+
.. math::
80+
s_{ab} = \\frac{ \\sum_{i=i}^{n} (a_{i} - \\bar{a}) (b_{i} - \\bar{b}) }
81+
{ \\sum_{i=1}^{n} (a_{i} - \\bar{a})^{2} }
82+
83+
Parameters
84+
----------
85+
a : xarray.Dataset or xarray.DataArray
86+
Labeled array(s) over which to apply the function.
87+
b : xarray.Dataset or xarray.DataArray
88+
Labeled array(s) over which to apply the function.
89+
dim : str, list
90+
The dimension(s) to apply the correlation along. Note that this dimension will
91+
be reduced as a result. Defaults to None reducing all dimensions.
92+
weights : xarray.Dataset or xarray.DataArray or None
93+
Weights matching dimensions of ``dim`` to apply during the function.
94+
skipna : bool
95+
If True, skip NaNs when computing function.
96+
keep_attrs : bool
97+
If True, the attributes (attrs) will be copied
98+
from the first input to the new one.
99+
If False (default), the new object will
100+
be returned without attributes.
101+
102+
Returns
103+
-------
104+
xarray.DataArray or xarray.Dataset
105+
Slope of linear fit.
106+
107+
See Also
108+
--------
109+
scipy.stats.linregress
110+
111+
Examples
112+
--------
113+
>>> a = xr.DataArray(np.random.rand(5, 3, 3),
114+
... dims=['time', 'x', 'y'])
115+
>>> b = xr.DataArray(np.random.rand(5, 3, 3),
116+
... dims=['time', 'x', 'y'])
117+
>>> xs.linslope(a, b, dim='time')
118+
<xarray.DataArray (x: 3, y: 3)>
119+
array([[-0.30948771, -0.21562529, -0.63141304],
120+
[ 0.31446077, 2.23858011, 0.44743617],
121+
[-0.22243944, 0.47034784, 1.08512859]])
122+
Dimensions without coordinates: x, y
123+
"""
124+
_fail_if_dim_empty(dim)
125+
dim, _ = _preprocess_dims(dim, a)
126+
a, b = xr.broadcast(a, b, exclude=dim)
127+
a, b, new_dim, weights = _stack_input_if_needed(a, b, dim, weights)
128+
weights = _preprocess_weights(a, dim, new_dim, weights)
129+
130+
input_core_dims = _determine_input_core_dims(new_dim, weights)
131+
132+
return xr.apply_ufunc(
133+
_linslope,
134+
a,
135+
b,
136+
weights,
137+
input_core_dims=input_core_dims,
138+
kwargs={"axis": -1, "skipna": skipna},
139+
dask="parallelized",
140+
output_dtypes=[float],
141+
keep_attrs=keep_attrs,
142+
)
143+
144+
74145
def pearson_r(a, b, dim=None, weights=None, skipna=False, keep_attrs=False):
75146
"""Pearson's correlation coefficient.
76147

xskillscore/core/np_deterministic.py

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,22 @@
66
from .utils import suppress_warnings
77

88
__all__ = [
9+
"_effective_sample_size",
10+
"_linslope",
11+
"_mae",
12+
"_mape",
13+
"_me",
14+
"_median_absolute_error",
15+
"_mse",
916
"_pearson_r",
10-
"_pearson_r_p_value",
1117
"_pearson_r_eff_p_value",
12-
"_me",
18+
"_pearson_r_p_value",
19+
"_r2",
1320
"_rmse",
14-
"_mse",
15-
"_mae",
16-
"_median_absolute_error",
1721
"_smape",
18-
"_mape",
1922
"_spearman_r",
20-
"_spearman_r_p_value",
2123
"_spearman_r_eff_p_value",
22-
"_effective_sample_size",
23-
"_r2",
24+
"_spearman_r_p_value",
2425
]
2526

2627

@@ -153,6 +154,55 @@ def _effective_sample_size(a, b, axis, skipna):
153154
return n_eff
154155

155156

157+
def _linslope(a, b, weights, axis, skipna):
158+
"""ndarray implementation of scipy.stats.linregress[slope].
159+
160+
Parameters
161+
----------
162+
a : ndarray
163+
Input array.
164+
b : ndarray
165+
Input array.
166+
axis : int
167+
The axis to apply the linear slope along.
168+
weights : ndarray
169+
Input array of weights for a and b.
170+
skipna : bool
171+
If True, skip NaNs when computing function.
172+
173+
Returns
174+
-------
175+
res : ndarray
176+
slope of linear fit.
177+
178+
See Also
179+
--------
180+
scipy.stats.linregress
181+
"""
182+
sumfunc, meanfunc = _get_numpy_funcs(skipna)
183+
if skipna:
184+
a, b, weights = _match_nans(a, b, weights)
185+
weights = _check_weights(weights)
186+
a = np.rollaxis(a, axis)
187+
b = np.rollaxis(b, axis)
188+
if weights is not None:
189+
weights = np.rollaxis(weights, axis)
190+
191+
am, bm = __compute_anomalies(a, b, weights=weights, axis=0, skipna=skipna)
192+
193+
if weights is not None:
194+
s_num = sumfunc(weights * am * bm, axis=0)
195+
s_den = sumfunc(weights * am * am, axis=0)
196+
else:
197+
s_num = sumfunc(am * bm, axis=0)
198+
s_den = sumfunc(am * am, axis=0)
199+
200+
with suppress_warnings("invalid value encountered in true_divide"):
201+
with suppress_warnings("invalid value encountered in double_scalars"):
202+
res = s_num / s_den
203+
return res
204+
205+
156206
def _r2(a, b, weights, axis, skipna):
157207
"""ndarray implementation of sklearn.metrics.r2_score.
158208

xskillscore/tests/test_accessor_deterministic.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from xskillscore.core.deterministic import (
66
effective_sample_size,
7+
linslope,
78
mae,
89
mape,
910
me,
@@ -21,6 +22,7 @@
2122
)
2223

2324
correlation_metrics = [
25+
linslope,
2426
pearson_r,
2527
r2,
2628
pearson_r_p_value,

xskillscore/tests/test_deterministic.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
_preprocess_dims,
88
_preprocess_weights,
99
effective_sample_size,
10+
linslope,
1011
mae,
1112
mape,
1213
me,
@@ -24,6 +25,7 @@
2425
)
2526
from xskillscore.core.np_deterministic import (
2627
_effective_sample_size,
28+
_linslope,
2729
_mae,
2830
_mape,
2931
_me,
@@ -41,6 +43,7 @@
4143
)
4244

4345
correlation_metrics = [
46+
(linslope, _linslope),
4447
(pearson_r, _pearson_r),
4548
(r2, _r2),
4649
(pearson_r_p_value, _pearson_r_p_value),
@@ -51,6 +54,7 @@
5154
(effective_sample_size, _effective_sample_size),
5255
]
5356
correlation_metrics_names = [
57+
"linslope",
5458
"pearson_r",
5559
"r2",
5660
"pearson_r_p_value",

xskillscore/tests/test_gridded_metrics.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import pytest
33

44
from xskillscore.core.deterministic import (
5+
linslope,
56
mae,
67
mape,
78
me,
@@ -17,6 +18,7 @@
1718
)
1819

1920
METRICS = [
21+
linslope,
2022
mae,
2123
mse,
2224
median_absolute_error,

xskillscore/tests/test_mask_skipna.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import pytest
33

44
from xskillscore.core.deterministic import (
5+
linslope,
56
mae,
67
mape,
78
median_absolute_error,

xskillscore/tests/test_metric_results_accurate.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import numpy as np
22
import pytest
33
import sklearn.metrics
4-
from scipy.stats import pearsonr, spearmanr
4+
from scipy.stats import linregress, pearsonr, spearmanr
55
from sklearn.metrics import (
66
mean_absolute_error,
77
mean_absolute_percentage_error,
@@ -11,6 +11,7 @@
1111

1212
import xskillscore as xs
1313
from xskillscore.core.deterministic import (
14+
linslope,
1415
mae,
1516
mape,
1617
me,
@@ -36,6 +37,7 @@
3637
]
3738

3839
xs_scipy_metrics = [
40+
(linslope, linregress, 0),
3941
(pearson_r, pearsonr, 0),
4042
(spearman_r, spearmanr, 0),
4143
(pearson_r_p_value, pearsonr, 1),

xskillscore/tests/test_skipna_functionality.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from xarray.tests import CountingScheduler, assert_allclose, raise_if_dask_computes
55

66
from xskillscore.core.deterministic import (
7+
linslope,
78
mae,
89
mape,
910
me,
@@ -19,6 +20,7 @@
1920
)
2021

2122
WEIGHTED_METRICS = [
23+
linslope,
2224
pearson_r,
2325
pearson_r_p_value,
2426
spearman_r,

0 commit comments

Comments
 (0)