nemos.basis._basis.AdditiveBasis#

class nemos.basis._basis.AdditiveBasis(basis1, basis2, label=None)[source]#

Bases: CompositeBasisMixin, Basis

Class representing the addition of two Basis objects.

Parameters:
  • basis1 (BasisMixin) – First basis object to add.

  • basis2 (BasisMixin) – Second basis object to add.

  • label (Optional[str])

Examples

>>> # Generate sample data
>>> import numpy as np
>>> import nemos as nmo
>>> X = np.random.normal(size=(30, 2))
>>> # define two basis objects and add them
>>> basis_1 = nmo.basis.BSplineEval(10)
>>> basis_2 = nmo.basis.RaisedCosineLinearEval(15)
>>> additive_basis = basis_1 + basis_2
>>> additive_basis
'(BSplineEval + RaisedCosineLinearEval)': AdditiveBasis(
    ...
)
>>> # can add another basis to the AdditiveBasis object
>>> X = np.random.normal(size=(30, 3))
>>> basis_3 = nmo.basis.RaisedCosineLogEval(100)
>>> additive_basis_2 = additive_basis + basis_3
>>> additive_basis_2
'((BSplineEval + RaisedCosineLinearEval) + RaisedCosineLogEval)': AdditiveBasis(
    ...
)

Attributes

basis1

Return first component.

basis2

Return second component.

bounds

input_shape

Expected per-sample input shape.

is_complex

label

Label for the basis.

n_basis_funcs

Compute the n-basis function runtime.

n_output_features

Return the number of output features.

__init__(basis1, basis2, label=None)[source]#
Parameters:
  • basis1 (BasisMixin)

  • basis2 (BasisMixin)

  • label (str | None)

Return type:

None

Methods

__init__(basis1, basis2[, label])

compute_features(*xi)

Apply the basis transformation to the input data.

evaluate(*xi)

Evaluate the basis at the sample points.

evaluate_on_grid(*n_samples)

Evaluate the basis set on a grid of equi-spaced sample points.

get_params([deep])

Get parameters using labels.

set_input_shape(*xi)

Set the expected input shape for the basis object.

set_params(**params)

Map parameters using labels and set.

setup_basis(*xi)

Set all basis states.

split_by_feature(x[, axis])

Decompose an array along a specified axis into sub-arrays based on the basis components.

to_transformer()

Turn the Basis into a TransformerBasis for use with scikit-learn.

update_default_label_id(basis1, basis2)

Update basis2 atomic element labels.

__add__(other)#

Add two Basis objects together.

Parameters:

other (BasisMixin) – The other Basis object to add.

Returns:

The resulting Basis object.

Return type:

AdditiveBasis

classmethod __init_subclass__(**kwargs)#

Set the set_{method}_request methods.

This uses PEP-487 [1] to set the set_{method}_request methods. It looks for the information available in the set default values which are set using __metadata_request__* class attributes, or inferred from method signatures.

The __metadata_request__* class attributes are used when a method does not explicitly accept a metadata through its arguments or if the developer would like to specify a request value for those metadata which are different from the default None.

References

__iter__()[source]#

Iterate over components.

__len__()[source]#

Return the number of additive basis.

__mul__(other)#

Multiply two Basis objects together.

Parameters:

other (BasisMixin | int) – The other Basis object to multiply.

Return type:

Basis

Returns:

The resulting Basis object.

__pow__(exponent)#

Exponentiation of a Basis object.

Define the power of a basis by repeatedly applying the method __multiply__. The exponent must be a positive integer.

Parameters:

exponent (int) – Positive integer exponent

Return type:

BasisMixin

Returns:

The product of the basis with itself “exponent” times. Equivalent to self * self * ... * self.

Raises:
  • TypeError – If the provided exponent is not an integer.

  • ValueError – If the integer is zero or negative.

__repr__(n=0)#

Nested repr for composite basis.

__rmul__(other)#

Right multiplication operator for basis.

Parameters:

other (BasisMixin | int)

__sklearn_clone__()#

Clone the basis while preserving attributes related to input shapes.

This method ensures that input shape attributes (e.g., _input_shape_product, _input_shape_) are preserved during cloning. Reinitializing the class as in the regular sklearn clone would drop these attributes, rendering cross-validation unusable. The method also handles recursive cloning for composite basis structures.

Return type:

Basis

Notes

The _shallow_copy attribute is set to True in the context, forcing a shallow copy, at before the klass definition, and reset to False after cloning.

__sklearn_get_params__(deep=True)#

Implement standard scikit-learn get parameters by inspecting init.

