Skip to content
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

ENH: Generalize split function to split matrix into sub-matrices. #15810

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

Conversation

KoningR
Copy link

@KoningR KoningR commented Mar 23, 2020

Closes #15380

Co-authored-by: KoningR r.m.koning@student.tudelft.nl
Co-authored-by: erwinvanthiel e.l.vanthiel@student.tudelft.nl

Closes numpy#15380

Co-authored-by: KoningR <r.m.koning@student.tudelft.nl>
Co-authored-by: erwinvanthiel <e.l.vanthiel@student.tudelft.nl>
@eric-wieser
Copy link
Member

eric-wieser commented Mar 23, 2020

Thanks for the contribution.

This is a good start, but in the context of the "0, 1, infinity rule", I don't think we should take this patch in its current form.
Essentially, if we're going to generalize a function that works on 1d arrays, we should generalize it to nd arrays, and not intermediately generalize to 2d.

If possible, do you think you could implement the suggestion in #15380 (comment)? Modifying one of your existing examples, that would give:

    >>> xs = np.array_split(x, [3, 3], axis=(0, 1))
    >>> xs.shape
    (3, 3)
    >>> xs
    array([[array([[0, 1], [4, 5]),
            array([[2], [6]]),
            array([[3], [7]])],
           [array([[8, 9]]),
            array([[10]]),
            array([[11]])],
           [array([[12, 13]]),
            array([[14]]),
            array([[15]])]], dtype=object)

KoningR and others added 3 commits March 24, 2020 18:01
Closes numpy#15380

Co-authored-by: KoningR <r.m.koning@student.tudelft.nl>
Co-authored-by: erwinvanthiel <e.l.vanthiel@student.tudelft.nl>
…ate.

Co-authored-by: KoningR <r.m.koning@student.tudelft.nl>
Co-authored-by: erwinvanthiel <e.l.vanthiel@student.tudelft.nl>
raise ValueError('Axis is a tuple, so indices_or_sections must be of the same length as axis.')

# Verify there is an index or section for every axis.
if len(indices_or_sections) is not len(axis):
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if len(indices_or_sections) is not len(axis):
if len(indices_or_sections) != len(axis):

Comparing integers with is is not guaranteed to work

@charris charris added the 56 - Needs Release Note. Needs an entry in doc/release/upcoming_changes label Mar 24, 2020
@charris
Copy link
Member

charris commented Mar 24, 2020

Also needs a release note if it goes in.

KoningR and others added 2 commits March 25, 2020 12:01
…n operator in N-dimensional split functionality.

Co-authored-by: KoningR <r.m.koning@student.tudelft.nl>
Co-authored-by: erwinvanthiel <e.l.vanthiel@student.tudelft.nl>
Comment on lines 759 to 784
>>> np.array_split(x, 3)
[array([0., 1., 2.]), array([3., 4.]), array([5., 6.])]

>>> x = np.reshape(np.arange(16), (4, 4))
>>> np.array_split(x, [3, 3], (0, 1))
[array([[0, 1],
[4, 5]]),
array([[2], [6]]),
array([[3], [7]]),
array([[8, 9]]),
array([[10]]),
array([[11]]),
array([[12, 13]]),
array([[14]]),
array([[15]])]

>>> x = np.reshape(np.arange(8), (2, 2, 2))
>>> np.array_split(x, [2, 2, 2], (0, 1, 2))
[array([[[0]]]),
array([[[1]]]),
array([[[2]]]),
array([[[3]]]),
array([[[4]]]),
array([[[5]]]),
array([[[6]]]),
array([[[7]]])]
Copy link
Member

Choose a reason for hiding this comment

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

Formatting is off here:

Suggested change
>>> np.array_split(x, 3)
[array([0., 1., 2.]), array([3., 4.]), array([5., 6.])]
>>> x = np.reshape(np.arange(16), (4, 4))
>>> np.array_split(x, [3, 3], (0, 1))
[array([[0, 1],
[4, 5]]),
array([[2], [6]]),
array([[3], [7]]),
array([[8, 9]]),
array([[10]]),
array([[11]]),
array([[12, 13]]),
array([[14]]),
array([[15]])]
>>> x = np.reshape(np.arange(8), (2, 2, 2))
>>> np.array_split(x, [2, 2, 2], (0, 1, 2))
[array([[[0]]]),
array([[[1]]]),
array([[[2]]]),
array([[[3]]]),
array([[[4]]]),
array([[[5]]]),
array([[[6]]]),
array([[[7]]])]
>>> np.array_split(x, 3)
[array([0., 1., 2.]), array([3., 4.]), array([5., 6.])]
>>> x = np.reshape(np.arange(16), (4, 4))
>>> np.array_split(x, [3, 3], (0, 1))
[array([[0, 1],
[4, 5]]),
array([[2], [6]]),
array([[3], [7]]),
array([[8, 9]]),
array([[10]]),
array([[11]]),
array([[12, 13]]),
array([[14]]),
array([[15]])]
>>> x = np.reshape(np.arange(8), (2, 2, 2))
>>> np.array_split(x, [2, 2, 2], (0, 1, 2))
[array([[[0]]]),
array([[[1]]]),
array([[[2]]]),
array([[[3]]]),
array([[[4]]]),
array([[[5]]]),
array([[[6]]]),
array([[[7]]])]

Comment on lines 922 to 930
>>> np.split(x, [4, 2], (0, 1))
[array([[0, 1]]),
array([[2, 3]]),
array([[4, 5]]),
array([[6, 7]]),
array([[8, 9]]),
array([[10, 11]]),
array([[12, 13]]),
array([[14, 15]])]
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
>>> np.split(x, [4, 2], (0, 1))
[array([[0, 1]]),
array([[2, 3]]),
array([[4, 5]]),
array([[6, 7]]),
array([[8, 9]]),
array([[10, 11]]),
array([[12, 13]]),
array([[14, 15]])]
[array([[0, 1]]),
array([[2, 3]]),
array([[4, 5]]),
array([[6, 7]]),
array([[8, 9]]),
array([[10, 11]]),
array([[12, 13]]),
array([[14, 15]])]

Comment on lines 327 to 328
[[0, 1],
[4, 5]],
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
[[0, 1],
[4, 5]],
[[0, 1],
[4, 5]],

Copy link
Member

Choose a reason for hiding this comment

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

or

Suggested change
[[0, 1],
[4, 5]],
[[0, 1], [4, 5]],

@eric-wieser
Copy link
Member

eric-wieser commented Mar 25, 2020

It's my opinion that blocks = np.split(arr, (X, Y), (0, 1)) should return an object array of shape (X, Y), not a list of length X*Y. The latter is easy to construct from the former as blocks.ravel().tolist(), but the former is much harder to construct from the latter.

The advantage would be that things like blocks[0,:] could be used to select the first row of blocks, etc.

For compatilibity, you'd only return an object array if a tuple is passed for the number of splits.

@KoningR
Copy link
Author

KoningR commented Mar 30, 2020

@eric-wieser

Hi,
We stumbled upon the problem that the sub-matrices within the array do not necessarily have the same dimensions.
This is the case quite often when splitting on indices instead of scalars. For example:

matrix = np.reshape(np.arange(16), (4, 4))
res = array_split(matrix, [[2, 3], [2, 3]], (0, 1))

In this case, res will be:
res = [
[[0, 1], [4, 5]],
[[2], [6]],
[[3], [7]],
[[8, 9]],
[[10]],
[[11]],
[[12, 13]],
[[14]],
[[15]]
]

The elements in res are clearly not of the same size.

What would be the best way to convert this to an N-dimensional array?
We already tried to reshape the res array but that seems to require all elements be of the same dimensions.

@eric-wieser
Copy link
Member

Just a tip - use triple backticks for multi-line codeblocks in comments.

@eric-wieser
Copy link
Member

eric-wieser commented Mar 30, 2020

One way to transform that would be:

# as you have it now
res = [
    np.array([[0, 1], [4, 5]]),
    np.array([[2], [6]]),
    np.array([[3], [7]]),
    np.array([[8, 9]]),
    np.array([[10]]),
    np.array([[11]]),
    np.array([[12, 13]]),
    np.array([[14]]),
    np.array([[15]]),
]

# convert list to object array.
# It's possible you can assemble it this way in the first place,
# but doesn't really matter if you do it this way
res_arr = np.empty(len(res), dtype=object)
for i, subarr in enumerate(res):
    res_arr[i] = subarr

# I assume you already have a way to compute this shape
return res.reshape((3, 3))

KoningR and others added 4 commits April 1, 2020 18:53
Co-authored-by: KoningR <r.m.koning@student.tudelft.nl>
Co-authored-by: erwinvanthiel <e.l.vanthiel@student.tudelft.nl>
Co-authored-by: KoningR <r.m.koning@student.tudelft.nl>
Co-authored-by: erwinvanthiel <e.l.vanthiel@student.tudelft.nl>
@KoningR
Copy link
Author

KoningR commented Apr 14, 2020

@eric-wieser

Hi,
Currently, two tests are failing the pipeline. Both tests appear to execute the commented examples on lines 761 and 922 of shape_base.py. The only differences between the expected and received outputs are whitespace characters. The output on lines 48-54 of the error log appears to be formatted in a rather odd fashion, though we cannot figure out what causes this as the object itself seems to behave correctly. Therefore, it appears that only the string representation of the object is odd. Should we reformat the example as to match the object array’s string formatting? Or did we overlook anything and are the contents of the object array wrong, causing the string to be wrong as well?

The string representation of the object array:

    array([[array([[0, 1],
           [4, 5]]), array([[2],
           [6]]),
            array([[3],
           [7]])],
           [array([[8, 9]]), array([[10]]), array([[11]])],
           [array([[12, 13]]), array([[14]]), array([[15]])]], dtype=object)

@eric-wieser
Copy link
Member

Unfortunately you're running up against #15869.

@KoningR
Copy link
Author

KoningR commented May 14, 2020

@eric-wieser

Hi. It appears PR #15997 was delayed because it might break doctests already in the code base. However, we are now dependent on this PR. If doctests will be refactored sometime in the future anyhow, can we push our doctests such that they pass the build, and be done with this issue?

@eric-wieser eric-wieser self-requested a review May 14, 2020 13:08
@eric-wieser
Copy link
Member

The PR you reference is now merged, so that question is moot :)

