Skip to content

Enable config setting sparse_interface to control sparray and spmatrix creation #31177

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

dschult
Copy link
Contributor

@dschult dschult commented Apr 11, 2025

This PR sets up a config parameter sparse_interface to indicate "sparray" or "spmatrix" outputs, as suggested in #26418.

The first commit sets everything up and implements the system for a few modules. Please take a look and provide feedback for whether this is the way to proceed. The next commit(s) will implement these same style of changes throughout the library. If you would prefer they be in separate PRs let me know. (I'll keep Draft status until there is feedback and the full library is convered.)

More specifically, this PR does the following:

  • adds sparse_interface to the config parameters. (I think this name is better than sparse_format because "format" means csr/coo/lil, etc in the sparse world.) The values it can hold are "sparray" or "spmatrix". Update config tests accordingly.
  • adds utils._sparse.py with (private) helper functions. The difference is how much checking is done. Tests added too.
    • _as_sparse(x_sparse), raises unless sparse input. converts to interface chosen by config.
    • _select_interface_if_sparse(x), allows dense input with no action, sparse input uses _as_sparse.
    • one-line convenience functions: _convert_from_spmatrix_to_sparray(x) and _convert_from_sparray_to_spmatrix(x)
  • updates the following modules to use this helper utility to return or store newly created sparse objects.
    • sklearn/feature_selection/text.py and adapts tests.
    • sklearn/linear_model/_coordinate_descent.py no tests change needed.
    • sklearn/manifold/_locally_linear.py no tests change needed.

@thomasjpfan can you see if this does what you had in mind? I tried to pick modules that cover returning sparse, setting estimators to hold sparse, and transforming to sparse, so you can see how this would work.

Let me know if you think _as_sparse should be a public function, and if my approach aligns with how you want it. The next steps for this PR are to repeat these type of changes throughout the library.

Copy link

github-actions bot commented Apr 11, 2025

✔️ Linting Passed

All linting checks passed. Your pull request is in excellent shape! ☀️

Generated for commit: 72fa791. Link to the linter CI: here

@dschult
Copy link
Contributor Author

dschult commented Apr 11, 2025

Another note:
The new sparse construction function eye_array(n) which is the sparray version of eye(n) was released in SciPy v1.12 along with other construction functions like diags_array. So they will not work with the oldest supported version being v1.8.

We can work around it for now with e.g. _as_sparse(eye(n)), but it will need to be updated later (before spmatrix is removed).

The recent features for sparse by version are:

  • v1.12 added construction function e.g. eye_array, diags_array, etc
  • v1.14 added 1D sparray support
  • v1.15 added indexing for sparray which returns 1D objects (like numpy.array does), e.g. A[3,:] -> 1D array
    goals: v1.16 nD support, v1.17 broadcasting of binary operations.

This info might help us decide when to support which versions. I think the construction functions are all that is currently needed. If/when we start using indexing code for both sparse and dense, we will likely want 1.15. If/when we want nD sparse we will need v1.16, and broadcasting binary operations in v1.17. But for now, 1.8 leaves out only construction functions from current code.

@dschult
Copy link
Contributor Author

dschult commented May 4, 2025

It sounds like the community has decided to convert to SciPy sparray using the config parameter. This PR is a start toward that. I've removed the "Draft status" from the PR. I am ready to implement this approach in other parts of the library.

I have two questions:

  • should I put the changes for other parts of the library in a different PR?
  • Is there a timeline for bumping up the minimum version dependence on scipy? I will likely want to add a few functions to fixes.py and knowing their expected lifetime would give context to that effort. If it isn't known, that's a fine answer too.

@dschult dschult marked this pull request as ready for review May 4, 2025 03:35
Copy link
Member

@thomasjpfan thomasjpfan left a comment

Choose a reason for hiding this comment

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

Yup, this is basically what I had in mind.

Comment on lines 1617 to 1620
if _as_sparse(X_csr) is X_csr:
assert X_transform is X_csr
else:
assert X_transform is not X_csr
Copy link
Member

Choose a reason for hiding this comment

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

Although the object identity is not the same, is the underlying data/indices/indexes pointing to the same data? If so, we can check the data?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes -- the underlying data is the same. Good idea. I've added a check for indptr.

from .._config import get_config


def _as_sparse(X_sparse):
Copy link
Member

Choose a reason for hiding this comment

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

Do you think it'll be simpler to have _as_sparse be _select_interface_if_sparse?

Copy link
Contributor Author

@dschult dschult May 6, 2025

Choose a reason for hiding this comment

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

I guess the difference between functions is whether we know already that the input is sparse. We usually know whether it is sparse. But there are some cases where the input could be dense or sparse.

Looking at this again now, the cost of a simpler one-function approach is small. We always check issparse so we could forego the exception and just pass-through anything that is not sparse.

Do you have a suggestion for the name of the single function? _as_sparse suggests it converts everything to sparse. But _select_interface_if_sparse is a long name. :) Maybe _align_api_if_sparse? Or _align_sparse_api?

Copy link
Member

Choose a reason for hiding this comment

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

I'm okay with _align_api_if_sparse.

@dschult dschult force-pushed the impl_as_sparse_function branch 2 times, most recently from 86b2de7 to 61d6dc0 Compare May 7, 2025 13:07
@dschult dschult force-pushed the impl_as_sparse_function branch from 5e3d3cd to 511de76 Compare May 8, 2025 03:29
@dschult
Copy link
Contributor Author

dschult commented May 8, 2025

I've updated the name and example code -- and I added a commit that implements (with a utils shim function for SciPy versions older than 1.12) the 4 sparray construction functions. I've named them _sparse_eye, _sparse_diags, _sparse_random and _sparse_block. They call eye_array, diags_array, random_array and block_array on recent versions of SciPy.

These changes are largely orthogonal to the return sparse interface issue we've been focusing on here. But it is a needed step that would be easier to get feedback on here before this gets any bigger. If you'd prefer this in a different PR let me know. And if it'd be good to put further changes in a separate PR let me know. The further changes are mostly switching csr_matrix calls to csr_array with _align_api_if_sparse at the end of the function if it will be returned or stored somewhere.

Do my choices for function names that bridge the old versions of SciPy look ok?
Thanks!

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