This function will be called by Base.set_params() to get the actual param structure, since the inherited``get_params`` is overridden to use basis labels instead of the nested structure.

Parameters:

deep – If true, call itself recursively on basis implementing the method.

Returns:

A dictionary containing the parameters. Key is the parameter name, value is the parameter value.

Return type:

out

property basis1#

Return first component.

property basis2#

Return second component.

property bounds#
compute_features(*xi)[source]#

Apply the basis transformation to the input data.

This method is designed to be a high-level interface for transforming input data using the basis functions defined by the subclass. Depending on the basis’ mode (‘Eval’ or ‘Conv’), it either evaluates the basis functions at the sample points or performs a convolution operation between the input data and the basis functions.

Parameters:

*xi (ArrayLike | Tsd | TsdFrame | TsdTensor) – Input data arrays to be transformed. The shape and content requirements depend on the subclass and mode of operation (‘Eval’ or ‘Conv’).

Return type:

FeatureMatrix

Returns:

Transformed features. In ‘Eval’ mode, it corresponds to the basis functions evaluated at the input samples. In ‘Conv’ mode, it consists of convolved input samples with the basis functions. The output shape varies based on the subclass and mode.

Notes

Subclasses should implement how to handle the transformation specific to their basis function types and operation modes.

Examples

>>> import numpy as np
>>> from nemos.basis import BSplineEval, RaisedCosineLogConv
>>> from nemos.glm import GLM
>>> basis1 = BSplineEval(n_basis_funcs=5, label="one_input")
>>> basis2 = RaisedCosineLogConv(n_basis_funcs=6, window_size=10, label="two_inputs")
>>> basis_add = basis1 + basis2
>>> X_multi = basis_add.compute_features(np.random.randn(20), np.random.randn(20, 2))
>>> print(X_multi.shape) # num_features: 17 = 5 + 2*6
(20, 17)
evaluate(*xi)[source]#

Evaluate the basis at the sample points.

Parameters:
  • xi[0] ((n_samples,)) – Tuple of input samples, each with the same number of samples. The number of input arrays must equal the number of combined bases.

  • ... ((n_samples,)) – Tuple of input samples, each with the same number of samples. The number of input arrays must equal the number of combined bases.

  • xi[n] ((n_samples,)) – Tuple of input samples, each with the same number of samples. The number of input arrays must equal the number of combined bases.

  • xi (ArrayLike | Tsd | TsdFrame | TsdTensor)

Return type:

FeatureMatrix

Returns:

The basis function evaluated at the samples, shape (n_samples, n_basis_funcs)

Notes

Each additive component can process inputs of different shapes, as long as the sample axis matches. Input of mis-matched shape cannot be concatenated, therefore we enforce 1-dimensional input only for evaluate.

Examples

>>> # Generate sample data
>>> import numpy as np
>>> import nemos as nmo
>>> x, y = np.random.normal(size=(2, 30))
>>> # define two basis objects and add them
>>> basis_1 = nmo.basis.BSplineEval(10)
>>> basis_2 = nmo.basis.RaisedCosineLinearEval(15)
>>> additive_basis = basis_1 + basis_2
>>> # call the basis.
>>> out = additive_basis.evaluate(x, y)
evaluate_on_grid(*n_samples)[source]#

Evaluate the basis set on a grid of equi-spaced sample points.

The i-th axis of the grid will be sampled with n_samples[i] equi-spaced points. The method uses numpy.meshgrid with indexing="ij", returning matrix indexing instead of the default cartesian indexing, see Notes.

Parameters:
  • n_samples[0] – The number of points in the uniformly spaced grid. The length of n_samples must equal the number of combined bases.

  • ... – The number of points in the uniformly spaced grid. The length of n_samples must equal the number of combined bases.

  • n_samples[n] – The number of points in the uniformly spaced grid. The length of n_samples must equal the number of combined bases.

  • n_samples (int)

Return type:

Tuple[Tuple[NDArray], NDArray]

Returns:

  • *Xs – A tuple of arrays containing the meshgrid values, one element for each of the n dimension of the grid, where n equals to the number of inputs. The size of Xs[i] is (n_samples[0], ... , n_samples[n]).

  • Y – The basis function evaluated at the samples, shape (n_samples[0], ... , n_samples[n], number of basis).

Raises:
  • ValueError – If the time point number is inconsistent between inputs or if the number of inputs doesn’t match what the Basis object requires.

  • ValueError – If one of the n_samples is <= 0.

Notes

Setting indexing = 'ij' returns a meshgrid with matrix indexing. In the N-D case with inputs of size \(M_1,...,M_N\), outputs are of shape \((M_1, M_2, M_3, ....,M_N)\). This differs from the numpy.meshgrid default, which uses Cartesian indexing. For the same input, Cartesian indexing would return an output of shape \((M_2, M_1, M_3, ....,M_N)\).

