Replace python list comprehension generating 3D array with numpy functions - python

I have two matrices (numpy arrays), mu and nu. From these I would like to create a third array as follows:
new_array_{j, k, l} = mu_{l, k} nu_{j, k}
I can do it naively using list comprehensions:
[[[mu[l, k] * nu[j, k] for k in np.arange(N)] for l in np.arange(N)] for j in np.arange(N)]
but it quickly becomes slow.
How can I create new_array using numpy functions which should be faster?

Two quick solutions (without my usual proofs and explanations):
res = np.einsum('lk,jk->jkl', mu, nu)
res = mu.T[None,:,:] * nu[:,:,None] # axes in same order as result

#!/usr/bin/env python
import numpy as np
# example data
mu = np.arange(10).reshape(2,5)
nu = np.arange(15).reshape(3,5) + 20
# get array sizes
nl, nk = mu.shape
nj, nk_ = nu.shape
assert(nk == nk_)
# get arrays with dimensions (nj, nk, nl)
# in the case of mu3d, we need to add a slowest varying dimension
# so (after transposing) this can be done by cycling through the data
# nj times along the slowest existing axis and then reshaping
mu3d = np.concatenate((mu.transpose(),) * nj).reshape(nj, nk, nl)
# in the case of nu3d, we need to add a new fastest varying dimension
# so this can be done by repeating each element nl times, and again it
# needs reshaping
nu3d = nu.repeat(nl).reshape(nj, nk, nl)
# now just multiple element by element
new_array = mu3d * nu3d
print(new_array)
Gives:
>>> mu
array([[0, 1, 2, 3, 4],
[5, 6, 7, 8, 9]])
>>> nu
array([[20, 21, 22, 23, 24],
[25, 26, 27, 28, 29],
[30, 31, 32, 33, 34]])
>>> nj, nk, nl
(3, 5, 2)
>>> mu3d
array([[[0, 5],
[1, 6],
[2, 7],
[3, 8],
[4, 9]],
[[0, 5],
[1, 6],
[2, 7],
[3, 8],
[4, 9]],
[[0, 5],
[1, 6],
[2, 7],
[3, 8],
[4, 9]]])
>>> nu3d
array([[[20, 20],
[21, 21],
[22, 22],
[23, 23],
[24, 24]],
[[25, 25],
[26, 26],
[27, 27],
[28, 28],
[29, 29]],
[[30, 30],
[31, 31],
[32, 32],
[33, 33],
[34, 34]]])
>>> new_array
array([[[ 0, 100],
[ 21, 126],
[ 44, 154],
[ 69, 184],
[ 96, 216]],
[[ 0, 125],
[ 26, 156],
[ 54, 189],
[ 84, 224],
[116, 261]],
[[ 0, 150],
[ 31, 186],
[ 64, 224],
[ 99, 264],
[136, 306]]])

Related

What's the best way to set the diagonals of a particular dimension to 1?

Let's say I have a tensor:
q = np.arange(5*3*3).reshape(5,3,3)
I want to set the 3x3 diagonals to be 1, across axis 0 (i.e. where j=k).
I thought this should do it:
np.apply_along_axis(lambda x: np.fill_diagonal(x,1), 0, q)
but it doesn't seem to work.
What's the cleanest way to do this?
Using the same index array in both dimensions selects a diagonal:
In [13]: q = np.arange(5*3*3).reshape(5,3,3)
In [14]: i=np.arange(3)
In [15]: q[:,i,i]
Out[15]:
array([[ 0, 4, 8],
[ 9, 13, 17],
[18, 22, 26],
[27, 31, 35],
[36, 40, 44]])
In [16]: q[:,i,i]=1
In [17]: q
Out[17]:
array([[[ 1, 1, 2],
[ 3, 1, 5],
[ 6, 7, 1]],
[[ 1, 10, 11],
[12, 1, 14],
[15, 16, 1]],
[[ 1, 19, 20],
[21, 1, 23],
[24, 25, 1]],
[[ 1, 28, 29],
[30, 1, 32],
[33, 34, 1]],
[[ 1, 37, 38],
[39, 1, 41],
[42, 43, 1]]])
this is not the cleanest way but this is one way:
q[np.full((5,3,3), np.identity(3)) == 1] = 1

