Skip to content

Commit e1576c9

Browse files
committed
New NestedColumn to better represent groups of hierarchical fields
1 parent 9579514 commit e1576c9

7 files changed

Lines changed: 100 additions & 28 deletions

File tree

doc/reference/classes.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ Columnar table containers, column views, indexes, and CTable schema helpers.
5555

5656
CTable
5757
Column
58+
NestedColumn
5859
Index
5960
NullPolicy
6061

doc/reference/ctable.rst

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,54 @@ Null sentinel values are automatically excluded from all aggregates.
615615
.. automethod:: Column.all
616616

617617

618+
----
619+
620+
.. _NestedColumn:
621+
622+
NestedColumn
623+
============
624+
625+
A read-only accessor for a nested (dotted) group of CTable columns, returned by
626+
attribute access on a :class:`CTable` (or on another ``NestedColumn``) when the
627+
name refers to an internal node of the dotted column tree rather than a leaf.
628+
629+
For a table flattened from a ``struct`` / ``list<struct>`` schema (see
630+
:ref:`Nested fields <NestedFields>`), ``t.trip`` is a ``NestedColumn`` grouping
631+
every leaf under the ``trip.`` prefix, while a leaf such as ``t.trip.sec`` or
632+
``t.trip.begin.lon`` is a :class:`Column`. Drilling into an intermediate node
633+
yields another ``NestedColumn``::
634+
635+
t.trip # <NestedColumn 'trip'>
636+
t.trip.col_names # ['sec', 'km', 'begin.lon', 'begin.lat', ...]
637+
t.trip.begin # <NestedColumn 'trip.begin'>
638+
t.trip.begin.lon # Column
639+
print(t.trip.info) # aggregate metadata over the group
640+
641+
Users do not instantiate ``NestedColumn`` directly.
642+
643+
.. autoclass:: NestedColumn
644+
645+
.. rubric:: Attributes
646+
647+
.. autosummary::
648+
649+
NestedColumn.col_names
650+
NestedColumn.nrows
651+
NestedColumn.ncols
652+
NestedColumn.nbytes
653+
NestedColumn.cbytes
654+
NestedColumn.cratio
655+
NestedColumn.info
656+
657+
.. autoproperty:: NestedColumn.col_names
658+
.. autoproperty:: NestedColumn.nrows
659+
.. autoproperty:: NestedColumn.ncols
660+
.. autoproperty:: NestedColumn.nbytes
661+
.. autoproperty:: NestedColumn.cbytes
662+
.. autoproperty:: NestedColumn.cratio
663+
.. autoproperty:: NestedColumn.info
664+
665+
618666
----
619667

620668
.. _SchemaSpecs:
@@ -760,6 +808,8 @@ to a typed representation. They are not used as an implicit fallback during
760808
Parquet import; unsupported Arrow/Parquet types still raise unless explicitly
761809
imported through :meth:`CTable.from_arrow` with ``object_fallback=True``.
762810

811+
.. _NestedFields:
812+
763813
Nested fields
764814
-------------
765815

@@ -803,6 +853,11 @@ attribute proxies::
803853
t["trip.begin.lon"].mean() # Column object (fast path)
804854
t.trip.begin.lon.max() # attribute proxy, same column
805855

856+
Accessing an intermediate prefix such as ``t.trip`` or ``t.trip.begin`` returns
857+
a :class:`~blosc2.NestedColumn` that groups all descendant leaves and reports
858+
aggregate metadata via :attr:`~blosc2.NestedColumn.info`; a leaf such as
859+
``t.trip.begin.lon`` returns a :class:`Column`.
860+
806861
A literal ``.``, ``/``, or ``\\`` inside an Arrow field name is escaped with a
807862
backslash in the logical column name. For example, path segments
808863
``("trip.info", "begin/point", "lon.deg")`` become::

src/blosc2/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,7 @@ def _raise(exc):
632632
DEFAULT_NULL_POLICY,
633633
Column,
634634
CTable,
635+
NestedColumn,
635636
NullPolicy,
636637
RowTransformer,
637638
get_null_policy,
@@ -827,8 +828,11 @@ def _raise(exc):
827828
"group_reduce",
828829
# Classes
829830
"C2Array",
831+
"Column",
830832
"CParams",
833+
"CTable",
831834
"CTableGroupBy",
835+
"NestedColumn",
832836
"RowTransformer",
833837
"Batch",
834838
"BatchArray",

src/blosc2/ctable.py

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2486,11 +2486,29 @@ def __iter__(self):
24862486
yield self._row_value_at_logical(i)
24872487

24882488