Examples

>>> import numpy as np
>>> import nemos as nmo
>>> # define two basis objects and add them
>>> basis_1 = nmo.basis.BSplineEval(10)
>>> basis_2 = nmo.basis.RaisedCosineLinearEval(15)
>>> additive_basis = basis_1 + basis_2
>>> # evaluate on a grid of 10 x 10 equi-spaced samples
>>> X, Y, Z = additive_basis.evaluate_on_grid(10, 10)
get_metadata_routing()#

Get metadata routing of this object.

Please check User Guide on how the routing mechanism works.

Returns:

routing – A MetadataRequest encapsulating routing information.

Return type:

MetadataRequest

get_params(deep=True)#

Get parameters using labels.

Return type:

dict

property input_shape#

Expected per-sample input shape.

Returns:

If inputs are shaped (n_samples, *shape), returns shape.

property is_complex#
property label: str#

Label for the basis.

property n_basis_funcs#

Compute the n-basis function runtime.

This plays well with cross-validation where the number of basis function of the underlying bases can be changed. It must be read-only since the number of basis is determined by the two basis elements and the type of composition.

property n_output_features#

Return the number of output features.

set_input_shape(*xi)[source]#

Set the expected input shape for the basis object.

This method sets the input shape for each component basis in the basis. One xi must be provided for each basis component, specified as an integer, a tuple of integers, or an array. The method calculates and stores the total number of output features based on the number of basis functions in each component and the provided input shapes.

Parameters:

*xi (Union[int, tuple[int, ...], NDArray]) –

The input shape specifications. For every k,``xi[k]`` can be: - An integer: Represents the dimensionality of the input. A value of 1 is treated as scalar input. - A tuple: Represents the exact input shape excluding the first axis (sample axis).

All elements must be integers.

  • An array: The shape is extracted, excluding the first axis (assumed to be the sample axis).

Raises:

ValueError – If a tuple is provided, and it contains non-integer elements. If not enough inputs are provided.

Returns:

Returns the instance itself to allow method chaining.

Return type:

Basis

Examples

>>> # Generate sample data
>>> import numpy as np
>>> import nemos as nmo
>>> # define an additive basis
>>> basis_1 = nmo.basis.BSplineEval(5)
>>> basis_2 = nmo.basis.RaisedCosineLinearEval(6)
>>> basis_3 = nmo.basis.RaisedCosineLinearEval(7)
>>> additive_basis = basis_1 + basis_2 + basis_3

Specify the input shape using all 3 allowed ways: integer, tuple, array >>> _ = additive_basis.set_input_shape(1, (2, 3), np.ones((10, 4, 5)))

Expected output features are: (5 bases * 1 input) + (6 bases * 6 inputs) + (7 bases * 20 inputs) = 181 >>> additive_basis.n_output_features 181

set_params(**params)#

Map parameters using labels and set.

Parameters:

params (Any)

setup_basis(*xi)#

Set all basis states.

This method corresponds sklearn transformer fit. As fit, it must receive the input and it must set all basis states, i.e. kernel_ and all the states relative to the input shape. The difference between this method and the transformer fit is in the expected input structure, where the transformer fit method requires the inputs to be concatenated in a 2D array, while here each input is provided as a separate time series for each basis element.

Parameters:

xi (NDArray) – Input arrays.

Return type:

Basis

Returns:

The basis with ready for evaluation.

split_by_feature(x, axis=1)[source]#

Decompose an array along a specified axis into sub-arrays based on the basis components.

This function takes an array (e.g., a design matrix or model coefficients) and splits it along a designated axis. Each split corresponds to a different additive component of the basis, preserving all dimensions except the specified axis.

How It Works:

Suppose the basis is made up of m components, each with \(b_i\) basis functions and \(n_i\) inputs. The total number of features, \(N\), is calculated as:

\[N = b_1 \cdot n_1 + b_2 \cdot n_2 + \ldots + b_m \cdot n_m\]

This method splits any axis of length \(N\) into sub-arrays, one for each basis component.

The sub-array for the i-th basis component is reshaped into dimensions \((n_i, b_i)\).

For example, if the array shape is \((1, 2, N, 4, 5)\), then each split sub-array will have shape:

\[(1, 2, n_i, b_i, 4, 5)\]

where:

  • \(n_i\) represents the number of inputs associated with the i-th component,

  • \(b_i\) represents the number of basis functions in that component.

