Skip to main content

Array View

CI PyPI version Python versions License Checked with mypy Ruff

A configurable view over a NumPy array — inspect, export, and display without writing boilerplate.

Overview

ArrayView wraps a NumPy array (structured, 1-D, or 2-D) without copying the underlying data and lets you describe how it should look — which columns, which rows, what labels — then render it as text, a pandas DataFrame, or HTML. The original array remains directly accessible as view.array, so numpy-level work isn't locked out.

It's intended for large arrays (CAE-scale, millions of rows) where printing everything isn't useful. Inspection usually needs something transformed:

  • Row selection — head/tail windows or boolean filters
  • Column selection — drop internal padding or bookkeeping fields
  • Column transformation — flatten position (3,) → position_x / y / z
  • Value transformation — resolve integer codes to human names (element_type=1 → "QUAD")

All of these apply lazily to the rendered rows only — a 10M-row array with a 25-row window does 25 rows of work.

import numpy as np
from vcti.arrayview import ArrayView

arr = np.array(
    [(1, 10.5), (2, 20.1), (3, 30.7)],
    dtype=[("id", "i4"), ("value", "f8")],
)

view = ArrayView(arr).set_index("id")

# Render as text.
print(view.to_table())

# The view is a live configuration, not a throwaway.  Reconfigure and
# render again — each render reads the current state.
view.set_column_labels({"value": "measurement"}).slice(head=2)
df = view.to_dataframe()

Configuration methods (set_column_labels, set_index, slice, etc.) return self, so if you prefer the one-expression style the same steps read top-to-bottom:

view = (ArrayView(arr)
        .set_column_labels({"value": "measurement"})
        .set_index("id")
        .slice(head=10))
text = view.to_table()

Installation

pip install vcti-array-view>=1.2.0

With pandas support (optional — enables to_dataframe() and to_html()):

pip install vcti-array-view[pandas]>=1.2.0

Quick Start

import numpy as np
from vcti.arrayview import ArrayView, FILLER_COLUMNS, LENGTH_COLUMNS

dt = np.dtype([
    ("node_id",      "i4"),
    ("f0",           "V4"),             # C++ alignment padding
    ("element_type", "i4"),             # enum ID
    ("label",        "U20"),
    ("label_len",    "i4"),             # string-length bookkeeping
    ("position",     "f8", (3,)),       # vector field
])
arr = np.zeros(1_000_000, dtype=dt)     # imagine this is full of real data

view = (ArrayView(arr)
        .exclude_patterns([FILLER_COLUMNS, LENGTH_COLUMNS])
        .set_component_names("position", ["x", "y", "z"])
        .add_enum_columns({
            "element_type_name": ("element_type", {1: "QUAD", 2: "HEX"}),
        })
        .set_index("node_id")
        .slice(head=10))

print(view.to_table())      # text table, first 10 rows
df = view.to_dataframe()    # pandas DataFrame, node_id as pd.Index
html = view.to_html()       # HTML string for dashboards

Input shapes

ArrayView(array, *, dtype=None, names=None) accepts three input shapes. Structured arrays are used as-is; plain 1-D and 2-D arrays are reinterpreted as structured via np.ndarray.view() (zero-copy for C-contiguous input).

# Structured array — fields already named
dt = np.dtype([("id", "i4"), ("value", "f8")])
arr = np.array([(1, 10.0), (2, 20.0)], dtype=dt)
view = ArrayView(arr)         # view_columns == ["id", "value"]

# Plain 1-D — names= assigns the field name
view = ArrayView(np.array([1.5, 2.0, 3.5]), names=["measurement"])
# view_columns == ["measurement"]

# Plain 2-D — names= avoids the col_0, col_1, ... defaults
view = ArrayView(np.random.rand(1000, 3), names=["x", "y", "z"])
# view_columns == ["x", "y", "z"]

# dtype= reinterprets bytes — accepts string, list-of-tuples, np.dtype,
# or a callable (np.ndarray) -> np.dtype
view = ArrayView(raw_arr, dtype=[("node_id", "i4"), ("value", "f8")])

dtype and names are mutually exclusive. Non-contiguous 2-D arrays raise ValueError — call np.ascontiguousarray() first if you accept the copy cost. ndim > 2 is rejected; reshape first.


Operations

Operations fall into four kinds — three axes of display shaping (columns, labels, rows) and the output methods. All return self and generally compose in any order — you can call them any number of times and reconfigure freely between renders.

Column shaping — which columns appear and in what order

All operations refer to columns by their dtype name, not their display label.

Method Effect
exclude_patterns(patterns) Drop columns matching any pattern (exact name, regex, or (name, dtype) -> bool). Pre-built: FILLER_COLUMNS, LENGTH_COLUMNS, VOID_COLUMNS.
set_view_columns(columns=None) Replace the visible list with a whitelist (or reset to all dtype columns when called without an argument). For pattern-based filtering, compose with exclude_patterns.
include_view_columns(columns) Append columns to the visible list.
drop_view_columns(columns) Remove columns from the visible list.

Label / value shaping — how columns and cells are displayed

Labels affect rendering only. The dtype identity of a column never changes, so other operations keep working after you relabel.

Method Effect
set_column_labels(mapping) Set display labels for top-level dtype fields. For a scalar field, the label is the column header; for a multi-component field, it's the level-1 group header.
set_component_names(field, names) Set level-2 labels for the flattened components of a multi-component field (e.g. ["x", "y", "z"]). Defaults to "0", "1", ... when unset.
add_column_group(name, columns) Attach a level-1 header label spanning columns. The chosen name is also the displayed label. Usable as an index source.
remove_column_group(name) Remove a group. Clears the index if the group was the current source.
add_enum_columns({name: (id_col, {int: str})}) Attach a virtual string column that maps integer codes in id_col to human names (e.g., {1: "QUAD", 2: "HEX"}). The new column takes id_col's place in the view. Mapping is applied only to rendered rows — a 10M-row array with a 20-row window does 20 lookups.

