Skip to content

Commit 755f39c

Browse files
bruAristimunhalucascolley
authored andcommitted
MNT: address cov review comments
- link numpy/torch functions with intersphinx in the cov docstring (torch added to the intersphinx mapping) - add canonical examples for correction, fweights and aweights - explain the int(correction) cast: torch.cov rejects integer-valued floats such as 1.0 at runtime, so typing.cast is not enough - simplify the integer-correction check to float(correction).is_integer() - comment that the NaN shape checks account for Dask reporting unknown dimensions as NaN instead of None
1 parent a03f59b commit 755f39c

3 files changed

Lines changed: 42 additions & 11 deletions

File tree

docs/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
"numpy": ("https://numpy.org/doc/stable", None),
6161
"jax": ("https://docs.jax.dev/en/latest", None),
6262
"pytest": ("https://docs.pytest.org/en/stable/", None),
63+
"torch": ("https://docs.pytorch.org/docs/stable", None),
6364
}
6465

6566
nitpick_ignore = [

src/array_api_extra/_delegation.py

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -208,25 +208,26 @@ def cov(
208208
correction : int or float, optional
209209
Degrees of freedom correction: normalization divides by
210210
``N - correction`` (for unweighted input). Default: ``1``, which
211-
gives the unbiased estimate (matches ``numpy.cov`` default of
211+
gives the unbiased estimate (matches :func:`numpy.cov` default of
212212
``bias=False``). Set to ``0`` for the biased estimate (``N``
213-
normalization). Corresponds to ``ddof`` in ``numpy.cov`` and to
214-
``correction`` in ``numpy.var``/``std`` and ``torch.cov``.
213+
normalization). Corresponds to ``ddof`` in :func:`numpy.cov` and to
214+
``correction`` in :func:`numpy.var`/:func:`numpy.std` and
215+
:func:`torch.cov`.
215216
Non-integer values are allowed for advanced use cases: the
216217
unbiased correction for weighted observations depends on the
217218
sum and dispersion of the weights and is generally not an
218219
integer, and autocorrelated data may also require a fractional
219220
correction. Non-integer ``correction`` routes through the
220-
generic implementation because ``numpy.cov``'s ``ddof`` and
221-
``torch.cov``'s ``correction`` both require integers.
221+
generic implementation because :func:`numpy.cov`'s ``ddof`` and
222+
:func:`torch.cov`'s ``correction`` both require integers.
222223
fweights : array, optional
223224
1-D array of integer frequency weights: the number of times each
224225
observation is repeated. Same as ``fweights`` in
225-
``numpy.cov``/``torch.cov``.
226+
:func:`numpy.cov`/:func:`torch.cov`.
226227
aweights : array, optional
227228
1-D array of observation-vector weights (analytic weights). Larger
228229
values mark more important observations. Same as ``aweights`` in
229-
``numpy.cov``/``torch.cov``.
230+
:func:`numpy.cov`/:func:`torch.cov`.
230231
xp : array_namespace, optional
231232
The standard-compatible namespace for `m`. Default: infer.
232233
@@ -238,7 +239,7 @@ def cov(
238239
239240
Notes
240241
-----
241-
Mapping from ``numpy.cov`` to this function::
242+
Mapping from :func:`numpy.cov` to this function::
242243
243244
numpy.cov(m, rowvar=True) -> cov(m, axis=-1) # default
244245
numpy.cov(m, rowvar=False) -> cov(m, axis=-2)
@@ -300,6 +301,30 @@ def cov(
300301
[ -4.286 , 2.14413333]],
301302
[[ 46.84 , -17.144 ],
302303
[-17.144 , 8.57653333]]], dtype=array_api_strict.float64)
304+
305+
The normalization can be adjusted with `correction`, and observations
306+
can be weighted with integer frequencies `fweights` or importance
307+
weights `aweights`:
308+
309+
>>> x = xp.asarray([0., 1., 2., 3., 4.])
310+
>>> xpx.cov(x, xp=xp) # unbiased variance: divide by N - 1
311+
Array(2.5, dtype=array_api_strict.float64)
312+
>>> xpx.cov(x, correction=0, xp=xp) # biased variance: divide by N
313+
Array(2., dtype=array_api_strict.float64)
314+
315+
Giving the two extreme observations frequency 2 via `fweights` is
316+
equivalent to repeating them in `x`:
317+
318+
>>> xpx.cov(x, fweights=xp.asarray([2, 1, 1, 1, 2]), xp=xp)
319+
Array(3., dtype=array_api_strict.float64)
320+
>>> xpx.cov(xp.asarray([0., 0., 1., 2., 3., 4., 4.]), xp=xp)
321+
Array(3., dtype=array_api_strict.float64)
322+
323+
`aweights` instead adjusts the relative importance of observations,
324+
here down-weighting the two extremes:
325+
326+
>>> xpx.cov(x, aweights=xp.asarray([0.5, 1., 1., 1., 0.5]), xp=xp)
327+
Array(1.92, dtype=array_api_strict.float64)
303328
"""
304329

305330
if xp is None:
@@ -319,14 +344,17 @@ def cov(
319344
# `numpy.cov` (and cupy/dask/jax) require integer `ddof`; `torch.cov`
320345
# requires integer `correction`. For non-integer-valued `correction`,
321346
# fall through to the generic implementation.
322-
integer_correction = isinstance(correction, int) or correction.is_integer()
347+
integer_correction = float(correction).is_integer()
323348
has_weights = fweights is not None or aweights is not None
324349

325350
if m.ndim <= 2 and integer_correction:
351+
# Not just for static typing: `correction` may be an integer-valued
352+
# float such as 1.0, which `torch.cov` rejects at runtime.
353+
int_correction = int(correction)
326354
if is_torch_namespace(xp):
327355
fw = None if fweights is None else xp.asarray(fweights)
328356
aw = None if aweights is None else xp.asarray(aweights)
329-
return xp.cov(m, correction=int(correction), fweights=fw, aweights=aw)
357+
return xp.cov(m, correction=int_correction, fweights=fw, aweights=aw)
330358
# `dask.array.cov` forces `.compute()` whenever weights are given:
331359
# its internal `if fact <= 0` check on a lazy 0-D scalar triggers
332360
# materialization. Route to the generic impl, which is fully lazy
@@ -339,7 +367,7 @@ def cov(
339367
):
340368
return xp.cov(
341369
m,
342-
ddof=int(correction),
370+
ddof=int_correction,
343371
fweights=fweights,
344372
aweights=aweights,
345373
)

src/array_api_extra/_lib/_funcs.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,8 @@ def cov(
293293
msg = f"`{name}` must be 1-D, got ndim={w_in.ndim}"
294294
raise ValueError(msg)
295295
weight_length = w_in.shape[0]
296+
# Unknown dims are `None` per the standard; Dask non-standardly
297+
# reports them as NaN, hence the `isnan` checks below.
296298
if (
297299
weight_length is not None
298300
and n_obs is not None

0 commit comments

Comments
 (0)