NumPy, short for *Numerical Python* is a library for scientific computing in Python. As the name suggests, it provides a host of tools to conduct mathematical and numerical routines. One amongst these high-performing tools is the NumPy array. This multidimensional array object is a powerful data structure for efficient computation on vectors and matrices. In this article, we will explore these arrays and their power-packed functionalities.

You are encouraged to **follow along** with the tutorial and play around with NumPy, trying various things and making sure you're getting the hang of it. Let's get started!

# Imports and installations

Installation instructions for NumPy can be found here: Installing packages — SciPy.org (NumPy is part of the *Scientific Python* (SciPy) stack). In addition, we recommend you install IPython, which provides a lot of nice features helpful when using Python interactively. (If you are using IPython, you can start the interactive Python session with command ipython).

As with any other package we start off by importing the library, NumPy in this case, by its most commonly used alias, np.

In [2]: import numpy as np

# Array Creation Routines

Now with our np library ready to use, we can jump right into array creation. This array data structure may look simple but comes with usefulness in multitudes.

# Create a 1-dimensional arrayIn [3]: a = np.array([1,2,3,4,5,6,7,8])In [4]: aOut[4]: array([1, 2, 3, 4, 5, 6, 7, 8])

Not only so, the beauty of this extends further to n-dimensional array objects, ndarray. Below is an example of a 2-dimensional array.

# Create a 2-dimensional arrayIn [5]: b = np.array([[1,0,1,0,2,3], [1,3,0,1,2,0], [0,1,0,0,1,3]])In [6]: bOut[6]:array([[1, 0, 1, 0, 2, 3],[1, 3, 0, 1, 2, 0],[0, 1, 0, 0, 1, 3]])

Alternatively, we can initialize an array by calling on common functions like zeros, ones, full, eye, and random. The first argument to all these functions is the shape of the array we would like.

# Create a 2x2 array of all zerosIn [7]: np.zeros((2, 2))Out[7]:array([[0., 0.],[0., 0.]]# Create an 1x2 array of all onesIn [8]: np.ones((1, 2))Out[8]: array([[1., 1.]])# Create a 2x2 constant array of the number 7In [9]: np.full((2, 2), 7)Out[9]:array([[7, 7],[7, 7]])# Create a 2x2 identity matrix# Shape is 2 instead of (2, 2) since identity matrix must be a square matrixIn [10]: np.eye(2)Out[10]:array([[1., 0.],[0., 1.]])# Creates a 2x2 array filled with random values between 0 and 1In [11]: c = np.random.random((2, 2))In [12]: cOut[12]:array([[0.11832557, 0.20434662],[0.6460374 , 0.70518256]])

# Reading documentation within the interactive shell

If you are using IPython, at any point of time, you can add a ? before a function call to read its documentation. For example,

In [14]: ?np.random.random=========================================================================Docstring:random_sample(size=None)Return random floats in the half-open interval [0.0, 1.0).Results are from the "continuous uniform" distribution over thestated interval. To sample :math:`Unif[a, b), b > a` multiplythe output of `random_sample` by `(b-a)` and add `a`::(b - a) * random_sample() + aParameters----------size : int or tuple of ints, optionalOutput shape. If the given shape is, e.g., ``(m, n, k)``, then``m * n * k`` samples are drawn. Default is None, in which case asingle value is returned.Returns-------out : float or ndarray of floatsArray of random floats of shape `size` (unless ``size=None``, in whichcase a single float is returned).Examples-------->>> np.random.random_sample()0.47108547995356098>>> type(np.random.random_sample())<type 'float'>>>> np.random.random_sample((5,))array([ 0.30220482, 0.86820401, 0.1654503 , 0.11659149, 0.54323428])Three-by-two array of random numbers from [-5, 0):>>> 5 * np.random.random_sample((3, 2)) - 5array([[-3.99149989, -0.52338984],[-2.99091858, -0.79479508],[-1.23204345, -1.75224494]])Type: builtin_function_or_method=========================================================================

The above documentation in this case starts with a description of the method. Then it describes the required input and the output returned by the method. Finally, it provides some illustrative examples.

Press q (**q**uit) to get out of the documentation reading mode, and back to coding.

# Defining features of NumPy Arrays

Once this data is structured into a NumPy array, it becomes useful to observe its defining features. Here are a few:

## Shape and size

Find the dimensions (shape) of an array

In [15]: a.shapeOut[15]: (8,)In [16]: b.shapeOut[16]: (3, 6)

