Skip to content

ValueError: The truth value of an empty array is ambiguous during materialization #6255

Description

@alan-gauthier-jt

Expected Behavior

feast materialize should complete successfully even when the source DataFrame
contains an empty numpy array (np.array([])) in a scalar feature column.
The empty array should be treated as a null / missing value and produce an empty
ProtoValue(), consistent with how None and np.nan are already handled.

Current Behavior

feast materialize crashes with:

ValueError: The truth value of an empty array is ambiguous.
Use `array.size > 0` to check that an array is not empty.

Full stack trace:

Traceback (most recent call last):
  File "/opt/app-root/bin/feast", line 10, in <module>
    sys.exit(cli())
             ^^^^^
  File "/opt/app-root/lib64/python3.11/site-packages/click/core.py", line 1485, in __call__
    return self.main(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/app-root/lib64/python3.11/site-packages/click/core.py", line 1406, in main
    rv = self.invoke(ctx)
         ^^^^^^^^^^^^^^^^
  File "/opt/app-root/lib64/python3.11/site-packages/click/core.py", line 1873, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/app-root/lib64/python3.11/site-packages/click/core.py", line 1269, in invoke
    return ctx.invoke(self.callback, **ctx.params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/app-root/lib64/python3.11/site-packages/click/core.py", line 824, in invoke
    return callback(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/app-root/lib64/python3.11/site-packages/click/decorators.py", line 34, in new_func
    return f(get_current_context(), *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/app-root/lib64/python3.11/site-packages/feast/cli/cli.py", line 393, in materialize_command
    store.materialize(
  File "/opt/app-root/lib64/python3.11/site-packages/feast/feature_store.py", line 1816, in materialize
    provider.materialize_single_feature_view(
  File "/opt/app-root/lib64/python3.11/site-packages/feast/infra/passthrough_provider.py", line 456, in materialize_single_feature_view
    raise e
  File "/opt/app-root/lib64/python3.11/site-packages/feast/infra/compute_engines/local/compute.py", line 84, in _materialize_one
    plan.execute(context)
  File "/opt/app-root/lib64/python3.11/site-packages/feast/infra/compute_engines/dag/plan.py", line 51, in execute
    output = node.execute(context)
             ^^^^^^^^^^^^^^^^^^^^^
  File "/opt/app-root/lib64/python3.11/site-packages/feast/infra/compute_engines/local/nodes.py", line 274, in execute
    rows_to_write = _convert_arrow_to_proto(
                    ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/app-root/lib64/python3.11/site-packages/feast/utils.py", line 281, in _convert_arrow_to_proto
    return _convert_arrow_fv_to_proto(table, feature_view, join_keys)  # type: ignore[arg-type]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/app-root/lib64/python3.11/site-packages/feast/utils.py", line 298, in _convert_arrow_fv_to_proto
    proto_values_by_column = {
                             ^
  File "/opt/app-root/lib64/python3.11/site-packages/feast/utils.py", line 299, in <dictcomp>
    column: python_values_to_proto_values(
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/app-root/lib64/python3.11/site-packages/feast/type_map.py", line 840, in python_values_to_proto_values
    proto_values = _python_value_to_proto_value(value_type, values)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/app-root/lib64/python3.11/site-packages/feast/type_map.py", line 772, in _python_value_to_proto_value
    elif not pd.isnull(value):
ValueError: The truth value of an empty array is ambiguous. Use `array.size > 0` to check that an array is not empty.

The root cause is in sdk/python/feast/type_map.py,
function _convert_scalar_values_to_proto (around line 968):

# Generic scalar conversion
out = []
for value in values:
    if isinstance(value, ProtoValue):
        out.append(value)
    elif not pd.isnull(value):          # ← crashes here
        out.append(ProtoValue(**{field_name: func(value)}))
    else:
        out.append(ProtoValue())

pd.isnull() is vectorised: when value is a numpy array (including an empty
one), it returns a numpy array of booleans instead of a scalar boolean. Applying
Python's not to that array raises ValueError. The same pattern exists a few lines
above in the ValueType.BOOL path (if not pd.isnull(value)).

Steps to reproduce

import numpy as np
from feast.type_map import python_values_to_proto_values
from feast.value_type import ValueType

# A scalar column where one row contains an empty array
python_values_to_proto_values([np.array([]), 1.0, 2.0], ValueType.DOUBLE)
# → ValueError: The truth value of an empty array is ambiguous

Specifications

  • Version: v0.60.0 (also reproducible on main as of 2026-04-10)
  • Platform: Linux (Python 3.11), macOS (Python 3.11)
  • Subsystem: feast/type_map.py – _convert_scalar_values_to_proto

Possible Solution

The fix belongs in sdk/python/feast/type_map.py, specifically the generic scalar conversion loop at line 963–975 (elif not pd.isnull(value)).

Before calling not pd.isnull(value), check whether the value is array-like.
pd.isnull() is vectorised and returns an np.ndarray for array inputs, so
calling not on it raises ValueError. The fix must handle three sub-cases:

Value Expected outcome
Empty array (size == 0) null → ProtoValue()
Non-empty array containing any null null → ProtoValue()
Non-empty array with all valid data convert → ProtoValue(**{field_name: func(value)})
Plain scalar null null → ProtoValue()
Plain scalar non-null convert → ProtoValue(**{field_name: func(value)})
# Generic scalar conversion
out = []
for value in values:
    if isinstance(value, ProtoValue):
        out.append(value)
    elif isinstance(value, np.ndarray) or (
        hasattr(value, "__len__") and not isinstance(value, (str, bytes))
    ):
        # Array-like value in a scalar column
        if hasattr(value, "size") and value.size == 0:
            # Empty numpy array – treat as null
            out.append(ProtoValue())
        else:
            is_null = pd.isnull(value)
            if hasattr(is_null, "any"):
                # pd.isnull returned an array; null if any element is null
                out.append(ProtoValue() if is_null.any() else ProtoValue(**{field_name: func(value)}))
            elif not is_null:
                out.append(ProtoValue(**{field_name: func(value)}))
            else:
                out.append(ProtoValue())
    elif not pd.isnull(value):
        out.append(ProtoValue(**{field_name: func(value)}))
    else:
        out.append(ProtoValue())
return out

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions