<a id="skfolio-containers-assetpanel"></a>

# skfolio.containers.AssetPanel

<a id="skfolio.containers.AssetPanel"></a>

### *class* skfolio.containers.AssetPanel(fields, observations, asset_names, active_mask=None, estimation_mask=None, \_validate_on_init=True)

Container for aligned cross-sectional asset data.

`AssetPanel` stores asset-level fields (e.g. returns, volumes, industry
classification, factor exposure), over shared observation and asset axes.
Every field uses `observations` as the first axis and `assets` as the second
axis. These two axes always have shape (n_observations, n_assets).

Three kinds of fields are supported:

- 2D numeric fields (e.g. `returns`, `volume`, `market_cap`). These are stored as 2D
  numpy array in a `Field2D`.
- 2D categorical fields (e.g. `country`, `industry`). These are stored as 2D numpy
  array of integer codes in a `FieldCategorical`, together with the category labels
  (e.g. “bank”, “technology”)
- 3D numeric fields (e.g. `factor_exposures`). These are stored in a 3D numpy array
  in a `Field3D` with shape (n_observations, n_assets, n_third_axis), together with
  the third axis labels such as factor names (e.g. “size”, “momentum”) and optional
  group labels such as factor families (e.g. “style”, “industry”)

The container is scikit-learn compatible: `len(panel)` returns `n_observations`
and `panel[start:stop]` returns an `AssetPanelView` that can be passed to
cross-validation and hyper-parameter tuning utilities. View creation is zero-copy
when the observation selector is a slice or a contiguous index.