2489-
class _NestedColumnNamespace:
2490-
"""Attribute proxy for dotted nested column paths.
2489+
class NestedColumn:
2490+
"""A read-only accessor for a nested (dotted) group of CTable columns.
2491+
2492+
Returned by attribute access on a :class:`CTable` (or on another
2493+
``NestedColumn``) when the name refers to an internal node of the dotted
2494+
column tree rather than a leaf. For a table flattened from a
2495+
``struct``/``list<struct>`` schema, ``t.trip`` is a ``NestedColumn``
2496+
grouping every leaf under the ``trip.`` prefix, while a leaf such as
2497+
``t.trip.sec`` (or ``t.trip.begin.lon``) is a :class:`Column`. Drilling
2498+
into an intermediate node (e.g. ``t.trip.begin``) yields another
2499+
``NestedColumn``.
2500+
2501+
Exposes aggregate metadata over its descendant leaf columns
2502+
(:attr:`col_names`, :attr:`nrows`, :attr:`ncols`, :attr:`nbytes`,
2503+
:attr:`cbytes`, :attr:`cratio`) and an :attr:`info` report.
24912504
2492-
Allows `t.trip.begin.lon` when the physical leaf column is named
2493-
`"trip.begin.lon"`.
2505+
Examples
2506+
--------
2507+
>>> t.trip # doctest: +SKIP
2508+
<NestedColumn 'trip'>
2509+
>>> t.trip.col_names # doctest: +SKIP
2510+
['sec', 'km', 'begin.lon', ...]
2511+
>>> t.trip.sec # a leaf -> Column # doctest: +SKIP
24942512
"""
24952513

24962514
def __init__(self, table: CTable, prefix: str):
@@ -2600,11 +2618,11 @@ def __getattr__(self, name: str):
26002618
for col_name in self._table.col_names:
26012619
parts = split_field_path(col_name)
26022620
if parts[: len(path_parts)] == path_parts and len(parts) > len(path_parts):
2603-
return _NestedColumnNamespace(self._table, path)
2621+
return NestedColumn(self._table, path)
26042622
raise AttributeError(path)
26052623

26062624
def __repr__(self) -> str:
2607-
return f"<NestedColumnNamespace {self._prefix!r}>"
2625+
return f"<NestedColumn {self._prefix!r}>"
26082626

26092627

26102628
class _LazyColumnDict(dict):
@@ -8536,7 +8554,7 @@ def _nested_namespace(self, prefix: str):
85368554
for name in self.col_names:
85378555
parts = split_field_path(name)
85388556
if parts[: len(prefix_parts)] == prefix_parts and len(parts) > len(prefix_parts):
8539-
return _NestedColumnNamespace(self, prefix)
8557+
return NestedColumn(self, prefix)
85408558
return None
85418559

85428560
def __getattr__(self, s: str):

src/blosc2/ndarray.py

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3935,23 +3935,16 @@ def info(self) -> InfoReporter:
39353935
chunks : (10,)
39363936
blocks : (10,)
39373937
dtype : int64
3938-
cratio : 0.73x
3939-
cparams : {'blocksize': 80,
3940-
'clevel': 1,
3941-
'codec': <Codec.ZSTD: 5>,
3942-
'codec_meta': 0,
3943-
'filters': [<Filter.NOFILTER: 0>,
3944-
<Filter.NOFILTER: 0>,
3945-
<Filter.NOFILTER: 0>,
3946-
<Filter.NOFILTER: 0>,
3947-
<Filter.NOFILTER: 0>,
3948-
<Filter.SHUFFLE: 1>],
3949-
'filters_meta': [0, 0, 0, 0, 0, 0],
3950-
'nthreads': 4,
3951-
'splitmode': <SplitMode.ALWAYS_SPLIT: 1>,
3952-
'typesize': 8,
3953-
'use_dict': 0}
3954-
dparams : {'nthreads': 4}
3938+
nbytes : 80 (80 B)
3939+
cbytes : 98 (98 B)
3940+
cratio : 0.82x
3941+
cparams : CParams(codec=<Codec.ZSTD: 5>, codec_meta=0, clevel=5, use_dict=False, typesize=8,
3942+
: nthreads=8, blocksize=80, splitmode=<SplitMode.AUTO_SPLIT: 3>,
3943+
: filters=[<Filter.NOFILTER: 0>, <Filter.NOFILTER: 0>, <Filter.NOFILTER: 0>,
3944+
: <Filter.NOFILTER: 0>, <Filter.NOFILTER: 0>, <Filter.SHUFFLE: 1>], filters_meta=[0, 0,
3945+
: 0, 0, 0, 0], tuner=<Tuner.STUNE: 0>)
3946+
dparams : DParams(nthreads=8)
3947+
<BLANKLINE>
39553948
"""
39563949
return InfoReporter(self)
39573950

src/blosc2/schunk.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -533,15 +533,16 @@ def info(self) -> InfoReporter:
533533
chunksize : 24000
534534
blocksize : 0
535535
typesize : 1
536-
nbytes : 24000
537-
cbytes : 82
536+
nbytes : 24000 (23.44 KiB)
537+
cbytes : 82 (82 B)
538538
cratio : 292.68x
539-
cparams : CParams(codec=<Codec.ZSTD: 5>, codec_meta=0, clevel=1, use_dict=False, typesize=1,
539+
cparams : CParams(codec=<Codec.ZSTD: 5>, codec_meta=0, clevel=5, use_dict=False, typesize=1,
540540
: nthreads=8, blocksize=0, splitmode=<SplitMode.AUTO_SPLIT: 3>,
541541
: filters=[<Filter.NOFILTER: 0>, <Filter.NOFILTER: 0>, <Filter.NOFILTER: 0>,
542542
: <Filter.NOFILTER: 0>, <Filter.NOFILTER: 0>, <Filter.SHUFFLE: 1>], filters_meta=[0,
543543
: 0, 0, 0, 0, 0], tuner=<Tuner.STUNE: 0>)
544544
dparams : DParams(nthreads=8)
545+
<BLANKLINE>
545546
"""
546547
return InfoReporter(self)
547548

tests/ctable/test_ctable_dataclass_schema.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ class NestedRow:
348348
assert t.trip.col_names == ["begin.lon", "begin.lat"]
349349

350350
text = repr(info)
351-
assert "NestedColumnNamespace" in text
351+
assert "NestedColumn" in text
352352
assert "storage" in text
353353
assert "schema" in text
354354
assert "begin.lon" in text

0 commit comments

Comments
 (0)