Find the number of dimensions (ndim) of an array

In [17]: a.ndimOut[17]: 1In [18]: b.ndimOut[18]: 2

Total number of elements (size) in an array

In [26]: a.sizeOut[26]: 8In [27]: b.sizeOut[27]: 18In [29]: c.sizeOut[29]: 4

## Datatype

Type of data stored in an array

In [20]: a.dtype.nameOut[20]: 'int64'In [21]: b.dtype.nameOut[21]: 'int64'In [22]: c.dtype.nameOut[22]: 'float64'

Depending on whether your computer is 32-bit or 64-bit, you'll get 'int32' or 'int64' in the outputs above. It tells us the *type* of each element stored in the array. When the array has integers, the type is 'int64', i.e. 64-bit integers. When the array has real numbers, the type is 'float64', i.e. 64-bit floating point values.

Size of an individual array element (in bytes)

In [23]: a.itemsizeOut[23]: 8In [24]: b.itemsizeOut[24]: 8In [25]: c.itemsizeOut[25]: 8

Since 8 bits = 1 byte, and we have 64-bit integers and floats, the number of bytes per array element is 64/8 = 8. In a 32-bit computer, this value will be 32/8 = 4.

The type of array itself

In [30]: type(a)Out[30]: numpy.ndarray

Often, we use these attributes to create new arrays as well. For example,

# Create an array of zeroes of the same size as "b"In [31]: np.zeros(b.shape)Out[31]:array([[0., 0., 0., 0., 0., 0.],[0., 0., 0., 0., 0., 0.],[0., 0., 0., 0., 0., 0.]])# Create an array of ones of the same size as "a"In [32]: np.ones(a.shape)Out[32]: array([1., 1., 1., 1., 1., 1., 1., 1.])

# Slicing and Indexing

Beyond creation and conversion to arrays, NumPy library also allows us to modify existing arrays. This is achievable through slicing and indexing.

## Indexing

We use the notation a[i] to pull out the *i*-th element from array a (assuming a is 1-dimensional). Indexing starts as 0, as in normal Python. Hence, a[0] gives the first element, a[1] gives the second element, and so on.

# Recall what array a looked likeIn [33]: aOut[33]: array([1, 2, 3, 4, 5, 6, 7, 8])# Indexing. As always, indexes start from 0.In [34]: a[0] # element 1Out[34]: 1In [35]: a[1] # element 2Out[35]: 2

For 2-dimensional arrays, we use the notation array[i, j] to pull out the element in row *i*, column *j*.

# Recall what array b looked likeIn [33]: bOut[33]:array([[1, 0, 1, 0, 2, 3],[1, 3, 0, 1, 2, 0],[0, 1, 0, 0, 1, 3]])# Indexing. As always, indexes start from 0.In [34]: b[0, 1] # (row 0, column 1)Out[34]: 0

## Slicing

Instead of just a single element, it is also possible for us to look at sub-arrays or sup-parts of the original array. This is done using slicing.

# Slicing pulls out a subarray# Example for pulling out elements 2 through 4# (element 2 is included and 4 is not, same as python list slicing).In [33]: a[2:4]Out[33]: array([2, 3])# Example for pulling out first 3 elementsIn [34]: a[:3]Out[34]: array([1, 2, 3])

Similar to indexing, slicing can be done on higher dimensional arrays as well.

# Example for pulling out first 2 rows and columns 1 though 3# (column 1 is included and 3 is not, same as python list slicing).In [35]: b[:2, 1:3]Out[35]:array([[0, 1],[3, 0]])# values from row 1 through 2 and all columnsIn [19]: row = b[1:2, :]Out[19]: array([[1, 3, 0, 1, 2, 0]])# values from all rows and column 2In [20]: b[:, 2]Out[20]: array([1, 0, 0])

Note that there is a small difference in the last two examples. b[1:2,:] returns a 2-D array of shape (1, 6). Whereas b[:,2] returns a 1-D array of shape (3, ).

## Modifying elements

A index or a slice of an array is a view into the same data, so modifying it will modify the original array.

In [17]: b[0, 0] = 7 # sets the first value in the first column to 7.Out[17]:array([[7, 0, 1, 0, 2, 3],[1, 3, 0, 1, 2, 0],[0, 1, 0, 0, 1, 3]])

We can also set an entire sub-array to be a particular value.

