Skip to content

Add sparse.COO support to harmonize_missing_values and infer_feature_types - #306

Draft
sueoglu wants to merge 3 commits into
mainfrom
fix/harmonize-missing-sparse
Draft

sueoglu wants to merge 3 commits into
mainfrom
fix/harmonize-missing-sparse

Conversation

@sueoglu

@sueoglu sueoglu commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator

Both functions previously either crashed or silently no-op'd on a sparse.COO X/layer. This adds proper support to both, without ever densifying the array.

infer_feature_types

  • sparse.COO is always numeric under the binsparse spec this project follows, so classification per variable only needs to decide "is this column exactly {0, 1}?" which is answerable directly from X.coords/X.data/X.fill_value
  • _detect_feature_types_sparse_coo helper implements this; infer_feature_types branches to it for sparse.COO input and keeps the existing dataframe-based path unchanged for everything else.

harmonize_missing_values

  • For sparse.COO, the implicit 0 fill value is now treated as missing (swapped to NaN) by default (done by flipping the array's fill_value from 0 to NaN, so the array stays sparse)
  • vars argument: variable names whose 0 is a real measured value, not missing. Previously-implicit zeros of these columns are materialized as explicit stored 0 entries (so they dont get changed to the new NaN fill value); every other column stays sparse
  • Left untouched: boolean arrays, scipy.sparse

Tests: added coverage in test_feature_types.py for 2D/3D sparse.COO harmonization, vars exclusion, an unknown-var error, scipy.sparse being unaffected, and sparse.COO feature-type inference (including binary detection and the all-NaN-column error). Two pre-existing IO round-trip tests (test_h5ed.py/test_zarr.py) updated to pass harmonize_missing_values=False, since they test binsparse encoding fidelity specifically and predate this behavior change.

@sueoglu
sueoglu requested a review from eroell September 18, 2026 08:49

@eroell eroell left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a benchmark with compute time and memory consumption in the PR comment, where a large dense 3D and a large sparse 3D, and also the 2D sparse and dense arrays are tested.

The sparse COO version should run for a ~500k x 1000k x 90 tensor of about 1% data density.

) -> list[tuple[Literal["categorical", "numeric"], bool]]:
"""Detect the feature type of every variable (axis 1) of a numeric `sparse.COO` array, without densifying.

A `sparse.COO` array is always numeric, so classification only needs to decide, per variable, whether its values

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this true?


if issparse(X):
X = to_dense(X)
if isinstance(X, sparse.COO):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we, now that we're at it, have this function single-dispatched for the different array types we're using? sparse.COO, scipy sparse CSR, scipy Sparse CSC. Without densifying


@_harmonize_missing_values_numeric.register(sparse.COO)
def _(X: sparse.COO, *, var_names: pd.Index, vars: Iterable[str] | None) -> tuple[sparse.COO, bool]:
"""Swap a numeric :class:`sparse.COO` array's zero fill value to `np.nan`, keeping `vars` columns real.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Less comments in internal functions please, they get very easily out of sync with actual code if they're that long

assert missing_value_string not in edata.layers[DEFAULT_TEM_LAYER_NAME].flatten()


def test_harmonize_missing_values_sparse_coo():

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we also have parametrized tests that show that the feature type inferences behaves the same across all array types?

Whenever we update a function to work with other array types, we most likely want to update existing tests with parametrization of the new array type :)

fill_is_nan = np.issubdtype(X.dtype, np.floating) and np.isnan(X.fill_value)

results = []
for j in range(n_vars):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is claude really sure that this for loop is needed?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants