I'm working on an optimization problem, but to avoid getting into the details, I'm going to provide a simple example of a bug that's been giving me headaches for a few days.
Say I have a 2D numpy array with observed x-y coordinates:
from scipy.optimize import distance
x = np.array([1,2], [2,3], [4,5], [5,6])
I also have a list of x-y coordinates to compare to these points (y):
y = np.array([11,13], [12, 14])
I have a function that takes the sum of manhattan differences between a value of x and all of the values in y:
def find_sum(ref_row, comp_rows):
modeled_counts = []
y = ref_row * len(comp_rows)
res = list(map(distance.cityblock, ref_row, comp_rows))
modeled_counts.append(sum(res))
return sum(modeled_counts)
Essentially, what I would like to do is find the sum of manhattan distances for every item in y with each item in x (so basically for each item in x, find the sum of the Manhattan distances between that (x,y) pair and every (x,y) pair in y).
I've tried this out with the following line of code:
z = list(map(find_sum, x, y))
However, z is of length 2 (like y), and not 4 like x. Is there a way to ensure that z is the result of consecutive one-to-all calculations? That is, I'd like to calculate the sum of all of the manhattan differences between x[0] and every set in y, and so on and so forth, so the length of z should be equal to the length of x.
Is there a simple way to do this without a for loop? My data is rather large (~ 4 million rows), so I'd really appreciate fast solutions. I'm fairly new to Python programming, so any explanations about why the solution works and is fast would be appreciated as well, but definitely isn't required!
Thanks!
This solution implements the distance in numpy, as I think it is a good example of broadcasting, which is a very useful thing to know if you need to use arrays and matrices.
By definition of Manhattan distance, you need to evaluate the sum of the absolute value of difference between each column. However, the first column of x, x[:, 0], has shape (4,) and the first column of y, y[:, 0], has shape (2,), so they are not compatible in the sense of applying subtraction: the broadcasting property says that each shape is compared starting with the trailing dimensions and two dimensions are compatible when they are equal or one of them is 1. Sadly, none of them are true for your columns.
However, you can add a new dimension of value 1 using np.newaxis, so
x[:, 0]
is array([1, 2, 4, 5]), but
x[:, 0, np.newaxis]
is
array([[1],
[2],
[4],
[5]])
and its shape is (4 ,1). Now, a matrix of shape (4, 1) subtracted by an array of shape 2 results in a matrix of shape (4, 2), by numpy's broadcasting treatment:
4 x 1
2
= 4 x 2
You can obtain the differences for each column:
first_column_difference = x[:, 0, np.newaxis] - y[:, 0]
second_column_difference = x[:, 1, np.newaxis] - y[:, 1]
and evaluate the sum of their absolute values:
np.abs(first_column_difference) + np.abs(second_column_difference)
which results in a (4, 2) matrix. Now, you want to sum the values for each row, so that you have 4 values:
np.sum(np.abs(first_column_difference) + np.abs(second_column_difference), axis=1)
which results in array([73, 69, 61, 57]). The rule is simple: the parameter axis will eliminate that dimension from the result, therefore using axis=1 for a (4, 2) matrix generates 4 values -- if you use axis=0, it will generate 2 values.
So, this will solve your problem:
x = np.array([[1, 2], [2, 3], [4, 5], [5, 6]])
y = np.array([[11, 13], [12, 43]])
first_column_difference = x[:, 0, np.newaxis] - y[:, 0]
second_column_difference = x[:, 1, np.newaxis] - y[:, 1]
z = np.abs(first_column_difference) + np.abs(second_column_difference)
print(np.sum(z, axis=1))
You can also skip the intermediate steps for each column and evaluate everything at once (it is a little bit harder to understand, so I prefer the method described above to explain what is happening):
print(np.abs(x[:, np.newaxis] - y).sum(axis=(1, 2)))
It is a general case for an n-dimensional Manhattan distance: if x is (u, n) and y is (v, n), it generates u rows by broadcasting (u, 1, n) by (v, n) = (u, v, n), then applying sum to eliminate the second and third axis.
Here is how you can do it using numpy broadcast with simplified explanation
Adjust Shape For Broadcasting
import numpy as np
start_points = np.array([[1,2], [2,3], [4,5], [5,6]])
dest_points = np.array([[11,13], [12, 14]])
## using np.newaxis as index add a new dimension at that position
## : give all the elements on that dimension
start_points = start_points[np.newaxis, :, :]
dest_points = dest_points[:, np.newaxis, :]
## Now lets check he shape of the point arrays
print('start_points.shape: ', start_points.shape) # (1, 4, 2)
print('dest_points.shape', dest_points.shape) # (2, 1, 2)
Lets try to understand
last element of shape represent x and y of a point, size 2
we can think of start_points as having 1 row and 4 columns of points
we can think of dest_points as having 2 rows and 1 columns of points
We can think start_points and dest_points as matrix or a table of points of size (1X4) and (2X1)
We clearly see that size are not compatible. What will happen if we perform arithmatic
operation between them? Here is where a smart part of numpy comes, called broadcast.
It will repeat rows of start_points to match that of dest_point making matrix of (2X4)
It will repeat columns of dest_point to match that of start_points making matrix of (2X4)
Result is arithmetic operation between every pair of elements on start_points and dest_points
Calculate the distance
diff_x_y = start_points - dest_points
print(diff_x_y.shape) # (2, 4, 2)
abs_diff_x_y = np.abs(start_points - dest_points)
man_distance = np.sum(abs_diff_x_y, axis=2)
print('man_distance:\n', man_distance)
sum_distance = np.sum(man_distance, axis=0)
print('sum_distance:\n', sum_distance)
Oneliner
start_points = np.array([[1,2], [2,3], [4,5], [5,6]])
dest_points = np.array([[11,13], [12, 14]])
np.sum(np.abs(start_points[np.newaxis, :, :] - dest_points[:, np.newaxis, :]), axis=(0,2))
Here is more detail explanation of broadcasting if you want to understand it more
With so many rows you can make substantial savings by using a smart algorithm. Let us for simplicity assume there is just one dimension; once we have established the algorithm, getting back to the general case is a simple matter of summing over coordinates.
The naive algorithm is O(mn) where m,n are the sizes of sets X,Y. Our algorithm is O((m+n)log(m+n)) so it scales much better.
We first have to sort the union of X and Y by coordinate and then form the cumsum over Y. Next, we find for each x in X the number YbefX of y in Y to its left and use it to look up the corresponding cumsum item YbefXval. The summed distances to all y to the left of x are YbefX times coordinate of x minus YbefXval, the distances to all y to the right are sum of all y coordinates minus YbefXval minus n - YbefX times coordinate of x.
Where does the saving come from? Sorting coordinates enables us to recycle the summations we have done before, instead of starting each time from scratch. This uses the fact that up to a sign we always sum the same y coordinates and going from left to right the signs flip one by one.
Code:
import numpy as np
from scipy.spatial.distance import cdist
from timeit import timeit
def pp(X,Y):
(m,k),(n,k) = X.shape,Y.shape
XY = np.concatenate([X.T,Y.T],1)
idx = XY.argsort(1)
Xmsk = idx<m
Ymsk = ~Xmsk
Xidx = np.arange(k)[:,None],idx[Xmsk].reshape(k,m)
Yidx = np.arange(k)[:,None],idx[Ymsk].reshape(k,n)
YbefX = Ymsk.cumsum(1)[Xmsk].reshape(k,m)
YbefXval = XY[Yidx].cumsum(1)[np.arange(k)[:,None],YbefX-1]
YbefXval[YbefX==0] = 0
XY[Xidx] = ((2*YbefX-n)*XY[Xidx]) - 2*YbefXval + Y.sum(0)[:,None]
return XY[:,:m].sum(0)
def summed_cdist(X,Y):
return cdist(X,Y,"minkowski",p=1).sum(1)
# demo
m,n,k = 1000,500,10
X,Y = np.random.randn(m,k),np.random.randn(n,k)
print("same result:",np.allclose(pp(X,Y),summed_cdist(X,Y)))
print("sort :",timeit(lambda:pp(X,Y),number=1000),"ms")
print("scipy cdist:",timeit(lambda:summed_cdist(X,Y),number=100)*10,"ms")
Sample run, comparing smart algo "sort" to naive algo implemented using cdist library function:
same result: True
sort : 1.4447695480193943 ms
scipy cdist: 36.41934019047767 ms
Rotation about the origin is a matrix product that can be done with numpy's dot function,
import numpy as np
points = np.random.rand(100,3) # 100 X, Y, Z tuples. shape = (100,3)
rotation = np.identity(3) # null rotation for example
out = np.empty(points.shape)
for idx, point in enumerate(points):
out[idx,:] = np.dot(rotation, point)
This involves a for loop, or numpy tile could be used to vectorize. I think there is an implementation involving np.tensordot, but the function is witchcraft to me. Is this possible?
There are several ways you can do that. With np.matmul you can do:
out = np.matmul(rotation, points[:, :, np.newaxis])[:, :, 0]
Or, equivalently, if you are using Python 3.5 or later:
out = (rotation # points[:, :, np.newaxis])[:, :, 0]
Another way is with np.einsum:
out = np.einsum('ij,nj->ni', rotation, points)
Finally, as you suggested, you can also use np.tensordot:
out = np.tensordot(points, rotation, axes=[1, 1])
Note that in this case points is the first argument and rotation the second, otherwise the dimensions at the output would be reversed.
I am looking for a way of calculating the mean of each given value in a 3d Numpy array with the 20 values in rows directly above and 20 values in rows directly below. This is similar to a previous question I asked (Taking minimum value of each entry +- 10 rows either side in numpy array) but calculating the mean of 41 values instead of the minimum of 21 values.
I have tried using Scipy's uniform 1d filter, but this does not have a mode which deals with the values close to the edge of the array correctly. The window which is outside of the array should not be included in the mean calculation (i.e. at the bottom/top locations in the array the mean should be taken from the edge value and the 20 rows above/below respectively).
Is there any way of using the uniform filter, or is there an alternative method which achieves this?
Thanks.
EDIT:
The Numpy array has dimensions 20x3200x18, so I was looking for a relatively efficient solution.
If you are really looking for performance in this, you can exploit cumsum in order to only have to calculate the sums once, this should make the implementation about 40 times faster.
See below for an example. Without your exact data and a reference implementation I cannot verify that this does exactly what you want, but it should be correct in spirit.
import numpy as np
import matplotlib.pyplot as plt
arr = np.random.rand(20, 3200, 18)
n = 20
cumsum = np.cumsum(arr, axis=1)
means_lower = cumsum[:, :n, :] / np.arange(1, n + 1)[None, :, None]
means_middle = (cumsum[:, 2 * n:, :] - cumsum[:, :-2 * n , :]) / (2 * n)
means_upper = (cumsum[:, -1, :][:, None, :] - cumsum[:, -n - 1:-1, :]) / np.arange(n, 0, -1)[None, :, None]
means = np.concatenate([means_lower, means_middle, means_upper], axis=1)
x = np.arange(3200)
plt.plot(x, means[0, :, 0])
You can use scipy.signal.convolve to do this.
import scipy.signal as sig
def windowed_mean(arr, n):
dims = len(arr.shape)
s = sig.convolve(arr, np.ones((2*n+1,)*dims), mode='same')
d = sig.convolve(np.ones_like(arr), np.ones((2*n+1,)*dims), mode='same')
return s/d
Basically, s is a windowed sum and d is a windowed counter, so you avoid errors at the edge
I have some 3D numpy arrays that need to be transformed in various ways. E.g.:
x.shape = (4, 17, 17)
This array is 1 sample of 4 planes, each of size 17x17. What is the most efficient way to transform each plane: flipud, fliplr, and rot90? Is there a better way than using a for loop? Thanks!
for p in range(4):
x[p, :, :] = np.fliplr(x[p, :, :])
Look at the code of these functions:
def fliplr(...):
....
return m[:, ::-1]
In other words it returns a view with reverse slicing on the 2nd dimension
Your x[p, :, :] = np.fliplr(x[p, :, :] applies that reverse slicing to the last dimension, so the equivalent for the whole array should be
x[:, :, ::-1]
flipping the 2nd axis would be
x[:, ::-1, :]
etc.
np.rot90 has 4 case (k); for k=1 it is
return fliplr(m).swapaxes(0, 1)
in other words m[:, ::-1].swapaxes(0,1)
To work on your planes you would do something like
m[:, :,::-1].swapaxes(1,2)
or you could do the swapaxes/transpose first
m.transpose(0,2,1)[:, :, ::-1]
Does that give you enough tools to transform the plane's in what ever way you want?
As I discussed in another recent question, https://stackoverflow.com/a/41291462/901925, the flip... returns a view, but the rot90, with both flip and swap, will, most likely return a copy. Either way, numpy will be giving you the most efficient version.