Python numpy function for matrix math

I have to np arrays
a = np.array[[1,2]
[2,3]
[3,4]
[5,6]]
b = np.array [[2,4]
[6,8]
[10,11]
I want to multiple each row of a against each element in array b so that array c is created with dimensions of a-rows x b rows (as columns)
c = np.array[[2,8],[6,16],[10,22]
[4,12],[12,21],[20,33]
....]
There are other options for doing this, but I would really like to leverage the speed of numpy's ufuncs...if possible.
any and all help is appreciated.
Does this do what you want?
>>> a
array([[1, 2],
[2, 3],
[3, 4],
[5, 6]])
>>> b
array([[ 2, 4],
[ 6, 8],
[10, 11]])
>>> a[:,None,:]*b
array([[[ 2, 8],
[ 6, 16],
[10, 22]],
[[ 4, 12],
[12, 24],
[20, 33]],
[[ 6, 16],
[18, 32],
[30, 44]],
[[10, 24],
[30, 48],
[50, 66]]])
>>> _.shape
(4, 3, 2)
Or if that doesn't have the right shape, you can reshape it:
>>> (a[:,None,:]*b).reshape((a.shape[0]*b.shape[0], 2))
array([[ 2, 8],
[ 6, 16],
[10, 22],
[ 4, 12],
[12, 24],
[20, 33],
[ 6, 16],
[18, 32],
[30, 44],
[10, 24],
[30, 48],
[50, 66]])

Could you please someone explain 3-d array slicing?

import numpy as np
A = np.array(
[ [ [45, 12, 4], [45, 13, 5], [46, 12, 6] ],
[ [46, 14, 4], [45, 14, 5], [46, 11, 5] ],
[ [47, 13, 2], [48, 15, 5], [52, 15, 1] ] ])
print(A[1:3, 0:2])
Please explain this. I have been struggling to understand
When accessing a 3D array this way, what you are acutally asking for is to cut a part of each nesting level of those arrays:
A[1:3, 0:2, 0:3]
# ↑↑↑
# Of the outer array (the outer []), take elements 1 (inclusive) to 3 (exclusive).
# Mind that counting starts at 0, so this is the second and third line in your example
A[1:3, 0:2, 0:3]
# ↑↑↑
# Out of the second level array, take the elements 0 (inclusive) to 2 (exclusive).
# This is the first and the second group of three numbers each
A[1:3, 0:2, 0:3]
# ↑↑↑
# This you did not specify, but it is added automatically
# Of the third level arrays, take element 0 (inclusive) to 3 (exclusive)
# Those arrays only have 3 numbers each, so they are left untouched.
In [483]: A = np.array(
...: [ [ [45, 12, 4], [45, 13, 5], [46, 12, 6] ],
...: [ [46, 14, 4], [45, 14, 5], [46, 11, 5] ],
...: [ [47, 13, 2], [48, 15, 5], [52, 15, 1] ] ])
The whole 3d array. If you need to put names on the dimensions, I'd suggest 'plane', 'row' and 'column':
In [484]: A
Out[484]:
array([[[45, 12, 4],
[45, 13, 5],
[46, 12, 6]],
[[46, 14, 4],
[45, 14, 5],
[46, 11, 5]],
[[47, 13, 2],
[48, 15, 5],
[52, 15, 1]]])
In [485]: A.shape
Out[485]: (3, 3, 3)
Taking a slice on the first dimension (the last 2 planes):
In [486]: A[1:3]
Out[486]:
array([[[46, 14, 4],
[45, 14, 5],
[46, 11, 5]],
[[47, 13, 2],
[48, 15, 5],
[52, 15, 1]]])
Taking 2 rows from each of those planes:
In [487]: A[1:3, 0:2]
Out[487]:
array([[[46, 14, 4],
[45, 14, 5]],
[[47, 13, 2],
[48, 15, 5]]])
The last dimension, columns, is left whole, the equivalent of A[1:3, 0:2, :] (trailing slices are automatic).
3D slicing is just the same as 1d and 2d (and 4d etc). There's nothing special or really different about 3d.

Numpy: Combine list of arrays by another array (np.choose alternative)

I have a list of numpy arrays, each of the same shape. Let's say:
a = [np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]),
np.array([[11, 12, 13],
[14, 15, 16],
[17, 18, 19]]),
np.array([[99, 98, 97],
[96, 95, 94],
[93, 92, 91]])]
And I have another array of the same shape that gives the list indices I want to take the elements from:
b = np.array([[0, 0, 1],
[2, 1, 0],
[2, 1, 2]])
What I want to get is the following:
np.array([[1, 2, 13],
[96, 15, 6],
[93, 18, 91]])
There was a simple solution that worked fine:
np.choose(b, a)
But this is limited to 32 arrays at most. But in my case, I have to combine more arrays (more than 100). So I need another way to do so.
I guess, it has to be something about advances indexing or maybe the np.take method. So probably, the first step is a = np.array(a) and then something like a[np.arange(a.shape[0]), b]. But I do not get it working.
Can somebody help? :)
You can try using np.ogrid. Based on this answer. Of course you will have to convert a to a NumPy array first
i, j = np.ogrid[0:3, 0:3]
print (a[b, i, j])
# array([[ 1, 2, 13],
# [96, 15, 6],
# [93, 18, 91]])
In [129]: a = [np.array([[1, 2, 3],
...: [4, 5, 6],
...: [7, 8, 9]]),
...: np.array([[11, 12, 13],
...: [14, 15, 16],
...: [17, 18, 19]]),
...: np.array([[99, 98, 97],
...: [96, 95, 94],
...: [93, 92, 91]])]
In [130]: b = np.array([[0, 0, 1],
...: [2, 1, 0],
...: [2, 1, 2]])
In [131]:
In [131]: A = np.array(a)
In [132]: A.shape
Out[132]: (3, 3, 3)
You want to use b to index the first dimension. For the other dimensions you need a indices that broadcast with b, i.e. a column vector and a row vector:
In [133]: A[b, np.arange(3)[:,None], np.arange(3)]
Out[133]:
array([[ 1, 2, 13],
[96, 15, 6],
[93, 18, 91]])
there are various convenience functions for creating these arrays, e.g.
In [134]: np.ix_(range(3),range(3))
Out[134]:
(array([[0],
[1],
[2]]), array([[0, 1, 2]]))
and ogrid as mentioned in the other answer.
Here's a relatively new function that also does the job:
In [138]: np.take_along_axis(A, b[None,:,:], axis=0)
Out[138]:
array([[[ 1, 2, 13],
[96, 15, 6],
[93, 18, 91]]])
I had to think a bit before I got the adjustment to b right.

