catii 1.0.5.dev1__tar.gz → 1.0.5.dev2__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.
Files changed (25) hide show
  1. {catii-1.0.5.dev1/src/catii.egg-info → catii-1.0.5.dev2}/PKG-INFO +1 -1
  2. {catii-1.0.5.dev1 → catii-1.0.5.dev2}/setup.py +1 -1
  3. {catii-1.0.5.dev1 → catii-1.0.5.dev2}/src/catii/xfuncs.py +519 -2
  4. {catii-1.0.5.dev1 → catii-1.0.5.dev2/src/catii.egg-info}/PKG-INFO +1 -1
  5. {catii-1.0.5.dev1 → catii-1.0.5.dev2}/LICENSE +0 -0
  6. {catii-1.0.5.dev1 → catii-1.0.5.dev2}/README.md +0 -0
  7. {catii-1.0.5.dev1 → catii-1.0.5.dev2}/pyproject.toml +0 -0
  8. {catii-1.0.5.dev1 → catii-1.0.5.dev2}/setup.cfg +0 -0
  9. {catii-1.0.5.dev1 → catii-1.0.5.dev2}/src/catii/__init__.py +0 -0
  10. {catii-1.0.5.dev1 → catii-1.0.5.dev2}/src/catii/ccubes.py +0 -0
  11. {catii-1.0.5.dev1 → catii-1.0.5.dev2}/src/catii/ffuncs.py +0 -0
  12. {catii-1.0.5.dev1 → catii-1.0.5.dev2}/src/catii/iindexes.py +0 -0
  13. {catii-1.0.5.dev1 → catii-1.0.5.dev2}/src/catii/indxio.py +0 -0
  14. {catii-1.0.5.dev1 → catii-1.0.5.dev2}/src/catii/set_operations.pyx +0 -0
  15. {catii-1.0.5.dev1 → catii-1.0.5.dev2}/src/catii/xcubes.py +0 -0
  16. {catii-1.0.5.dev1 → catii-1.0.5.dev2}/src/catii.egg-info/SOURCES.txt +0 -0
  17. {catii-1.0.5.dev1 → catii-1.0.5.dev2}/src/catii.egg-info/dependency_links.txt +0 -0
  18. {catii-1.0.5.dev1 → catii-1.0.5.dev2}/src/catii.egg-info/requires.txt +0 -0
  19. {catii-1.0.5.dev1 → catii-1.0.5.dev2}/src/catii.egg-info/top_level.txt +0 -0
  20. {catii-1.0.5.dev1 → catii-1.0.5.dev2}/tests/test_ccubes.py +0 -0
  21. {catii-1.0.5.dev1 → catii-1.0.5.dev2}/tests/test_iindexes.py +0 -0
  22. {catii-1.0.5.dev1 → catii-1.0.5.dev2}/tests/test_iindexes_nonint.py +0 -0
  23. {catii-1.0.5.dev1 → catii-1.0.5.dev2}/tests/test_indxio.py +0 -0
  24. {catii-1.0.5.dev1 → catii-1.0.5.dev2}/tests/test_set_operations.py +0 -0
  25. {catii-1.0.5.dev1 → catii-1.0.5.dev2}/tests/test_xcubes.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: catii
3
- Version: 1.0.5.dev1
3
+ Version: 1.0.5.dev2
4
4
  Summary: A library for N-dimensional categorical data.
5
5
  Home-page: https://github.com/Crunch-io/catii
6
6
  Author: Robert Brewer, Slobodan Ilić
@@ -28,7 +28,7 @@ with open("README.md", "r") as fh:
28
28
 
