clear authoritative explanation of numpy axis numbers? - python

I am getting confused by contradictory explanations of what exactly the term axis means in numpy and how these constructs are numbered.
Here's one explanation:
Axes are defined for arrays with more than one dimension.
A 2-dimensional array has two corresponding axes:
the first running vertically downwards across rows (axis 0), and
the second running horizontally across columns (axis 1).
So, in this 3x4 matrix ...
>>> b = np.arange(12).reshape(3,4)
>>> b
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
(axis 0) is the 3 rows
(axis 1) is the 4 columns
So the rule might be ...
In an MxN matrix, (axis 0) is M and (axis 1) is N.
Is this correct?
So, in a 3 dimensional matrix AxBxC
(axis 0) is A
(axis 1) is B
(axis 2) is C
Is this correct?

Everything you said is correct, with the exception of
Axes are defined for arrays with more than one dimension.
Axes are also defined for one dimensional arrays - there is just one of them (i.e. axis 0).
One intuitive way to think about axes is to consider what happens when you apply a reduction operation over one axis, such as summation. For example, suppose I have some array x:
x = np.arange(60).reshape(3, 4, 5)
If I compute x.sum(0) I am "collapsing" x over the first dimension (i.e. axis 0), so I end up with a (4, 5) array. Likewise, x.sum(1) gives me a (3, 5) array and x.sum(2) gives me a (3, 4) array.
An integer index into a single axis of x will also give me an output with one fewer axis. For example, x[0, :, :] gives me the first "row" of x, which has shape (4, 5), x[:, 0, :] gives me the first "column" with shape (3, 5), and x[:, :, 0] gives me the first slice in the third dimension of x with shape (3, 4).

If someone need a clear idea, here is the picture:

A smart way to remember this is that
axis =0 collapses the rows
Whilst
axis=1 collapses the columns
a three 3*4 array when operated upon with sum function and axis =0 would yield 1*4 output that is all the rows would be collapsed and the aggregation would be done column-wise.
The same function when performed with axis=1 would collapse the columns and yield 3*1 output with aggregation along rows.
the image link would further help assimilating this concept.
Example for understanding

Although it is possible to imagine this in 3D, I personally feel it is difficult to imagine when we go to 4D or 5D... So I decide to give up but rather think about this in an implementation perspective. Basically, it has N-number of nested for loop, and if we want to reduce one specific axis, we just work on the for loop of that axis. For example, if given a 3x3x3 tensor, axis = 0 is the for loop of a[i][x][x], axis = 1 is to loop a[x][i][x], axis = 2 is to loop a[x][x][i]. 4D, 5D, ... should have the same way.
def my_reduce_max(a, axis=0):
b = [[-1 for _ in range(3)] for _ in range(3)]
for j in range(3):
for k in range(3):
tmp_max = -1
for i in range(3):
if axis == 0:
get_value = a[i][j][k]
elif axis == 1:
get_value = a[j][i][k]
else:
get_value = a[j][k][i]
tmp_max = max(get_value, tmp_max)
b[j][k] = tmp_max
return b
a = np.arange(27).reshape((3,3,3))
print(a)
my_reduce_max(a, 2)

Related

RuntimeError: Sizes of arrays must match except in dimension 1

I have a list of different shapes array that I wish to stack. Of course, np.stack doesn't work here because of the different shapes so is there a way to handle this using np.stack on dim=1?
is it possible to stack these tensors with different shapes along the second dimension so I would have the result array with shape [ -, 2, 5]? I want the result to be 3d.
data = [np.random.randn([2, 5]), np.random.randn([3, 5])]
stacked = np.stack(data, dim=1)
I tried another solution
f, s = data[0].shape, data[1].shape
stacked = np.concatenate((f.unsqueeze(dim=1), s.unsqueeze(dim=1)), dim=1)
where I unsqueeze the dimension but I also get this error:
RuntimeError: Sizes of arrays must match except in dimension 1. Expected size 2 but got size 3 for array number 1 in the list.
another solution that didn't work:
l = torch.cat(f[:, None, :], s[:, None, :])
the expected output should have shape [:, 2, 4]
Stacking 2d arrays as in your example, to become 3d, would require you to impute some missing data. There is not enough info to create the 3d array if the dimensions of your input data don't match.
I see two options:
concatenate along axis = 1 to get shape (5, 5)
a = data[0]
b = data[1]
combined = np.concatenate((a, b)) # shape (5, 5)
add dummy rows to data[0] to be able to create a 3d result
a = data[0]
b = data[1]
a = np.concatenate((a, np.zeros((b.shape[0] - a.shape[0], a.shape[1]))))
combined = np.stack((a, b)) # shape (2, 3, 5)
Another option could be to delete rows from data[1] to do something similar as option 2), but deleting data is in general not recommended.

How to flatten an array to a matrix in Numpy?