vectorizations of matrix multiplication

I have many 3*2 matrices(A1,A2,A3..), and each of the 3*2 is a draw. In the case two draws, we have a 3*4 ( we horizontally stack each draw of A1,A2). Clearly, it is easier for me to draw the 3*4 matrix (A) as a larger matrices once instead of draw a 3*2 over and over again.
But I need to perform a matrix multiplication for each draw(each A1,A2...) to a matrix B. Say A1*B, and A2*B ...AN*B
#each draw of the 3*2 matrix
A1 = np.array([[ 0, 1],
[ 4, 5],
[ 8, 9]])
A2 = np.array([[ 2, 3],
[ 6, 7],
[ 10, 11]])
# A is [A1,A2]
# Easier to draw A once for all (the larger matrix)
A = np.array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
b = np.array([[ 0, 1],
[ 4, 5]
])
desired output
array([[ 4, 5, 12, 17],
[20, 29, 28, 41],
[36, 53, 44, 65]])
You can reshape matrix A to 2 columns so that it is conformable to b, do the matrix multiplication, and then reshape it back:
np.dot(A.reshape(-1, 2), b).reshape(3, -1)
#array([[ 4, 5, 12, 17],
# [20, 29, 28, 41],
# [36, 53, 44, 65]])
If you are unsure about how to store/stack the incoming arrays, one way would be stacking those as a 3D array, such that the each of those incoming arrays are index-able by its first axis -
a = np.array((A1,A2))
Sample run -
In [143]: a = np.array((A1,A2))
In [144]: a.shape
Out[144]: (2, 3, 2)
|-----------------> axis of stacking
Then, to get the equivalent output of matrix-multiplications of each incoming array with b, we could use np.tensordot on the 3D stacked array a with b, thus losing the last axis from a and first from b in the sum-reduction, like so -
out = np.tensordot(a,b,axes=((2),(0)))
Let's have a look at the output values and compare against each matrix-multiplication with A1, A2, etc. -
In [138]: out[0]
Out[138]:
array([[ 4, 5],
[20, 29],
[36, 53]])
In [139]: out[1]
Out[139]:
array([[12, 17],
[28, 41],
[44, 65]])
In [140]: A1.dot(b)
Out[140]:
array([[ 4, 5],
[20, 29],
[36, 53]])
In [141]: A2.dot(b)
Out[141]:
array([[12, 17],
[28, 41],
[44, 65]])
Thus, essentially with this stacking operation and later on tensordot we have :
out[0], out[1], .... = A1.dot(b), A2.dot(b), ....
Alternative to np.tensordot -
We could use a simpler version with np.matmul, to get the same output as with tensordot -
out = np.matmul(a,b)
On Python 3.5, there's an even simpler version that replaces np.matmul, the # operator -
out = a # b
Even if not needed for the calculation einsum can help us think through the problem:
In [584]: np.einsum('ij,jk->ik', A1,b)
Out[584]:
array([[ 4, 5],
[20, 29],
[36, 53]])
In [585]: np.einsum('ij,jk->ik', A2,b)
Out[585]:
array([[12, 17],
[28, 41],
[44, 65]])
A is (3,4), which won't work with the (2,2) b. Think of it as trying work with a doubled j dimension: 'i(2j),jk->i?k'. But what if we inserted an axis? 'imk,jk->imk'? Or added the extra dimension to i?
In [587]: np.einsum('imj,jk->imk', A.reshape(3,2,2),b)
Out[587]:
array([[[ 4, 5],
[12, 17]],
[[20, 29],
[28, 41]],
[[36, 53],
[44, 65]]])
The numbers are there, just the shape is (3,2,2).
In [590]: np.einsum('imj,jk->imk', A.reshape(3,2,2),b).reshape(3,4)
Out[590]:
array([[ 4, 5, 12, 17],
[20, 29, 28, 41],
[36, 53, 44, 65]])
Or you could build A from the start so that mij,jk->mik works (#Divaker)
#Psidom:
np.einsum('ij,jk->ik', A.reshape(3,2,2).reshape(-1,2) ,b).reshape(3,-1)
`#piRSquared':
'kj,jI->kI`
Shift you perspective. You are locking yourself into 3 x 2 unnecessarily.
You can think of A1 and A2 as 2x3 instead, then A would be
array([[ 0, 4, 8, 2, 6, 10],
[ 1, 5, 9, 3, 7, 11]])
Then take the transpose of b = b.T
array([[0, 4],
[1, 5]])
So that you can do you operation
b # A
array([[ 4, 20, 36, 12, 28, 44],
[ 5, 29, 53, 17, 41, 65]])
Let your "draws" look like this
A = np.random.randint(10, size=(2, 9))
A
array([[7, 2, 1, 0, 9, 9, 1, 0, 2],
[8, 6, 1, 6, 6, 2, 4, 2, 9]])
b # A
array([[32, 24, 4, 24, 24, 8, 16, 8, 36],
[47, 32, 6, 30, 39, 19, 21, 10, 47]])
​

Categories

Resources