In [17]: d = np.copy(b)In [18]: d[1:3, 2:4] = 9 # sets all values in the subarray to 9.Out[18]:array([[7, 0, 1, 0, 2, 3],[1, 3, 9, 9, 2, 0],[0, 1, 9, 9, 1, 3]])

Or set each value in a sub-array to a specific value, as defined by another array of the same shape as the sub-array.

In [19]: d[:2, 1:3] = np.array([[2, 0], [4, 3]])Out[19]:array([[7, 2, 0, 0, 2, 3],[1, 4, 3, 9, 2, 0],[0, 1, 9, 9, 1, 3]])

## Conditionals and Conditional Indexing

Let's see a conditional. A conditional returns an output of True and False values of the same size as the original array.

# Recall what array a looked likeIn [33]: aOut[33]: array([1, 2, 3, 4, 5, 6, 7, 8])In [37]: (a > 2)Out[37]: array([False, False, True, True, True, True, True, True])

This array of True and False can be used for indexing.

In [38]: a[a > 2] # values of a larger than 2Out[38]: array([3, 4, 5, 6, 7, 8])# Let's do the same thing for array b# Recall what array b looked likeIn [33]: bOut[33]:array([[7, 0, 1, 0, 2, 3],[1, 3, 0, 1, 2, 0],[0, 1, 0, 0, 1, 3]])In [39]: b > 2Out[39]:array([[ True, False, False, False, False, True],[False, True, False, False, False, False],[False, False, False, False, False, True]])In [40]: b[b > 2]Out[40]: array([7, 3, 3, 3])

Note above that when we performed conditional indexing on a 2-D array, the result was a *flattened* array. This is true when the conditional (in this case, b > 2) has the same shape as the original array (in this case, b).

NumPy doesn't have a choice here but to flatten the result, since it is possible for the result to include different number of values from each row and column. In the example above, 2 values in row 1 are greater than 2, but only 1 value from rows 2 and 3 are greater than 2.

# Array Manipulations

Now, we'll see how to change the shape of NumPy arrays, and how to combine multiple arrays into one.

## Reshape, flatten, resize and transpose

Reshape can be used to change the shape of an array. Note that the number of elements in the original array and the final array must be equal.

# Recall what array a looks likeIn [43]: aOut[43]: array([1, 2, 3, 4, 5, 6, 7, 8])# Reshape to shape 4 x 2In [44]: a = a.reshape(4, 2)In [45]: aOut[45]:array([[1, 2],[3, 4],[5, 6],[7, 8]])# Reshape to shape 2 x 4In [44]: a = a.reshape(2, 4)In [45]: aOut[45]:array([[1, 2, 3, 4],[5, 6, 7, 8]])# Reshape-ing such that new size != old size is illegalIn [48]: a = a.reshape(3, 5)---------------------------------------------------------------------------ValueError Traceback (most recent call last)<ipython-input-48-06b9efd6dc74> in <module>()----> 1 a = a.reshape(3, 5)ValueError: cannot reshape array of size 8 into shape (3, 5)

As you can see above, during reshaping to a 2-D object, values are filled row by row.

"Flatten" is equivalent to reshaping to vector of length a.size.

In [46]: a = a.flatten()In [47]: aOut[47]: array([1, 2, 3, 4, 5, 6, 7, 8])

If we would like to reshape while changing the total number of elements in the array, we can use resize. Resize will repeat elements (if resizing to a larger size), or it will throw away elements (if resizing to a smaller size).

# Resize allows changing number of elements (repeats elements of a)In [65]: np.resize(a, (3, 5))Out[65]:array([[1, 2, 3, 4, 5],[6, 7, 8, 1, 2],[3, 4, 5, 6, 7]])

*Transpose* calculates the transpose of an array.

# permutes the dimensions of the arrayIn [51]: b.transpose()Out[51]:array([[7, 1, 0],[0, 3, 1],[1, 0, 0],[0, 1, 0],[2, 2, 1],[3, 0, 3]])

## Broadcasting

*Broadcasting* makes copies of the existing array. The first argument is the array itself, and the second argument is the shape of the new array.

In [58]: np.broadcast_to(a, (6, 8))Out[58]:array([[1, 2, 3, 4, 5, 6, 7, 8],[1, 2, 3, 4, 5, 6, 7, 8],[1, 2, 3, 4, 5, 6, 7, 8],[1, 2, 3, 4, 5, 6, 7, 8],[1, 2, 3, 4, 5, 6, 7, 8],[1, 2, 3, 4, 5, 6, 7, 8]])In [62]: np.broadcast_to(b, (2, 3, 6))Out[62]:array([[[7, 0, 1, 0, 2, 3],[1, 3, 0, 1, 2, 0],[0, 1, 0, 0, 1, 3]],[[7, 0, 1, 0, 2, 3],[1, 3, 0, 1, 2, 0],[0, 1, 0, 0, 1, 3]]])