29
29
  setup(
30
30
  name="catii",
31
- version="1.0.5.dev1",
31
+ version="1.0.5.dev2",
32
32
  author="Robert Brewer, Slobodan Ilić",
33
33
  author_email="dev@crunch.io",
34
34
  description="A library for N-dimensional categorical data.",
@@ -50,6 +50,8 @@ replace NaN values in the `values` array with 0 in that case. If you prefer
50
50
  `sum([])` to return 0, for example, without a second "validity" array,
51
51
  pass `return_missing_as=0`.
52
52
  """
53
+ import functools
54
+ import operator
53
55
  import warnings
54
56
 
55
57
  import numpy
@@ -1105,14 +1107,14 @@ class xfunc_quantile(xfunc):
1105
1107
  if self.weights.shape:
1106
1108
  w = self.weights[rowmask]
1107
1109
  else:
1108
- w = numpy.repeat(self.weights, len(seg))
1110
+ w = numpy.repeat(self.weights, len(rowmask))
1109
1111
  qs[i] = self.weighted_quantile(seg, self.probability, w)
1110
1112
 
1111
1113
  def weighted_quantile(self, arr, probability, weights):
1112
1114
  def weighted_quantile_1d(a):
1113
1115
  w = weights
1114
1116
 
1115
- ind = numpy.lexsort((w, a))
1117
+ ind = a.argsort()
1116
1118
  a = a[ind]
1117
1119
  w = w[ind]
1118
1120
 
@@ -1490,3 +1492,518 @@ class xfunc_covariance(xfunc):
1490
1492
  return covs, ~missings
1491
1493
  else:
1492
1494
  return covs
1495
+
1496
+
1497
+ # Default quantile probabilities
1498
+ DEFAULT_QUANTILES = [0.0, 0.25, 0.5, 0.75, 1.0]
1499
+
1500
+
1501
+ class xfunc_numeric_summary(xfunc):
1502
+ """Calculate multiple numeric statistics in a single pass over the data.
1503
+
1504
+ This xfunc computes any combination of:
1505
+ - mean: weighted arithmetic mean
1506
+ - stddev: weighted standard deviation (ddof=1)
1507
+ - valid_count: unweighted count of non-missing values
1508
+ - sum: weighted sum
1509
+ - quantiles: weighted quantiles at specified probabilities
1510
+
1511
+ The goal is to reduce both the number of cube calls and the number of
1512
+ calculations by sharing intermediate computations.
1513
+
1514
+ Parameters
1515
+ ----------
1516
+ arr : array or (values, validity) tuple
1517
+ NumPy array of numeric values, or a tuple of (values, validity) arrays.
1518
+ Missing values are represented by NaN or validity=False.
1519
+
1520
+ weights : array or (weights, validity) tuple, optional
1521
+ NumPy array of weight values.
1522
+
1523
+ ignore_missing : bool, default False
1524
+ If False, any missing value in a bin causes that bin's result to be
1525
+ missing. If True, missing values are skipped.
1526
+
1527
+ return_missing_as : scalar or tuple, default NaN
1528
+ If NaN, missing results are returned as NaN. If a tuple like (0, False),
1529
+ results are returned as (values, validity) tuples.
1530
+
1531
+ include : dict, optional
1532
+ Specifies which statistics to compute. Keys are statistic names,
1533
+ values are True or (for quantiles) a list of probabilities.
1534
+ Default: all statistics with quantiles=[0, 0.25, 0.5, 0.75, 1.0]
1535
+
1536
+ Examples:
1537
+ include={"mean": True, "stddev": True}
1538
+ include={"quantiles": [0.1, 0.5, 0.9]}
1539
+ include={"mean": True, "quantiles": [0.5]}
1540
+
1541
+ Returns
1542
+ -------
1543
+ dict
1544
+ A dict with requested statistics as keys.
1545
+ For dimensioned cubes, values are NumPy arrays shaped like the cube.
1546
+ The "quantiles" value is itself a dict mapping probability -> array.
1547
+ """
1548
+
1549
+ def __init__(
1550
+ self,
1551
+ arr,
1552
+ weights=None,
1553
+ ignore_missing=False,
1554
+ return_missing_as=NaN,
1555
+ include=None,
1556
+ ):
1557
+ # Parse include configuration
1558
+ if include is None:
1559
+ include = {
1560
+ "mean": True,
1561
+ "stddev": True,
1562
+ "valid_count": True,
1563
+ "sum": True,
1564
+ "quantiles": DEFAULT_QUANTILES,
1565
+ }
1566
+
1567
+ if not include:
1568
+ raise ValueError("include must specify at least one statistic")
1569
+
1570
+ self.include_mean = include.get("mean", False)
1571
+ self.include_stddev = include.get("stddev", False)
1572
+ self.include_valid_count = include.get("valid_count", False)
1573
+ self.include_sum = include.get("sum", False)
1574
+ self.include_quantiles = include.get("quantiles", False)
1575
+
1576
+ if self.include_quantiles:
1577
+ if self.include_quantiles is True:
1578
+ self.quantile_probs = numpy.array(DEFAULT_QUANTILES, dtype=float)
1579
+ else:
1580
+ self.quantile_probs = numpy.array(self.include_quantiles, dtype=float)
1581
+ else:
1582
+ self.quantile_probs = None
1583
+
1584
+ # Process input array
1585
+ arr, validity = as_separate_validity(arr)
1586
+ self.shape = arr.shape[1:]
1587
+ self.size = numpy.prod(self.shape, 0)
1588
+
1589
+ # Process weights
1590
+ if weights is not None:
1591
+ weights, weights_validity = as_separate_validity(weights)
1592
+ validity = (validity.T & weights_validity).T
1593
+ # Set negative weights to 0, like SAS EXCLNPWGT option
1594
+ with numpy.errstate(invalid="ignore"):
1595
+ neg_weights = weights < 0
1596
+ if neg_weights.any():
1597
+ weights = weights.copy()
1598
+ weights[neg_weights] = 0
1599
+
1600
+ self.weights = weights
1601
+ self.validity = validity
1602
+ self.ignore_missing = ignore_missing
1603
+ self.return_missing_as = return_missing_as
1604
+
1605
+ if isinstance(self.return_missing_as, tuple):
1606
+ self.null = self.return_missing_as[0]
1607
+ else:
1608
+ self.null = self.return_missing_as
1609
+
1610
+ # Prepare data arrays for different computations
1611
+ # For quantiles, keep original values with NaN for missing
1612
+ self.arr = arr.astype(float).copy()
1613
+ self.arr[~validity] = NaN
1614
+
1615
+ # For mean/sum, prepare weighted summables
1616
+ if self.include_mean or self.include_sum or self.include_stddev:
1617
+ summables = arr.astype(float).copy()
1618
+ if weights is not None:
1619
+ self.wsummables = (summables.T * weights).T
1620
+ else:
1621
+ self.wsummables = summables
1622
+ self.wsummables[~validity] = 0
1623
+
1624
+ # For valid_count computation (weighted counts for mean denominator)
1625
+ if weights is not None:
1626
+ self.countables = (validity.astype(float).T * weights).T
1627
+ else:
1628
+ self.countables = validity.astype(float)
1629
+ self.countables[~validity] = 0
1630
+
1631
+ # Unweighted summables for stddev variance calculation
1632
+ self.summables = arr.astype(float).copy()
1633
+ self.summables[~validity] = 0
1634
+
1635
+ def get_initial_regions(self, cube):
1636
+ """Return empty NumPy arrays to fill."""
1637
+ shape = cube.shape + self.shape
1638
+ if not shape:
1639
+ shape = (1,)
1640
+
1641
+ regions = {}
1642
+
1643
+ # valid_count is needed for mean denominator and stddev, so always allocate
1644
+ regions["valid_count"] = numpy.zeros(shape, dtype=int)
1645
+
1646
+ # For weighted mean, we need weighted counts separately
1647
+ if self.include_mean and self.weights is not None:
1648
+ regions["weighted_counts"] = numpy.zeros(shape, dtype=float)
1649
+
1650
+ if self.include_mean or self.include_sum:
1651
+ regions["sums"] = numpy.zeros(shape, dtype=float)
1652
+
1653
+ if self.include_stddev:
1654
+ regions["stddev_values"] = numpy.full(shape, NaN, dtype=float)
1655
+
1656
+ if self.include_quantiles:
1657
+ regions["quantiles"] = {
1658
+ p: numpy.full(shape, NaN, dtype=float) for p in self.quantile_probs
1659
+ }
1660
+
1661
+ # Track missing for output
1662
+ if not self.ignore_missing:
1663
+ regions["missing_counts"] = numpy.zeros(shape, dtype=int)
1664
+
1665
+ return regions
1666
+
1667
+ def calculate(self, cube):
1668
+ """Return a dict with requested statistics.
1669
+
1670
+ This overrides the base calculate to properly handle the regions dict
1671
+ format and compute coordinates from the cube.
1672
+ """
1673
+ regions = self.get_initial_regions(cube)
1674
+
1675
+ # Compute coordinates from cube dimensions (same logic as xcube.calculate)
1676
+ strided_dims = cube.strided_dims()
1677
+ coordinates = (
1678
+ functools.reduce(operator.add, strided_dims) if strided_dims else None
1679
+ )
1680
+
1681
+ self.fill(coordinates, regions)
1682
+ return self.reduce(cube, regions)
1683
+
1684
+ def fill(self, coordinates, regions):
1685
+ """Fill regions with statistics computed in a single pass."""
1686
+ if coordinates is None:
1687
+ self._fill_no_coordinates(regions)
1688
+ else:
1689
+ self._fill_with_coordinates(coordinates, regions)
1690
+
1691
+ def _fill_no_coordinates(self, regions):
1692
+ """Fill for scalar cube (no dimensions)."""
1693
+ valid_count = numpy.count_nonzero(self.validity, axis=0)
1694
+ regions["valid_count"][:] = valid_count
1695
+
1696
+ if not self.ignore_missing:
1697
+ regions["missing_counts"][:] = numpy.count_nonzero(~self.validity, axis=0)
1698
+
1699
+ # Mean and sum share weighted sums
1700
+ if self.include_mean or self.include_sum:
1701
+ value_sums = numpy.nansum(self.wsummables, axis=0)
1702
+ regions["sums"][:] = value_sums
1703
+
1704
+ if self.include_mean and self.weights is not None:
1705
+ regions["weighted_counts"][:] = numpy.sum(self.countables, axis=0)
1706
+
1707
+ if self.include_stddev:
1708
+ # Compute stddev
1709
+ if self.weights is not None:
1710
+ weighted_counts = numpy.sum(self.countables, axis=0)
1711
+ value_sums = numpy.nansum(self.wsummables, axis=0)
1712
+ with numpy.errstate(divide="ignore", invalid="ignore"):
1713
+ means = value_sums / weighted_counts
1714
+ else:
1715
+ value_sums = numpy.nansum(self.wsummables, axis=0)
1716
+ with numpy.errstate(divide="ignore", invalid="ignore"):
1717
+ means = value_sums / valid_count
1718
+
1719
+ squared_variances = (self.summables - means) ** 2
1720
+ if self.weights is not None:
1721
+ squared_variances = (squared_variances.T * self.weights).T
1722
+ squared_variances[~self.validity] = 0
1723
+ varsums = numpy.sum(squared_variances, axis=0)
1724
+
1725
+ with numpy.errstate(divide="ignore", invalid="ignore"):
1726
+ if self.weights is None:
1727
+ regions["stddev_values"][:] = numpy.sqrt(varsums / (valid_count - 1))
1728
+ else:
1729
+ weight_sums = numpy.sum(
1730
+ numpy.where(self.validity, self.weights, 0), axis=0
1731
+ )
1732
+ regions["stddev_values"][:] = numpy.sqrt(
1733
+ (varsums / weight_sums) * (valid_count / (valid_count - 1))
1734
+ )
1735
+
1736
+ if self.include_quantiles:
1737
+ if self.weights is None:
1738
+ # Unweighted: numpy handles multiple quantiles efficiently
1739
+ for p in self.quantile_probs:
1740
+ if len(self.arr) and not numpy.all(numpy.isnan(self.arr)):
1741
+ if self.ignore_missing:
1742
+ regions["quantiles"][p][:] = numpy.nanquantile(
1743
+ self.arr, p, axis=0
1744
+ )
1745
+ else:
1746
+ regions["quantiles"][p][:] = numpy.quantile(
1747
+ self.arr, p, axis=0
1748
+ )
1749
+ else:
1750
+ # Weighted: sort once and compute all quantiles
1751
+ results = self._weighted_quantiles_all(
1752
+ self.arr, self.quantile_probs, self.weights
1753
+ )
1754
+ for p, val in zip(self.quantile_probs, results):
1755
+ regions["quantiles"][p][:] = val
1756
+
1757
+
1758
+ def _fill_with_coordinates(self, coordinates, regions):
1759
+ """Fill for cube with dimensions."""
1760
+ # Get size from valid_count region
1761
+ size = regions["valid_count"].size
1762
+
1763
+ # valid_count is the unweighted count of non-missing values
1764
+ valid_count = numpy.bincount(
1765
+ coordinates, weights=self.validity.astype(float), minlength=size
1766
+ )[:size].astype(int)
1767
+ regions["valid_count"].flat[:] = valid_count
1768
+
1769
+ if not self.ignore_missing:
1770
+ missing = numpy.bincount(
1771
+ coordinates, weights=(~self.validity).astype(float), minlength=size
1772
+ )[:size].astype(int)
1773
+ regions["missing_counts"].flat[:] = missing
1774
+
1775
+ if self.include_mean or self.include_sum or self.include_stddev:
1776
+ value_sums = numpy.bincount(
1777
+ coordinates, weights=self.wsummables, minlength=size
1778
+ )[:size]
1779
+ if self.include_mean or self.include_sum:
1780
+ regions["sums"].flat[:] = value_sums
1781
+
1782
+ if self.include_mean and self.weights is not None:
1783
+ weighted_counts = numpy.bincount(
1784
+ coordinates, weights=self.countables, minlength=size
1785
+ )[:size]
1786
+ regions["weighted_counts"].flat[:] = weighted_counts
1787
+
1788
+ if self.include_stddev:
1789
+ if self.weights is not None:
1790
+ weighted_counts = numpy.bincount(
1791
+ coordinates, weights=self.countables, minlength=size
1792
+ )[:size]
1793
+ with numpy.errstate(divide="ignore", invalid="ignore"):
1794
+ means = value_sums / weighted_counts
1795
+ else:
1796
+ with numpy.errstate(divide="ignore", invalid="ignore"):
1797
+ means = value_sums / valid_count
1798
+
1799
+ # Compute variance sums
1800
+ squared_variances = (self.summables - means[coordinates]) ** 2
1801
+ if self.weights is not None:
1802
+ squared_variances = squared_variances * self.weights
1803
+ squared_variances[~self.validity] = 0
1804
+ varsums = numpy.bincount(
1805
+ coordinates, weights=squared_variances, minlength=size
1806
+ )[:size]
1807
+
1808
+ with numpy.errstate(divide="ignore", invalid="ignore"):
1809
+ if self.weights is None:
1810
+ regions["stddev_values"].flat[:] = numpy.sqrt(
1811
+ varsums / (valid_count - 1)
1812
+ )
1813
+ else:
1814
+ weight_sums = numpy.bincount(
1815
+ coordinates,
1816
+ weights=numpy.where(self.validity, self.weights, 0),
1817
+ minlength=size,
1818
+ )[:size]
1819
+ regions["stddev_values"].flat[:] = numpy.sqrt(
1820
+ (varsums / weight_sums) * (valid_count / (valid_count - 1))
1821
+ )
1822
+
1823
+ if self.include_quantiles:
1824
+ # Quantiles require per-bin iteration (sorting-based)
1825
+ for i, rowmask in self.bins(coordinates, size):
1826
+ seg = self.arr[rowmask]
1827
+ if len(seg) == 0 or numpy.all(numpy.isnan(seg)):
1828
+ continue
1829
+
1830
+ if self.weights is None:
1831
+ for p in self.quantile_probs:
1832
+ if self.ignore_missing:
1833
+ regions["quantiles"][p].flat[i] = numpy.nanquantile(
1834
+ seg, p, axis=0
1835
+ )
1836
+ else:
1837
+ regions["quantiles"][p].flat[i] = numpy.quantile(
1838
+ seg, p, axis=0
1839
+ )
1840
+ else:
1841
+ w = self.weights[rowmask]
1842
+ for p in self.quantile_probs:
1843
+ regions["quantiles"][p].flat[i] = self._weighted_quantile(
1844
+ seg, p, w
1845
+ )
1846
+
1847
+ def _weighted_quantiles_all(self, arr, probabilities, weights):
1848
+ """Compute multiple weighted quantiles with a single sort.
1849
+
1850
+ This is much faster than calling _weighted_quantile for each probability
1851
+ because sorting is O(n log n) and dominates the computation.
1852
+ """
1853
+
1854
+ def weighted_quantiles_1d(a):
1855
+ w = weights
1856
+
1857
+ # Sort once
1858
+ ind = a.argsort()
1859
+ a = a[ind]
1860
+ w = w[ind]
1861
+
1862
+ if self.ignore_missing:
1863
+ missing = numpy.isnan(a) | numpy.isnan(w)
1864
+ if numpy.any(missing):
1865
+ valid = ~missing
1866
+ a = a[valid]
1867
+ w = w[valid]
1868
+
1869
+ N = len(w)
1870
+ if N == 0:
1871
+ return [NaN] * len(probabilities)
1872
+
1873
+ cs = w.cumsum(axis=0)
1874
+ xdiff = numpy.diff(a, append=[0], axis=0)
1875
+
1876
+ # Compute all quantiles from the same sorted data
1877
+ results = []
1878
+ for prob_val in probabilities:
1879
+ prob = prob_val * cs[-1]
1880
+ right = numpy.digitize(prob, cs)
1881
+ left = (right - 1).clip(min=0)
1882
+ with numpy.errstate(divide="ignore", invalid="ignore"):
1883
+ frac = (prob - cs[left]).clip(min=0) / w[right.clip(max=len(w) - 1)]
1884
+ results.append(a[left] + frac * xdiff[left])
1885
+ return results
1886
+
1887
+ if arr.ndim == 1:
1888
+ return weighted_quantiles_1d(arr)
1889
+
1890
+ # For 2D arrays, apply along axis 0
1891
+ n_probs = len(probabilities)
1892
+ result_shape = arr.shape[1:] if arr.ndim > 1 else (1,)
1893
+ results = [numpy.full(result_shape, NaN, dtype=float) for _ in range(n_probs)]
1894
+
1895
+ for col_idx in range(arr.shape[1] if arr.ndim > 1 else 1):
1896
+ col = arr[:, col_idx] if arr.ndim > 1 else arr
1897
+ col_results = weighted_quantiles_1d(col)
1898
+ for i, val in enumerate(col_results):
1899
+ if arr.ndim > 1:
1900
+ results[i][col_idx] = val
1901
+ else:
1902
+ results[i][:] = val
1903
+
1904
+ return results
1905
+
1906
+ def _weighted_quantile(self, arr, probability, weights):
1907
+ """Compute weighted quantile for 1D array."""
1908
+
1909
+ def weighted_quantile_1d(a):
1910
+ w = weights
1911
+
1912
+ ind = a.argsort()
1913
+ a = a[ind]
1914
+ w = w[ind]
1915
+
1916
+ if self.ignore_missing:
1917
+ missing = numpy.isnan(a) | numpy.isnan(w)
1918
+ if numpy.any(missing):
1919
+ valid = ~missing
1920
+ a = a[valid]
1921
+ w = w[valid]
1922
+
1923
+ N = len(w)
1924
+ if N == 0:
1925
+ return NaN
1926
+
1927
+ cs = w.cumsum(axis=0)
1928
+ prob = probability * cs[-1]
1929
+ right = numpy.digitize(prob, cs)
1930
+ left = (right - 1).clip(min=0)
1931
+ xdiff = numpy.diff(a, append=[0], axis=0)
1932
+ with numpy.errstate(divide="ignore", invalid="ignore"):
1933
+ frac = (prob - cs[left]).clip(min=0) / w[right.clip(max=len(w) - 1)]
1934
+ return a[left] + frac * xdiff[left]
1935
+
1936
+ if arr.ndim == 1:
1937
+ return weighted_quantile_1d(arr)
1938
+ return numpy.apply_along_axis(weighted_quantile_1d, 0, arr)
1939
+
1940
+ def reduce(self, cube, regions):
1941
+ """Reduce regions to final output."""
1942
+ result = {}
1943
+
1944
+ valid_count = regions["valid_count"]
1945
+ if not self.ignore_missing:
1946
+ missing_counts = regions["missing_counts"]
1947
+ has_missing = missing_counts != 0
1948
+ else:
1949
+ has_missing = numpy.zeros_like(valid_count, dtype=bool)
1950
+
1951
+ no_data = valid_count == 0
1952
+
1953
+ if self.include_mean:
1954
+ if self.weights is not None:
1955
+ denominator = regions["weighted_counts"]
1956
+ else:
1957
+ denominator = valid_count
1958
+ with numpy.errstate(divide="ignore", invalid="ignore"):
1959
+ mean = regions["sums"] / denominator
1960
+ output_is_missing = no_data | has_missing
1961
+ mean = self.adjust_zeros(mean, self.null, condition=output_is_missing)
1962
+ if isinstance(self.return_missing_as, tuple):
1963
+ result["mean"] = (mean, ~output_is_missing)
1964
+ else:
1965
+ result["mean"] = mean
1966
+
1967
+ if self.include_stddev:
1968
+ stddev = regions["stddev_values"]
1969
+ # stddev needs at least 2 values
1970
+ output_is_missing = (valid_count < 2) | has_missing
1971
+ stddev = self.adjust_zeros(stddev, self.null, condition=output_is_missing)
1972
+ if isinstance(self.return_missing_as, tuple):
1973
+ result["stddev"] = (stddev, ~output_is_missing)
1974
+ else:
1975
+ result["stddev"] = stddev
1976
+
1977
+ if self.include_valid_count:
1978
+ # valid_count is always returned, even for empty bins
1979
+ if isinstance(self.return_missing_as, tuple):
1980
+ result["valid_count"] = (valid_count, ~no_data)
1981
+ else:
1982
+ result["valid_count"] = valid_count
1983
+
1984
+ if self.include_sum:
1985
+ sum_values = regions["sums"]
1986
+ output_is_missing = no_data | has_missing
1987
+ sum_values = self.adjust_zeros(
1988
+ sum_values, self.null, condition=output_is_missing
1989
+ )
1990
+ if isinstance(self.return_missing_as, tuple):
1991
+ result["sum"] = (sum_values, ~output_is_missing)
1992
+ else:
1993
+ result["sum"] = sum_values
1994
+
1995
+ if self.include_quantiles:
1996
+ quantiles_result = {}
1997
+ output_is_missing = no_data | has_missing
1998
+ for p in self.quantile_probs:
1999
+ q = regions["quantiles"][p]
2000
+ if isinstance(self.return_missing_as, tuple):
2001
+ missings = numpy.isnan(q) | output_is_missing
2002
+ q = q.copy()
2003
+ q[missings] = self.null
2004
+ quantiles_result[p] = (q, ~missings)
2005
+ else:
2006
+ quantiles_result[p] = q
2007
+ result["quantiles"] = quantiles_result
2008
+
2009
+ return result
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: catii
3
- Version: 1.0.5.dev1
3
+ Version: 1.0.5.dev2
4
4
  Summary: A library for N-dimensional categorical data.
5
5
  Home-page: https://github.com/Crunch-io/catii
6
6
  Author: Robert Brewer, Slobodan Ilić
File without changes
File without changes
File without changes
File without changes