|
2 | 2 | # SPDX-License-Identifier: BSD-3-Clause |
3 | 3 |
|
4 | 4 | from collections import Counter |
5 | | -from contextlib import suppress |
| 5 | +from collections.abc import Iterable |
| 6 | +from numbers import Real |
6 | 7 | from typing import NamedTuple |
7 | 8 |
|
8 | 9 | import numpy as np |
@@ -96,6 +97,9 @@ class MissingValues(NamedTuple): |
96 | 97 |
|
97 | 98 | nan: bool |
98 | 99 | none: bool |
| 100 | + # All the different nan instances seen, useful because they end up merged |
| 101 | + # into just np.nan: |
| 102 | + all_nans: Iterable[Real] = (np.nan,) |
99 | 103 |
|
100 | 104 | def to_list(self): |
101 | 105 | """Convert tuple to a list where None is always first.""" |
@@ -130,15 +134,20 @@ def _extract_missing(values): |
130 | 134 | if not missing_values_set: |
131 | 135 | return values, MissingValues(nan=False, none=False) |
132 | 136 |
|
| 137 | + all_nans = missing_values_set - {None} |
133 | 138 | if None in missing_values_set: |
134 | 139 | if len(missing_values_set) == 1: |
135 | | - output_missing_values = MissingValues(nan=False, none=True) |
| 140 | + output_missing_values = MissingValues( |
| 141 | + nan=False, none=True, all_nans=all_nans |
| 142 | + ) |
136 | 143 | else: |
137 | 144 | # If there is more than one missing value, then it has to be |
138 | 145 | # float('nan') or np.nan |
139 | | - output_missing_values = MissingValues(nan=True, none=True) |
| 146 | + output_missing_values = MissingValues( |
| 147 | + nan=True, none=True, all_nans=all_nans |
| 148 | + ) |
140 | 149 | else: |
141 | | - output_missing_values = MissingValues(nan=True, none=False) |
| 150 | + output_missing_values = MissingValues(nan=True, none=False, all_nans=all_nans) |
142 | 151 |
|
143 | 152 | # create set without the missing values |
144 | 153 | output = values - missing_values_set |
@@ -189,7 +198,7 @@ def _unique_python(values, *, return_inverse, return_counts): |
189 | 198 | ret += (_map_to_integer(values, uniques),) |
190 | 199 |
|
191 | 200 | if return_counts: |
192 | | - ret += (_get_counts(values, uniques),) |
| 201 | + ret += (_get_counts(values, uniques, missing_values.all_nans),) |
193 | 202 |
|
194 | 203 | return ret[0] if len(ret) == 1 else ret |
195 | 204 |
|
@@ -321,40 +330,21 @@ def is_valid(value): |
321 | 330 | return diff |
322 | 331 |
|
323 | 332 |
|
324 | | -class _NaNCounter(Counter): |
325 | | - """Counter with support for nan values.""" |
326 | | - |
327 | | - def __init__(self, items): |
328 | | - super().__init__(self._generate_items(items)) |
329 | | - |
330 | | - def _generate_items(self, items): |
331 | | - """Generate items without nans. Stores the nan counts separately.""" |
332 | | - for item in items: |
333 | | - if not is_scalar_nan(item): |
334 | | - yield item |
335 | | - continue |
336 | | - if not hasattr(self, "nan_count"): |
337 | | - self.nan_count = 0 |
338 | | - self.nan_count += 1 |
339 | | - |
340 | | - def __missing__(self, key): |
341 | | - if hasattr(self, "nan_count") and is_scalar_nan(key): |
342 | | - return self.nan_count |
343 | | - raise KeyError(key) |
344 | | - |
345 | | - |
346 | | -def _get_counts(values, uniques): |
| 333 | +def _get_counts(values, uniques, nan_values=(np.nan,)): |
347 | 334 | """Get the count of each of the `uniques` in `values`. |
348 | 335 |
|
349 | | - The counts will use the order passed in by `uniques`. For non-object dtypes, |
350 | | - `uniques` is assumed to be sorted and `np.nan` is at the end. |
| 336 | + The counts will use the order passed in by `uniques`. `np.nan` is assumed |
| 337 | + to be the last item in `uniques`, if it was one of the values. For |
| 338 | + non-object dtypes, `uniques` is assumed to be sorted. |
351 | 339 | """ |
352 | 340 | if values.dtype.kind in "OU": |
353 | | - counter = _NaNCounter(values) |
| 341 | + counter = Counter(values) |
354 | 342 | output = np.zeros(len(uniques), dtype=np.int64) |
355 | 343 | for i, item in enumerate(uniques): |
356 | | - with suppress(KeyError): |
357 | | - output[i] = counter[item] |
| 344 | + output[i] = counter[item] |
| 345 | + if len(uniques) > 0 and is_scalar_nan(uniques[-1]): |
| 346 | + # Should be the sum of all nans: |
| 347 | + output[-1] = sum(counter[nan] for nan in nan_values) |
358 | 348 | return output |
359 | 349 |
|
360 | 350 | unique_values, counts = _unique_np(values, return_counts=True) |
|
0 commit comments