instead of splitting in parts, just like a one-dimensional call to `numpy.split` or `numpy.array_split`.

To use this new functionality, write ``np.split(array, [4, 2], (0, 1))`` or ``np.array_split(array, [4, 2], (0, 1))``
to split ``array`` in ``4`` parts over axis ``0`` and then in ``2`` parts over axis ``1``.
Copy link
Member

@mattip mattip May 20, 2020

Choose a reason for hiding this comment

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

needs some rewrapping to fit into 80 columns

Edit: ... and in the rest of the PR as well

``axis``. Also note that these elements can also be lists themselves, to allow for splitting on indices
instead of splitting in parts, just like a one-dimensional call to `numpy.split` or `numpy.array_split`.

To use this new functionality, write ``np.split(array, [4, 2], (0, 1))`` or ``np.array_split(array, [4, 2], (0, 1))``
Copy link
Member

Choose a reason for hiding this comment

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

Probably clearer to explain this with a kwarg.

Suggested change
To use this new functionality, write ``np.split(array, [4, 2], (0, 1))`` or ``np.array_split(array, [4, 2], (0, 1))``
To use this new functionality, write ``np.split(array, [4, 2], axis=(0, 1))`` or ``np.array_split(array, [4, 2], axis=(0, 1))``

Base automatically changed from master to main March 4, 2021 02:04
>>> np.array_split(x, 3)
[array([0., 1., 2.]), array([3., 4.]), array([5., 6.])]
>>> x = np.reshape(np.arange(16), (4, 4))
>>> np.array_split(x, [3, 3], (0, 1))
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 we should allow

