nested-pandas 0.3.2__tar.gz → 0.3.3__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.
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/.copier-answers.yml +2 -1
- nested_pandas-0.3.3/.github/ISSUE_TEMPLATE/README.md +9 -0
- nested_pandas-0.3.3/.github/workflows/README.md +9 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/.github/workflows/asv-main.yml +1 -1
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/.github/workflows/asv-nightly.yml +1 -1
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/.github/workflows/asv-pr.yml +1 -1
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/.github/workflows/build-documentation.yml +2 -2
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/.github/workflows/pre-commit-ci.yml +2 -2
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/.github/workflows/publish-benchmarks-pr.yml +1 -1
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/.github/workflows/publish-to-pypi.yml +1 -1
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/.github/workflows/smoke-test.yml +1 -1
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/.github/workflows/testing-and-coverage.yml +2 -2
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/.pre-commit-config.yaml +1 -1
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/.readthedocs.yml +1 -1
- {nested_pandas-0.3.2/src/nested_pandas.egg-info → nested_pandas-0.3.3}/PKG-INFO +1 -1
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/benchmarks/asv.conf.json +1 -1
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/docs/index.rst +5 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/src/nested_pandas/_version.py +2 -2
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/src/nested_pandas/nestedframe/core.py +117 -43
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/src/nested_pandas/series/packer.py +28 -4
- {nested_pandas-0.3.2 → nested_pandas-0.3.3/src/nested_pandas.egg-info}/PKG-INFO +1 -1
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/src/nested_pandas.egg-info/SOURCES.txt +2 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/tests/nested_pandas/nestedframe/test_nestedframe.py +77 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/tests/nested_pandas/series/test_packer.py +36 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/.git_archival.txt +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/.gitattributes +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/.github/ISSUE_TEMPLATE/0-general_issue.md +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/.github/ISSUE_TEMPLATE/1-bug_report.md +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/.github/ISSUE_TEMPLATE/2-feature_request.md +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/.github/dependabot.yml +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/.github/pull_request_template.md +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/.gitignore +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/.setup_dev.sh +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/LICENSE +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/README.md +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/benchmarks/README.md +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/benchmarks/__init__.py +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/benchmarks/benchmarks.py +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/docs/Makefile +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/docs/_static/custom.css +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/docs/conf.py +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/docs/gettingstarted/contributing.rst +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/docs/gettingstarted/installation.rst +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/docs/gettingstarted/quickstart.ipynb +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/docs/gettingstarted.rst +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/docs/requirements.txt +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/docs/tutorials/README.md +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/docs/tutorials/data_loading_notebook.ipynb +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/docs/tutorials/data_manipulation.ipynb +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/docs/tutorials/low_level.ipynb +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/docs/tutorials/nested_spectra.ipynb +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/docs/tutorials.rst +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/nestedframe.png +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/pyproject.toml +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/setup.cfg +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/src/nested_pandas/__init__.py +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/src/nested_pandas/datasets/__init__.py +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/src/nested_pandas/datasets/generation.py +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/src/nested_pandas/nestedframe/__init__.py +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/src/nested_pandas/nestedframe/io.py +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/src/nested_pandas/py.typed +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/src/nested_pandas/series/__init__.py +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/src/nested_pandas/series/accessor.py +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/src/nested_pandas/series/dtype.py +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/src/nested_pandas/series/ext_array.py +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/src/nested_pandas/series/utils.py +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/src/nested_pandas/utils/__init__.py +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/src/nested_pandas/utils/utils.py +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/src/nested_pandas.egg-info/dependency_links.txt +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/src/nested_pandas.egg-info/requires.txt +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/src/nested_pandas.egg-info/top_level.txt +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/tests/nested_pandas/conftest.py +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/tests/nested_pandas/datasets/test_generation.py +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/tests/nested_pandas/e2e_tests/test_issue89.py +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/tests/nested_pandas/nestedframe/test_io.py +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/tests/nested_pandas/series/test_accessor.py +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/tests/nested_pandas/series/test_dtype.py +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/tests/nested_pandas/series/test_ext_array.py +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/tests/nested_pandas/test_packaging.py +0 -0
- {nested_pandas-0.3.2 → nested_pandas-0.3.3}/tests/nested_pandas/utils/test_utils.py +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Changes here will be overwritten by Copier
|
|
2
|
-
_commit: v2.0.
|
|
2
|
+
_commit: v2.0.5
|
|
3
3
|
_src_path: gh:lincc-frameworks/python-project-template
|
|
4
4
|
author_email: brantd@uw.edu
|
|
5
5
|
author_name: LINCC Frameworks
|
|
@@ -22,3 +22,4 @@ python_versions:
|
|
|
22
22
|
- '3.10'
|
|
23
23
|
- '3.11'
|
|
24
24
|
- '3.12'
|
|
25
|
+
- '3.13'
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Configurations
|
|
2
|
+
|
|
3
|
+
Templates for various different issue types are defined in this directory
|
|
4
|
+
and a pull request template is defined as ``../pull_request_template.md``. Adding,
|
|
5
|
+
removing, and modifying these templates to suit the needs of your project is encouraged.
|
|
6
|
+
|
|
7
|
+
For more information about these templates, look here: https://lincc-ppt.readthedocs.io/en/latest/practices/issue_pr_templating.html
|
|
8
|
+
|
|
9
|
+
Or if you still have questions contact us: https://lincc-ppt.readthedocs.io/en/latest/source/contact.html
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Workflows
|
|
2
|
+
|
|
3
|
+
The .yml files in this directory are used to define the various continuous
|
|
4
|
+
integration scripts that will be run on your behalf e.g. nightly as a smoke check,
|
|
5
|
+
or when you create a new PR.
|
|
6
|
+
|
|
7
|
+
For more information about CI and workflows, look here: https://lincc-ppt.readthedocs.io/en/latest/practices/ci.html
|
|
8
|
+
|
|
9
|
+
Or if you still have questions contact us: https://lincc-ppt.readthedocs.io/en/latest/source/contact.html
|
|
@@ -21,10 +21,10 @@ jobs:
|
|
|
21
21
|
|
|
22
22
|
steps:
|
|
23
23
|
- uses: actions/checkout@v4
|
|
24
|
-
- name: Set up Python 3.
|
|
24
|
+
- name: Set up Python 3.11
|
|
25
25
|
uses: actions/setup-python@v5
|
|
26
26
|
with:
|
|
27
|
-
python-version: '3.
|
|
27
|
+
python-version: '3.11'
|
|
28
28
|
- name: Install dependencies
|
|
29
29
|
run: |
|
|
30
30
|
sudo apt-get update
|
|
@@ -20,7 +20,7 @@ jobs:
|
|
|
20
20
|
- name: Set up Python
|
|
21
21
|
uses: actions/setup-python@v5
|
|
22
22
|
with:
|
|
23
|
-
python-version: '3.
|
|
23
|
+
python-version: '3.11'
|
|
24
24
|
- name: Install dependencies
|
|
25
25
|
run: |
|
|
26
26
|
sudo apt-get update
|
|
@@ -32,5 +32,5 @@ jobs:
|
|
|
32
32
|
extra_args: --all-files --verbose
|
|
33
33
|
env:
|
|
34
34
|
SKIP: "check-lincc-frameworks-template-version,no-commit-to-branch,check-added-large-files,validate-pyproject,sphinx-build,pytest-check"
|
|
35
|
-
- uses: pre-commit-ci/lite-action@v1.0
|
|
35
|
+
- uses: pre-commit-ci/lite-action@v1.1.0
|
|
36
36
|
if: failure() && github.event_name == 'pull_request' && github.event.pull_request.draft == false
|
|
@@ -27,7 +27,7 @@ jobs:
|
|
|
27
27
|
echo "Conclusion: ${{ github.event.workflow_run.conclusion }}"
|
|
28
28
|
echo "Event: ${{ github.event.workflow_run.event }}"
|
|
29
29
|
- name: Download artifact
|
|
30
|
-
uses: dawidd6/action-download-artifact@
|
|
30
|
+
uses: dawidd6/action-download-artifact@v7
|
|
31
31
|
with:
|
|
32
32
|
name: benchmark-artifacts
|
|
33
33
|
run_id: ${{ github.event.workflow_run.id }}
|
|
@@ -15,7 +15,7 @@ jobs:
|
|
|
15
15
|
runs-on: ubuntu-latest
|
|
16
16
|
strategy:
|
|
17
17
|
matrix:
|
|
18
|
-
python-version: ['3.9', '3.10', '3.11', '3.12']
|
|
18
|
+
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
|
|
19
19
|
|
|
20
20
|
steps:
|
|
21
21
|
- uses: actions/checkout@v4
|
|
@@ -33,6 +33,6 @@ jobs:
|
|
|
33
33
|
run: |
|
|
34
34
|
python -m pytest --cov=nested_pandas --cov-report=xml
|
|
35
35
|
- name: Upload coverage report to codecov
|
|
36
|
-
uses: codecov/codecov-action@
|
|
36
|
+
uses: codecov/codecov-action@v5
|
|
37
37
|
with:
|
|
38
38
|
token: ${{ secrets.CODECOV_TOKEN }}
|
|
@@ -18,7 +18,7 @@ repos:
|
|
|
18
18
|
description: Clear output from Jupyter notebooks.
|
|
19
19
|
files: \.ipynb$
|
|
20
20
|
exclude: ^docs/pre_executed
|
|
21
|
-
stages: [commit]
|
|
21
|
+
stages: [pre-commit]
|
|
22
22
|
language: system
|
|
23
23
|
entry: jupyter nbconvert --clear-output
|
|
24
24
|
# Prevents committing directly branches named 'main' and 'master'.
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
// The Pythons you'd like to test against. If not provided, defaults
|
|
38
38
|
// to the current version of Python used to run `asv`.
|
|
39
39
|
"pythons": [
|
|
40
|
-
"3.
|
|
40
|
+
"3.11"
|
|
41
41
|
],
|
|
42
42
|
// The matrix of dependencies to test. Each key is the name of a
|
|
43
43
|
// package (in PyPI) and the values are version numbers. An empty
|
|
@@ -19,10 +19,15 @@ is used internally to aid in scalability and performance.
|
|
|
19
19
|
:align: center
|
|
20
20
|
:alt: Example NestedFrame
|
|
21
21
|
|
|
22
|
+
<<<<<<< before updating
|
|
22
23
|
Nested-Pandas is motivated by time-domain astronomy use cases, where we see
|
|
23
24
|
typically two levels of information, information about astronomical objects and
|
|
24
25
|
then an associated set of `N` measurements of those objects. Nested-Pandas offers
|
|
25
26
|
a performant and memory-efficient package for working with these types of datasets.
|
|
27
|
+
=======
|
|
28
|
+
>> conda create env -n <env_name> python=3.11
|
|
29
|
+
>> conda activate <env_name>
|
|
30
|
+
>>>>>>> after updating
|
|
26
31
|
|
|
27
32
|
Core advantages being:
|
|
28
33
|
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
|
|
4
4
|
import ast
|
|
5
5
|
import os
|
|
6
|
+
import re
|
|
6
7
|
|
|
7
8
|
import numpy as np
|
|
8
9
|
import pandas as pd
|
|
@@ -18,6 +19,10 @@ from pandas.core.computation.parsing import clean_column_name
|
|
|
18
19
|
from nested_pandas.series.dtype import NestedDtype
|
|
19
20
|
from nested_pandas.series.packer import pack, pack_lists, pack_sorted_df_into_struct
|
|
20
21
|
|
|
22
|
+
# Used to identify backtick-protected names in the expressions
|
|
23
|
+
# used in NestedFrame.eval() and NestedFrame.query().
|
|
24
|
+
_backtick_protected_names = re.compile(r"`[^`]+`", re.MULTILINE)
|
|
25
|
+
|
|
21
26
|
|
|
22
27
|
class NestedPandasExprVisitor(PandasExprVisitor):
|
|
23
28
|
"""
|
|
@@ -83,9 +88,9 @@ class _NestResolver(dict):
|
|
|
83
88
|
super().__init__()
|
|
84
89
|
# Pre-load the field resolvers for all columns which are known at present.
|
|
85
90
|
for column in outer.nested_columns:
|
|
86
|
-
self.
|
|
91
|
+
self._initialize_column_resolver(column, outer)
|
|
87
92
|
|
|
88
|
-
def
|
|
93
|
+
def _initialize_column_resolver(self, column: str, outer: NestedFrame):
|
|
89
94
|
"""
|
|
90
95
|
Initialize a resolver for the given nested column, and also an alias
|
|
91
96
|
for it, in the case of column names that have spaces or are otherwise
|
|
@@ -107,7 +112,7 @@ class _NestResolver(dict):
|
|
|
107
112
|
if not super().__contains__(top_nest):
|
|
108
113
|
if top_nest not in self._outer.nested_columns:
|
|
109
114
|
raise KeyError(f"Unknown nest {top_nest}")
|
|
110
|
-
self.
|
|
115
|
+
self._initialize_column_resolver(top_nest, self._outer)
|
|
111
116
|
return super().__getitem__(top_nest)
|
|
112
117
|
|
|
113
118
|
def __setitem__(self, item, _):
|
|
@@ -139,8 +144,20 @@ class _NestedFieldResolver:
|
|
|
139
144
|
# Flattened only once for every access of this particular nest
|
|
140
145
|
# within the expression.
|
|
141
146
|
self._flat_nest = outer[nest_name].nest.to_flat()
|
|
147
|
+
# Save aliases to any columns that are not identifier-like.
|
|
148
|
+
# If our given frame has aliases for identifiers, use these instead
|
|
149
|
+
# of generating our own.
|
|
150
|
+
self._aliases = getattr(outer, "_aliases", None)
|
|
151
|
+
if self._aliases is None:
|
|
152
|
+
self._aliases = {}
|
|
153
|
+
for column in self._flat_nest.columns:
|
|
154
|
+
clean_id = clean_column_name(column)
|
|
155
|
+
if clean_id != column:
|
|
156
|
+
self._aliases[clean_id] = column
|
|
142
157
|
|
|
143
158
|
def __getattr__(self, item_name: str):
|
|
159
|
+
if self._aliases:
|
|
160
|
+
item_name = self._aliases.get(item_name, item_name)
|
|
144
161
|
if item_name in self._flat_nest:
|
|
145
162
|
result = _SeriesFromNest(self._flat_nest[item_name])
|
|
146
163
|
# Assigning these properties directly in order to avoid any complication
|
|
@@ -193,6 +210,25 @@ def _subexprs_by_nest(parents: list, node) -> dict[str, list]:
|
|
|
193
210
|
return result
|
|
194
211
|
|
|
195
212
|
|
|
213
|
+
def _identify_aliases(expr: str) -> tuple[str, dict[str, str]]:
|
|
214
|
+
"""
|
|
215
|
+
Given an expression string, identify backtick-quoted names
|
|
216
|
+
and replace them with cleaned names, returning the cleaned
|
|
217
|
+
expression and a dictionary of aliases, where the keys are
|
|
218
|
+
clean aliases to the original names.
|
|
219
|
+
"""
|
|
220
|
+
aliases = {}
|
|
221
|
+
|
|
222
|
+
def sub_and_alias(match):
|
|
223
|
+
original = match.group(0)[1:-1] # remove backticks
|
|
224
|
+
alias = clean_column_name(original)
|
|
225
|
+
if alias != original:
|
|
226
|
+
aliases[alias] = original
|
|
227
|
+
return alias
|
|
228
|
+
|
|
229
|
+
return _backtick_protected_names.sub(sub_and_alias, expr), aliases
|
|
230
|
+
|
|
231
|
+
|
|
196
232
|
class NestedFrame(pd.DataFrame):
|
|
197
233
|
"""A Pandas Dataframe extension with support for nested structure.
|
|
198
234
|
|
|
@@ -205,6 +241,12 @@ class NestedFrame(pd.DataFrame):
|
|
|
205
241
|
# Series produce instances of this class, preserving the type and origin.
|
|
206
242
|
__pandas_priority__ = 4500
|
|
207
243
|
|
|
244
|
+
# The "_aliases" attribute is usually None or not even present, but when it is present,
|
|
245
|
+
# it indicates that an evaluation is in progress, and that columns and fields with names
|
|
246
|
+
# that are not identifier-like have been aliases to cleaned names, and this attribute
|
|
247
|
+
# contains those aliases, keyed by the cleaned name.
|
|
248
|
+
_metadata = ["_aliases"]
|
|
249
|
+
|
|
208
250
|
@property
|
|
209
251
|
def _constructor(self) -> Self: # type: ignore[name-defined] # noqa: F821
|
|
210
252
|
return NestedFrame
|
|
@@ -232,62 +274,86 @@ class NestedFrame(pd.DataFrame):
|
|
|
232
274
|
nest_cols.append(column)
|
|
233
275
|
return nest_cols
|
|
234
276
|
|
|
235
|
-
def
|
|
277
|
+
def _parse_hierarchical_components(self, delimited_path: str, delimiter: str = ".") -> list[str]:
|
|
278
|
+
"""
|
|
279
|
+
Given a string that may be a delimited path, parse it into its components,
|
|
280
|
+
respecting backticks that are used to protect component names that may contain the delimiter.
|
|
281
|
+
"""
|
|
282
|
+
aliases = getattr(self, "_aliases", None)
|
|
283
|
+
if aliases is None:
|
|
284
|
+
delimited_path, aliases = _identify_aliases(delimited_path)
|
|
285
|
+
return [aliases.get(x, x) for x in delimited_path.split(delimiter)]
|
|
286
|
+
|
|
287
|
+
def _is_known_hierarchical_column(self, components: list[str] | str) -> bool:
|
|
236
288
|
"""Determine whether a string is a known hierarchical column name"""
|
|
237
|
-
if
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
# TODO: only handles one level of nesting for now
|
|
241
|
-
nested_name = ".".join(colname.split(".")[1:])
|
|
242
|
-
return nested_name in self.all_columns[base_name]
|
|
289
|
+
if isinstance(components, str):
|
|
290
|
+
components = self._parse_hierarchical_components(components)
|
|
291
|
+
if len(components) < 2:
|
|
243
292
|
return False
|
|
293
|
+
base_name = components[0]
|
|
294
|
+
if base_name in self.nested_columns:
|
|
295
|
+
nested_name = ".".join(components[1:])
|
|
296
|
+
return nested_name in self.all_columns[base_name]
|
|
244
297
|
return False
|
|
245
298
|
|
|
246
|
-
def _is_known_column(self,
|
|
247
|
-
"""Determine whether a
|
|
248
|
-
|
|
299
|
+
def _is_known_column(self, components: list[str] | str) -> bool:
|
|
300
|
+
"""Determine whether a list of field components describes a known column name"""
|
|
301
|
+
if isinstance(components, str):
|
|
302
|
+
components = self._parse_hierarchical_components(components)
|
|
303
|
+
if ".".join(components) in self.columns:
|
|
304
|
+
return True
|
|
305
|
+
return self._is_known_hierarchical_column(components)
|
|
249
306
|
|
|
250
307
|
def __getitem__(self, item):
|
|
251
308
|
"""Adds custom __getitem__ behavior for nested columns"""
|
|
252
309
|
|
|
253
|
-
if isinstance(item, str):
|
|
254
|
-
# Preempt the nested check if the item is a base column
|
|
255
|
-
if item in self.columns:
|
|
256
|
-
return super().__getitem__(item)
|
|
257
|
-
# If a nested column name is passed, return a flat series for that column
|
|
258
|
-
# flat series is chosen over list series for utility
|
|
259
|
-
# e.g. native ability to do something like ndf["nested.a"] + 3
|
|
260
|
-
elif self._is_known_hierarchical_column(item):
|
|
261
|
-
# TODO: only handles one level of nesting for now
|
|
262
|
-
nested = item.split(".")[0]
|
|
263
|
-
col = ".".join(item.split(".")[1:])
|
|
264
|
-
return self[nested].nest.get_flat_series(col)
|
|
265
|
-
else:
|
|
266
|
-
raise KeyError(f"Column '{item}' not found in nested columns or base columns")
|
|
267
|
-
else:
|
|
310
|
+
if not isinstance(item, str):
|
|
268
311
|
return super().__getitem__(item)
|
|
269
312
|
|
|
313
|
+
# Preempt the nested check if the item is a base column, with or without
|
|
314
|
+
# dots and backticks.
|
|
315
|
+
if item in self.columns:
|
|
316
|
+
return super().__getitem__(item)
|
|
317
|
+
components = self._parse_hierarchical_components(item)
|
|
318
|
+
# One more check on the entirety of the item name, in case backticks were used
|
|
319
|
+
# (even if they weren't necessary).
|
|
320
|
+
cleaned_item = ".".join(components)
|
|
321
|
+
if cleaned_item in self.columns:
|
|
322
|
+
return super().__getitem__(cleaned_item)
|
|
323
|
+
|
|
324
|
+
# If a nested column name is passed, return a flat series for that column
|
|
325
|
+
# flat series is chosen over list series for utility
|
|
326
|
+
# e.g. native ability to do something like ndf["nested.a"] + 3
|
|
327
|
+
if self._is_known_hierarchical_column(components):
|
|
328
|
+
nested = components[0]
|
|
329
|
+
field = ".".join(components[1:])
|
|
330
|
+
return self[nested].nest.get_flat_series(field)
|
|
331
|
+
else:
|
|
332
|
+
raise KeyError(f"Column '{cleaned_item}' not found in nested columns or base columns")
|
|
333
|
+
|
|
270
334
|
def __setitem__(self, key, value):
|
|
271
335
|
"""Adds custom __setitem__ behavior for nested columns"""
|
|
272
|
-
|
|
336
|
+
components = self._parse_hierarchical_components(key)
|
|
273
337
|
# Replacing or adding columns to a nested structure
|
|
274
338
|
# Allows statements like ndf["nested.t"] = ndf["nested.t"] - 5
|
|
275
339
|
# Or ndf["nested.base_t"] = ndf["nested.t"] - 5
|
|
276
340
|
# Performance note: This requires building a new nested structure
|
|
277
341
|
# TODO: Support assignment of a new column to an existing nested col from a list series
|
|
278
|
-
if self._is_known_hierarchical_column(
|
|
279
|
-
|
|
342
|
+
if self._is_known_hierarchical_column(components) or (
|
|
343
|
+
len(components) > 1 and components[0] in self.nested_columns
|
|
280
344
|
):
|
|
281
|
-
|
|
282
|
-
|
|
345
|
+
if len(components) != 2:
|
|
346
|
+
raise ValueError(f"Only one level of nesting is supported; given {key}")
|
|
347
|
+
nested, field = components
|
|
348
|
+
new_nested_series = self[nested].nest.with_flat_field(field, value)
|
|
283
349
|
return super().__setitem__(nested, new_nested_series)
|
|
284
350
|
|
|
285
351
|
# Adding a new nested structure from a column
|
|
286
352
|
# Allows statements like ndf["new_nested.t"] = ndf["nested.t"] - 5
|
|
287
|
-
if
|
|
288
|
-
new_nested,
|
|
353
|
+
if len(components) > 1:
|
|
354
|
+
new_nested, field = components
|
|
289
355
|
if isinstance(value, pd.Series):
|
|
290
|
-
value.name =
|
|
356
|
+
value.name = field
|
|
291
357
|
value = value.to_frame()
|
|
292
358
|
new_df = self.add_nested(value, name=new_nested)
|
|
293
359
|
self._update_inplace(new_df)
|
|
@@ -512,10 +578,15 @@ class NestedFrame(pd.DataFrame):
|
|
|
512
578
|
--------
|
|
513
579
|
https://pandas.pydata.org/docs/reference/api/pandas.eval.html
|
|
514
580
|
"""
|
|
581
|
+
_, aliases = _identify_aliases(expr)
|
|
582
|
+
self._aliases: dict[str, str] | None = aliases
|
|
583
|
+
|
|
515
584
|
kwargs["resolvers"] = tuple(kwargs.get("resolvers", ())) + (_NestResolver(self),)
|
|
516
585
|
kwargs["inplace"] = inplace
|
|
517
586
|
kwargs["parser"] = "nested-pandas"
|
|
518
|
-
|
|
587
|
+
answer = super().eval(expr, **kwargs)
|
|
588
|
+
self._aliases = None
|
|
589
|
+
return answer
|
|
519
590
|
|
|
520
591
|
def extract_nest_names(
|
|
521
592
|
self,
|
|
@@ -801,7 +872,7 @@ class NestedFrame(pd.DataFrame):
|
|
|
801
872
|
Takes a function and applies it to each top-level row of the NestedFrame.
|
|
802
873
|
|
|
803
874
|
The user may specify which columns the function is applied to, with
|
|
804
|
-
columns from the 'base' layer being
|
|
875
|
+
columns from the 'base' layer being passed to the function as
|
|
805
876
|
scalars and columns from the nested layers being passed as numpy arrays.
|
|
806
877
|
|
|
807
878
|
Parameters
|
|
@@ -838,12 +909,15 @@ class NestedFrame(pd.DataFrame):
|
|
|
838
909
|
# Parse through the initial args to determine the columns to apply the function to
|
|
839
910
|
requested_columns = []
|
|
840
911
|
for arg in args:
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
912
|
+
# Stop when we reach an argument that is not a valid column, as we assume
|
|
913
|
+
# that the remaining args are extra arguments to the function
|
|
914
|
+
if not isinstance(arg, str):
|
|
915
|
+
break
|
|
916
|
+
components = self._parse_hierarchical_components(arg)
|
|
917
|
+
if not self._is_known_column(components):
|
|
844
918
|
break
|
|
845
|
-
layer = "base" if
|
|
846
|
-
col =
|
|
919
|
+
layer = "base" if len(components) < 2 else components[0]
|
|
920
|
+
col = components[-1]
|
|
847
921
|
requested_columns.append((layer, col))
|
|
848
922
|
|
|
849
923
|
# We require the first *args to be the columns to apply the function to
|
|
@@ -196,10 +196,34 @@ def pack_lists(df: pd.DataFrame, name: str | None = None, *, validate: bool = Tr
|
|
|
196
196
|
nested_pandas.series.dtype.NestedDtype : The dtype of the output series.
|
|
197
197
|
nested_pandas.series.packer.pack_flat : Pack a "flat" dataframe with repeated indexes.
|
|
198
198
|
"""
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
199
|
+
# When series is converted to pa.array it may be both Array and ChunkedArray
|
|
200
|
+
# We convert it to chunked for the sake of consistency
|
|
201
|
+
pa_arrays_maybe_chunked = {column: pa.array(df[column]) for column in df.columns}
|
|
202
|
+
pa_chunked_arrays = {
|
|
203
|
+
column: arr if isinstance(arr, pa.ChunkedArray) else pa.chunked_array([arr])
|
|
204
|
+
for column, arr in pa_arrays_maybe_chunked.items()
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
# If all chunk arrays have the same chunk lengths, we can build a chunked struct array with no
|
|
208
|
+
# data copying.
|
|
209
|
+
chunk_lengths = pa.array([[len(chunk) for chunk in arr.chunks] for arr in pa_chunked_arrays.values()])
|
|
210
|
+
if all(chunk_length == chunk_lengths[0] for chunk_length in chunk_lengths):
|
|
211
|
+
chunks = []
|
|
212
|
+
numpy_chunks = next(iter(pa_chunked_arrays.values())).num_chunks
|
|
213
|
+
for i in range(numpy_chunks):
|
|
214
|
+
chunks.append(
|
|
215
|
+
pa.StructArray.from_arrays(
|
|
216
|
+
[arr.chunk(i) for arr in pa_chunked_arrays.values()],
|
|
217
|
+
names=pa_chunked_arrays.keys(),
|
|
218
|
+
)
|
|
219
|
+
)
|
|
220
|
+
struct_array = pa.chunked_array(chunks)
|
|
221
|
+
else: # "flatten" the chunked arrays
|
|
222
|
+
struct_array = pa.StructArray.from_arrays(
|
|
223
|
+
[arr.combine_chunks() for arr in pa_chunked_arrays.values()],
|
|
224
|
+
names=pa_chunked_arrays.keys(),
|
|
225
|
+
)
|
|
226
|
+
|
|
203
227
|
ext_array = NestedExtensionArray(struct_array, validate=validate)
|
|
204
228
|
return pd.Series(
|
|
205
229
|
ext_array,
|
|
@@ -14,6 +14,8 @@ pyproject.toml
|
|
|
14
14
|
.github/ISSUE_TEMPLATE/0-general_issue.md
|
|
15
15
|
.github/ISSUE_TEMPLATE/1-bug_report.md
|
|
16
16
|
.github/ISSUE_TEMPLATE/2-feature_request.md
|
|
17
|
+
.github/ISSUE_TEMPLATE/README.md
|
|
18
|
+
.github/workflows/README.md
|
|
17
19
|
.github/workflows/asv-main.yml
|
|
18
20
|
.github/workflows/asv-nightly.yml
|
|
19
21
|
.github/workflows/asv-pr.yml
|
{nested_pandas-0.3.2 → nested_pandas-0.3.3}/tests/nested_pandas/nestedframe/test_nestedframe.py
RENAMED
|
@@ -85,6 +85,35 @@ def test_is_known_hierarchical_column():
|
|
|
85
85
|
assert not base._is_known_hierarchical_column("base.a")
|
|
86
86
|
|
|
87
87
|
|
|
88
|
+
def test_is_known_column():
|
|
89
|
+
"""
|
|
90
|
+
Test that known (non-hierarchical) columns can be identified. The key
|
|
91
|
+
point to test is that columns which might look like they are nested,
|
|
92
|
+
but which are already known to not be, are correctly identified.
|
|
93
|
+
"""
|
|
94
|
+
base = NestedFrame(data={"R. A.": [1, 2, 3], "nested.b": [2, 4, 6]}, index=[0, 1, 2])
|
|
95
|
+
nested = pd.DataFrame(
|
|
96
|
+
data={"c": [0, 2, 4, 1, 4, 3, 1, 4, 1], "d": [5, 4, 7, 5, 3, 1, 9, 3, 4]},
|
|
97
|
+
index=[0, 0, 0, 1, 1, 1, 2, 2, 2],
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
base = base.add_nested(nested, "nested")
|
|
101
|
+
|
|
102
|
+
assert base._is_known_column("R. A.")
|
|
103
|
+
assert base._is_known_column("`R. A.`")
|
|
104
|
+
assert base._is_known_column("nested.b")
|
|
105
|
+
|
|
106
|
+
# In this context, the "." delimiter matters a lot, so the following, which would be
|
|
107
|
+
# acceptable in an .eval() context, is not acceptable here.
|
|
108
|
+
assert not base._is_known_column("nested . b")
|
|
109
|
+
assert not base._is_known_column("nested. c")
|
|
110
|
+
assert not base._is_known_column("nested .d")
|
|
111
|
+
|
|
112
|
+
# But hierarchical ones should also work
|
|
113
|
+
assert base._is_known_column("nested.c")
|
|
114
|
+
assert base._is_known_column("nested.d")
|
|
115
|
+
|
|
116
|
+
|
|
88
117
|
def test_get_nested_column():
|
|
89
118
|
"""Test that __getitem__ can retrieve a nested column"""
|
|
90
119
|
|
|
@@ -167,6 +196,21 @@ def test_get_dot_names():
|
|
|
167
196
|
assert len(nf["nested.R.A."]) == 4
|
|
168
197
|
|
|
169
198
|
|
|
199
|
+
def test_nesting_limit():
|
|
200
|
+
"""Test the ability to prevent nesting beyond a depth of 1."""
|
|
201
|
+
nf = NestedFrame.from_flat(
|
|
202
|
+
NestedFrame({"a": [1, 2, 3, 4], ".b.": [1, 1, 3, 3], "R.A.": [3, None, 6, 5]}, index=[1, 1, 2, 2]),
|
|
203
|
+
base_columns=[".b."],
|
|
204
|
+
)
|
|
205
|
+
with pytest.raises(ValueError):
|
|
206
|
+
# The error gets triggered for the attempt to create new nested columns; if the column has
|
|
207
|
+
# already been created, it should be fine.
|
|
208
|
+
nf["nested.c.d.e"] = nf[".b."]
|
|
209
|
+
nf["nested.c"] = nf["nested.R.A."]
|
|
210
|
+
# Test that the above works with backticks, too, even in cases where they are not strictly necessary.
|
|
211
|
+
nf["`nested.d`"] = nf["`.b.`"]
|
|
212
|
+
|
|
213
|
+
|
|
170
214
|
def test_add_nested_with_flat_df():
|
|
171
215
|
"""Test that add_nested correctly adds a nested column to the base df"""
|
|
172
216
|
|
|
@@ -612,6 +656,17 @@ def test_query_on_non_identifier_columns():
|
|
|
612
656
|
nf3 = nf.query("`bad dog`.a > 2")
|
|
613
657
|
assert nf3["bad dog"].nest["a"].size == 4
|
|
614
658
|
|
|
659
|
+
# And also for fields within the nested columns.
|
|
660
|
+
# Taken from GH#176
|
|
661
|
+
nf = NestedFrame(data={"dog": [1, 2, 3], "good dog": [2, 4, 6]}, index=[0, 1, 2])
|
|
662
|
+
nested = pd.DataFrame(
|
|
663
|
+
data={"n/a": [0, 2, 4, 1, 4, 3, 1, 4, 1], "n/b": [5, 4, 7, 5, 3, 1, 9, 3, 4]},
|
|
664
|
+
index=[0, 0, 0, 1, 1, 1, 2, 2, 2],
|
|
665
|
+
)
|
|
666
|
+
nf = nf.add_nested(nested, "bad dog")
|
|
667
|
+
nf4 = nf.query("`bad dog`.`n/a` > 2")
|
|
668
|
+
assert nf4["bad dog"].nest["n/a"].size == 4
|
|
669
|
+
|
|
615
670
|
|
|
616
671
|
def test_dropna():
|
|
617
672
|
"""Test that dropna works on all layers"""
|
|
@@ -815,6 +870,14 @@ def test_reduce():
|
|
|
815
870
|
for i in range(len(result)):
|
|
816
871
|
assert result["offset_avg"].values[i] == expected_offset_avg[i]
|
|
817
872
|
|
|
873
|
+
# Verify that we can understand a string argument to the reduce function,
|
|
874
|
+
# so long as it isn't a column name.
|
|
875
|
+
def make_id(col1, prefix_str):
|
|
876
|
+
return f"{prefix_str}{col1}"
|
|
877
|
+
|
|
878
|
+
result = nf.reduce(make_id, "b", "some_id_")
|
|
879
|
+
assert result[0][1] == "some_id_4"
|
|
880
|
+
|
|
818
881
|
|
|
819
882
|
def test_reduce_duplicated_cols():
|
|
820
883
|
"""Tests nf.reduce() to correctly handle duplicated column names."""
|
|
@@ -1034,6 +1097,20 @@ def test_eval_assignment():
|
|
|
1034
1097
|
assert (nf["p2.e"] == nf["packed.d"] * 2 + nf.c).all()
|
|
1035
1098
|
assert (nf["p2.f"] == nf["p2.e"] + nf.b).all()
|
|
1036
1099
|
|
|
1100
|
+
# Verify that assignment can be done to nested columns and fields
|
|
1101
|
+
# having names which are not valid Python identifiers, and must
|
|
1102
|
+
# be quoted with backticks.
|
|
1103
|
+
nf = NestedFrame(data={"dog": [1, 2, 3], "good dog": [2, 4, 6]}, index=[0, 1, 2])
|
|
1104
|
+
nested = pd.DataFrame(
|
|
1105
|
+
data={"n/a": [0, 2, 4, 1, 4, 3, 1, 4, 1], "n/b": [5, 4, 7, 5, 3, 1, 9, 3, 4]},
|
|
1106
|
+
index=[0, 0, 0, 1, 1, 1, 2, 2, 2],
|
|
1107
|
+
)
|
|
1108
|
+
nf = nf.add_nested(nested, "bad dog")
|
|
1109
|
+
nfx = nf.eval("`bad dog`.`n/c` = `bad dog`.`n/b` + 2.5")
|
|
1110
|
+
# The number of columns at the top should not have changed
|
|
1111
|
+
assert len(nfx.columns) == len(nf.columns)
|
|
1112
|
+
assert (nfx["bad dog"].nest["n/c"] == nf["bad dog"].nest["n/b"] + 2.5).all()
|
|
1113
|
+
|
|
1037
1114
|
|
|
1038
1115
|
def test_access_non_existing_column():
|
|
1039
1116
|
"""Test that accessing a non-existing column raises a KeyError"""
|
|
@@ -286,6 +286,42 @@ def test_pack_lists():
|
|
|
286
286
|
assert_series_equal(series.nest.get_list_series(field_name), packed_df[field_name])
|
|
287
287
|
|
|
288
288
|
|
|
289
|
+
def test_pack_lists_with_chunked_arrays():
|
|
290
|
+
"""Issue https://github.com/lincc-frameworks/nested-pandas/issues/180"""
|
|
291
|
+
chunked_a = pd.Series(
|
|
292
|
+
pa.chunked_array([pa.array([[1, 2, 3], [4, 5]])] * 3),
|
|
293
|
+
dtype=pd.ArrowDtype(pa.list_(pa.int64())),
|
|
294
|
+
name="a",
|
|
295
|
+
)
|
|
296
|
+
chunked_b = pd.Series(
|
|
297
|
+
pa.chunked_array([pa.array([[0.0, 1.0, 2.0], [3.0, 4.0]])] * 3),
|
|
298
|
+
dtype=pd.ArrowDtype(pa.list_(pa.float64())),
|
|
299
|
+
name="b",
|
|
300
|
+
)
|
|
301
|
+
list_df = pd.DataFrame({"a": chunked_a, "b": chunked_b}, index=[0, 1, 2, 3, 4, 5])
|
|
302
|
+
series = packer.pack_lists(list_df)
|
|
303
|
+
assert_series_equal(series.nest.get_list_series("a"), chunked_a)
|
|
304
|
+
assert_series_equal(series.nest.get_list_series("b"), chunked_b)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def test_pack_lists_with_uneven_chunked_arrays():
|
|
308
|
+
"""Issue https://github.com/lincc-frameworks/nested-pandas/issues/180"""
|
|
309
|
+
chunked_a = pd.Series(
|
|
310
|
+
pa.chunked_array([pa.array([[1, 2, 3], [4, 5]])] * 3),
|
|
311
|
+
dtype=pd.ArrowDtype(pa.list_(pa.int64())),
|
|
312
|
+
name="a",
|
|
313
|
+
)
|
|
314
|
+
chunked_b = pd.Series(
|
|
315
|
+
pa.array([[0.0, 1.0, 2.0], [3.0, 4.0]] * 3),
|
|
316
|
+
dtype=pd.ArrowDtype(pa.list_(pa.float64())),
|
|
317
|
+
name="b",
|
|
318
|
+
)
|
|
319
|
+
list_df = pd.DataFrame({"a": chunked_a, "b": chunked_b}, index=[0, 1, 2, 3, 4, 5])
|
|
320
|
+
series = packer.pack_lists(list_df)
|
|
321
|
+
assert_series_equal(series.nest.get_list_series("a"), chunked_a)
|
|
322
|
+
assert_series_equal(series.nest.get_list_series("b"), chunked_b)
|
|
323
|
+
|
|
324
|
+
|
|
289
325
|
def test_pack_seq_with_dfs_and_index():
|
|
290
326
|
"""Test pack_seq()."""
|
|
291
327
|
dfs = [
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|