Row shaping — which rows appear and how they are identified

Method Effect
set_index(source) Designate row identity. Use a str — a dtype field name for a single-column index, or a column-group name for a multi-column index using the group's members. Use an np.ndarray of matching length for an external identifier (useful when several arrays share the same row identity without duplicating the id into each); a structured ndarray's fields each become a separate index level. Becomes the pandas Index / MultiIndex.
clear_index() Remove the row identifier.
slice(head=..., tail=..., mask=..., indices=...) Configure a bounded window. Output methods render only within this window.
clear_slice() Remove the slice configuration — full range.
compute_slice() Return the integer index array for the current slice (ndarray of np.intp).

Output methods — materialize the configured view

Method Returns
to_table(...) Text table as str (pure numpy, no dependencies).
to_dataframe(...) pandas DataFrame.
to_html() HTML string (pandas under the hood).

Output methods return the rendered result; they do not consume state. Reconfigure and call again as often as you like.


Immutability

Once a view is fully configured, freeze() locks its configuration. Slicing and rendering continue to work; any call that would modify shaping, labels, groups, index, enums, or joins raises FrozenViewError (subclass of RuntimeError).

Method / Property Effect
freeze() Lock the view's configuration. Permanent for this instance. Returns self.
is_frozen True if this view has been frozen.
copy() Branch a modifiable view — the copy is always unfrozen.
from vcti.arrayview import ArrayView, FrozenViewError

view = ArrayView(arr).set_index("id").freeze()

view.slice(head=50)                 # OK — slicing allowed
print(view.to_table())              # OK — rendering allowed
try:
    view.drop_view_columns(["x"])   # raises FrozenViewError
except FrozenViewError as exc:
    ...

branch = view.copy()                # branch is unfrozen
branch.drop_view_columns(["x"])     # OK on the branch

There is no unfreeze() by design — use copy() to produce a modifiable branch.


Why laziness matters

ArrayView is built to keep large arrays tractable — cost scales with what you render, not with what you hold:

  • Bounded slicing — .slice(head=20, tail=5) renders 25 rows regardless of array size. compute_slice() returns a tiny integer array, not a copy of the data.
  • Lazy enum resolution — add_enum_columns({...}) stores a recipe. The integer → string mapping is applied only to rows actually rendered. A 10M-row array with a 10-row slice performs 10 lookups.
  • Reference semantics — the underlying NumPy array is never copied. Multiple ArrayView instances can share the same array.
  • Zero-copy dtype reinterpretation — vector/matrix field flattening uses np.ndarray.view(), an O(1) dtype change, no memory movement.
  • pandas categorical columns — enum names materialize as pd.Categorical, ~100× smaller than string columns.

Branching views with copy()

copy() creates an independent ArrayView over the same underlying array. Configuration is deep-copied so branches don't affect each other. Use this when passing a view to code that might reconfigure it, or when you need two renders with different slices side-by-side.

export_view = view.copy().drop_view_columns(["label"])
view.array is export_view.array   # True — underlying array is shared

Row index

set_index(source) designates what each row represents. Two ways to specify the source:

1. A column (or group) already in the array — pass a string. For a single-column index, pass a dtype field name:

view = ArrayView(arr).set_index("node_id")

For a multi-column index, register a group first and pass its name:

view = (ArrayView(arr)
        .add_column_group("face_id", ["element_idx", "face_num"])
        .set_index("face_id"))

2. An external identifier array — pass an np.ndarray of matching length. Useful when several arrays share the same row identity and you don't want to duplicate the id into each:

face_id = np.array(
    [(1, 0), (1, 1), (2, 0)],
    dtype=[("element_idx", "i4"), ("face_num", "i4")],
)
stress = ArrayView(stress_arr).set_index(face_id)
strain = ArrayView(strain_arr).set_index(face_id)

A structured ndarray produces a pandas MultiIndex; a plain 1-D array becomes a single "id" level. See docs/patterns.md for more recipes.


Dependencies

  • numpy (>=2.0) — required
  • vcti-nputils (>=1.0.0) — required
  • pandas (>=2.2) — optional, for to_dataframe(), to_html(), and Jupyter display

Further reading

Metadata

Release files for vcti-array-view 1.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for vcti-array-view 1.2.0
File Size Uploaded
vcti_array_view-1.2.0.tar.gz 40.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for vcti-array-view 1.2.0
File Interpreter ABI Platform
vcti_array_view-1.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 71.6 kB

Release files / vcti_array_view-1.2.0.tar.gz

Download URL vcti_array_view-1.2.0.tar.gz
Size 40.6 kB
Tags Source
SHA-256 checksum
How to use checksums
95975f780cf6e9268cc315d6019e78007ff23dd29218c291b3139be7decfbaab
BLAKE2b-256 checksum
How to use checksums
0408a923ece7e48d00c2b75ea16f401cb5c193f270f4e92e05e3012eee88b19d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Apr 19, 2026.

Transparency log

Release files / vcti_array_view-1.2.0-py3-none-any.whl

Download URL vcti_array_view-1.2.0-py3-none-any.whl
Size 31.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
572a7b1c25ca440ff03ea6e3b75fab847ad0afb4bc0b5046e8f5119329b8afe6
BLAKE2b-256 checksum
How to use checksums
ee59112eadd5b286722e7c4d5ee0e99ef51a3cce580304c220de23c71df61aff
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Apr 19, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.2.0 This release

2 release files

1.1.1

2 release files

1.1.0

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page