List multiplication in Python - python

I need to multiply different lists to calculate areas of irregular polygons.
X = [1,1,1,1,1,1]
Y = [5,4,3,2,1,0]
This means that coordinates from point 1 are (1,5), for point 2 are (1,4) and so on. To calculate the area I need to multiply X[i] * Y[i+1] which is equal to 1 * 4, 1 * 3, 1 * 2 and exlude the last multiplication, such as 1 * empty.
How can I do this?

So, in my understanding, you need a lag between X and Y, where the first element of Y is excluded, and the last element of X is excluded. In other words, you need something like:
[(1, 4), (1, 3), (1, 2), (1, 1), (1, 0)]
You can produce the above via:
zipped = zip(X[:-1], Y[1:])
and you can compute the products of each pair like so:
[a * b for a, b in zipped]
Of course, if X and Y were numpy arrays, you could do this much more efficiently:
>>> X[:-1] * Y[1:]
array([4, 3, 2, 1, 0])

Something like
[x * y for x, y in zip(X, Y[1:])]
would do it. But you should really use Numpy for anything non-trivial.

Related

In numpy, multipy two structured matrices concisely

I have two matrices. The first has the following structure:
[[1, 0, a],
[0, 1, b],
[1, 0, c],
[0, 1, d]]
where 1, 0, a, b, c, and d are scalars. The matrix is 4 by 3
The second is just a 2 by 3 matrix:
[[r1],
[r2]]
where r1 and r2 are the first and second rows respectively, each having 3 elements.
I would like the output to be:
[[r1, 0, a*r1],
[0, r1, b*r1],
[r2, 0, c*r2],
[0, r2, d*r2]]
which would be a 4 by 9 matrix.
This is similar to the Kronecker product, except separately for each row of the second matrix. Of course this could be done with cumbersome loops which I want to avoid.
How can I do this concisely?
You can do exactly what you said in the last line: do a separate Kronecker product for each row of the second column and then concatenate the results.
Let's assume that the two matrices are called x (4 by 3) and y (2 by 3). The first thing to do is to split x in two parts because only half matrix participates in each part of the product.
x = x.reshape(2, 2, 3)
Then you can calculate the two products separately:
z0 = np.kron(x[0], y[0])
z1 = np.kron(x[1], y[1])
Finally, concatenate the two results along the first axis:
z = np.concatenate([z0, z1], axis=0)
Or if, like me, you enjoy big ugly one-liners you can do:
z = np.concatenate([np.kron(xr, yr) for xr, yr in zip(x.reshape(2, 2, 3), y)], axis=0)
In the general case you mentioned in the comments, it would become:
z = np.concatenate([np.kron(xr, yr) for xr, yr in zip(x.reshape(int(n / 2), 2, 3), y)], axis=0)
This gives equal results to the explicit loop, which can be numba.jit compiled I believe:
def solve_explicit(x, y):
# sanity checks
assert x.shape[0] == 2*y.shape[0]
assert x.shape[1] == y.shape[1]
n = x.shape[0]
z = np.zeros((n, 9))
for i in range(n):
for j in range(3):
for k in range(3):
z[i, k + 3 * j] = x[i, j] * y[int(i / 2), k]
return z
Using broadcasting, with x.shape (n, 3), and y.shape (n//2, 3):
out = (x.reshape(-1, 2, 3, 1) * y.reshape(-1, 1, 1, 3)).reshape(-1, 9)
I personally would use np.einsum in this situation because I think it's easier to understand than broadcasting.
import numpy as np
(a, b, c, d) = np.random.rand(4)
x = np.array([[1, 0, a], [0, 1, b], [1, 0, c], [0, 1, d]])
y = np.random.rand(2, 3)
z = np.einsum("ij,ik->ijk", x.reshape(-1, 6), y).reshape(-1, 9)
# timeit magic commands.
# %timeit -n 50000 np.einsum("ij,ik->ijk", x.reshape(-1, 6), y).reshape(-1, 9)
# %timeit -n 50000 (x.reshape(-1, 2, 3, 1) * y.reshape(-1, 1, 1, 3)).reshape(-1, 9)
Some good references on Einstein summation in NumPy: [2, 3, 4].

Sum up the third value for conditions

I want to write a quick calculation in python, not sure how to approach this:
values = [(0, 4, .3), (6, 2, 3), (3, 7, 2.2), (5, 5, .5), (3, 5, .8), (7, 7, .3)]
say we use (x, y, z) to represent tuples in values
criteria = [(a, b, c, d)]
if (a <= x < b) and (c <= y < d),
then sum up all the z in the values.
My result is just to show the sum of z that satisfy the above criteria
You could use list comprehension to achieve that in one line.
# Criteria
(a, b, c, d) = (-1, 10, 0, 10)
# Output
zs = [z for (x, y, z) in values if a <= x < b and c <= y < d]
print(sum(zs))
Not fully sure why you specified the criteria as an array, but I think that example is easy to extend in case you want to check for multiple criterias.

Subtracting one dimensional array (list of scalars) from 3 dimensional arrays using broadcasting

I have a one dimesional array of scalar values
Y = np.array([1, 2])
I also have a 3-dimensional array:
X = np.random.randint(0, 255, size=(2, 2, 3))
I am attempting to subtract each value of Y from X, so I should get back Z which should be of shape (2, 2, 2, 3) or maybe (2, 2, 2, 3).
I can"t seem to figure out how to do this via broadcasting.
I tried changing the change of Y:
Y = np.array([[[1, 2]]])
but not sure what the correct shape should be.
Broadcasting lines up dimensions on the right. So you're looking to operate on a (2, 1, 1, 1) array and a (2, 2, 3) array.
The simplest way I can think of is using reshape:
Y = Y.reshape(-1, 1, 1, 1)
More generally:
Y = Y.reshape(-1, *([1] * X.ndim))
At most one of the arguments to reshape can be -1, indicating all the remaining size not accounted for by other dimensions.
To get Z of shape (2, 2, 2, 3):
Z = X - Y.reshape(-1, *([1] * X.ndim))
If you were OK with having Z of shape (2, 2, 3, 2), the operation would be much simpler:
Z = X[..., None] - Y
None or np.newaxis will insert a unit axis into the end of X's shape, making it broadcast properly with the 1D Y.
I am not entirely sure on which dimension you want your subtraction to take place, but X - Y will not return an error if you define Y such as Y = numpy.array([1,2]).reshape(2, 1, 1) or Y = numpy.array([1,2]).reshape(1, 2, 1).

array of Weighted random numbers with constant sum

Is there any well-known algorithm which can generate an array of weighted random numbers having the same constant sum?
This problem can be stated in another way: Say, I have a total value of 20. Which should be distributed into 3 parts each of them has a weight of 2,4,3 respectively.
So, I need 3 random numbers which will have a sum of 20 but the distribution will follow the weight.
I have tried:
Range=20
W=[2,4,3]
Prob=[i/float(sum(W)) for i in W]
Weighted_array=np.random.multinomial(Range, Prob)
Is there any better option?
Symbolic:
This is a linear Diophantine equation of n=3 variables. To solve it analytically in python you can use sympy, as in this answer:
from random import randint
from sympy.solvers.diophantine import diop_linear
from sympy.abc import x, y, z
# Our original equation is 2 * x + 4 * y + 3 * z = 20
# Re-arrange to 2 * x + 4 * y + 3 * z - 20 = 0, and input the left side to sympy
general_solution = diop_linear(2 * x + 4 * y + 3 * z - 20)
def get_random_valid_triple():
t0,t1 = general_solution[2].free_symbols
# You can pick whatever bounds you want here
a = randint(-100, 100)
b = randint(-100, 100)
solution = [s.subs({t0: a, t1:b}) for s in general_solution]
print(solution)
# Get a random solution
get_random_valid_triple()
Brute Force:
Alternately, at least for small n and tight bounds on each variable, you can just precompute all possible solutions and use random.choice to choose one each time. For example, we restrict all of the variables to be positive, then they're forced to be in [0, 20 / coefficient], and there are only 14 solutions. We can generate these in python as follows:
import random
import itertools
n = 20
coefficients = [2, 4, 3]
valid_solutions = []
ranges = [range(0, n // k + 1) for k in coefficients]
for value in itertools.product(*ranges):
if sum(value[j] * i for j, i in enumerate(coefficients)) == n:
valid_solutions.append(value)
print("All solutions:")
print("\n".join(str(i) for i in valid_solutions))
print("Random solution:")
print(random.choice(valid_solutions))
This yields:
All solutions:
(0, 2, 4)
(0, 5, 0)
(1, 0, 6)
(1, 3, 2)
(2, 1, 4)
(2, 4, 0)
(3, 2, 2)
(4, 0, 4)
(4, 3, 0)
(5, 1, 2)
(6, 2, 0)
(7, 0, 2)
(8, 1, 0)
(10, 0, 0)
Random solution:
(10, 0, 0)

Reduce array over ranges

Say I have an array of numbers
np.array(([1, 4, 2, 1, 2, 5]))
And I want to compute the sum over a list of slices
((0, 3), (2, 4), (2, 6))
Giving
[(1 + 4 + 2), (2 + 1), (2 + 1 + 2 + 5)]
Is there a nice way to do this in numpy?
Looking for something equivalent to
def reduce(a, ranges):
np.array(list(np.sum(a[low:high]) for (low, high) in ranges))
Seems like there is probably some fancy numpy way to do this though. Anyone know?
One way is to use np.add.reduceat. If a is the array of values [1, 4, 2, 1, 2, 5]:
>>> np.add.reduceat(a, [0,3, 2,4, 2])[::2]
array([ 7, 3, 10], dtype=int32)
Here the slice indexes are passed in a list and are summed to return [ 7, 1, 3, 2, 10] (i.e. the sums of a[0:3], a[3:], a[2:4], a[4:], a[2:]). We only want every other element from this array.
Longer alternative approach...
The fact that the slices are of different lengths makes this slightly trickier to vectorise in NumPy, but here is one way you approach the problem.
Given an array of values and an array of slices to make...
a = np.array(([1, 4, 2, 1, 2, 5]))
slices = np.array([(0, 3), (2, 4), (2, 6)])
...create a mask-like array z that, for each slice, will be used to "zero-out" the values from a we don't want to sum:
z = np.zeros((3, 6))
s1 = np.arange(6) >= s[:, 0][:,None]
s2 = np.arange(6) < s[:, 1][:,None]
z[s1 & s2] = 1
Then you can do:
>>> (z * a).sum(axis=1)
array([ 7., 3., 10.])
A quick %timeit shows this is slightly faster than the list comprehension, even though we had to construct z and z * a. If slices is made to be of length 3000, this method is around 40 times quicker.
However note that the array z will be of shape (len(slices), len(a)) which may not be as practical if a or slices are both very long - an iterative approach might be preferred to avoid large temporary arrays in memory.

Categories