# Numpy

- NumPy is a python library used for working with arrays.
- It also has functions for working in domain of linear algebra, fourier transform, and matrices.
- NumPy was created in 2005 by Travis Oliphant. It is an open source project and you can use it freely.
- NumPy stands for Numerical Python.
- In Python we have lists that serve the purpose of arrays, but they are slow to process.
- NumPy aims to provide an array object that is up to 50x faster that traditional Python lists.
- The array object in NumPy is called ndarray, it provides a lot of supporting functions that make working with ndarray very easy.
- Arrays are very frequently used in data science, where speed and resources are very important.
- NumPy arrays are stored at one continuous place in memory unlike lists, so processes can access and manipulate them very efficiently.

## ndarray object¶

NumPy is used to work with arrays. The array object in NumPy is called ndarray We can create a NumPy ndarray object by using the array() function. To create an ndarray, we can pass a list, tuple or any array-like object into the array() method, and it will be converted into an ndarray:

```
import numpy as np
arr = np.array([1, 2, 3, 4, 5])
print(arr)
[1 2 3 4 5]
```

## Dimensions in Arrays¶

A dimension in arrays is one level of array depth (nested arrays).
`nested array:`

are arrays that have arrays as their elements.

### 0-D Arrays¶

`0-D arrays`

, or `Scalars`

, are the elements in an array. Each value in an array is a 0-D array.

```
import numpy as np
arr = np.array(42)
print(arr)
42
```

### 1-D Arrays¶

An array that has 0-D arrays as its elements is called uni-dimensional or 1-D array. These are the most common and basic arrays.

```
import numpy as np
arr = np.array([1, 2, 3, 4, 5])
print(arr)
[1 2 3 4 5]
```

### 2-D, 3-D, 4-D Arrays and so on...¶

An array that has `1-D`

arrays as its elements is called a `2-D`

array.
These are often used to represent `matrix`

or `2nd order tensors`

.
`NumPy has a whole sub module dedicated towards matrix operations called numpy.mat`

An array that has `2-D`

arrays (`matrices`

) as its elements is called `3-D`

array.
These are often used to represent a `3rd order tensor`

.

```
# 2-D Array
import numpy as np
arr = np.array([[1, 2, 3], [4, 5, 6]])
print(arr)
[[1 2 3]
[4 5 6]]
# 3-D Array
arr = np.array([[[1, 2, 3], [4, 5, 6]], [[1, 2, 3], [4, 5, 6]]])
print(arr)
[[[1 2 3]
[4 5 6]]
[[1 2 3]
[4 5 6]]]
```

## ndim (numberr of Dimensions)¶

NumPy Arrays provides the ndim attribute that returns an integer that tells us how many dimensions the array have. An array can have any number of dimensions. When the array is created, you can define the number of dimensions by using the ndmin argument.

```
import numpy as np
d = np.array([[[1, 2, 3], [4, 5, 6]], [[1, 2, 3], [4, 5, 6]]])
print(d.ndim)
3
arr = np.array([1, 2, 3, 4], ndmin=5)
print(arr)
[[[[[1 2 3 4]]]]]
print('number of dimensions :', arr.ndim)
number of dimensions : 5
```

## Array Indexing¶

Array indexing is the same as accessing an array element. You can access an array element by referring to its index number. The indexes in NumPy arrays start with 0, meaning that the first element has index 0, and the second has index 1 etc.

```
import numpy as np
# Get the first element from the following array:
arr = np.array([1, 2, 3, 4])
print(arr[0])
1
```

## Access 2-D, 3-D arrays¶

```
import numpy as np
# Access the 2nd element on 1st dim:
arr = np.array([[1,2,3,4,5], [6,7,8,9,10]])
print('2nd element on 1st dim: ', arr[0, 1])
2nd element on 1st dim: 2
#Access the 5th element on 2nd dim:
arr = np.array([[1,2,3,4,5], [6,7,8,9,10]])
print('5th element on 2nd dim: ', arr[1, 4])
5th element on 2nd dim: 10
# Access the third element of the second array of the first array:
arr = np.array([[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]])
print(arr[0, 1, 2])
6
# Print the last element from the 2nd dim:
arr = np.array([[1,2,3,4,5], [6,7,8,9,10]])
print('Last element from 2nd dim: ', arr[1, -1])
Last element from 2nd dim: 10
```

