unitx 0.16.0__tar.gz → 0.16.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {unitx-0.16.0 → unitx-0.16.2}/.github/consumers/arrays.py +32 -0
- {unitx-0.16.0 → unitx-0.16.2}/CHANGELOG.md +23 -0
- {unitx-0.16.0 → unitx-0.16.2}/PKG-INFO +1 -1
- {unitx-0.16.0 → unitx-0.16.2}/docs/numpy/selection.md +18 -0
- {unitx-0.16.0 → unitx-0.16.2}/docs/numpy/statistics.md +85 -1
- {unitx-0.16.0 → unitx-0.16.2}/docs/numpy/writes.md +1 -1
- {unitx-0.16.0 → unitx-0.16.2}/docs/numpy.md +14 -10
- {unitx-0.16.0 → unitx-0.16.2}/docs/typing.md +2 -1
- {unitx-0.16.0 → unitx-0.16.2}/pyproject.toml +1 -1
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/_numerics/numpy.py +69 -28
- {unitx-0.16.0 → unitx-0.16.2}/tests/numpy_typing/check_numpy.py +16 -5
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_numpy_affine.py +2 -0
- unitx-0.16.2/tests/test_numpy_nan_reductions.py +260 -0
- unitx-0.16.2/tests/test_numpy_nan_statistics.py +194 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_numpy_outputs.py +1 -1
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_numpy_products.py +23 -23
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_numpy_statistics.py +37 -21
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_numpy_weighted_statistics.py +34 -18
- {unitx-0.16.0 → unitx-0.16.2}/.github/consumers/pyrightconfig.json +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/.github/consumers/scalars.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/.github/workflows/ci.yml +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/.github/workflows/pages.yml +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/.github/workflows/release.yml +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/.gitignore +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/.readthedocs.yaml +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/CONTRIBUTING.md +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/LICENSE +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/README.md +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/benchmarks/README.md +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/benchmarks/numpy_writes.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/benchmarks/scalars.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/conftest.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/docs/README.md +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/docs/affine.md +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/docs/architecture.md +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/docs/catalogues/README.md +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/docs/catalogues/cgs.md +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/docs/catalogues/imperial.md +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/docs/catalogues/si.md +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/docs/custom-units.md +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/docs/display.md +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/docs/images/blackbody.svg +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/docs/javascripts/mermaid.mjs +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/docs/logarithmic.md +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/docs/migration.md +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/docs/numpy/calculus.md +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/docs/requirements.txt +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/docs/scalars.md +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/docs/serialization.md +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/docs/stdlib.md +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/docs/units.md +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/examples/README.md +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/examples/arrhenius_fit.ipynb +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/examples/blackbody.ipynb +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/examples/damped_oscillator.ipynb +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/examples/gaussian_wavepacket.ipynb +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/examples/impedance.md +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/examples/impedance.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/examples/optical_attenuation.ipynb +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/examples/pendulum_uncertainty.ipynb +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/examples/smith_chart.ipynb +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/mkdocs.yml +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/pyrightconfig.json +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/__init__.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/_constnamespace.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/_coordinates/__init__.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/_coordinates/affine.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/_coordinates/definition.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/_coordinates/logarithmic.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/_coordinates/logarithms.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/_coordinates/points.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/_coordinates/resolve.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/_electromagnetism/__init__.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/_electromagnetism/coefficients.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/_electromagnetism/conversion.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/_formatting/__init__.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/_formatting/render.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/_formatting/unicode.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/_immutable.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/_numerics/__init__.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/_numerics/conversion.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/_numerics/numpy_typing.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/_numerics/scaling.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/_operands.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/_registry/__init__.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/_registry/builtin_units.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/_registry/prefixes.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/_registry/reconstruction.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/affinequantity.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/affineunit.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/cgs/__init__.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/cgs/units.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/dimension.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/exceptions.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/factor.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/imperial/__init__.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/imperial/units.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/logquantity.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/logunit.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/math.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/monomial.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/py.typed +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/quantity.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/serialization.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/si/__init__.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/si/bases.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/si/constants.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/si/units.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/singleunit.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/time.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/unit.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/unitcatalog.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/unitdefinition.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/unitparser.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/src/unitx/unitregistry.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/__init__.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/_notebooks.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/numpy_typing/check_asarray.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/numpy_typing/check_cgs.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/numpy_typing/check_logarithmic.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/numpy_typing/check_math.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_affine.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_affinequantity.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_builtin_definitions.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_catalog_collection.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_cgs.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_cgs_numpy.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_cgs_systems.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_decibel_call.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_definition_scope.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_dimension.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_display.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_examples.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_factor.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_immutable.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_impedance_example.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_imperial.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_import.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_linear_boundaries.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_logarithmic_model.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_math.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_math_numpy.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_monomial.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_numeric.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_numpy.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_numpy_differentiation.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_numpy_integration.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_numpy_interpolation.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_numpy_multi_outputs.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_numpy_mutation.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_numpy_selection.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_quantity.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_registry.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_registry_scope.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_serialization.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_si.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_singleunit.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_siunitx_display.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_time.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_typing.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_unicode.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_unicode_units.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_unit.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_unit_definition.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_unitparser.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_unitregistry.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/test_warnings.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/typing/check_affine.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/typing/check_cgs.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/typing/check_impedance_example.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/typing/check_imperial.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/typing/check_logarithmic.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/typing/check_math_time.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/typing/check_quantity.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/typing/check_registry.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/typing/check_serialization.py +0 -0
- {unitx-0.16.0 → unitx-0.16.2}/tests/typing/check_si.py +0 -0
|
@@ -93,6 +93,38 @@ def check_arrays(values: NDArray[np.float64]) -> None:
|
|
|
93
93
|
assert_type(median_target.value.dtype, np.dtype[np.float64])
|
|
94
94
|
np.testing.assert_allclose(median_target.value, 2000)
|
|
95
95
|
|
|
96
|
+
gapped = Quantity(np.array([1.0, np.nan, 3]), si.m)
|
|
97
|
+
for operation in (
|
|
98
|
+
np.nanmean,
|
|
99
|
+
np.nanmedian,
|
|
100
|
+
np.nanstd,
|
|
101
|
+
np.nanvar,
|
|
102
|
+
np.nanmin,
|
|
103
|
+
np.nanmax,
|
|
104
|
+
np.nansum,
|
|
105
|
+
):
|
|
106
|
+
result = cast(Quantity[Array], operation(gapped))
|
|
107
|
+
assert isinstance(result, Quantity)
|
|
108
|
+
assert result.unit == (si.m**2 if operation is np.nanvar else si.m)
|
|
109
|
+
np.testing.assert_allclose(result.value, operation(gapped.value))
|
|
110
|
+
product = cast(Quantity[Array], np.nanprod(gapped))
|
|
111
|
+
assert isinstance(product, Quantity) and product.unit == si.m**2
|
|
112
|
+
np.testing.assert_allclose(product.value, 3)
|
|
113
|
+
for operation in (np.nanargmin, np.nanargmax):
|
|
114
|
+
indices = np.empty((), dtype=np.intp)
|
|
115
|
+
assert operation(gapped, out=indices) is indices
|
|
116
|
+
assert_type(indices.dtype, np.dtype[np.intp])
|
|
117
|
+
np.testing.assert_array_equal(indices, operation(gapped.value))
|
|
118
|
+
for operation, probability in ((np.nanquantile, 0.5), (np.nanpercentile, 50)):
|
|
119
|
+
result = cast(Quantity[Array], operation(gapped, probability))
|
|
120
|
+
assert isinstance(result, Quantity) and result.unit == si.m
|
|
121
|
+
np.testing.assert_allclose(result.value, 2)
|
|
122
|
+
gapped.__array_function__(
|
|
123
|
+
operation, (Quantity,), (gapped, probability), {'out': median_target}
|
|
124
|
+
)
|
|
125
|
+
np.testing.assert_allclose(median_target.value, 200)
|
|
126
|
+
assert_type(median_target.value.dtype, np.dtype[np.float64])
|
|
127
|
+
|
|
96
128
|
sample_time = Quantity(np.array([0.0, 1, 3]), si.s)
|
|
97
129
|
sample_current = Quantity(np.array([0.0, 2, 6]), si.A)
|
|
98
130
|
grid = Quantity(np.array([0.0, 500, 1000, 2000, 3000]), si.ms)
|
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.16.2] - 2026-10-25
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Support NumPy `nanmin`, `nanmax`, `nanargmin` and `nanargmax`, preserving
|
|
10
|
+
units or point coordinates for extrema and returning bare indices. Retain
|
|
11
|
+
NumPy's all-NaN warnings/errors and stage output writes before mutation.
|
|
12
|
+
- Support `nansum` with the existing sum unit, initial and mask rules, and
|
|
13
|
+
`nanprod` with unit powers determined by selected non-NaN element counts.
|
|
14
|
+
Dimensional product slices require equal counts; dimensionless products
|
|
15
|
+
normalize to coherent values and may have different counts.
|
|
16
|
+
|
|
17
|
+
## [0.16.1] - 2026-10-25
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- Support NumPy `nanmean`, `nanmedian`, `nanstd`, `nanvar`, `nanquantile` and
|
|
22
|
+
`nanpercentile`, retaining the corresponding statistical unit and coordinate
|
|
23
|
+
rules. Omit NaN samples with NumPy's dtype, mask, weight and warning behavior;
|
|
24
|
+
preserve staged output writes, including when warnings become exceptions.
|
|
25
|
+
Weighted NaN quantiles support one integer axis or `axis=None`, with full-shape
|
|
26
|
+
weights for multidimensional samples, due to NumPy 2.1/2.5 kernel limitations.
|
|
27
|
+
|
|
5
28
|
## [0.16.0] - 2026-10-25
|
|
6
29
|
|
|
7
30
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: unitx
|
|
3
|
-
Version: 0.16.
|
|
3
|
+
Version: 0.16.2
|
|
4
4
|
Summary: Physical units, quantities, and unit conversion with exact unit algebra, type hints, and NumPy integration.
|
|
5
5
|
Project-URL: Homepage, https://github.com/DaitTHU/unitx
|
|
6
6
|
Project-URL: Documentation, https://daitthu.github.io/unitx/
|
|
@@ -50,9 +50,27 @@ Empty reductions, axes, NaNs and `keepdims` follow NumPy. These reductions
|
|
|
50
50
|
write the exact result shape to `out`; elementwise selection and clipping
|
|
51
51
|
allow the ufunc output broadcasting rules.
|
|
52
52
|
|
|
53
|
+
`np.nanmin/max` share the same unit, point, mask, initial and output rules,
|
|
54
|
+
while omitting NaNs. An all-NaN slice returns NaN and emits RuntimeWarning;
|
|
55
|
+
an empty reduction still needs an initial value. A warning promoted to an
|
|
56
|
+
exception leaves `out` unchanged.
|
|
57
|
+
|
|
58
|
+
`np.argmin/argmax` and `np.nanargmin/nanargmax` return bare indices and accept
|
|
59
|
+
one integer axis or `axis=None`, `keepdims`, and a bare integer ndarray `out`
|
|
60
|
+
of exactly the result shape. They also accept affine and logarithmic points
|
|
61
|
+
and operate on stored readings. The NaN variants omit NaNs and raise
|
|
62
|
+
ValueError for all-NaN slices before writing `out`. NumPy's caveat remains:
|
|
63
|
+
`nanargmin` results cannot be trusted for slices containing only NaNs and
|
|
64
|
+
positive infinity; the corresponding `nanargmax` case is NaNs and negative
|
|
65
|
+
infinity. unitx delegates these numerical choices to NumPy.
|
|
66
|
+
|
|
53
67
|
See NumPy's [where](https://numpy.org/doc/stable/reference/generated/numpy.where.html),
|
|
54
68
|
[clip](https://numpy.org/doc/stable/reference/generated/numpy.clip.html) and
|
|
55
69
|
[minimum](https://numpy.org/doc/stable/reference/generated/numpy.minimum.html)
|
|
56
70
|
references for the underlying numerical behavior.
|
|
71
|
+
See also [nanmin](https://numpy.org/doc/stable/reference/generated/numpy.nanmin.html),
|
|
72
|
+
[nanargmin](https://numpy.org/doc/stable/reference/generated/numpy.nanargmin.html)
|
|
73
|
+
and [nanargmax](https://numpy.org/doc/stable/reference/generated/numpy.nanargmax.html)
|
|
74
|
+
for NaN and infinity behavior.
|
|
57
75
|
|
|
58
76
|
[NumPy overview and support matrix](../numpy.md) · [Documentation index](../README.md)
|
|
@@ -106,7 +106,8 @@ with the additional single-unit and explicit-output rules described here.
|
|
|
106
106
|
their first, positional argument. Results retain its complete unit, including
|
|
107
107
|
an exact extra scale or a scaled dimensionless unit. AffineQuantity and
|
|
108
108
|
LogQuantity points are unsupported; convert explicitly before using these
|
|
109
|
-
functions.
|
|
109
|
+
functions. `nanquantile` and `nanpercentile` share these boundaries and omit
|
|
110
|
+
NaN samples as described [below](#nan-statistics).
|
|
110
111
|
|
|
111
112
|
Weights and the `q` parameter must be dimensionless: use bare numeric scalars,
|
|
112
113
|
plain numeric ndarrays, numeric lists/tuples, or dimensionless Quantities.
|
|
@@ -188,4 +189,87 @@ See [NumPy average](https://numpy.org/doc/stable/reference/generated/numpy.avera
|
|
|
188
189
|
for the numerical definitions. unitx adds the unit, parameter and output rules
|
|
189
190
|
described here and uses the installed NumPy kernel.
|
|
190
191
|
|
|
192
|
+
## NaN statistics
|
|
193
|
+
|
|
194
|
+
`nansum` omits NaN samples and otherwise shares `sum`'s unit, `axis`, `dtype`,
|
|
195
|
+
`keepdims`, `where`, scalar `initial` and staged `out` rules. All-NaN, empty
|
|
196
|
+
and fully masked slices return zero in the input unit, or the converted
|
|
197
|
+
initial when supplied. Affine and logarithmic point sums remain undefined.
|
|
198
|
+
|
|
199
|
+
`nanprod` shares `prod`'s options and initial-unit rules. For dimensional
|
|
200
|
+
input U, its unit is `U**N * initial.unit`, where N counts elements selected
|
|
201
|
+
by `where` that are **not NaN**. Zero and infinity still count. All result
|
|
202
|
+
slices must have equal N, even when a zero initial makes their magnitudes
|
|
203
|
+
equal; otherwise DimensionError is raised before writing `out`. All-NaN,
|
|
204
|
+
empty and fully masked slices have N = 0, with value 1 and unit `Unit()`
|
|
205
|
+
before applying initial. When the output has no elements, use `prod`'s
|
|
206
|
+
nominal-extent convention. Dimensionless inputs normalize to coherent values
|
|
207
|
+
first, so unequal non-NaN counts are allowed. Point products remain undefined.
|
|
208
|
+
NaN initial values are passed through to NumPy; they are not omitted.
|
|
209
|
+
|
|
210
|
+
`nanmean`, `nanmedian`, `nanstd`, `nanvar`, `nanquantile`, and `nanpercentile`
|
|
211
|
+
omit NaN samples. Their units, supported options, parameter normalization and
|
|
212
|
+
staged `out` writes match the corresponding functions above. Sample units,
|
|
213
|
+
including exact extra scales and scaled dimensionless units, stay unchanged;
|
|
214
|
+
`nanvar` squares the complete input unit.
|
|
215
|
+
|
|
216
|
+
`nanmean` and `nanmedian` also accept AffineQuantity and LogQuantity points,
|
|
217
|
+
returning points in the same coordinate. `nanstd` returns the coordinate's
|
|
218
|
+
full difference unit, and `nanvar` its square. Logarithmic point statistics
|
|
219
|
+
operate on stored readings; convert to physical quantities explicitly for
|
|
220
|
+
power or amplitude statistics. `nanquantile` and `nanpercentile` accept only
|
|
221
|
+
linear Quantities, following quantile and percentile.
|
|
222
|
+
|
|
223
|
+
`nanmean`, `nanstd`, and `nanvar` accept bare boolean `where` masks; NaN samples
|
|
224
|
+
are omitted from the selected elements. For `nanstd` and `nanvar`, N in
|
|
225
|
+
`N - ddof` is the selected non-NaN count. Precomputed `mean`, `correction` and
|
|
226
|
+
`initial` remain unsupported. `nanmedian`, `nanquantile` and `nanpercentile`
|
|
227
|
+
reject `overwrite_input=True`, and do not accept `where` or `dtype`.
|
|
228
|
+
Weighted NaN quantiles require `method="inverted_cdf"`; a NaN sample and its
|
|
229
|
+
associated weight are omitted together. Weights themselves are not cleaned.
|
|
230
|
+
Weighted NaN quantiles accept one integer axis or `axis=None`; tuple axes
|
|
231
|
+
are unsupported. For multidimensional samples, supply weights with the full
|
|
232
|
+
sample shape; reduced-axis weight shapes are rejected. NumPy 2.1 mishandles
|
|
233
|
+
weighted tuple axes, and NumPy 2.1 and 2.5's NaN kernels do not reliably handle
|
|
234
|
+
reduced-axis weights. Broadcast weights explicitly to the full sample shape
|
|
235
|
+
before calling. One-dimensional samples accept a matching weight vector as
|
|
236
|
+
usual. Unweighted NaN quantiles and ordinary quantile/percentile retain tuple
|
|
237
|
+
axis support; ordinary quantile/percentile also retain reduced-axis weights.
|
|
238
|
+
|
|
239
|
+
NumPy determines dtype, complex-sample support, Inf behavior, empty/all-NaN
|
|
240
|
+
results and RuntimeWarnings. unitx does not suppress these warnings. If a
|
|
241
|
+
warning is promoted to an exception, the staged output target stays unchanged.
|
|
242
|
+
Ordinary statistics continue to propagate NaNs.
|
|
243
|
+
|
|
244
|
+
```python
|
|
245
|
+
gapped = Quantity(np.array([[1.0, np.nan, 3], [np.nan, 5, 7]]), 'm')
|
|
246
|
+
means = np.nanmean(gapped, axis=1)
|
|
247
|
+
assert means.unit == gapped.unit
|
|
248
|
+
np.testing.assert_allclose(means.value, [2, 6])
|
|
249
|
+
variances = np.nanvar(gapped, axis=1, ddof=1)
|
|
250
|
+
assert variances.unit == Unit('m2')
|
|
251
|
+
np.testing.assert_allclose(variances.value, [2, 2])
|
|
252
|
+
medians = np.nanpercentile(gapped, 50, axis=1)
|
|
253
|
+
np.testing.assert_allclose(medians.value, [2, 6])
|
|
254
|
+
totals = np.nansum(gapped, axis=1)
|
|
255
|
+
assert totals.unit == Unit('m')
|
|
256
|
+
np.testing.assert_array_equal(totals.value, [4, 12])
|
|
257
|
+
areas = np.nanprod(gapped, axis=1)
|
|
258
|
+
assert areas.unit == Unit('m2')
|
|
259
|
+
np.testing.assert_array_equal(areas.value, [3, 35])
|
|
260
|
+
minima = np.nanmin(gapped, axis=1)
|
|
261
|
+
assert minima.unit == Unit('m')
|
|
262
|
+
np.testing.assert_array_equal(minima.value, [1, 5])
|
|
263
|
+
np.testing.assert_array_equal(np.nanargmax(gapped, axis=1), [2, 2])
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
See [NumPy nanmean](https://numpy.org/doc/stable/reference/generated/numpy.nanmean.html),
|
|
267
|
+
[nanstd](https://numpy.org/doc/stable/reference/generated/numpy.nanstd.html) and
|
|
268
|
+
[nanquantile](https://numpy.org/doc/stable/reference/generated/numpy.nanquantile.html)
|
|
269
|
+
for the numerical definitions and warnings.
|
|
270
|
+
The [nansum](https://numpy.org/doc/stable/reference/generated/numpy.nansum.html)
|
|
271
|
+
and [nanprod](https://numpy.org/doc/stable/reference/generated/numpy.nanprod.html)
|
|
272
|
+
kernels determine NaN identities and accumulator behavior, with unitx's
|
|
273
|
+
additional single-unit rules above.
|
|
274
|
+
|
|
191
275
|
[NumPy overview and support matrix](../numpy.md) · [Documentation index](../README.md)
|
|
@@ -125,7 +125,7 @@ length target. `Unit` factors participate in the conversion. Every numeric
|
|
|
125
125
|
result, including a coherent or scaled dimensionless result, requires a Quantity
|
|
126
126
|
target; writing it to a bare ndarray would discard its unit. Boolean predicates
|
|
127
127
|
and comparisons instead accept only a bare boolean ndarray output.
|
|
128
|
-
`argmin` and `
|
|
128
|
+
`argmin`, `argmax`, `nanargmin` and `nanargmax` accept only a bare integer ndarray output, with the
|
|
129
129
|
same exact shape requirement as other function outputs.
|
|
130
130
|
|
|
131
131
|
Final casting uses `casting='same_kind'` by default, or the explicitly requested
|
|
@@ -116,18 +116,18 @@ functions, including exp/log. Python scalar numerical semantics remain native.
|
|
|
116
116
|
| --- | --- | --- |
|
|
117
117
|
| reshape, transpose | Preserve unit | shape/order/copy; axes |
|
|
118
118
|
| concatenate, stack | Convert to first Quantity's unit; bare arrays remain dimensionless | axis, dtype, casting, out; dtype and out are mutually exclusive |
|
|
119
|
-
| sum | Preserve unit | axis, dtype, keepdims, initial, where, out |
|
|
120
|
-
| mean | Preserve unit | axis, dtype, keepdims, where, out |
|
|
121
|
-
| min/amin, max/amax | Preserve unit or point coordinate | axis, keepdims, initial, where, out |
|
|
119
|
+
| sum, nansum | Preserve unit | axis, dtype, keepdims, initial, where, out |
|
|
120
|
+
| mean, nanmean | Preserve unit or point coordinate | axis, dtype, keepdims, where, out |
|
|
121
|
+
| min/amin/nanmin, max/amax/nanmax | Preserve unit or point coordinate | axis, keepdims, initial, where, out |
|
|
122
122
|
| where | Select between compatible quantities or points | condition, x, y, all positional; no out |
|
|
123
123
|
| clip | Preserve input unit or point coordinate | a_min/a_max or min/max, out; ufunc call options |
|
|
124
|
-
| prod | Product unit rules below | axis, dtype, keepdims, initial, where, out |
|
|
125
|
-
| median | Preserve unit | axis, keepdims, out; overwrite_input=False |
|
|
126
|
-
| std, var |
|
|
124
|
+
| prod, nanprod | Product unit rules below; nanprod counts selected non-NaN elements | axis, dtype, keepdims, initial, where, out |
|
|
125
|
+
| median, nanmedian | Preserve unit or point coordinate | axis, keepdims, out; overwrite_input=False |
|
|
126
|
+
| std/nanstd, var/nanvar | Difference unit / square difference unit | axis, dtype, ddof, keepdims, where, out |
|
|
127
127
|
| average | Preserve sample unit; returned weight sum is a dimensionless Quantity | axis, weights, returned, keepdims; linear Quantity only |
|
|
128
|
-
| quantile, percentile | Preserve sample unit; dimensionless q and weights | axis, out, method, keepdims, weights; overwrite_input=False; linear Quantity only |
|
|
128
|
+
| quantile, percentile, nanquantile, nanpercentile | Preserve sample unit; dimensionless q and weights | axis, out, method, keepdims, weights; overwrite_input=False; linear Quantity only |
|
|
129
129
|
| cumsum | Preserve unit | axis, dtype, out |
|
|
130
|
-
| argmin, argmax | Bare indices | axis, keepdims, out |
|
|
130
|
+
| argmin/nanargmin, argmax/nanargmax | Bare indices | axis, keepdims, out |
|
|
131
131
|
| interp | Align x to xp's unit; retain fp's unit | finite strictly increasing 1-D xp; scalar left/right, period=None; no out |
|
|
132
132
|
| trapezoid | Multiply ordinate and coordinate units | x, scalar dx, axis; no out |
|
|
133
133
|
| diff | Preserve input unit | n, axis, prepend, append; no out |
|
|
@@ -138,8 +138,9 @@ Supply the first magnitude argument positionally; concatenate/stack take a
|
|
|
138
138
|
sequence. Options follow each supported signature, including positional out
|
|
139
139
|
checks. Function and reduction outputs follow the rules below;
|
|
140
140
|
overwrite_input=True is rejected. Reduction/statistical `where` is supported
|
|
141
|
-
for sum, mean, std, var, add.reduce, prod,
|
|
142
|
-
|
|
141
|
+
for sum/nansum, mean/nanmean, std/nanstd, var/nanvar, add.reduce, prod/nanprod,
|
|
142
|
+
multiply.reduce, and min/max/nanmin/nanmax; `initial` is supported for
|
|
143
|
+
sum/nansum/add.reduce, prod/nanprod/multiply.reduce, and min/max/nanmin/nanmax. Precomputed `mean`,
|
|
143
144
|
`correction`, and other unsupported keywords raise TypeError.
|
|
144
145
|
Indexed assignment and augmented arithmetic on ndarrays follow the fixed-target
|
|
145
146
|
rules below. Python and NumPy scalar magnitudes retain ordinary result/rebinding
|
|
@@ -149,6 +150,7 @@ behavior for augmented arithmetic.
|
|
|
149
150
|
|
|
150
151
|
See [selection and clipping](numpy/selection.md) for `where`, `clip`,
|
|
151
152
|
`minimum/maximum`, and `min/max`, including coordinate and bound rules.
|
|
153
|
+
The NaN extrema and index functions share those coordinate rules and omit NaNs.
|
|
152
154
|
|
|
153
155
|
## Indexed assignment and augmented arithmetic
|
|
154
156
|
|
|
@@ -164,6 +166,8 @@ See [integration, differences and gradients](numpy/calculus.md) for
|
|
|
164
166
|
|
|
165
167
|
See [reductions and statistics](numpy/statistics.md#masked-sums-and-statistics)
|
|
166
168
|
for sum, mean, standard deviation, variance, initial values and input masks.
|
|
169
|
+
The [NaN statistics](numpy/statistics.md#nan-statistics) omit NaN samples using
|
|
170
|
+
the same unit and output rules as their ordinary counterparts.
|
|
167
171
|
|
|
168
172
|
## Product reductions
|
|
169
173
|
|
|
@@ -95,7 +95,8 @@ numerical boundary, use `np.expm1(np.asarray(q.value_in(si.one)))` after
|
|
|
95
95
|
checking dimensions and normalizing the scale.
|
|
96
96
|
|
|
97
97
|
The same NumPy stub limitation applies to `where`, `clip`,
|
|
98
|
-
`minimum/maximum`, `average`, `quantile`, `percentile`,
|
|
98
|
+
`minimum/maximum`, `average`, `quantile`, `percentile`, the supported `nan*`
|
|
99
|
+
statistics, and `interp`: runtime results
|
|
99
100
|
preserve the documented Quantity/point wrappers, but NumPy may annotate them
|
|
100
101
|
as ndarrays or Any. NumPy's `out` annotations also accept only ndarrays.
|
|
101
102
|
Runtime support and installed-consumer checks cover
|
|
@@ -4,7 +4,7 @@ build-backend = 'hatchling.build'
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = 'unitx'
|
|
7
|
-
version = '0.16.
|
|
7
|
+
version = '0.16.2'
|
|
8
8
|
description = 'Physical units, quantities, and unit conversion with exact unit algebra, type hints, and NumPy integration.'
|
|
9
9
|
readme = 'README.md'
|
|
10
10
|
requires-python = '>=3.12'
|
|
@@ -403,7 +403,7 @@ def _dispatch_ufunc(
|
|
|
403
403
|
}:
|
|
404
404
|
return NotImplemented
|
|
405
405
|
handler = _sum if ufunc is np.add else _prod
|
|
406
|
-
return handler(inputs[0], **{'axis': 0, **kwargs})
|
|
406
|
+
return handler(ufunc.reduce, inputs[0], **{'axis': 0, **kwargs})
|
|
407
407
|
if method == 'accumulate' and ufunc is np.add:
|
|
408
408
|
if kwargs.keys() - {'axis', 'dtype'}:
|
|
409
409
|
return NotImplemented
|
|
@@ -606,14 +606,22 @@ def dispatch_function(
|
|
|
606
606
|
np.transpose,
|
|
607
607
|
np.argmin,
|
|
608
608
|
np.argmax,
|
|
609
|
+
np.nanargmin,
|
|
610
|
+
np.nanargmax,
|
|
609
611
|
np.median,
|
|
610
612
|
np.mean,
|
|
611
613
|
np.std,
|
|
612
614
|
np.var,
|
|
615
|
+
np.nanmedian,
|
|
616
|
+
np.nanmean,
|
|
617
|
+
np.nanstd,
|
|
618
|
+
np.nanvar,
|
|
613
619
|
np.min,
|
|
614
620
|
np.max,
|
|
615
621
|
np.amin,
|
|
616
622
|
np.amax,
|
|
623
|
+
np.nanmin,
|
|
624
|
+
np.nanmax,
|
|
617
625
|
):
|
|
618
626
|
raise TypeError(
|
|
619
627
|
'This NumPy function is undefined for affine points; convert explicitly.'
|
|
@@ -854,11 +862,17 @@ def _argmin_max(
|
|
|
854
862
|
|
|
855
863
|
|
|
856
864
|
def _median(
|
|
857
|
-
|
|
865
|
+
func: Any,
|
|
866
|
+
q: Any,
|
|
867
|
+
axis: Any = None,
|
|
868
|
+
out: Any = None,
|
|
869
|
+
overwrite_input: bool = False,
|
|
870
|
+
keepdims: bool = False,
|
|
858
871
|
) -> Any:
|
|
859
872
|
if overwrite_input:
|
|
860
|
-
|
|
861
|
-
|
|
873
|
+
error_message = 'overwrite_input is not supported for Quantity.'
|
|
874
|
+
raise TypeError(error_message)
|
|
875
|
+
return _write_output(_wrap(func(q.value, axis=axis, keepdims=keepdims), q.unit), out)
|
|
862
876
|
|
|
863
877
|
|
|
864
878
|
def _cumsum(q: Any, axis: Any = None, dtype: Any = None, out: Any = None) -> Any:
|
|
@@ -867,6 +881,7 @@ def _cumsum(q: Any, axis: Any = None, dtype: Any = None, out: Any = None) -> Any
|
|
|
867
881
|
|
|
868
882
|
|
|
869
883
|
def _mean(
|
|
884
|
+
func: Any,
|
|
870
885
|
q: Any,
|
|
871
886
|
axis: Any = None,
|
|
872
887
|
dtype: Any = None,
|
|
@@ -877,7 +892,7 @@ def _mean(
|
|
|
877
892
|
) -> Any:
|
|
878
893
|
_validate_dtype(dtype)
|
|
879
894
|
_validate_where({'where': where})
|
|
880
|
-
result = _wrap(
|
|
895
|
+
result = _wrap(func(q.value, axis=axis, dtype=dtype, keepdims=keepdims, where=where), q.unit)
|
|
881
896
|
return _write_output(result, out)
|
|
882
897
|
|
|
883
898
|
|
|
@@ -931,15 +946,26 @@ def _quantile(
|
|
|
931
946
|
error_message = 'overwrite_input is not supported for Quantity.'
|
|
932
947
|
raise TypeError(error_message)
|
|
933
948
|
q = _statistic_parameter(q, 'q')
|
|
949
|
+
value = _numeric_value(a.value)
|
|
934
950
|
if weights is not None:
|
|
935
951
|
weights = _statistic_parameter(weights, 'weights')
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
952
|
+
if func in (np.nanquantile, np.nanpercentile):
|
|
953
|
+
# NumPy 2.1 mishandles tuple axes with weights; its NaN kernels
|
|
954
|
+
# also index reduced-axis weights without broadcasting in 2.1/2.5.
|
|
955
|
+
if isinstance(axis, tuple):
|
|
956
|
+
error_message = 'Weighted NaN quantiles require one integer axis or axis=None.'
|
|
957
|
+
raise TypeError(error_message)
|
|
958
|
+
if np.ndim(value) > 1 and axis is not None and np.shape(weights) != np.shape(value):
|
|
959
|
+
error_message = (
|
|
960
|
+
'NaN quantiles require full-shape weights for multidimensional samples.'
|
|
961
|
+
)
|
|
962
|
+
raise ValueError(error_message)
|
|
963
|
+
result = func(value, q, axis=axis, method=method, keepdims=keepdims, weights=weights)
|
|
939
964
|
return _write_output(Quantity(result, a.unit), out)
|
|
940
965
|
|
|
941
966
|
|
|
942
967
|
def _sum(
|
|
968
|
+
func: Any,
|
|
943
969
|
q: Any,
|
|
944
970
|
axis: Any = None,
|
|
945
971
|
dtype: Any = None,
|
|
@@ -954,33 +980,34 @@ def _sum(
|
|
|
954
980
|
initial_value = None
|
|
955
981
|
if initial is not None:
|
|
956
982
|
if not _is_supported_operand(initial):
|
|
957
|
-
|
|
983
|
+
error_message = 'initial must be a numeric scalar or scalar Quantity.'
|
|
984
|
+
raise TypeError(error_message)
|
|
958
985
|
initial_value, initial_unit = _linear_parts(initial)
|
|
959
986
|
if np.ndim(initial_value) != 0:
|
|
960
|
-
|
|
987
|
+
error_message = 'initial must be a numeric scalar or scalar Quantity.'
|
|
988
|
+
raise TypeError(error_message)
|
|
961
989
|
if not compatible(initial_unit, unit):
|
|
962
990
|
# Bare zero is the additive identity in any dimension. A Quantity
|
|
963
991
|
# zero still has its declared dimension and must be compatible.
|
|
964
992
|
if isinstance(initial, (Quantity, _Point)) or initial != 0:
|
|
965
|
-
|
|
993
|
+
error_message = 'initial has an incompatible dimension.'
|
|
994
|
+
raise DimensionError(error_message)
|
|
966
995
|
else:
|
|
967
996
|
initial_value = scale_magnitude(initial_value, initial_unit.factor / unit.factor)
|
|
968
997
|
result = _wrap(
|
|
969
|
-
|
|
970
|
-
value, axis=axis, dtype=dtype, keepdims=keepdims, initial=initial_value, where=where
|
|
971
|
-
),
|
|
998
|
+
func(value, axis=axis, dtype=dtype, keepdims=keepdims, initial=initial_value, where=where),
|
|
972
999
|
unit,
|
|
973
1000
|
)
|
|
974
1001
|
return _write_output(result, out)
|
|
975
1002
|
|
|
976
1003
|
|
|
977
1004
|
def _prod(
|
|
1005
|
+
func: Any,
|
|
978
1006
|
q: Any,
|
|
979
1007
|
axis: Any = None,
|
|
980
1008
|
dtype: Any = None,
|
|
981
1009
|
out: Any = None,
|
|
982
1010
|
keepdims: bool = False,
|
|
983
|
-
*,
|
|
984
1011
|
initial: Any = 1,
|
|
985
1012
|
where: Any = True,
|
|
986
1013
|
) -> Any:
|
|
@@ -989,10 +1016,12 @@ def _prod(
|
|
|
989
1016
|
initial_value, initial_unit = None, Unit()
|
|
990
1017
|
if initial is not None:
|
|
991
1018
|
if not _is_supported_operand(initial):
|
|
992
|
-
|
|
1019
|
+
error_message = 'initial must be a numeric scalar or scalar Quantity.'
|
|
1020
|
+
raise TypeError(error_message)
|
|
993
1021
|
initial_value, initial_unit = _linear_parts(initial)
|
|
994
1022
|
if np.ndim(initial_value) != 0:
|
|
995
|
-
|
|
1023
|
+
error_message = 'initial must be a numeric scalar or scalar Quantity.'
|
|
1024
|
+
raise TypeError(error_message)
|
|
996
1025
|
|
|
997
1026
|
value, unit = _linear_parts(q)
|
|
998
1027
|
mask = np.broadcast_to(where, np.shape(value))
|
|
@@ -1000,7 +1029,7 @@ def _prod(
|
|
|
1000
1029
|
if unit.dimension == DIMENSIONLESS:
|
|
1001
1030
|
value = scale_magnitude(value, unit.factor, where=mask)
|
|
1002
1031
|
unit = Unit()
|
|
1003
|
-
result =
|
|
1032
|
+
result = func(
|
|
1004
1033
|
value, axis=axis, dtype=dtype, keepdims=keepdims, initial=initial_value, where=where
|
|
1005
1034
|
)
|
|
1006
1035
|
if unit.dimension != DIMENSIONLESS:
|
|
@@ -1008,16 +1037,17 @@ def _prod(
|
|
|
1008
1037
|
axes = range(len(shape)) if axis is None else axis if isinstance(axis, tuple) else (axis,)
|
|
1009
1038
|
# NumPy has already validated axes, including its scalar 0/-1 special case.
|
|
1010
1039
|
count = math.prod(shape[i] for i in axes) if shape else 1
|
|
1040
|
+
if func is np.nanprod:
|
|
1041
|
+
mask = mask & ~np.isnan(value)
|
|
1011
1042
|
if np.ndim(where) == 0:
|
|
1012
1043
|
count = count if bool(where) else 0
|
|
1013
|
-
|
|
1044
|
+
if np.ndim(where) != 0 or func is np.nanprod:
|
|
1014
1045
|
counts = np.sum(mask, axis=axis)
|
|
1015
1046
|
if np.size(counts):
|
|
1016
1047
|
count = int(np.asarray(counts).flat[0])
|
|
1017
1048
|
if np.any(counts != count):
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
)
|
|
1049
|
+
error_message = 'Product reductions require equal element counts per result.'
|
|
1050
|
+
raise DimensionError(error_message)
|
|
1021
1051
|
# With no output elements, retain the nominal reduction extent.
|
|
1022
1052
|
unit = unit**count
|
|
1023
1053
|
assert isinstance(unit, Unit) and isinstance(initial_unit, Unit)
|
|
@@ -1038,7 +1068,7 @@ def _std_var(
|
|
|
1038
1068
|
_validate_dtype(dtype)
|
|
1039
1069
|
_validate_where({'where': where})
|
|
1040
1070
|
delta = q.unit.delta_unit if isinstance(q.unit, _Coordinate) else q.unit
|
|
1041
|
-
unit = delta**2 if func
|
|
1071
|
+
unit = delta**2 if func in (np.var, np.nanvar) else delta
|
|
1042
1072
|
result = _wrap(
|
|
1043
1073
|
func(q.value, axis=axis, dtype=dtype, ddof=ddof, keepdims=keepdims, where=where), unit
|
|
1044
1074
|
)
|
|
@@ -1170,7 +1200,6 @@ def _extremum(
|
|
|
1170
1200
|
axis: Any = None,
|
|
1171
1201
|
out: Any = None,
|
|
1172
1202
|
keepdims: bool = False,
|
|
1173
|
-
*,
|
|
1174
1203
|
initial: Any = _UNSET,
|
|
1175
1204
|
where: Any = True,
|
|
1176
1205
|
) -> Any:
|
|
@@ -1189,18 +1218,30 @@ _QUANTITY_FUNCTIONS = {
|
|
|
1189
1218
|
np.transpose: _transpose,
|
|
1190
1219
|
np.argmin: partial(_argmin_max, np.argmin),
|
|
1191
1220
|
np.argmax: partial(_argmin_max, np.argmax),
|
|
1192
|
-
np.
|
|
1221
|
+
np.nanargmin: partial(_argmin_max, np.nanargmin),
|
|
1222
|
+
np.nanargmax: partial(_argmin_max, np.nanargmax),
|
|
1223
|
+
np.median: partial(_median, np.median),
|
|
1224
|
+
np.nanmedian: partial(_median, np.nanmedian),
|
|
1193
1225
|
np.cumsum: _cumsum,
|
|
1194
|
-
np.prod: _prod,
|
|
1226
|
+
np.prod: partial(_prod, np.multiply.reduce),
|
|
1227
|
+
np.nanprod: partial(_prod, np.nanprod),
|
|
1195
1228
|
np.std: partial(_std_var, np.std),
|
|
1196
1229
|
np.var: partial(_std_var, np.var),
|
|
1197
|
-
np.
|
|
1198
|
-
np.
|
|
1230
|
+
np.nanstd: partial(_std_var, np.nanstd),
|
|
1231
|
+
np.nanvar: partial(_std_var, np.nanvar),
|
|
1232
|
+
np.sum: partial(_sum, np.add.reduce),
|
|
1233
|
+
np.nansum: partial(_sum, np.nansum),
|
|
1234
|
+
np.mean: partial(_mean, np.mean),
|
|
1235
|
+
np.nanmean: partial(_mean, np.nanmean),
|
|
1199
1236
|
np.average: _average,
|
|
1200
1237
|
np.quantile: partial(_quantile, np.quantile),
|
|
1201
1238
|
np.percentile: partial(_quantile, np.percentile),
|
|
1239
|
+
np.nanquantile: partial(_quantile, np.nanquantile),
|
|
1240
|
+
np.nanpercentile: partial(_quantile, np.nanpercentile),
|
|
1202
1241
|
np.min: partial(_extremum, np.min),
|
|
1203
1242
|
np.max: partial(_extremum, np.max),
|
|
1204
1243
|
np.amin: partial(_extremum, np.amin),
|
|
1205
1244
|
np.amax: partial(_extremum, np.amax),
|
|
1245
|
+
np.nanmin: partial(_extremum, np.nanmin),
|
|
1246
|
+
np.nanmax: partial(_extremum, np.nanmax),
|
|
1206
1247
|
}
|
|
@@ -119,17 +119,27 @@ def output_contracts(a: npt.NDArray[np.float64]) -> None:
|
|
|
119
119
|
scalar_target = Quantity(np.empty(()), 'cm')
|
|
120
120
|
q.__array_ufunc__(np.add, 'reduce', q, out=(scalar_target,))
|
|
121
121
|
q.__array_function__(np.sum, (Quantity,), (q,), {'out': scalar_target})
|
|
122
|
-
for operation in (np.quantile, np.percentile):
|
|
122
|
+
for operation in (np.quantile, np.percentile, np.nanquantile, np.nanpercentile):
|
|
123
123
|
q.__array_function__(operation, (Quantity,), (q, 0.5), {'out': scalar_target})
|
|
124
124
|
mask = np.array([True, False, True])
|
|
125
125
|
q.__array_ufunc__(
|
|
126
126
|
np.add, 'reduce', q, out=(scalar_target,), where=mask, initial=Quantity(2, 'cm')
|
|
127
127
|
)
|
|
128
|
-
for operation in (np.sum, np.mean, np.std):
|
|
128
|
+
for operation in (np.sum, np.nansum, np.mean, np.std, np.nanmean, np.nanstd):
|
|
129
129
|
q.__array_function__(operation, (Quantity,), (q,), {'where': mask, 'out': scalar_target})
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
130
|
+
for operation in (np.nanmin, np.nanmax):
|
|
131
|
+
q.__array_function__(
|
|
132
|
+
operation, (Quantity,), (q,), {'initial': Quantity(0, 'cm'), 'out': scalar_target}
|
|
133
|
+
)
|
|
134
|
+
for operation in (np.nanargmin, np.nanargmax):
|
|
135
|
+
indices = np.empty((), dtype=np.intp)
|
|
136
|
+
q.__array_function__(operation, (Quantity,), (q,), {'out': indices})
|
|
137
|
+
assert_type(indices.dtype, np.dtype[np.intp])
|
|
138
|
+
q.__array_function__(np.nanmedian, (Quantity,), (q,), {'out': scalar_target})
|
|
139
|
+
for operation in (np.var, np.nanvar):
|
|
140
|
+
q.__array_function__(
|
|
141
|
+
operation, (Quantity,), (q,), {'where': mask, 'out': Quantity(np.empty(()), 'm2')}
|
|
142
|
+
)
|
|
133
143
|
q.__array_function__(np.diff, (Quantity,), (q,), {'prepend': Quantity(2, 'cm')})
|
|
134
144
|
q.__array_function__(np.gradient, (Quantity,), (q, Quantity(2, 's')), {'axis': 0})
|
|
135
145
|
product_target = Quantity(np.empty(()), 'm2')
|
|
@@ -142,6 +152,7 @@ def output_contracts(a: npt.NDArray[np.float64]) -> None:
|
|
|
142
152
|
(q,),
|
|
143
153
|
{'initial': Quantity(2, 's'), 'out': Quantity(np.empty(()), 'm2 s')},
|
|
144
154
|
)
|
|
155
|
+
q.__array_function__(np.nanprod, (Quantity,), (q,), {'where': mask, 'out': product_target})
|
|
145
156
|
assert_type(product_target.value.dtype, np.dtype[np.float64])
|
|
146
157
|
assert_type(scalar_target.value.dtype, np.dtype[np.float64])
|
|
147
158
|
assert_type(widened.value, Array)
|