ML Foundations

NumPy masked division: initialize out before using where

A ufunc mask leaves skipped output positions unchanged. Make per-group means deterministic with an explicit floating output and missing-value policy.

Evidence and testing

Review record: Tensor Almanac automated evidence gate (no independent human review). Example versions, recorded outputs and limitations appear below.

The mask does not choose the skipped value

When calculating mean loss per group, an empty group needs an explicit result policy. Use an initialized floating output array and pass both out and where to np.divide. The mask controls which divisions write results; it does not assign a replacement value to the other positions. NumPy documents that those positions retain the existing output value. With the default out=None, skipped positions in the newly allocated output remain uninitialized. [1]

A value that happens to look reasonable in an uninitialized array is therefore not a reliable representation of an empty group. The example below uses a visible sentinel to demonstrate retained output deterministically, then uses NaN to represent an undefined mean.

References: [1]

Test the empty groups as well as the valid ratios

The fixture has four groups. Two contain observations and produce means of 2.5 and 4.5; two have zero counts. The sentinel pass shows exactly which positions the division leaves untouched. The second pass initializes those positions to NaN. Assertions check both the valid values and the missing positions, so a plausible result for the nonempty groups cannot conceal an incorrect empty-group policy.

The integer totals are deliberate. full_like normally follows the input dtype, so dtype=float is supplied explicitly for fractional ratios and NaN. The documented dtype parameter overrides the inherited type. [2]

Executed with Python 3.12.14 and NumPy 2.3.5; a bounded synthetic four-group fixture. Exact stdout and source-byte checksum are preserved in private evidence.

"""Show that a ufunc mask preserves, rather than fills, skipped outputs."""
import numpy as np

counts = np.array([4, 0, 2, 0])
totals = np.array([10, 7, 9, 0])
valid = counts > 0

# A sentinel makes the retained positions deterministic and easy to inspect.
retained = np.full_like(totals, -999, dtype=float)
np.divide(totals, counts, out=retained, where=valid)
print('retained output:', retained.tolist())
assert retained.tolist() == [2.5, -999.0, 4.5, -999.0]

# Undefined means stay missing; they are not genuine zero-valued means.
means = np.full_like(totals, np.nan, dtype=float)
with np.errstate(divide='raise', invalid='raise'):
    np.divide(totals, counts, out=means, where=valid)
print('means:', means.tolist())
print('defined mask:', valid.tolist())
assert means[valid].tolist() == [2.5, 4.5]
assert np.isnan(means[~valid]).all()
assert means.dtype.kind == 'f'

Recorded output

retained output: [2.5, -999.0, 4.5, -999.0]
means: [2.5, nan, 4.5, nan]
defined mask: [True, False, True, False]

References: [1], [2]

Decide what an empty group means downstream

For this fixture, NaN means no defined group mean. Replacing it with zero would assert a real zero-valued result instead. Choose the representation based on the consumer: preserve the validity mask when a report or aggregation must distinguish missing groups from observed zeroes. This is an application decision illustrated by the fixture, not a universal missing-data convention.

The example assumes equal-shaped, finite inputs and nonnegative counts. It does not validate an upstream counting pipeline or implement arbitrary broadcasting. In a wider pipeline, check those assumptions before dividing and ensure the output shape matches the intended broadcast result. The divide documentation specifies that output shape requirement. [1]

References: [1], [2]

Sources

  1. NumPy divide: out and where semantics
  2. NumPy full_like: fill value and dtype