## Expanding and squeezing dimensions

expand_dims and squeeze can be used to add or remove dimensions from an array.

# Expand the shape of an array by inserting axisIn [66]: c = np.expand_dims(b, axis=1) # expand at position 1In [67]: cOut[67]:array([[[7, 0, 1, 0, 2, 3]],[[1, 3, 0, 1, 2, 0]],[[0, 1, 0, 0, 1, 3]]])# Remove single-dimensional entries from the shape of an arrayIn [68]: np.squeeze(c)Out[68]:array([[7, 0, 1, 0, 2, 3],[1, 3, 0, 1, 2, 0],[0, 1, 0, 0, 1, 3]])

## Concatenating and stacking

concatenate and stack can be used to join multiple arrays into one. concatenate joins arrays along an existing axis, thereby, the total number of dimensions stays the same. stack on the other hand, creates a new dimension.

In [69]: c = b + 5In [70]: cOut[70]:array([[12, 5, 6, 5, 7, 8],[ 6, 8, 5, 6, 7, 5],[ 5, 6, 5, 5, 6, 8]])# Join arrays along an existing axis, axis 0In [71]: np.concatenate((b, c), axis=0)Out[71]:array([[ 7, 0, 1, 0, 2, 3],[ 1, 3, 0, 1, 2, 0],[ 0, 1, 0, 0, 1, 3],[12, 5, 6, 5, 7, 8],[ 6, 8, 5, 6, 7, 5],[ 5, 6, 5, 5, 6, 8]])# Join arrays along an existing axis, axis 1In [72]: np.concatenate((b, c), axis=1)Out[72]:array([[ 7, 0, 1, 0, 2, 3, 12, 5, 6, 5, 7, 8],[ 1, 3, 0, 1, 2, 0, 6, 8, 5, 6, 7, 5],[ 0, 1, 0, 0, 1, 3, 5, 6, 5, 5, 6, 8]])# Join arrays along a new axis, 0In [73]: np.stack((b, c), 0)Out[73]:array([[[ 7, 0, 1, 0, 2, 3],[ 1, 3, 0, 1, 2, 0],[ 0, 1, 0, 0, 1, 3]],[[12, 5, 6, 5, 7, 8],[ 6, 8, 5, 6, 7, 5],[ 5, 6, 5, 5, 6, 8]]])# Join arrays along a new axis, 1In [74]: np.stack((b, c), 1)Out[74]:array([[[ 7, 0, 1, 0, 2, 3],[12, 5, 6, 5, 7, 8]],[[ 1, 3, 0, 1, 2, 0],[ 6, 8, 5, 6, 7, 5]],[[ 0, 1, 0, 0, 1, 3],[ 5, 6, 5, 5, 6, 8]]])

## Splitting

split, hsplit (horizontal split) and vsplit (vertical split) can be used to split an array into equal-sized subarrays.

In [75]: np.split(b, 3)Out[75]:[array([[7, 0, 1, 0, 2, 3]]),array([[1, 3, 0, 1, 2, 0]]),array([[0, 1, 0, 0, 1, 3]])]In [78]: np.split(c, 3)Out[78]:[array([[12, 5, 6, 5, 7, 8]]),array([[6, 8, 5, 6, 7, 5]]),array([[5, 6, 5, 5, 6, 8]])]In [79]: np.split(c, 3, axis=1)Out[79]:[array([[12, 5],[ 6, 8],[ 5, 6]]),array([[6, 5],[5, 6],[5, 5]]),array([[7, 8],[7, 5],[6, 8]])]In [80]: np.hsplit(b, 2)Out[80]:[array([[7, 0, 1],[1, 3, 0],[0, 1, 0]]),array([[0, 2, 3],[1, 2, 0],[0, 1, 3]])]In [81]: np.hsplit(b, 3)Out[81]:[array([[7, 0],[1, 3],[0, 1]]),array([[1, 0],[0, 1],[0, 0]]),array([[2, 3],[2, 0],[1, 3]])]

## Append, insert and delete