### Array Slicing¶

Slicing in python means taking elements from one given index to another given index.

```
import numpy as np
# Slice elements from index 1 to index 5 from the following array
arr = np.array([1, 2, 3, 4, 5, 6, 7])
print(arr[1:5])
[2 3 4 5]
# Slice elements from index 4 to the end of the array
arr = np.array([1, 2, 3, 4, 5, 6, 7])
print(arr[4:])
[5 6 7]
# Slice from the index 3 from the end to index 1 from the end:
arr = np.array([1, 2, 3, 4, 5, 6, 7])
print(arr[-3:-1])
[5 6]
# Return every other element from index 1 to index 5:
arr = np.array([1, 2, 3, 4, 5, 6, 7])
print(arr[1:5:2])
[2 4]]
# From the second element, slice elements from index 1 to index 4 (not included):
arr = np.array([[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]])
print(arr[1, 1:4])
[7 8 9]
# From both elements, return index 2:From both elements, return index 2:
arr = np.array([[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]])
print(arr[0:2, 2])
[3 8]
# From both elements, slice index 1 to index 4 (not included), this will return a 2-D array:
arr = np.array([[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]])
print(arr[0:2, 1:4])
[[2 3 4]
[7 8 9]]
```

## Joining Arrays¶

We pass a sequence of arrays that we want to join to the `concatenate()`

function, along with the axis. If axis is not explicitly passed, it is taken as 0.

### Using `concatenate()`

¶

```
import numpy as np
# Join two arrays
arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])
arr = np.concatenate((arr1, arr2))
print(arr)
[1 2 3 4 5 6]
```

### Join two 2-D arrays along rows (axis=1):¶

```
arr1 = np.array([[1, 2], [3, 4]])
arr2 = np.array([[5, 6], [7, 8]])
arr = np.concatenate((arr1, arr2), axis=1)
print(arr)
[[1 2 5 6]
[3 4 7 8]]
```

### Stacking Along Rows using `stack()`

¶

```
arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])
arr = np.stack((arr1, arr2), axis=1)
print(arr)
[[1 4]
[2 5]
[3 6]]
```

### Stacking Along Columns using `vstack()`

¶

```
import numpy as np
arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])
arr = np.vstack((arr1, arr2))
print(arr)
[[1 2 3]
[4 5 6]]
```

## Splitting NumPy Arrays¶

Splitting is reverse operation of Joining. Joining merges multiple arrays into one and Splitting breaks one array into multiple.

```
import numpy as np
arr = np.array([1, 2, 3, 4, 5, 6])
newarr = np.array_split(arr, 3)
print(newarr)
[array([1, 2]), array([3, 4]), array([5, 6])]
```

If the array has less elements than required, it will adjust from the end accordingly.

```
arr = np.array([1, 2, 3, 4, 5, 6])
newarr = np.array_split(arr, 4)
print(newarr)
[array([1, 2]), array([3, 4]), array([5]), array([6])]
```

Split the 2-D array into three 2-D arrays.

```
import numpy as np
arr = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12], [13, 14, 15], [16, 17, 18]])
newarr = np.array_split(arr, 3)
print(newarr)
[array([[1, 2, 3],
[4, 5, 6]]), array([[ 7, 8, 9],
[10, 11, 12]]), array([[13, 14, 15],
[16, 17, 18]])]
```

## Search¶

```
import numpy as np
arr = np.array([1, 2, 3, 4, 5, 4, 4])
x = np.where(arr == 4)
print(x)
(array([3, 5, 6]),)
# Find the indexes where the values are even:
arr = np.array([1, 2, 3, 4, 5, 6, 7, 8])
x = np.where(arr%2 == 0)
print(x)
(array([1, 3, 5, 7]),)
# Search Sorted
arr = np.array([6, 7, 8, 9])
x = np.searchsorted(arr, 7)
print(x)
1
# Sort the array alphabetically:
arr = np.array(['banana', 'cherry', 'apple'])
print(np.sort(arr))
['apple' 'banana' 'cherry']
# sorting 2-d array
arr = np.array([[3, 2, 4], [5, 0, 1]])
print(np.sort(arr))
[[2 3 4]
[0 1 5]]
```