0

I have the following xy numpy array which represents the locations of the vertices of some triangles:

array([[[ 0.30539728, 49.82845203],
        [ 0.67235022, 49.95042185],
        [ 0.268982  , 49.95195348]],
       [[ 0.268982  , 49.95195348],
        [ 0.67235022, 49.95042185],
        [ 0.27000135, 50.16334035]],
       ...
       [[ 1.00647459, 50.25958169],
        [ 0.79479121, 50.3010079 ],
        [ 0.67235022, 49.95042185]],
       [[ 0.79479121, 50.3010079 ],
        [ 0.6886783 , 50.25867683],
        [ 0.67235022, 49.95042185]]])

Here, it's an array of shape (10, 3, 2) but it could as well be (5, 3, 2) or (18, 3, 2), you name it. In any case it's of shape (N, 3, 2). I have another numpy array to_replace of shape (4, 2) but it could as well be (6, 2) or (7, 2), but always of shape (M, 2):

array([[ 1.08267406, 49.88690993],
       [ 1.1028248 , 50.01440407],
       [ 0.74114309, 49.73183549],
       [ 1.08267406, 49.88690993]])

It represents the locations of pairs of coordinates that can be found in my first array. Note that each of these pairs is present at least once in xy but could be present more than once. Finally, I have a third array replace_by of which shape (8,) (or of shape (M*2) based on the indication above) and which values are meant to replace exactly those contained in to_replace in my first xy array. It looks like this:

array([ 0.87751214, 49.91866589,  0.88758751, 49.98241296,  0.70674665, 49.84112867,  0.87751214, 49.91866589])

So basically all pairs [1.08267406, 49.88690993] in xy should be replaced by [0.87751214, 49.91866589] for example.

My current code looks like this but it works only if to_replace and replace_by are strictly of shape (2, 2).

indices = (xy == to_replace[:, None][:, None])[0]
xy[indices] = replace_by

I already looked at a number of answers and actually got inspired by some of them but I still can't get it to work.

Guillaume
  • 1,782
  • 1
  • 25
  • 42

2 Answers2

1

You can use numpy.isclose to compare rows and then use .all(axis=2) to find where all last rows are the same. Numpy will broadcast each row to fit xy shape.

import numpy as np
xy = np.array([[[ 0.30539728, 49.82845203],
        [ 0.67235022, 49.95042185],
        [ 0.268982  , 49.95195348]],
       [[ 0.268982  , 49.95195348],
        [ 0.67235022, 49.95042185],
        [ 0.27000135, 50.16334035]],
       [[ 1.00647459, 50.25958169],
        [ 0.79479121, 50.3010079 ],
        [ 0.67235022, 49.95042185]],
       [[ 0.79479121, 50.3010079 ],
        [ 0.6886783 , 50.25867683],
        [ 0.67235022, 49.95042185]]])
xy_start = xy.copy()


to_replace = np.array([[ 1.08267406, 49.88690993],
       [ 1.1028248 , 50.01440407],
       # [ 0.74114309, 49.73183549],
       [ 0.6886783 , 50.25867683],
       [ 1.08267406, 49.88690993]])

replace_by = np.array([ 0.87751214, 49.91866589,  0.88758751, 49.98241296,  0.70674665, 49.84112867,  0.87751214, 49.91866589])
replace_by_reshaped = replace_by.reshape(-1, 2)

for i, row in enumerate(to_replace):
    xy[np.isclose(xy, row).all(axis=2)] = replace_by_reshaped[i]
print(xy_start)
# [[[ 0.30539728 49.82845203]
#   [ 0.67235022 49.95042185]
#   [ 0.268982   49.95195348]]

#  [[ 0.268982   49.95195348]
#   [ 0.67235022 49.95042185]
#   [ 0.27000135 50.16334035]]

#  [[ 1.00647459 50.25958169]
#   [ 0.79479121 50.3010079 ]
#   [ 0.67235022 49.95042185]]

#  [[ 0.79479121 50.3010079 ]
#   [ 0.6886783  50.25867683]
#   [ 0.67235022 49.95042185]]]
print(xy)
# [[[ 0.30539728 49.82845203]
#   [ 0.67235022 49.95042185]
#   [ 0.268982   49.95195348]]

#  [[ 0.268982   49.95195348]
#   [ 0.67235022 49.95042185]
#   [ 0.27000135 50.16334035]]

#  [[ 1.00647459 50.25958169]
#   [ 0.79479121 50.3010079 ]
#   [ 0.67235022 49.95042185]]

#  [[ 0.79479121 50.3010079 ]
#   [ 0.70674665 49.84112867]
#   [ 0.67235022 49.95042185]]]

EDIT

.all(axis=2) shrink axis=2 to True if all values along axis=2 are True and False else. I think little 2d example made it clear what is happening here.

>>> import numpy as np
>>> a = np.array([[0, 1], [0, 2], [3, 4]])
>>> a
array([[0, 1],
       [0, 2],
       [3, 4]])
>>> np.isclose(a, [0, 1])
array([[ True,  True],
       [ True, False],
       [False, False]])
>>> np.isclose(a, [0, 1]).all(axis=1)
array([ True, False, False])
>>> a[np.isclose(a, [0, 1]).all(axis=1)]
array([[0, 1]])
>>> a[np.isclose(a, [0, 1]).all(axis=1)] = [12, 14]
>>> a
array([[12, 14],
       [ 0,  2],
       [ 3,  4]])
V. Ayrat
  • 2,499
  • 9
  • 10
  • Thanks, it's working. Could you please provide more information on what's the purpose of this `.all(axis=2)` and what it's doing please? I'm not sure to get how the magic is happening. – Guillaume Jun 03 '20 at 16:07
0

The numpy-indexed package (disclaimer: I am its author) contains functionality to solve this problem in a vectorized and elegant manner.

Given the arrays as you have defined them, this one-liner should do the trick:

import numpy_indexed as npi    
npi.remap(xy.reshape(-1, 2), to_replace, replace_by.reshape(-1, 2)).reshape(-1, 3, 2)
Eelco Hoogendoorn
  • 10,459
  • 1
  • 44
  • 42