I am looking for an elegant way to flatten an array of arbitrary shape to a matrix based on a single parameter that specifies the dimension to retain. For illustration, I would like
def my_func(input, dim):
# code to compute output
return output
Given for example an input array of shape 2x3x4, output should be for dim=0 an array of shape 12x2; for dim=1 an array of shape 8x3; for dim=2 an array of shape 6x8. If I want to flatten the last dimension only, then this is easily accomplished by
input.reshape(-1, input.shape[-1])
But I would like to add the functionality of adding dim (elegantly, without going through all possible cases + checking with if conditions, etc.). It might be possible by first swapping dimensions, so that the dimension of interest is trailing and then applying the operation above.
Any help?
We can permute axes and reshape -
# a is input array; axis is input axis/dim
np.moveaxis(a,axis,-1).reshape(-1,a.shape[axis])
Functionally, it's basically pushing the specified axis to the back and then reshaping keeping that axis length to form the second axis and merging rest of the axes to form the first axis.
Sample runs -
In [32]: a = np.random.rand(2,3,4)
In [33]: axis = 0
In [34]: np.moveaxis(a,axis,-1).reshape(-1,a.shape[axis]).shape
Out[34]: (12, 2)
In [35]: axis = 1
In [36]: np.moveaxis(a,axis,-1).reshape(-1,a.shape[axis]).shape
Out[36]: (8, 3)
In [37]: axis = 2
In [38]: np.moveaxis(a,axis,-1).reshape(-1,a.shape[axis]).shape
Out[38]: (6, 4)

numpy: summing along all but last axis

If I have an ndarray of arbitrary shape and I would like to compute the sum along all but the last axis I can, for instance, achieve it by doing
all_but_last = tuple(range(arr.ndim - 1))
sum = arr.sum(axis=all_but_last)
Now, tuple(range(arr.ndim - 1)) is not exactly intuitive I feel. Is there a more elegant/numpy-esque way to do this?
Moreover, if I want to do this for multiple arrays of varying shape, I'll have to calculate a separate dimension tuple for each of them. Is there a more canonical way to say "regardless of what the dimensions are, just give me all but one axis"?
You could reshape the array so that all axes except the last are flattened (e.g. shape (k, l, m, n) becomes (k*l*m, n)), and then sum over the first axis.
For example, here's your calculation:
In [170]: arr.shape
Out[170]: (2, 3, 4)
In [171]: arr.sum(axis=tuple(range(arr.ndim - 1)))
Out[171]: array([2.85994792, 2.8922732 , 2.29051163, 2.77275709])
Here's the alternative:
In [172]: arr.reshape(-1, arr.shape[-1]).sum(axis=0)
Out[172]: array([2.85994792, 2.8922732 , 2.29051163, 2.77275709])
You can use np.apply_over_axes to sum over multiple axes.
np.apply_over_axes(np.sum, arr, [0,2]) #sum over axes 0 and 2
np.apply_over_axes(np.sum, arr, range(arr.ndim - 1)) #sum over all but last axis

Transposing a 3-d numpy array

Suppose I have a numpy array A with shape (5,24,1).
I want to take five separate transposes along axis = 0, such that the resulting shape after transposes is (5,1,24).
How can I do this using some of the numpy functions?
Three approaches could be suggested -
A.swapaxes(1,2)
A.transpose(0,2,1)
np.rollaxis(A,2,1)
For swapaxes and transpose, please follow their docs.
With np.rollaxis, we are rolling the axes to bring the third axis into the second position and thus pushing back the second axis into third axis position. More info in the linked docs.
Using the fact that the last axis is singleton -
A[:,None,:,0]
You could use np.moveaxis (one option missing from Divakars excellent answer) where you can define which axis should be moved:
np.moveaxis(A, (0, 1, 2), (0, 2, 1))
This moves axis 0 to 0 (no change, could also be omitted), 1 to 2 and 2 to 1. So this basically just swaps axis 1 and 2.
>>> A = np.ones((5,24,1))
>>> res = np.moveaxis(A, (1, 2), (2, 1)) # this time without the zeros
>>> res.shape
(5, 1, 24)

indexing a numpy array with indices from another array

I have a numpy array "data" with dimensions [t, z, x, y]. The
dimensions represent time (t) and three spatial dimensions (x, y, z).
I have a separate array "idx" of indices with dimensions [t, x, y]
describing vertical coordinates in data: each value in idx describes a
single vertical level in data.
I want to pull out the values from data indexed by idx. I've done it
successfully using loops (below). I've read several SO threads and numpy's indexing docs but I haven't been able to make it more pythonic/vectorized.
Is there an easy way I'm just not getting quite right? Or maybe loops
are a clearer way to do this anyway...
import numpy as np
dim = (4, 4, 4, 4) # dimensions time, Z, X, Y
data = np.random.randint(0, 10, dim)
idx = np.random.randint(0, 3, dim[0:3])
# extract vertical indices in idx from data using loops
foo = np.zeros(dim[0:3])
for this_t in range(dim[0]):
for this_x in range(dim[2]):
for this_y in range(dim[3]):
foo[this_t, this_x, this_y] = data[this_t,
idx[this_t, this_x, this_y],
this_x,
this_y]
# surely there's a better way to do this with fancy indexing
# data[idx] gives me an array with dimensions (4, 4, 4, 4, 4, 4)
# data[idx[:, np.newaxis, ...]] is a little closer
# data[tuple(idx[:, np.newaxis, ...])] doesn't quite get it either
# I tried lots of variations on those ideas but no luck yet
In [7]: I,J,K = np.ogrid[:4,:4,:4]
In [8]: data[I,idx,J,K].shape
Out[8]: (4, 4, 4)
In [9]: np.allclose(foo, data[I,idx,J,K])
Out[9]: True
I,J,K broadcast together to the same shape as idx (4,4,4).
More detail on this kind of indexing at
How to take elements along a given axis, given by their indices?

Categories

Resources