Lazy arrays¶
The lazy implementation, LazyFrameLabeledArray, is backed entirely by
Narwhals LazyFrames and evaluates all
operations lazily. Where the eager implementation stores its values in
a real array, the lazy implementation stores everything (both values and labels) in a
relational, long format, where there is one row per array element.
This is useful when you want to express your computation in an array style but do not need any special interoperation with array libraries, and either do not care how the values are stored or already have your data in a DataFrame format supported by Narwhals. By staying in that format, you keep your data in the same backend it already lives in, and you let that backend's query engine optimize large computations.
Early development
The lazy implementation is still being built out and does not yet support the whole protocol. The places where a method is not yet available are called out below. For the full feature set today, use the eager implementation.
Creating a lazy array¶
There are two ways to create a lazy array.
From values and labels¶
You can use the same from_values_and_labels function as for eager arrays, but pass
implementation=pld.LAZY.
import numpy as np
import polars as pl
import polder as pld
values = np.array([
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
])
labels = [
pl.DataFrame({"city": ["Amsterdam", "Rotterdam"]}),
pl.DataFrame({"year": [2021, 2022, 2023]}),
]
array = pld.from_values_and_labels(values, labels, implementation=pld.LAZY)
From a single frame¶
If your data already lives in a single DataFrame in long format, from_frame turns it
into a one-dimensional lazy array directly. One column holds the values and the remaining
columns become the labels. The value column is called "value" by default, which you can
override with value_column.
import narwhals as nw
frame = pl.DataFrame({
"city": ["Amsterdam", "Rotterdam", "Utrecht"],
"value": [1.0, 2.0, 3.0],
})
array = pld.from_frame(nw.from_native(frame), value_column="value")
from_frame produces a lazy array by default, since keeping the data in its DataFrame
backend is the whole point. An array created this way is always one-dimensional. To get a
higher-dimensional array, use from_values_and_labels, or reshape with
Pivoting.
Resolving lazy computations¶
Because the implementation is lazy, an operation does not necessarily do any work when you
call it. The work happens when you extract concrete results. As a rule, you should expect a
computation to be resolved as soon as the array values are converted to an eager form, for
example when you call array.values().
values() returns a NumPy array regardless of the underlying DataFrame backend, by
collecting the lazy frames and reshaping the result.
If you only require a subset of the values, you can also using indexing syntax on values directly, which may allow for skipping some calculations:
You can also force the underlying frames to be collected without leaving the lazy
implementation, using collect. This resolves the pending computations and stores the
results in a new lazy array, which is useful when you want to materialize an intermediate
result.
Eager evaluation for debugging¶
Lazy evaluation can make errors surface far from where they originate. For debugging it can
help to evaluate eagerly, using DataFrames instead of LazyFrames internally. The
config.use_eager_evaluation_for_lazy_arrays setting controls this.
with pld.config.use_eager_evaluation_for_lazy_arrays(True):
array = pld.from_values_and_labels(values, labels, implementation=pld.LAZY)
# Errors now surface closer to the operation that caused them.
Note that some errors will still only surface lazily. For example, an invalid-shape error may only arise when the shape is extracted, not on the operation that produced it.
What is supported today¶
The lazy implementation supports decomposition (values, labels, shape), the unary and
binary mathematical operations, and pivoting.
The following protocol members currently raise NotImplementedError on lazy arrays:
- Indexing with
__getitem__. - Equality checking with
equals. - Matrix multiplication with
@.
If you need any of these, convert to the eager implementation first.