Suggested change
>>> np.array_split(x, [3, 3], (0, 1))
>>> np.array_split(x, 3, (0, 1))

to mean the same thing?

Copy link
Author

Choose a reason for hiding this comment

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

I like the idea and have added the functionality. Based on your proposed test_zero_dimensional_split, I assumed that ret = split(matrix, 3, axis=()) should operate the same as ret = split(matrix, [], axis=()) and therefore do nothing.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, "split into three parts on no axes" seems like a legal instruction to do nothing, and an error would just be annoying.

@KoningR
Copy link
Author

KoningR commented May 6, 2021

Hi. I think this PR is done. Would anyone mind having a look at it?

@KoningR KoningR requested a review from mattip May 15, 2021 08:43
@mattip mattip removed their request for review May 16, 2021 13:27
@eric-wieser eric-wieser removed 55 - Needs work 56 - Needs Release Note. Needs an entry in doc/release/upcoming_changes labels May 19, 2021
KoningR and others added 2 commits July 7, 2022 15:14
@InessaPawson InessaPawson added the triage review Issue/PR to be discussed at the next triage meeting label May 3, 2023
@seberg
Copy link
Member

seberg commented Jul 26, 2023

We discussed it today, we think this can be pushed forward API wise. We haven't reviewed the implementation though, so we will need to find/prioritize some review time actually push it forward.

@seberg seberg removed the triage review Issue/PR to be discussed at the next triage meeting label Jul 26, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Awaiting a code review
Status: Awaiting a code review
Development

Successfully merging this pull request may close these issues.

Feature request: generalizing split
6 participants