Cleb
Cleb

Reputation: 25997

How to efficiently apply functions to values in an array based on condition?

I have an array arorg like this:

import numpy as np
arorg = np.array([[-1., 2., -4.], [0.5, -1.5, 3]])

and another array values that looks as follows:

values = np.array([1., 0., 2.])

values has the same number of entries as arorg has columns.

Now I want to apply functions to the entries or arorg depending on whether they are positive or negative:

def neg_fun(val1, val2):
    return val1 / (val1 + abs(val2))

def pos_fun(val1, val2):
    return 1. / ((val1 / val2) + 1.)

Thereby, val2 is the (absolute) value in arorg and val1 - this is the tricky part - comes from values; if I apply pos_fun and neg_fun to column i in arorg, val1 should be values[i].

I currently implement that as follows:

ar = arorg.copy()

for (x, y) in zip(*np.where(ar > 0)):
    ar.itemset((x, y), pos_fun(values[y], ar.item(x, y)))

for (x, y) in zip(*np.where(ar < 0)):
    ar.itemset((x, y), neg_fun(values[y], ar.item(x, y)))

which gives me the desired output:

array([[ 0.5       ,  1.        ,  0.33333333],
       [ 0.33333333,  0.        ,  0.6       ]])

As I have to do these calculations very often, I am wondering whether there is a more efficient way of doing this. Something like

np.where(arorg > 0, pos_fun(xxxx), arorg)

would be great but I don't know how to pass the arguments correctly (the xxx). Any suggestions?

Upvotes: 2

Views: 854

Answers (3)

Allen Qin
Allen Qin

Reputation: 19947

import numpy as np
arorg = np.array([[-1., 2., -4.], [0.5, -1.5, 3]])
values = np.array([1., 0., 2.])
p = 1.0/(values/arorg+1)
n = values/(values+abs(arorg))
#using np.place to extract negative values and put them to p
np.place(p,arorg<0,n[arorg<0])
print(p)
[[ 0.5         1.          0.33333333]
 [ 0.33333333  0.          0.6       ]]

Upvotes: 1

Divakar
Divakar

Reputation: 221524

As hinted in the question, here's one using np.where.

First off, we are using a direct translation of the function implementation to generate values/arrays for both positive and negative cases. Then, with a mask of positive values, we will choose between those two arrays using np.where.

Thus, the implementation would look something along these lines -

# Get positive and negative values for all elements
val1 = values
val2 = arorg
neg_vals = val1 / (val1 + np.abs(val2))
pos_vals = 1. / ((val1 / val2) + 1.)

# Get a positive mask and choose between positive and negative values 
pos_mask = arorg > 0
out = np.where(pos_mask, pos_vals, neg_vals)

Upvotes: 2

James
James

Reputation: 36608

You don't need to apply function to zipped elements of arrays, you can accomplish the same thing through simple array operations and slicing.

First, get the positive and negative calculation, saved as arrays. Then create a return array of zeros (just as a default value), and populate it using boolean slices of pos and neg:

import numpy as np
arorg = np.array([[-1., 2., -4.], [0.5, -1.5, 3]])
values = np.array([1., 0., 2.])

pos = 1. / ((values / arorg) + 1)
neg = values / (values + np.abs(arorg))

ret = np.zeros_like(arorg)
ret[arorg>0] = pos[arorg>0]
ret[arorg<=0] = neg[arorg<=0]

ret
# returns:
array([[ 0.5       ,  1.        ,  0.33333333],
       [ 0.33333333,  0.        ,  0.6       ]])

Upvotes: 1

Related Questions