And finally, append, insert and delete can be used to insert and delete elements from an array (the same way as in Python). append is equivalent to inserting elements to the end of the array.

# Append values to the end of an arrayIn [82]: np.append(a, [1, 2])Out[82]: array([1, 2, 3, 4, 5, 6, 7, 8, 1, 2])# Insert values (0, 0) along the given axis (0) at the given index (3)In [83]: np.insert(a, 3, [0, 0], axis=0)Out[83]: array([1, 2, 3, 0, 0, 4, 5, 6, 7, 8])# Delete the element at given locations (1) along an axis (0)In [87]: np.delete(a, [1, 3], axis=0)Out[87]: array([1, 3, 5, 6, 7, 8])

# Element-wise operations (add, multiply, etc)

Additionally, we can conduct mathematical calculations on these array. Some of the standard mathematical formulas (additional, subtraction, multiplication, division, etc) are shown below.

In [88]: bOut[88]:array([[7, 0, 1, 0, 2, 3],[1, 3, 0, 1, 2, 0],[0, 1, 0, 0, 1, 3]])In [89]: cOut[89]:array([[12, 5, 6, 5, 7, 8],[ 6, 8, 5, 6, 7, 5],[ 5, 6, 5, 5, 6, 8]])# element-wise additionIn [90]: b + cOut[90]:array([[19, 5, 7, 5, 9, 11],[ 7, 11, 5, 7, 9, 5],[ 5, 7, 5, 5, 7, 11]])In [91]: np.add(b, c)Out[91]:array([[19, 5, 7, 5, 9, 11],[ 7, 11, 5, 7, 9, 5],[ 5, 7, 5, 5, 7, 11]])# element-wise subtractionIn [92]: b - cOut[92]:array([[-5, -5, -5, -5, -5, -5],[-5, -5, -5, -5, -5, -5],[-5, -5, -5, -5, -5, -5]])In [93]: np.subtract(b, c)Out[93]:array([[-5, -5, -5, -5, -5, -5],[-5, -5, -5, -5, -5, -5],[-5, -5, -5, -5, -5, -5]])# element-wise multiplicationIn [94]: b * cOut[94]:array([[84, 0, 6, 0, 14, 24],[ 6, 24, 0, 6, 14, 0],[ 0, 6, 0, 0, 6, 24]])In [95]: np.multiply(b, c)Out[95]:array([[84, 0, 6, 0, 14, 24],[ 6, 24, 0, 6, 14, 0],[ 0, 6, 0, 0, 6, 24]])# element-wise division# oops. all zeroes.In [96]: b / cOut[96]:array([[0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0]])# we need to first multiply by 1.0 to convert everything to floatIn [100]: 1. * b / cOut[100]:array([[0.583, 0. , 0.167, 0. , 0.286, 0.375],[0.167, 0.375, 0. , 0.167, 0.286, 0. ],[0. , 0.167, 0. , 0. , 0.167, 0.375]])

NumPy also includes a number of element-wise operations, such as sqrt (square-root) and log (logarithm).

# element-wise square-rootIn [101]: np.sqrt(b)Out[101]:array([[2.646, 0. , 1. , 0. , 1.414, 1.732],[1. , 1.732, 0. , 1. , 1.414, 0. ],[0. , 1. , 0. , 0. , 1. , 1.732]])

# Aggregation operations (sum, min, max, etc)

Apart from element-wise operations, NumPy also includes a host of aggregation operations like sum, min, max, mean, etc.

All of these functions are available directly in the NumPy import, as in np.sum(b), np.min(b), etc. And most of the functions are also available as methods of the array class. Hence, we can also do b.sum(), b.min(), etc.

Lastly, aggregation operations can work over all elements of the array, or over specific dimensions. For example, we can use axis=0 to perform an operation for each column.

Some example will help:

# sum of all elementsIn [102]: b.sum()Out[102]: 25# sum of each columnIn [103]: b.sum(axis=0)Out[103]: array([8, 4, 1, 1, 5, 6])# sum of each rowIn [104]: b.sum(axis=1)Out[104]: array([13, 7, 5])

Similarly, we have functions like min (minimum), max (maximum), cumsum (cumulative summation).

# minimum of all valuesIn [105]: b.min()Out[105]: 0# maximum across axis 1In [106]: b.max(axis=1)Out[106]: array([7, 3, 3])# cumsum across axis 0In [107]: b.cumsum(axis=0)Out[107]:array([[7, 0, 1, 0, 2, 3],[8, 3, 1, 1, 4, 3],[8, 4, 1, 1, 5, 6]])

