ialdev-core 0.2.4__tar.gz → 0.2.5__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.
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/PKG-INFO +1 -1
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/pyproject.toml +2 -1
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/src/iad/core/data/pdtools.py +116 -21
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/tests/test_pdtools.py +49 -2
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/.gitignore +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/.pixi/config.toml +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/README.md +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/src/iad/core/__init__.py +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/src/iad/core/cache.py +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/src/iad/core/codetools.py +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/src/iad/core/data/__init__.py +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/src/iad/core/data/array.py +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/src/iad/core/data/binary.py +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/src/iad/core/data/label.py +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/src/iad/core/data/nptools.py +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/src/iad/core/data/unc_panda.py +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/src/iad/core/data/units.py +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/src/iad/core/datatools.py +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/src/iad/core/docs/locators.ipynb +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/src/iad/core/events.py +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/src/iad/core/fnctools.py +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/src/iad/core/fs/__init__.py +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/src/iad/core/fs/env.py +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/src/iad/core/fs/filesproc.py +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/src/iad/core/fs/paths.py +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/src/iad/core/logs.py +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/src/iad/core/one_dark.puml +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/src/iad/core/param/__init__.py +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/src/iad/core/param/confargparse.py +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/src/iad/core/param/paramaze.py +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/src/iad/core/regexp.py +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/src/iad/core/short.py +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/src/iad/core/strings.py +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/src/iad/core/tbox.py +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/src/iad/core/wrap.py +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/tests/array.ipynb +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/tests/conftest.py +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/tests/data/.env +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/tests/dot_styles_examples.ipynb +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/tests/test_array.py +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/tests/test_binar.py +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/tests/test_cachepipe.py +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/tests/test_codetools.py +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/tests/test_coltools.py +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/tests/test_datatools.py +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/tests/test_events.py +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/tests/test_filesproc.py +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/tests/test_nptools.py +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/tests/test_paths.py +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/tests/test_regexp.py +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/tests/test_shorts.py +0 -0
- {ialdev_core-0.2.4 → ialdev_core-0.2.5}/tests/test_tbox.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "flit_core.buildapi"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ialdev-core"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.5"
|
|
8
8
|
description = "iad.core — algorithmic utilities: data tools, parameters, paths, caching, and more"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -63,6 +63,7 @@ name = "iad.core"
|
|
|
63
63
|
|
|
64
64
|
[tool.pytest.ini_options]
|
|
65
65
|
testpaths = ["tests"]
|
|
66
|
+
addopts = "--import-mode=importlib"
|
|
66
67
|
python_files = ["test_*.py"]
|
|
67
68
|
python_classes = ["Test*"]
|
|
68
69
|
python_functions = ["test_*"]
|
|
@@ -4,6 +4,7 @@ import logging
|
|
|
4
4
|
import re
|
|
5
5
|
import warnings
|
|
6
6
|
from collections import namedtuple
|
|
7
|
+
from collections.abc import Mapping
|
|
7
8
|
from dataclasses import dataclass
|
|
8
9
|
from functools import cmp_to_key
|
|
9
10
|
from io import StringIO
|
|
@@ -767,7 +768,7 @@ class TablePainter:
|
|
|
767
768
|
styled.set_caption(self.cap)
|
|
768
769
|
|
|
769
770
|
if self.prec is not None:
|
|
770
|
-
styled.
|
|
771
|
+
styled.format(precision=self.prec)
|
|
771
772
|
|
|
772
773
|
if not self.disp:
|
|
773
774
|
return styled
|
|
@@ -817,8 +818,8 @@ def side_tables(*tables, caps=None, hide_index=None, painter=None, **kws):
|
|
|
817
818
|
index.names = [None] * len(index.names)
|
|
818
819
|
columns = table.columns[[0]]
|
|
819
820
|
t = pd.DataFrame(data=np.arange(len(table.index)), index=index, columns=columns).style
|
|
820
|
-
yield t.
|
|
821
|
-
yield table.
|
|
821
|
+
yield t.hide([*columns], axis="columns"), None
|
|
822
|
+
yield table.hide(axis="index"), cap
|
|
822
823
|
else:
|
|
823
824
|
yield table, cap
|
|
824
825
|
|
|
@@ -1320,6 +1321,24 @@ def trans(s, max_col_width=20):
|
|
|
1320
1321
|
return ss
|
|
1321
1322
|
|
|
1322
1323
|
|
|
1324
|
+
def _format_display_cell(value, fnc):
|
|
1325
|
+
"""Apply a ``TableFormats`` cell formatter, while leaving null cells empty.
|
|
1326
|
+
|
|
1327
|
+
NA detection is guarded to scalars so array-like cells (the very ones we
|
|
1328
|
+
want to compact, e.g. ndarrays) never reach the ambiguous ``pd.isna``
|
|
1329
|
+
truth-value check.
|
|
1330
|
+
"""
|
|
1331
|
+
if value is None:
|
|
1332
|
+
return ""
|
|
1333
|
+
if np.ndim(value) == 0:
|
|
1334
|
+
try:
|
|
1335
|
+
if pd.isna(value):
|
|
1336
|
+
return ""
|
|
1337
|
+
except (TypeError, ValueError):
|
|
1338
|
+
pass
|
|
1339
|
+
return fnc(value)
|
|
1340
|
+
|
|
1341
|
+
|
|
1323
1342
|
def _invert_levels(levels, names):
|
|
1324
1343
|
if isinstance(levels, (str, int)):
|
|
1325
1344
|
levels = [levels]
|
|
@@ -1329,8 +1348,71 @@ def _invert_levels(levels, names):
|
|
|
1329
1348
|
return names.difference(levels)
|
|
1330
1349
|
|
|
1331
1350
|
|
|
1351
|
+
def _reduce(obj: PTable, op: str, *, axis=0, level=None, numeric_only=None):
|
|
1352
|
+
"""Apply a reduction (``'mean'`` | ``'sum'`` | ``'count'``) over an axis, or,
|
|
1353
|
+
if ``level`` is given, by grouping that index level.
|
|
1354
|
+
|
|
1355
|
+
This replaces the ``level=`` argument of the aggregation methods, which was
|
|
1356
|
+
removed in pandas 2.0 in favour of ``groupby(level=...)``.
|
|
1357
|
+
"""
|
|
1358
|
+
num_kws = {} if (numeric_only is None or op == 'count') else dict(numeric_only=numeric_only)
|
|
1359
|
+
if level is not None:
|
|
1360
|
+
# GroupBy methods are the stock pandas implementations, so this also
|
|
1361
|
+
# bypasses any aggregation overrides on the table subclasses.
|
|
1362
|
+
return getattr(obj.groupby(level=level), op)(**num_kws)
|
|
1363
|
+
# Call the unbound pandas method explicitly to bypass subclass overrides
|
|
1364
|
+
# (e.g. _TableMixIn.mean), matching the pre-pandas-2 behaviour.
|
|
1365
|
+
base = pd.Series if isinstance(obj, pd.Series) else pd.DataFrame
|
|
1366
|
+
if base is pd.Series:
|
|
1367
|
+
return getattr(base, op)(obj, **num_kws)
|
|
1368
|
+
return getattr(base, op)(obj, axis=axis, **num_kws)
|
|
1369
|
+
|
|
1370
|
+
|
|
1332
1371
|
class _TableMixIn:
|
|
1333
1372
|
|
|
1373
|
+
def _display_frame(self) -> pd.DataFrame:
|
|
1374
|
+
"""Build a plain ``pd.DataFrame`` copy for marimo's interactive table.
|
|
1375
|
+
|
|
1376
|
+
Object columns (which may hold large ndarrays, dicts, paths, ...) are
|
|
1377
|
+
replaced by their compact ``TableFormats`` string representation, the
|
|
1378
|
+
same one used by ``_repr_html_``. Numeric / bool / datetime / string
|
|
1379
|
+
columns are left native so marimo keeps real sorting and filtering.
|
|
1380
|
+
|
|
1381
|
+
A plain ``pd.DataFrame`` (not a ``DataTable``) is returned on purpose:
|
|
1382
|
+
it has no ``_display_`` of its own, so marimo applies its built-in
|
|
1383
|
+
opinionated formatter and renders an interactive, sortable, filterable
|
|
1384
|
+
table whose cells are already short.
|
|
1385
|
+
"""
|
|
1386
|
+
src = self if self.ndim == 2 else self._prep_repr()
|
|
1387
|
+
plain = pd.DataFrame(src)
|
|
1388
|
+
formatters = TableFormats.formatters(plain)
|
|
1389
|
+
for i, fnc in enumerate(formatters):
|
|
1390
|
+
if fnc is None or plain.dtypes.iloc[i] != object:
|
|
1391
|
+
continue
|
|
1392
|
+
col = plain.iloc[:, i]
|
|
1393
|
+
plain.isetitem(i, col.map(lambda v, _f=fnc: _format_display_cell(v, _f)))
|
|
1394
|
+
return plain
|
|
1395
|
+
|
|
1396
|
+
def _display_(self):
|
|
1397
|
+
"""marimo display protocol.
|
|
1398
|
+
|
|
1399
|
+
Has the highest precedence in marimo's formatter resolution, so it
|
|
1400
|
+
overrides marimo's built-in opinionated formatter for ``pd.DataFrame``
|
|
1401
|
+
/ ``pd.Series`` (which would otherwise dump full ndarray contents for
|
|
1402
|
+
the ``data`` column and similar).
|
|
1403
|
+
|
|
1404
|
+
We return a plain ``pd.DataFrame`` with object columns pre-rendered to
|
|
1405
|
+
compact strings, so marimo still shows its interactive table (sorting,
|
|
1406
|
+
filtering, pagination) but without dumping large arrays. On any failure
|
|
1407
|
+
we fall back to the static rich HTML used by Jupyter.
|
|
1408
|
+
"""
|
|
1409
|
+
try:
|
|
1410
|
+
return self._display_frame()
|
|
1411
|
+
except Exception:
|
|
1412
|
+
import marimo as mo
|
|
1413
|
+
html = self._repr_html_()
|
|
1414
|
+
return mo.plain(self) if html is None else mo.Html(html)
|
|
1415
|
+
|
|
1334
1416
|
def as_labels(self, index=True, data=False, squeeze=False) -> Labels | list[Labels]:
|
|
1335
1417
|
"""
|
|
1336
1418
|
Convert into list of datacast.labels.Labels objects.
|
|
@@ -1436,7 +1518,7 @@ class _TableMixIn:
|
|
|
1436
1518
|
if strict:
|
|
1437
1519
|
missing = set(levels).difference(index_names)
|
|
1438
1520
|
if missing:
|
|
1439
|
-
tbl = tbl.stack([*missing]) # will raise her if not found in columns
|
|
1521
|
+
tbl = tbl.stack([*missing], future_stack=True) # will raise her if not found in columns
|
|
1440
1522
|
index_names.update(missing)
|
|
1441
1523
|
|
|
1442
1524
|
tbl = tbl.unstack([*index_names.difference(levels)], **kws)
|
|
@@ -1469,7 +1551,13 @@ class _TableMixIn:
|
|
|
1469
1551
|
raise KeyError(f"Levels {missing} not found in index {tbl.index.names}")
|
|
1470
1552
|
col_names.update(missing)
|
|
1471
1553
|
|
|
1472
|
-
|
|
1554
|
+
# pandas 2: the new stack (future_stack=True) no longer accepts/handles
|
|
1555
|
+
# `dropna`/`sort` and never drops NaN rows, so emulate the old default.
|
|
1556
|
+
dropna = kws.pop('dropna', True)
|
|
1557
|
+
kws.pop('sort', None)
|
|
1558
|
+
tbl = tbl.stack([*col_names.difference(levels)], future_stack=True, **kws)
|
|
1559
|
+
if dropna:
|
|
1560
|
+
tbl = tbl.dropna(how='all') if tbl.ndim > 1 else tbl.dropna()
|
|
1473
1561
|
|
|
1474
1562
|
if tbl.ndim > 1 and tbl.columns.nlevels > 1: # if there are col levels
|
|
1475
1563
|
levels = [lvl for lvl in levels if lvl in col_names]
|
|
@@ -1730,8 +1818,6 @@ class _TableMixIn:
|
|
|
1730
1818
|
Tutorial:
|
|
1731
1819
|
Using this DataTable:
|
|
1732
1820
|
|
|
1733
|
-
>>> from iad.core.tests.test_pdtools import sdt_general
|
|
1734
|
-
>>> dt= sdt_general()
|
|
1735
1821
|
>>> dt # doctest: +NORMALIZE_WHITESPACE
|
|
1736
1822
|
z data1 data2
|
|
1737
1823
|
w a b c d a b c d
|
|
@@ -1896,15 +1982,13 @@ class _TableMixIn:
|
|
|
1896
1982
|
Include only float, int, boolean columns. If None, will attempt to use
|
|
1897
1983
|
everything, then use only numeric data. Not implemented for Series.
|
|
1898
1984
|
"""
|
|
1899
|
-
|
|
1900
|
-
kws = dict(axis=axis, level=level, numeric_only=numeric_only)
|
|
1901
|
-
mean = (pd.Series if series else pd.DataFrame).mean(self.nominal_value, **kws)
|
|
1985
|
+
mean = _reduce(self.nominal_value, 'mean', axis=axis, level=level, numeric_only=numeric_only)
|
|
1902
1986
|
if not (hasattr(mean, 'index') and hasattr(self, 'nominal_value')) or self.std_dev.sum().sum() == 0:
|
|
1903
1987
|
return mean
|
|
1904
1988
|
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1989
|
+
num = _reduce(self.std_dev, 'count', axis=axis, level=level)
|
|
1990
|
+
sd = (_reduce(self.std_dev ** 2, 'sum', axis=axis, level=level,
|
|
1991
|
+
numeric_only=numeric_only) / num) ** 0.5
|
|
1908
1992
|
from uncertainties.unumpy import uarray
|
|
1909
1993
|
return mean.__class__(uarray(mean, sd), index=mean.index,
|
|
1910
1994
|
**({} if mean.ndim == 1 else {'columns': mean.columns}))
|
|
@@ -1930,11 +2014,14 @@ class _TableMixIn:
|
|
|
1930
2014
|
self = self.unstack_but(ws.index.names) # align indices
|
|
1931
2015
|
assert ws.shape[0] == self.shape[0], f"{ws.shape=} vs {self.shape=}"
|
|
1932
2016
|
|
|
1933
|
-
|
|
1934
|
-
|
|
2017
|
+
if level is None:
|
|
2018
|
+
ws = ws / ws.sum()
|
|
2019
|
+
else:
|
|
2020
|
+
ws = ws / ws.groupby(level=level).transform('sum')
|
|
2021
|
+
avr = _reduce(self.multiply(ws, 0), 'sum', level=level).unstack_but(level)
|
|
1935
2022
|
if unc:
|
|
1936
2023
|
from uncertainties.unumpy import uarray
|
|
1937
|
-
sd = ((self.sub(avr) ** 2).multiply(ws, 0)
|
|
2024
|
+
sd = (_reduce((self.sub(avr) ** 2).multiply(ws, 0), 'sum', level=level)) ** 0.5
|
|
1938
2025
|
if unc is True:
|
|
1939
2026
|
data = uarray(avr.values, sd.values)
|
|
1940
2027
|
return DataSeries(data, index=avr.index) if avr.ndim == 1 else \
|
|
@@ -1948,11 +2035,17 @@ class _TableMixIn:
|
|
|
1948
2035
|
index_names = self.index.names
|
|
1949
2036
|
return pd.concat([self.reset_index(), other.reset_index()]).set_index(list(index_names))
|
|
1950
2037
|
|
|
1951
|
-
def __or__(self: DTable, other
|
|
2038
|
+
def __or__(self: DTable, other):
|
|
2039
|
+
# Only the dict form adds index levels; delegate everything else
|
|
2040
|
+
# (e.g. boolean masks ``mask_a | mask_b``) to the pandas operator.
|
|
2041
|
+
if not isinstance(other, Mapping):
|
|
2042
|
+
return super().__or__(other)
|
|
1952
2043
|
(out := self.copy()).__ior__(other)
|
|
1953
2044
|
return out
|
|
1954
2045
|
|
|
1955
|
-
def __ior__(self: DTable, other
|
|
2046
|
+
def __ior__(self: DTable, other):
|
|
2047
|
+
if not isinstance(other, Mapping):
|
|
2048
|
+
return super().__ior__(other)
|
|
1956
2049
|
new_levels = list(other)
|
|
1957
2050
|
self[new_levels] = [*other.values()]
|
|
1958
2051
|
self.set_index(new_levels, append=True, inplace=True)
|
|
@@ -2021,7 +2114,8 @@ def path_fixer(root, fixer_name, cols='path') -> Callable[[pd.DataFrame], pd.Dat
|
|
|
2021
2114
|
|
|
2022
2115
|
def fix_path(df):
|
|
2023
2116
|
if columns := [c for c in df.columns if c in cols]:
|
|
2024
|
-
df
|
|
2117
|
+
df = df.copy()
|
|
2118
|
+
df[columns] = df[columns].map(fixer(root))
|
|
2025
2119
|
return df
|
|
2026
2120
|
|
|
2027
2121
|
return fix_path
|
|
@@ -2262,8 +2356,9 @@ def append_col(df: pd.DataFrame,
|
|
|
2262
2356
|
by cols and the added into `df`
|
|
2263
2357
|
:param levels: optionally provide names for the levels,
|
|
2264
2358
|
must be same number of them as the final levels
|
|
2265
|
-
:return:
|
|
2359
|
+
:return: a new data frame with the column(s) added (the input is not modified)
|
|
2266
2360
|
"""
|
|
2361
|
+
df = df.copy()
|
|
2267
2362
|
|
|
2268
2363
|
def add_levels(n):
|
|
2269
2364
|
df.columns = pd.MultiIndex.from_product([df.columns, *[*[['']] * n]])
|
|
@@ -2516,7 +2611,7 @@ def filter_full_groups(df, group) -> (pd.DataFrame, int):
|
|
|
2516
2611
|
group = as_list(group)
|
|
2517
2612
|
mv_idx = df.index.names.difference(group)
|
|
2518
2613
|
df = df.reset_index(mv_idx) # leave only 'scene' in the index
|
|
2519
|
-
full_groups = df.groupby(group).apply(lambda x: issubset_report(x.kind, ))
|
|
2614
|
+
full_groups = df.groupby(group).apply(lambda x: issubset_report(x.kind, ), include_groups=False)
|
|
2520
2615
|
df = df.loc[full_groups[full_groups].index]
|
|
2521
2616
|
bad_scenes = (full_groups == False).sum()
|
|
2522
2617
|
groups_num = len(full_groups) - bad_scenes
|
|
@@ -373,11 +373,58 @@ def test_append_col():
|
|
|
373
373
|
[4, 5, 6],
|
|
374
374
|
[7, 8, 9]], index=pd.MultiIndex.from_tuples([(1, 1), (1, 2), (1, 3)]))
|
|
375
375
|
dt.columns = pd.MultiIndex.from_tuples([(1, 1), (1, 2), (1, 3)])
|
|
376
|
-
append_col(dt, col='4', values=[10, 11, 12])
|
|
377
|
-
append_col(dt, col='4', values=[3, 1, 11])
|
|
376
|
+
dt = append_col(dt, col='4', values=[10, 11, 12])
|
|
377
|
+
dt = append_col(dt, col='4', values=[3, 1, 11])
|
|
378
378
|
assert int(dt.loc[(1, 3), '4']) == 11
|
|
379
379
|
|
|
380
380
|
|
|
381
|
+
def test_append_col_does_not_mutate_input():
|
|
382
|
+
"""append_col must return a new frame and leave the input untouched (pandas 2 / CoW)."""
|
|
383
|
+
base = DT(data=[[1], [2]], columns=['a'])
|
|
384
|
+
out = pdt.append_col(base, col='b', values=[10, 20])
|
|
385
|
+
assert 'b' not in base.columns # input untouched
|
|
386
|
+
assert out['b'].tolist() == [10, 20] # result carries the new column
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def test_reduce_level_aggregation():
|
|
390
|
+
"""`_reduce` replaces the removed pandas-1 `level=` aggregation argument."""
|
|
391
|
+
idx = pd.MultiIndex.from_product([['a', 'b'], [1, 2]], names=['g', 'i'])
|
|
392
|
+
s = DS([1.0, 3.0, 10.0, 20.0], index=idx)
|
|
393
|
+
df = DT({'v': [1.0, 3.0, 10.0, 20.0], 'w': [2.0, 4.0, 6.0, 8.0]}, index=idx)
|
|
394
|
+
|
|
395
|
+
# by level -> grouped result indexed by that level
|
|
396
|
+
assert pdt._reduce(s, 'mean', level='g').to_dict() == {'a': 2.0, 'b': 15.0}
|
|
397
|
+
assert pdt._reduce(s, 'sum', level='g').to_dict() == {'a': 4.0, 'b': 30.0}
|
|
398
|
+
assert pdt._reduce(s, 'count', level='g').to_dict() == {'a': 2, 'b': 2}
|
|
399
|
+
assert pdt._reduce(df, 'sum', level='g').loc['a'].to_dict() == {'v': 4.0, 'w': 6.0}
|
|
400
|
+
|
|
401
|
+
# no level -> plain reduction
|
|
402
|
+
assert pdt._reduce(s, 'sum') == 34.0
|
|
403
|
+
assert pdt._reduce(df, 'mean')['v'] == 8.5
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def test_bitwise_or_is_elementwise_for_masks():
|
|
407
|
+
"""The custom __or__ must not hijack boolean-mask combination (`mask | mask`)."""
|
|
408
|
+
idx = pd.MultiIndex.from_product([['a', 'b'], [1, 2]], names=['g', 'i'])
|
|
409
|
+
s = DS([1.0, np.nan, np.inf, -np.inf], index=idx)
|
|
410
|
+
assert (s.isnull() | np.isinf(s)).tolist() == [False, True, True, True]
|
|
411
|
+
|
|
412
|
+
df = DT({'x': [1.0, np.nan], 'y': [np.inf, 2.0]})
|
|
413
|
+
assert (df.isnull() | ~np.isfinite(df)).values.tolist() == [[False, True], [True, False]]
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def test_or_dict_still_adds_index_level():
|
|
417
|
+
"""The dict form of `|` keeps its index-level-appending behaviour."""
|
|
418
|
+
dt = DT({'v': [1, 2]})
|
|
419
|
+
out = dt | dict(mode='X', fid='001')
|
|
420
|
+
assert 'mode' in out.index.names and 'fid' in out.index.names
|
|
421
|
+
assert out.index.get_level_values('mode').tolist() == ['X', 'X']
|
|
422
|
+
|
|
423
|
+
dt2 = DT({'v': [1, 2]})
|
|
424
|
+
dt2 |= dict(mode='Y')
|
|
425
|
+
assert dt2.index.get_level_values('mode').tolist() == ['Y', 'Y']
|
|
426
|
+
|
|
427
|
+
|
|
381
428
|
def test_sample(sdt):
|
|
382
429
|
sample = pdt.sample
|
|
383
430
|
assert len(sample(sdt, 2)) == 2
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|