The specified axis (axis) determines where the split occurs, and all other dimensions remain unchanged. See the example section below for the most common use cases.

Parameters:
  • x (NDArray) –

    The input array to be split, representing concatenated features, coefficients, or other data. The shape of x along the specified axis must match the total number of features generated by the basis, i.e., self.n_output_features.

    Examples: - For a design matrix: (n_samples, total_n_features) - For model coefficients: (total_n_features,) or (total_n_features, n_neurons).

  • axis (int) – The axis along which to split the features. Defaults to 1. Use axis=1 for design matrices (features along columns) and axis=0 for coefficient arrays (features along rows). All other dimensions are preserved.

Raises:

ValueError – If the shape of x along the specified axis does not match self.n_output_features.

Returns:

A dictionary where:

  • Keys: Labels of the additive basis components.

  • Values: Sub-arrays corresponding to each component. Each sub-array has the shape:

\[(..., n_i1, n_i2,..,n_im, b_i, ...)\]
  • (n_samples, n_i1, n_i2,..,n_im): is the shape of the input to the i-th basis.

  • b_i: The number of basis functions for the i-th basis component.

These sub-arrays are reshaped along the specified axis, with all other dimensions remaining the same.

Return type:

dict

Examples

>>> import numpy as np
>>> from nemos.basis import BSplineConv
>>> from nemos.glm import GLM
>>> # Define an additive basis
>>> basis = (
...     BSplineConv(n_basis_funcs=5, window_size=10, label="feature_1") +
...     BSplineConv(n_basis_funcs=6, window_size=10, label="feature_2")
... )
>>> # Generate a sample input array and compute features
>>> x1, x2 = np.random.randn(20), np.random.randn(20)
>>> X = basis.compute_features(x1, x2)
>>> # Split the feature matrix along axis 1
>>> split_features = basis.split_by_feature(X, axis=1)
>>> for feature, arr in split_features.items():
...     print(f"{feature}: shape {arr.shape}")
feature_1: shape (20, 5)
feature_2: shape (20, 6)
>>> # If one of the basis components accepts multiple inputs, the resulting dictionary will be nested:
>>> multi_input_basis = BSplineConv(n_basis_funcs=6, window_size=10,
... label="multi_input")
>>> X_multi = multi_input_basis.compute_features(np.random.randn(20, 2))
>>> split_features_multi = multi_input_basis.split_by_feature(X_multi, axis=1)
>>> for feature, sub_dict in split_features_multi.items():
...        print(f"{feature}, shape {sub_dict.shape}")
multi_input, shape (20, 2, 6)
>>> # the method can be used to decompose the glm coefficients in the various features
>>> counts = np.random.poisson(size=20)
>>> model = GLM().fit(X, counts)
>>> split_coef = basis.split_by_feature(model.coef_, axis=0)
>>> for feature, coef in split_coef.items():
...     print(f"{feature}: shape {coef.shape}")
feature_1: shape (5,)
feature_2: shape (6,)
to_transformer()#

Turn the Basis into a TransformerBasis for use with scikit-learn.

Return type:

TransformerBasis

Examples

Jointly cross-validating basis and GLM parameters with scikit-learn.

>>> import nemos as nmo
>>> from sklearn.pipeline import Pipeline
>>> from sklearn.model_selection import GridSearchCV
>>> # load some data
>>> X, y = np.random.normal(size=(30, 1)), np.random.poisson(size=30)
>>> basis = nmo.basis.RaisedCosineLinearEval(10).set_input_shape(1).to_transformer()
>>> glm = nmo.glm.GLM(regularizer="Ridge", regularizer_strength=1.)
>>> pipeline = Pipeline([("basis", basis), ("glm", glm)])
>>> param_grid = dict(
...     glm__regularizer_strength=(0.1, 0.01, 0.001, 1e-6),
...     basis__n_basis_funcs=(3, 5, 10, 20, 100),
... )
>>> gridsearch = GridSearchCV(
...     pipeline,
...     param_grid=param_grid,
...     cv=5,
... )
>>> gridsearch = gridsearch.fit(X, y)
static update_default_label_id(basis1, basis2)#

Update basis2 atomic element labels.

When composing bases, update basis2 labels in order to disambiguate them with respect to basis1 labels. Here we assume that each tree (basis1 and basis2) have unique basis labels if taken separately, while they may have overlapping labels between them. This function updates the label of basis2 to avoid overlaps with basis1.

Parameters:
  • basis1 (Basis) – The first basis object.

  • basis2 (Basis) – The second basis object.

Notes

The method only operates on default labels (i.e. class names). User-defined labels will not be edited, and overlaps will result in ValueError at composite basis initialization.