And also mean, median, std (standard deviation).

In [108]: b.mean(axis=0)Out[108]: array([2.667, 1.333, 0.333, 0.333, 1.667, 2. ])In [110]: np.median(b, axis=1)Out[110]: array([1.5, 1. , 0.5])In [113]: np.std(b, axis=0)Out[113]: array([3.091, 1.247, 0.471, 0.471, 0.471, 1.414])In [114]: np.mean(b, axis=0)Out[114]: array([2.667, 1.333, 0.333, 0.333, 1.667, 2. ])

We can also find all the unique elements in an array.

In [115]: np.unique(b)Out[115]: array([0, 1, 2, 3, 7])

# Matrix Operations (dot products, matrix multiplication, etc)

Now, lets look at some matrix operations that NumPy supports. np.dot(x, y) can be used to perform both dot-products as well as matrix multiplication. Let's see how:

In [117]: v = b[:, 0]In [118]: w = b[:, 1]In [119]: vOut[119]: array([7, 1, 0])In [120]: wOut[120]: array([0, 3, 1])

Dot product:

# Inner product of vectors: v[0]*w[0] + v[1]*w[1] + ... v[n]*w[n]In [121]: v.dot(w) # or np.dot(v, w)Out[121]: 3In [122]: v.dot(v) # or np.dot(v, v)Out[122]: 50

When the arrays are 2-dimensional, dot-product is equivalent to performing matrix multiplication. Below are examples of matrix-vector multiplication and matrix-matrix multiplication.

# Transpose of a matrixIn [125]: b.TOut[125]:array([[7, 1, 0],[0, 3, 1],[1, 0, 0],[0, 1, 0],[2, 2, 1],[3, 0, 3]])# Matrix-vector productIn [124]: np.dot(b.T, v)Out[124]: array([50, 3, 7, 1, 16, 21])# Matrix-matrix product (matrix multiplication)In [126]: np.dot(b.T, b)Out[126]:array([[50, 3, 7, 1, 16, 21],[ 3, 10, 0, 3, 7, 3],[ 7, 0, 1, 0, 2, 3],[ 1, 3, 0, 1, 2, 0],[16, 7, 2, 2, 9, 9],[21, 3, 3, 0, 9, 18]])In [127]: np.dot(b.T, c)Out[127]:array([[90, 43, 47, 41, 56, 61],[23, 30, 20, 23, 27, 23],[12, 5, 6, 5, 7, 8],[ 6, 8, 5, 6, 7, 5],[41, 32, 27, 27, 34, 34],[51, 33, 33, 30, 39, 48]])

For dot products and matrix multiplication to be performed, the dimensions of the arrays need to be compatible. If they are not, NumPy raises an exception.

# Raises ValueError if arrays of incompatible dimensions are passed (matrix-vector)In [125]: np.dot(b, v)---------------------------------------------------------------------------ValueError Traceback (most recent call last)<ipython-input-123-8b442221e0a2> in <module>()----> 1 np.dot(b, v)ValueError: shapes (3,6) and (3,) not aligned: 6 (dim 1) != 3 (dim 0)# Raises ValueError if arrays of incompatible dimensions are passed (matrix-matrix)In [128]: np.dot(b, c)---------------------------------------------------------------------------ValueError Traceback (most recent call last)<ipython-input-128-5350d63f2f76> in <module>()----> 1 np.dot(b, c)ValueError: shapes (3,6) and (3,6) not aligned: 6 (dim 1) != 3 (dim 0)

# Input / Output with NumPy

The ndarray objects can be saved to and loaded from the disk files. The IO functions available are:-

- load() and save() functions handle NumPy binary files (files have extension npy). See more: numpy.load and numpy.save.
- loadtxt() and savetxt() functions handle normal text files. See more: numpy.loadtxt and numpy.savetxt.

# Conclusion

In summary, advantages of using NumPy

- array oriented computing
- efficiently implemented multi-dimensional arrays
- designed for scientific computation
- sophisticated functions for initializing, slicing and manipulating arrays (and shaping, stacking, splitting).
- performing vector and matrix operations like sum, cumulative sum, mean, normalization, matrix multiplication, etc.
- useful linear algebra, Fourier transform, and random number capabilities

Besides its obvious scientific uses, NumPy can also be used as an efficient multi-dimensional container of generic data (including strings and objects). For more information, you can check the documentation found here: NumPy Reference