* **Parameters:**
  **fields** *dict[str, BaseField or ndarray]*
  : Field mapping. Raw arrays must be 2D and are converted to `Field2D`.
    Use `FieldCategorical` for integer-coded categorical fields and `Field3D` for 3D
    fields; both carry the metadata needed to interpret their codes or third axes.

  **observations** *ndarray of shape (n_observations,)*
  : Unique observation labels for the sample axis. NumPy dtypes are preserved.
    Object-dtype datetime-like labels are converted with NumPy, object-dtype string
    labels are converted to strings and mixed object labels are rejected.

  **asset_names** *ndarray of shape (n_assets,)*
  : Unique asset labels. Object-dtype labels are converted to strings.

  **active_mask** *boolean ndarray of shape (n_observations, n_assets), optional*
  : Boolean mask indicating whether each asset belongs to the universe at each
    observation. This separates assets that are outside the universe (e.g. before
    listing, after delisting) from assets that are in the universe but have a
    missing observation (e.g. holiday, missing quote). If `None`, all pairs are
    active.

  **estimation_mask** *boolean ndarray of shape (n_observations, n_assets), optional*
  : Boolean mask indicating which active `(observation, asset)` pairs should be used
    for estimator-specific statistics by `skfolio` estimators that support it (e.g.
    [`CSStandardScaler`](https://skfolio.org/generated/skfolio.preprocessing.CSStandardScaler.html.md#skfolio.preprocessing.CSStandardScaler),
    [`RegimeAdjustedEWCovariance`](https://skfolio.org/generated/skfolio.moments.RegimeAdjustedEWCovariance.html.md#skfolio.moments.RegimeAdjustedEWCovariance)
    If `None`, all active pairs are eligible for estimation. Values are always
    enforced as a subset of `active_mask`.
* **Attributes:**
  [`n_observations`](#skfolio.containers.AssetPanel.n_observations) *int*
  : Number of observations.

  [`n_assets`](#skfolio.containers.AssetPanel.n_assets) *int*
  : Number of assets.

  [`n_fields`](#skfolio.containers.AssetPanel.n_fields) *int*
  : Number of fields.

### Methods

| [`add_2d_field`](#skfolio.containers.AssetPanel.add_2d_field)(name, values, \*[, inactive_policy])   | Add or replace a numeric 2D field.                                                                              |
|------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------|
| [`add_3d_field`](#skfolio.containers.AssetPanel.add_3d_field)(name, values, \*, ...[, ...])          | Add or replace a numeric 3D field.                                                                              |
| [`add_categorical_field`](#skfolio.containers.AssetPanel.add_categorical_field)(name, values, \*, levels)     | Add or replace a 2D categorical field.                                                                          |
| [`align_active_mask_to`](#skfolio.containers.AssetPanel.align_active_mask_to)(fields)                        | Align active periods to valid field values.                                                                     |
| [`bfill`](#skfolio.containers.AssetPanel.bfill)(fields, \*[, limit, inplace])                 | Backward fill NaN values along the observation axis.                                                            |
| [`copy`](#skfolio.containers.AssetPanel.copy)(\*[, deep])                                    | Return a copy of the panel.                                                                                     |
| [`decode_categorical_field`](#skfolio.containers.AssetPanel.decode_categorical_field)(name, \*[, ...])           | Decode a categorical field to labels.                                                                           |
| [`describe`](#skfolio.containers.AssetPanel.describe)(\*[, by])                                  | Return a structured missingness summary.                                                                        |
| [`drop`](#skfolio.containers.AssetPanel.drop)(\*[, observations, assets])                    | Return a panel with selected labels removed.                                                                    |
| [`edit_masks`](#skfolio.containers.AssetPanel.edit_masks)(\*[, \_validate])                        | Temporarily make masks editable.                                                                                |
| [`ffill`](#skfolio.containers.AssetPanel.ffill)(fields, \*[, limit, inplace])                 | Forward fill NaN values along the observation axis.                                                             |
| [`get_field`](#skfolio.containers.AssetPanel.get_field)(name)                                     | Return a field object.                                                                                          |
| [`info`](#skfolio.containers.AssetPanel.info)()                                              | Multi-line report with panel dimensions, mask coverage, field missingness and categorical field level coverage. |
| [`isel`](#skfolio.containers.AssetPanel.isel)(\*[, observations, assets])                    | Select observations and assets by integer position.                                                             |
| [`keys`](#skfolio.containers.AssetPanel.keys)()                                              | Return field names.                                                                                             |
| [`load`](#skfolio.containers.AssetPanel.load)(path, \*[, mmap_mode, fields])                 | Load a panel saved with `save`.                                                                                 |
| [`rename`](#skfolio.containers.AssetPanel.rename)([fields, overwrite])                         | Rename fields in place.                                                                                         |
| [`save`](#skfolio.containers.AssetPanel.save)(path, \*[, overwrite])                         | Save the panel to a directory of `.npy` files.                                                                  |
| [`sel`](#skfolio.containers.AssetPanel.sel)(\*[, observations, assets, fields])             | Select observations, assets and fields by label.                                                                |
| [`sel_3d`](#skfolio.containers.AssetPanel.sel_3d)(name, \*[, labels, groups])                  | Select entries from the third axis of a 3D field by label.                                                      |
| [`to_dataframe`](#skfolio.containers.AssetPanel.to_dataframe)(\*[, fields, assets, ...])             | Convert 2D fields to a pandas DataFrame.                                                                        |

### Notes

`AssetPanel` is an optimized middle ground between raw NumPy arrays and
general-purpose labeled containers (e.g. pandas, polars, xarray). It is optimized
for portfolio, factor and alpha workflows:

- observations are the sample axis, so scikit-learn cross-validation can slice over
  time without grouping rows
- assets are fixed on axis 1, while `active_mask` represents listings, delistings
  and other universe changes
- payload arrays remain numeric, with categorical fields stored as integer codes
- categorical levels, third-axis labels and third-axis groups are stored with their
  fields
- shape, mask and universe invariants are validated by the container
- estimators can rely on validated axes, masks and float-field universe invariants
  without repeating full container validation

Compared with DataFrames, this avoids repeated `groupby`, `pivot` and
index-alignment work while keeping the arrays ready for vectorized cross-sectional
and time-series operations. Compared with xarray, it keeps a smaller API optimized
for quant workflows.

Performance benefits come from the same layout:

- observation slices return `AssetPanelView` objects, so walk-forward folds can
  reuse field arrays
- native `Field3D` fields avoid restacking large lists of 2D arrays
- integer-coded categoricals and dense boolean masks keep memory and conversion
  overhead low
- with `Parallel(..., prefer="threads")`, workers can read the same big panel in
  memory instead of receiving separate process copies. This is useful for
  NumPy-heavy computations where the numeric kernels release the GIL.
- saved panels use `.npy` files and support memory-mapped loading with
  `AssetPanel.load(..., mmap_mode=...)`.

**Indexing.**

- `panel["name"]` returns the underlying field array.
- `panel.fields["name"]` returns the field object with its metadata.
- `panel[start:stop]` returns an `AssetPanelView` with shared field
  arrays.
- `panel.isel(...)` and `panel.sel(...)` select observations and assets
  by position or label.

### Examples

```pycon
>>> import numpy as np
>>> from skfolio.containers import AssetPanel, concat
>>>
>>> n_observations = 252
>>> observations = np.arange(n_observations)
>>> assets = ["AAPL", "MSFT", "GOOG", "AMZN"]
>>> n_assets = len(assets)
>>>
>>> panel = AssetPanel(
...     fields={
...         "returns": np.random.randn(n_observations, n_assets),
...         "volume": np.random.lognormal(size=(n_observations, n_assets)),
...         "market_cap": np.random.lognormal(size=(n_observations, n_assets)),
...     },
...     observations=observations,
...     asset_names=assets,
... )
>>> panel.add_categorical_field(
...     name="industry",
...     values=np.random.randint(0, 3, size=(n_observations, n_assets)),
...     levels=["energy", "bank", "technology"],
... )
AssetPanel(n_observations=252, n_assets=4, n_fields=4)
>>> factor_labels = ["size", "momentum", "value"]
>>> panel.add_3d_field(
...     name="factor_exposure",
...     values=np.random.randn(n_observations, n_assets, len(factor_labels)),
...     third_axis_name="factor",
...     third_axis_labels=factor_labels,
...     third_axis_groups=["style", "style", "style"],
... )
AssetPanel(n_observations=252, n_assets=4, n_fields=5)
>>> panel.n_observations, panel.n_assets, panel.n_fields
(252, 4, 5)
```

Access raw NumPy arrays:

```pycon
>>> returns = panel["returns"]
>>> industry_codes = panel["industry"]
>>> factor_exposure = panel["factor_exposure"]
```

Use field objects or decoding helpers when labels or metadata are needed:

```pycon
>>> industry_labels = panel.decode_categorical_field("industry")
>>> exposure_field = panel.fields["factor_exposure"]
>>> exposure_field.third_axis_labels
array(['size', 'momentum', 'value'], dtype='<U8')
```

Slice observations:

```pycon
>>> view = panel[100:200]
>>> view.n_observations
100
```

Select observations and assets by position or label:

```pycon
>>> panel.isel(observations=slice(0, 60), assets=[0, 1])
AssetPanel(n_observations=60, n_assets=2, n_fields=5)
>>> panel.sel(observations=slice(0, 59), assets=["AAPL", "MSFT"])
AssetPanel(n_observations=60, n_assets=2, n_fields=5)
```

Select entries from a 3D field by third-axis label or group:

```pycon
>>> panel.sel_3d("factor_exposure", labels="momentum").shape
(252, 4)
>>> panel.sel_3d("factor_exposure", groups="style").shape
(252, 4, 3)
```

Convert to pandas:

```pycon
>>> df = panel.to_dataframe(fields=["returns", "industry"], output_format="wide")
```

Get summary and inspect missingness:

```pycon
>>> summary = panel.describe(by="industry")
>>> report = panel.info()
```

Clean selected fields:

```pycon
>>> panel.ffill("returns", inplace=False)
AssetPanel(n_observations=252, n_assets=4, n_fields=5)
>>> panel.bfill("returns", inplace=False)
AssetPanel(n_observations=252, n_assets=4, n_fields=5)
>>> panel.align_active_mask_to("returns")
0
```

Rename field and drop assets:

```pycon
>>> panel.rename({"market_cap": "capitalization"})
AssetPanel(n_observations=252, n_assets=4, n_fields=5)
>>> panel.drop(assets=["AMZN"])
AssetPanel(n_observations=252, n_assets=3, n_fields=5)
```

Concatenate, copy, save and load panels:

```pycon
>>> concat([panel[:126], panel[126:]])
AssetPanel(n_observations=252, n_assets=4, n_fields=5)
>>> panel.copy(deep=True)
AssetPanel(n_observations=252, n_assets=4, n_fields=5)
>>> panel.save("asset_panel")
>>> loaded = AssetPanel.load("asset_panel", mmap_mode="r")
```

<a id="skfolio.containers.AssetPanel.add_2d_field"></a>

#### add_2d_field(name, values, \*, inactive_policy=MISSING)

Add or replace a numeric 2D field.

* **Parameters:**
  **name** *str*
  : Field name.

  **values** *array-like of shape (n_observations, n_assets)*
  : Numeric 2D values.

  **inactive_policy** *InactivePolicy, default=InactivePolicy.MISSING*
  : Validation policy for values outside `active_mask`.
* **Returns:**
  **self** *BaseAssetPanel*
  : The modified container.

<a id="skfolio.containers.AssetPanel.add_3d_field"></a>

#### add_3d_field(name, values, \*, third_axis_name, third_axis_labels, third_axis_groups=None, inactive_policy=MISSING)

Add or replace a numeric 3D field.

This is a convenience wrapper around assigning a `Field3D`. The first two axes
of `values` must be observations and assets with shape (n_observations, n_assets).
The third axis stores a homogeneous block such as factors.

* **Parameters:**
  **name** *str*
  : Field name.

  **values** *array-like of shape (n_observations, n_assets, n_third_axis)*
  : Numeric 3D values.

  **third_axis_name** *str*
  : Name describing what the third axis represents (e.g. `factor`).

  **third_axis_labels** *array-like of shape (n_third_axis,)*
  : Labels for entries along the third axis such as factor names (e.g. `size`,
    `momentum`).

  **third_axis_groups** *array-like of shape (n_third_axis,), optional*
  : Optional group label for each third-axis entry such as factor families (e.g.
    `style`, `industry`).

  **inactive_policy** *InactivePolicy, default=InactivePolicy.MISSING*
  : Validation policy for values outside `active_mask`.
* **Returns:**
  **self** *BaseAssetPanel*
  : The modified container.

<a id="skfolio.containers.AssetPanel.add_categorical_field"></a>

#### add_categorical_field(name, values, \*, levels, inactive_policy=MISSING)

Add or replace a 2D categorical field.

This is a convenience wrapper around assigning a `FieldCategorical`. The field
values must be integer codes with shape (n_observations, n_assets). Code -1 is
reserved for missing values. Code 0 selects `levels[0]`, code 1 selects
`levels[1]` and so on.

* **Parameters:**
  **name** *str*
  : Field name.

  **values** *array-like of integers, shape (n_observations, n_assets)*
  : Integer category codes.

  **levels** *array-like of shape (n_levels,)*
  : Category labels selected by codes 0, 1 and so on.

  **inactive_policy** *InactivePolicy, default=InactivePolicy.MISSING*
  : Validation policy for codes outside `active_mask`.
* **Returns:**
  **self** *BaseAssetPanel*
  : The modified container.

<a id="skfolio.containers.AssetPanel.align_active_mask_to"></a>

#### align_active_mask_to(fields)

Align active periods to valid field values.

For each asset, remove leading `active_mask` entries until the selected fields
have valid values in the remaining active history. If an asset has no valid
active value for a selected field, all active entries for that asset are
removed. Only leading active entries are removed. Missing values after the first
valid active value are left unchanged.

* **Parameters:**
  **fields** *str or iterable of str*
  : Field names used to determine when each asset can become active. Floating
    values must be finite, categorical values must not be missing, and 3D
    floating values must be finite across the third axis.
* **Returns:**
  **n_removed** *int*
  : Number of `(observation, asset)` entries removed from `active_mask`.

<a id="skfolio.containers.AssetPanel.bfill"></a>

#### bfill(fields, \*, limit=None, inplace=True)

Backward fill NaN values along the observation axis.

* **Parameters:**
  **fields** *str or iterable of str*
  : Numeric `Field2D` names to fill.

  **limit** *int or None, optional*
  : Maximum number of consecutive NaN values to fill. If `None`, all consecutive
    NaN values are eligible.

  **inplace** *bool, default=True*
  : If `True`, modify this panel. If `False`, return a shallow copy with filled
    fields.
* **Returns:**
  **panel** *AssetPanel*
  : Modified panel or copied panel.

<a id="skfolio.containers.AssetPanel.copy"></a>

#### copy(\*, deep=False)

Return a copy of the panel.

* **Parameters:**
  **deep** *bool, default=False*
  : If `True`, copy field arrays and label arrays. If `False`, field arrays and
    labels are shared. Masks are always copied so the copy owns independent
    lockable mask arrays.
* **Returns:**
  **panel** *AssetPanel*
  : Copied panel.

<a id="skfolio.containers.AssetPanel.decode_categorical_field"></a>

#### decode_categorical_field(name, \*, missing_label='MISSING')

Decode a categorical field to labels.

* **Parameters:**
  **name** *str*
  : Name of a `FieldCategorical` field.

  **missing_label** *str, default=”MISSING”*
  : Label assigned to missing or out-of-bound codes.
* **Returns:**
  **decoded** *ndarray*
  : Decoded labels with shape (n_observations, n_assets).

<a id="skfolio.containers.AssetPanel.describe"></a>

#### describe(\*, by=None)

Return a structured missingness summary.

* **Parameters:**
  **by** *str or None, optional*
  : Categorical field used to group missingness statistics. If `None`,
    missingness is summarized by field.
* **Returns:**
  **summary** *pandas.DataFrame*
  : Missingness summary indexed by field, or by `(field, category)`
    when `by` is provided.

<a id="skfolio.containers.AssetPanel.drop"></a>

#### drop(\*, observations=None, assets=None)

Return a panel with selected labels removed.

* **Parameters:**
  **observations** *scalar, iterable, or None, optional*
  : Observation labels to remove.

  **assets** *scalar, iterable, or None, optional*
  : Asset labels to remove.
* **Returns:**
  **panel** *AssetPanel*
  : New panel with the selected observations or assets removed.

<a id="skfolio.containers.AssetPanel.edit_masks"></a>

#### edit_masks(\*, \_validate=True)

Temporarily make masks editable.

On exit, `estimation_mask` is re-enforced as a subset of `active_mask`,
field inactive policies are applied, and both masks are locked again.

* **Parameters:**
  **\_validate** *bool, default=True*
  : Internal flag controlling the per-observation non-empty mask check after the
    context exits.
* **Yields:**
  None
  : The panel with editable mask arrays.

<a id="skfolio.containers.AssetPanel.ffill"></a>

#### ffill(fields, \*, limit=None, inplace=True)

Forward fill NaN values along the observation axis.

* **Parameters:**
  **fields** *str or iterable of str*
  : Numeric `Field2D` names to fill.

  **limit** *int or None, optional*
  : Maximum number of consecutive NaN values to fill. If `None`, all consecutive
    NaN values are eligible.

  **inplace** *bool, default=True*
  : If `True`, modify this panel. If `False`, return a shallow copy with filled
    fields.
* **Returns:**
  **panel** *AssetPanel*
  : Modified panel or copied panel.

<a id="skfolio.containers.AssetPanel.get_field"></a>

#### get_field(name)

Return a field object.

* **Parameters:**
  **name** *str*
  : Field name.
* **Returns:**
  **field** *BaseField*
  : Field object that owns the field values and metadata.

<a id="skfolio.containers.AssetPanel.info"></a>

#### info()

Multi-line report with panel dimensions, mask coverage, field missingness
and categorical field level coverage.

* **Returns:**
  **report** *str*
  : Multi-line report.

<a id="skfolio.containers.AssetPanel.isel"></a>

#### isel(\*, observations=None, assets=None)

Select observations and assets by integer position.

* **Parameters:**
  **observations** *int, slice, array-like, or None, optional*
  : Positional selector for the observation axis. If `None`, all  observations
    are selected.

  **assets** *int, slice, array-like, or None, optional*
  : Positional selector for the asset axis. If `None`, assets are not sliced and
    an `AssetPanelView` is returned.
* **Returns:**
  **panel or view** *AssetPanel or AssetPanelView*
  : Observation-only selections return a view. Selections that slice assets
    return a new panel.

<a id="skfolio.containers.AssetPanel.keys"></a>

#### keys()

Return field names.

* **Returns:**
  **names** *KeysView of str*
  : Names of all fields in the panel.

<a id="skfolio.containers.AssetPanel.load"></a>

#### *classmethod* load(path, \*, mmap_mode=None, fields=None)

Load a panel saved with `save`.

* **Parameters:**
  **path** *str or pathlib.Path*
  : Directory containing a saved panel.

  **mmap_mode** *str or None, optional*
  : Memory-mapping mode passed to `numpy.load` for field and mask arrays.
    Use `r` for read-only memory maps.

  **fields** *list of str or None, optional*
  : Field names to load. If `None`, all fields are loaded.
* **Returns:**
  **panel** *AssetPanel*
  : Loaded panel.

<a id="skfolio.containers.AssetPanel.n_assets"></a>

#### *property* n_assets

Number of assets.

<a id="skfolio.containers.AssetPanel.n_fields"></a>

#### *property* n_fields

Number of fields.

<a id="skfolio.containers.AssetPanel.n_observations"></a>

#### *property* n_observations

Number of observations.

<a id="skfolio.containers.AssetPanel.ndim"></a>

#### *property* ndim

Number of dimensions used by scikit-learn sample indexing.

<a id="skfolio.containers.AssetPanel.rename"></a>

#### rename(fields=None, \*, overwrite=False)

Rename fields in place.

* **Parameters:**
  **fields** *mapping of str to str or None, optional*
  : Mapping from existing field names to replacement names.

  **overwrite** *bool, default=False*
  : If `True`, an existing target field can be replaced by a renamed field.
    If `False`, name conflicts raise `KeyError`.
* **Returns:**
  **self** *AssetPanel*
  : The modified panel.

<a id="skfolio.containers.AssetPanel.save"></a>

#### save(path, \*, overwrite=False)

Save the panel to a directory of `.npy` files.

The directory contains one `.npy` file per field, small metadata files for
categorical and third-axis labels and a `_metadata.json` manifest.
Object-dtype axis labels and field metadata labels are converted to strings
so the panel is loaded with `allow_pickle=False`.

* **Parameters:**
  **path** *str or pathlib.Path*
  : Destination directory.

  **overwrite** *bool, default=False*
  : If `True`, replace an existing saved panel at `path`. Existing directories
    that do not contain `_metadata.json` are never overwritten.

<a id="skfolio.containers.AssetPanel.sel"></a>

#### sel(\*, observations=None, assets=None, fields=None)

Select observations, assets and fields by label.

* **Parameters:**
  **observations** *scalar, slice, iterable, or None, optional*
  : Observation labels to select. If `None`, all observations are selected.

  **assets** *scalar, slice, iterable, or None, optional*
  : Asset labels to select. If `None`, assets are not sliced and an
    : `AssetPanelView` is returned.

  **fields** *str, iterable of str, or None, optional*
  : Field names to select. If `None`, all fields are selected.
* **Returns:**
  **panel or view** *AssetPanel or AssetPanelView*
  : Observation-only selections without field filtering return a view.
    Selections that slice assets or fields return a new panel.

<a id="skfolio.containers.AssetPanel.sel_3d"></a>

#### sel_3d(name, \*, labels=None, groups=None)

Select entries from the third axis of a 3D field by label.

Exactly one of `labels` or `groups` must be provided. Selecting a single label
returns a 2D array with shape (n_observations, n_assets). Selecting multiple
labels or any group returns a 3D array whose first two axes are unchanged.

* **Parameters:**
  **name** *str*
  : Name of a `Field3D`.

  **labels** *scalar, iterable, or None, optional*
  : Third-axis labels to select.

  **groups** *scalar, iterable, or None, optional*
  : Third-axis group labels to select. The field must define `third_axis_groups`.
* **Returns:**
  **values** *ndarray*
  : Selected values. A scalar `labels` selection returns 2D values. All other
    selections return 3D values.

<a id="skfolio.containers.AssetPanel.shape"></a>

#### *property* shape

Shape tuple used by scikit-learn sample indexing.

<a id="skfolio.containers.AssetPanel.to_dataframe"></a>

#### to_dataframe(\*, fields=None, assets=None, output_format='long', decode_categoricals=True)

Convert 2D fields to a pandas DataFrame.

`Field3D` entries are skipped with a warning when multiple fields are converted.
Selecting a single `Field3D` raises `ValueError`.

* **Parameters:**
  **fields** *str, iterable of str, or None, optional*
  : Field names to include. If a single string is passed, the result is a simple
    field DataFrame with observations as index and assets as columns. If `None`,
    all 2D fields are included.

  **assets** *str, iterable of str, or None, optional*
  : Asset labels to include. If `None`, all assets are included.

  **output_format** *{“long”, “wide”}, default=”long”*
  : Output format used when `fields` is not a single string. In long format,
    rows are indexed by `(observation, asset)` and filtered by `active_mask`.
    In wide format, columns are indexed by `(field, asset)`.

  **decode_categoricals** *bool, default=True*
  : If `True`, categorical codes are decoded to labels.
* **Returns:**
  **df** *pandas.DataFrame*
  : DataFrame representation of the selected 2D fields.

