简体   繁体   中英

Faster way to replace elements in a np.array

Lets say M is a very large 2D numpy.array that its elements can only be 5 different integers (-4, -2, 0, 2, 4). I want to calculate np.exp(M). Is there a faster way to do this than using the above function exploiting the fact that the matrix contains only a very small number of different elements?

Building on the answer mentioned by @alkasm, you can use a look-up table (LUT), as long as you can transform the input matrix into a matrix of non-negative integers, since they are used as indices into the LUT. In your case:

import numpy as np

M = np.array([[-4, -2,  0],
              [-2,  0,  2],
              [ 0,  2,  4]])

lut = np.exp(np.arange(-4, 4+1))

E = lut[M + 4]

print(f"M:\n{M}\nlut:\n{lut}\nE:\n{E}")

# M:
# [[-4 -2  0]
#  [-2  0  2]
#  [ 0  2  4]]
# lut:
# [1.83156389e-02 4.97870684e-02 1.35335283e-01 3.67879441e-01
#  1.00000000e+00 2.71828183e+00 7.38905610e+00 2.00855369e+01
#  5.45981500e+01]
# E:
# [[1.83156389e-02 1.35335283e-01 1.00000000e+00]
#  [1.35335283e-01 1.00000000e+00 7.38905610e+00]
#  [1.00000000e+00 7.38905610e+00 5.45981500e+01]]

You could also use a smaller LUT like this:

lut = np.exp(np.arange(-4, 4+1, 2))

E = lut[M // 2 + 2]

but this trades 4 exponentiations for 9 integer divisions in my example, and 4 exponentiations for n×m integer divisions if M is an n×m matrix. If your matrices are bigger than 3×3, you better use the “long” LUT with unused indices.

In cases, different from this one, where the indices are wider apart, using a “long” LUT but calculating only the used indices could be the best solution. The above example becomes:

import numpy as np
import math

M = np.array([[-4, -2,  0],
              [-2,  0,  2],
              [ 0,  2,  4]])

values = range(-4, 4+1, 2)  # or whatever integers can appear in your matrix

offset = -min(values)
span = max(values) + 1 + offset

lut = np.zeros(span)  # or, even faster, np.empty(span)

for x in values:
    lut[x + offset] = math.exp(x)  # or whatever your mapping is

E = lut[M + offset]

print(f"M:\n{M}\nlut:\n{lut}\nE:\n{E}")

# M:
# [[-4 -2  0]
#  [-2  0  2]
#  [ 0  2  4]]
# lut:
# [1.83156389e-02 0.00000000e+00 1.35335283e-01 0.00000000e+00
#  1.00000000e+00 0.00000000e+00 7.38905610e+00 0.00000000e+00
#  5.45981500e+01]
# E:
# [[1.83156389e-02 1.35335283e-01 1.00000000e+00]
#  [1.35335283e-01 1.00000000e+00 7.38905610e+00]
#  [1.00000000e+00 7.38905610e+00 5.45981500e+01]]

BTW, I also timed using np.take(lut, M) instead of lut[M] , and found no difference. There's no reason to use np.take() here.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM