GH-50906: [Python] Reshape 1D tensors to 2D in SparseCSR/CSC matrix conversion - #50907
GH-50906: [Python] Reshape 1D tensors to 2D in SparseCSR/CSC matrix conversion#50907pratyushadk wants to merge 1 commit into
Conversation
|
|
fd4c81d to
0e17ebe
Compare
|
@pratyushadk I've renamed the issue and the PR to reflect that this should be implemented in Python. Let's move the discussion here. |
0e17ebe to
cccd951
Compare
|
@rok Thanks for the direction. I'm new to open source and your feedback really helped me understand where the fix actually belongs. Updated the PR with the Python reshape approach. Looking forward to contributing more in the future! |
| def test_sparse_csr_matrix_from_1d(): | ||
| array = np.array([1, 0, 2, 0, 0, 3, 0, 4], dtype=np.int64) | ||
| tensor = pa.Tensor.from_numpy(array) | ||
|
|
||
| sparse = pa.SparseCSRMatrix.from_dense_numpy(array) | ||
| assert sparse.shape == (1, 8) | ||
| assert sparse.non_zero_length == 4 | ||
|
|
||
| sparse = pa.SparseCSRMatrix.from_tensor(tensor) | ||
| assert sparse.shape == (1, 8) | ||
| assert sparse.non_zero_length == 4 | ||
|
|
||
| dense = sparse.to_tensor() | ||
| assert dense.shape == (1, 8) | ||
| assert np.array_equal(np.array(dense).ravel(), array) | ||
|
|
||
|
|
||
| def test_sparse_csc_matrix_from_1d(): | ||
| array = np.array([1, 0, 2, 0, 0, 3, 0, 4], dtype=np.int64) | ||
| tensor = pa.Tensor.from_numpy(array) | ||
|
|
||
| sparse = pa.SparseCSCMatrix.from_dense_numpy(array) | ||
| assert sparse.shape == (1, 8) | ||
| assert sparse.non_zero_length == 4 | ||
|
|
||
| sparse = pa.SparseCSCMatrix.from_tensor(tensor) | ||
| assert sparse.shape == (1, 8) | ||
| assert sparse.non_zero_length == 4 |
There was a problem hiding this comment.
Please make these parametric so they can be expressed as a single test and compare pyarrow's behavior to scipy's.
cccd951 to
6c7f030
Compare
| csr_array, csc_array, coo_array, csr_matrix, csc_matrix, coo_matrix | ||
| ) | ||
| except ImportError: | ||
| coo_matrix = None | ||
| csr_matrix = None | ||
| csc_matrix = None | ||
| csr_array = None | ||
| csc_array = None |
There was a problem hiding this comment.
You don't seem to be using csc_array. Please remove the import.
| if sc_class is None: | ||
| pytest.skip('scipy not available') |
There was a problem hiding this comment.
Elsewhere in this file we use:
@pytest.mark.skipif(not coo_matrix, reason="requires scipy")as a test decorator. Let's do the same here.
6c7f030 to
9eec858
Compare
| except ImportError: | ||
| coo_matrix = None | ||
| csr_matrix = None | ||
| csc_matrix = None |
There was a problem hiding this comment.
Could we use csc_array and avoid even this new import?
There was a problem hiding this comment.
Yes, I removed that new import. Thanks for pointing it out!
9eec858 to
b6e8abf
Compare
| sparse_tensor = pa_class.from_tensor(tensor) | ||
| assert np.array_equal(sparse_tensor.to_tensor().to_numpy(), | ||
| scipy_matrix.toarray()) |
| @pytest.mark.skipif(not csr_matrix, reason="requires scipy") | ||
| @pytest.mark.parametrize('pa_class', [ | ||
| pa.SparseCSRMatrix, | ||
| pa.SparseCSCMatrix, | ||
| ]) |
There was a problem hiding this comment.
We've lost csc and csr scipy objects. Try introducing csr_array and csc_array.
There was a problem hiding this comment.
I added csr_array and csc_array coverage by checking the type returned from to_scipy() for both CSR and CSC.
For the 1D dense-input reference, I retained csr_matrix and csc_matrix: csc_array does not accept 1D input (ValueError: CSC arrays don't support 1D input. Use 2D), while csr_array preserves it as 1D (n,). The matrix classes normalize the input to (1, n), matching Arrow's result. I also removed the redundant from_dense_numpy check.
b6e8abf to
ab6a7e0
Compare
Rationale for this change
Calling
SparseCSRMatrix.from_tensor()orSparseCSCMatrix.from_tensor()with a 1D Arrow tensor currently raises aNotImplementederror from the C++ layer. A 1D vector has a natural sparse representation as a single-row matrix — the same way scipy treats 1D input tocsr_array. This change adds that support at the Python layer by reshaping the tensor to[1, n]before passing it to the C++ backend.What changes are included in this PR?
python/pyarrow/tensor.pxiSparseCSRMatrix.from_tensorandSparseCSCMatrix.from_tensornow check if the input tensor is 1D. If so, it is reshaped to[1, n](a single-row matrix) before being passed to the C++ conversion layer.python/pyarrow/tests/test_sparse_tensor.pyTwo new tests:
test_sparse_csr_matrix_from_1dandtest_sparse_csc_matrix_from_1d. Each verifies creation from a 1D numpy array (from_dense_numpy), creation from a 1D Arrow tensor (from_tensor), and that the resulting shape is(1, n)with the correct non-zero count.Are these changes tested?
Yes. The two new tests cover both
from_dense_numpyandfrom_tensorpaths for CSR and CSC. The existing sparse tensor test suite is unchanged.Are there any user-facing changes?
SparseCSRMatrix.from_tensor,SparseCSRMatrix.from_dense_numpy,SparseCSCMatrix.from_tensor, andSparseCSCMatrix.from_dense_numpynow accept 1D tensors and numpy arrays. Previously these raised aNotImplementederror.