# NumPy Library

NumPy - It stands for Numerical Python, it is a library in python that provides support mainly for large arrays and matrices along with numerous collection of functions to operate on these array defined by NumPy effectively. One of the most used packages within data science, machine learning or any other computationally intensive task is NumPy - a basic package for scientific computing with Python.

### How to import :

```python
 import numpy as np
```

Here **np** is alias that means instead of having type NumPy every time when you want to use NumPy library function , you can simply use **np.** but its's not necessary that you use np as alias you can choose any name accordingly you but it's a common practice to use **np** as alias.

### How to create Array:

We use `array()` function for creating Array.

We can create 1-D Array and Multidimensional Array with use same function.

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

### How can we see it's type and dimension:

You can determine the type and dimensions of a NumPy array using the `type` and `shape` attributes, respectively.

```python
import numpy as np
arr = np.array([1, 2, 3, 4, 5])   # Create a 1D array
matrix = np.array([[1, 2, 3], [4, 5, 6]])  # Create a 2D array
# Get the type of the arrays
arr_type = type(arr)
matrix_type = type(matrix)
# Get the number of dimensions of the arrays
arr_ndim = arr.ndim
matrix_ndim = matrix.ndim

print("Array type:", arr_type)
print("Array number of dimensions (ndim):", arr_ndim)
print("Matrix type:", matrix_type)
print("Matrix number of dimensions (ndim):", matrix_ndim)
```

## Accessing Elements in a NumPy Array:

To access the Array you can use indexing similar to Python List.

```python
import numpy as np
arr = np.array([[1, 2, 3, 4], 
                [5, 6, 7, 8], 
                [9, 10, 11, 12]])    # Create a 2D arra
```

In above 2-D is Given , to access the individual elements , you can use the row and column indices.

```python
print(arr[0][0])  # Output: 1   
print(arr[0][1])  # Output: 2
print(arr[0][2])  # Output: 3
print(arr[0][3])  # Output: 4
print(arr[1][0])  # Output: 5
print(arr[1][1])  # Output: 6
print(arr[1][2])  # Output: 7
print(arr[1][3])  # Output: 8
print(arr[2][0])  # Output: 9
print(arr[2][1])  # Output: 10
print(arr[2][2])  # Output: 11
print(arr[2][3])  # Output: 12
```

You can also use single set of bracket with a comma . There will be no change in output.

```python
print(arr[0, 0])  # Output: 1
print(arr[0, 1])  # Output: 2
print(arr[0, 2])  # Output: 3
print(arr[0, 3])  # Output: 4
```

# Special NumPy Array :-

### Zero Array:

Zero array means all the element of the array will be zero. and You can use `zeros` function to create zero array.

```python
import numpy as np
ar_zero = np.zeros(5) 
print(ar_zero)                 # [0. 0. 0. 0. 0.]
# Create 2-D array
ar_zero1 = np.zeros((3,4))
print(ar_zero1)              ''' [[0. 0. 0. 0.]
                                  [0. 0. 0. 0.]
                                  [0. 0. 0. 0.]]'''
```

### One Array:

You can `ones` function to create one array.

```python
ar_one = np.ones(6)
print(ar_one)   #  [1. 1. 1. 1. 1. 1.]
```

### Diagonal Array:

You can use `eye` function to create diagonal Array.

```python
dig1 = np.eye(3,3)
dig2 = np.eye(3,5)
print(dig1)                   ''' [[1. 0. 0.]
                                   [0. 1. 0.]
                                   [0. 0. 1.]]'''
print(dig2)          ''' [[1. 0. 0. 0. 0.]
                          [0. 1. 0. 0. 0.]
                          [0. 0. 1. 0. 0.]]'''
```

### Range Array:

You can use `range`*function to create range array.*

```python
ar_ran = np.arange(10)
print(ar_ran)   # [0 1 2 3 4 5 6 7 8 9]
```

### Line space Array:

You can use `linspace` function to create line space array. line space mean create arrays with evenly spaced numbers over a specified interval.

```python
import numpy as np
arr = np.linspace(1, 10, 10)
print(arr)          # [ 1.  2.  3.  4.  5.  6.  7.  8.  9. 10.]
arr1 = np.linspace(1,100,20)
print(arr1)  #[  1.  12.  23.  34.  45.  56.  67.  78.  89. 100.]
```

# Array with Random Numbers:-

We have 4 functions to print random numbers array-

***1- rand() -*** This function is used to generate a random value between 0-1 #

***2- randn() -*** This function is used to generate a random value close to 0 this may return positive or negative as well .

***3- ranf() -*** the function for doing random sampling in NumPy. it returns an array of specified shape and fills it with random floats in the half open interval \[0.0,1.0)

***4- randint -*** This Function is used to generate a random number a given range.

### rand() function:

```python
import numpy as np
n = np.random.rand(4)
n1 = np.random.rand(3,4)
print(n)            
print(n1)
```

### randn() function:

```python
import numpy as np
n = np.random.randn(4)
n1 = np.random.randn(3,4)
print(n)            
print(n1)
```

### ranf() function:

```python
import numpy as np
rf = np.random.ranf(4)
print(rf)
```

### randint() function:

***Syntax:***`randint(min,max,total_value)`

```python
import numpy as np
rnt = np.random.randint(1,10,5)
print(rnt)
```

# Arithmetic Operation's:

We can perform arithmetic operation's on an array with the help of arithmetic operators or we can use functions also.

First We perform arithmetic operations using arithmetic operators after that we will perform using NumPy functions.

### Addition:

Here the example of 1-D array-

```python
import numpy as np
arr1 = np.array([1,2,3,4])
arr2 = np.array([5,6,7,8])
print(arr1 + 3)       #  [4 5 6 7]
print(arr1 + arr2)    # [ 6  8 10 12]
```

Here the Example of Multidimensional array-

```python
import numpy as np
arr1 = np.array([[1,2,3],[1,2,3,]])
arr2 = np.array([[5,6,7],[5,6,7]])
print(arr1 + 3)
print(arr1 + arr2)
```

### Substruction:

Here the example of 1-D array-

```python
import numpy as np

arr1 = np.array([1, 2, 3, 4])
arr2 = np.array([5, 6, 7, 8])
print(arr1 - 1)       # Output: [0 1 2 3]
print(arr1 - arr2)    # Output: [-4 -4 -4 -4]
```

Here the Example of Multidimensional array-

```python
import numpy as np

arr1 = np.array([[1, 2, 3], [1, 2, 3]])
arr2 = np.array([[5, 6, 7], [5, 6, 7]])

print(arr1 - 1)         # Output:
                        '''[[0 1 2]
                            [0 1 2]]'''

print(arr1 - arr2)          # Output:
                           '''[[-4 -4 -4]
                               [-4 -4 -4]]'''
```

### Multiplication:

Here the example of 1-D array-

```python
import numpy as np

arr1 = np.array([1, 2, 3, 4])
arr2 = np.array([5, 6, 7, 8])

print(arr1 * 2)       # Output: [2 4 6 8]
print(arr1 * arr2)    # Output: [ 5 12 21 32]
```

Here the Example of Multidimensional array-

```python
import numpy as np

arr1 = np.array([[1, 2, 3], [1, 2, 3]])
arr2 = np.array([[5, 6, 7], [5, 6, 7]])
print(arr1 * 2)
print(arr1 * arr2)
```

### Division:

Here the example of 1-D array-

```python
import numpy as np
arr1 = np.array([1, 2, 3, 4])
arr2 = np.array([5, 6, 7, 8])
print(arr1 / 2)       # Output: [0.5 1.  1.5 2. ]
print(arr1 / arr2)    # Output: [0.2        0.33333333 0.42857143 0.5       ]
```

Here the Example of Multidimensional array-

```python
import numpy as np

arr1 = np.array([[1, 2, 3], [1, 2, 3]])
arr2 = np.array([[5, 6, 7], [5, 6, 7]])
print(arr1 / 2)
print(arr1 / arr2)
```

### Modulus:

Here the example of 1-D array-

```python
import numpy as np

arr1 = np.array([1, 2, 3, 4])
arr2 = np.array([5, 6, 7, 8])
print(arr1 % 2)       # Output: [1 0 1 0]
print(arr1 % arr2)    # Output: [1 2 3 4]
```

Here the Example of Multidimensional array-

```python
import numpy as np
arr1 = np.array([[1, 2, 3], [1, 2, 3]])
arr2 = np.array([[5, 6, 7], [5, 6, 7]])
print(arr1 % 2)
print(arr1 % arr2)
```

### Exponential:

Here the example of 1-D array-

```python
import numpy as np
arr1 = np.array([1, 2, 3, 4])
arr2 = np.array([5, 6, 7, 8])
print(arr1 ** 2)      # Output: [ 1  4  9 16]
print(arr1 ** arr2)   # Output: [    1    64  2187 65536]
```

Here the Example of Multidimensional array-

```python
import numpy as np

arr1 = np.array([[1, 2, 3], [1, 2, 3]])
arr2 = np.array([[5, 6, 7], [5, 6, 7]])
print(arr1 ** 2)
print(arr1 ** arr2)
```

## Using NumPy functions:

### Addition:

For Addition We use `add()` function in NumPy.

```python
import numpy as np
# 1-D Arrays
arr1 = np.array([1, 2, 3, 4])
arr2 = np.array([5, 6, 7, 8])
print(np.add(arr1, 3))       # Output: [4 5 6 7]
print(np.add(arr1, arr2))    # Output: [ 6  8 10 12]

# Multidimensional Arrays
arr1 = np.array([[1, 2, 3], [1, 2, 3]])
arr2 = np.array([[5, 6, 7], [5, 6, 7]])
print(np.add(arr1, 3))
print(np.add(arr1, arr2))
```

### Subtraction:

For Subtraction we use `subtract()`function in NumPy.

```python
import numpy as np
# 1-D Arrays
arr1 = np.array([1, 2, 3, 4])
arr2 = np.array([5, 6, 7, 8])
print(np.subtract(arr1, 1))       # Output: [0 1 2 3]
print(np.subtract(arr1, arr2))    # Output: [-4 -4 -4 -4]

# Multidimensional Arrays
arr1 = np.array([[1, 2, 3], [1, 2, 3]])
arr2 = np.array([[5, 6, 7], [5, 6, 7]])
print(np.subtract(arr1, 1))
print(np.subtract(arr1, arr2))
```

### Multiplication:

For Multiplication we use `multiply()` function in NumPy.

```python
import numpy as np
# 1-D Arrays
arr1 = np.array([1, 2, 3, 4])
arr2 = np.array([5, 6, 7, 8])
print(np.multiply(arr1, 2))       # Output: [2 4 6 8]
print(np.multiply(arr1, arr2))    # Output: [ 5 12 21 32]

# Multidimensional Arrays
arr1 = np.array([[1, 2, 3], [1, 2, 3]])
arr2 = np.array([[5, 6, 7], [5, 6, 7]])
print(np.multiply(arr1, 2))
print(np.multiply(arr1, arr2))
```

### Division:

For Division we use `divide()` function in NumPy.

```python
import numpy as np
# 1-D Arrays
arr1 = np.array([1, 2, 3, 4])
arr2 = np.array([5, 6, 7, 8])
print(np.divide(arr1, 2))       # Output: [0.5 1.  1.5 2. ]
print(np.divide(arr1, arr2))    # Output: [0.2        0.33333333 0.42857143 0.5       ]

# Multidimensional Arrays
arr1 = np.array([[1, 2, 3], [1, 2, 3]])
arr2 = np.array([[5, 6, 7], [5, 6, 7]])
print(np.divide(arr1, 2))
print(np.divide(arr1, arr2))
```

### Modulus:

For Modulus we use `mod()` function in NumPy.

```python
import numpy as np
# 1-D Arrays
arr1 = np.array([1, 2, 3, 4])
arr2 = np.array([5, 6, 7, 8])
print(np.mod(arr1, 2))       # Output: [1 0 1 0]
print(np.mod(arr1, arr2))    # Output: [1 2 3 4]

# Multidimensional Arrays
arr1 = np.array([[1, 2, 3], [1, 2, 3]])
arr2 = np.array([[5, 6, 7], [5, 6, 7]])
print(np.mod(arr1, 2))
print(np.mod(arr1, arr2))
```

### Exponential:

For Exponential we use `power()` function in NumPy.

```python
import numpy as np
# 1-D Arrays
arr1 = np.array([1, 2, 3, 4])
arr2 = np.array([5, 6, 7, 8])
print(np.power(arr1, 2))      # Output: [ 1  4  9 16]
print(np.power(arr1, arr2))   # Output: [ 1    64  2187 65536]

# Multidimensional Arrays
arr1 = np.array([[1, 2, 3], [1, 2, 3]])
arr2 = np.array([[5, 6, 7], [5, 6, 7]])
print(np.power(arr1, 2))
print(np.power(arr1, arr2))
```

## Array Functions :

There are many functions in array like max, min, sqrt, sin, cos, argmin, cumsum etc.

***max() :*** Returns the maximum value of an array.

***min():*** Returns the minimum value of an array.

```python
import numpy as np

arr = np.array([1, 2, 3, 4, 5])
print(np.max(arr))  # Output: 5
print(np.min(arr))  # Output: 1
```

***sqrt():*** Computes the square root of each element in the array.

```python
import numpy as np
arr = np.array([1, 4, 9, 16])
print(np.sqrt(arr))  # Output: [1. 2. 3. 4.]
```

***sin():*** Compute the sin of each element.

***cos():*** Compute the cosine of each element.

```python
import numpy as np
arr = np.array([0, np.pi/2, np.pi])
print(np.sin(arr))  # Output: [0. 1. 0.]
print(np.cos(arr))  # Output: [ 1.  0. -1.]
```

***argmax():*** Gives the index number of maximum element in the array.

***argmin():*** Gives the index number of minimum element in the array.

```python
import numpy as np
arr = np.array([1, 3, 7, 0, 5])
print(np.argmin(arr))  # Output: 3
print(np.argmax(arr))  # Output: 2
```

cumsum(): Gives the cumulative sum of the elements.

```python
import numpy as np
arr = np.array([1, 2, 3, 4])
print(np.cumsum(arr))  # Output: [ 1  3  6 10]
```

### How can we find the shape of an Array:

shape: The `shape` attribute of a NumPy array returns a tuple representing the dimensions of the array.

```python
import numpy as np
# 1-D array
arr1 = np.array([1, 2, 3, 4, 5])
print("Shape of arr1:", arr1.shape)  # Output: (5,)
# 2-D array
arr2 = np.array([[1, 2, 3], [4, 5, 6]])
print("Shape of arr2:", arr2.shape)  # Output: (2, 3)
# 3-D array
arr3 = np.array([[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]])
print("Shape of arr3:", arr3.shape)  # Output: (2, 2, 3)
```

### How we can change shape of an Array:

We can use `reshape` function to change the shape of an array.

### `reshape`

The `reshape` function is used to change the shape of an array without changing its data. You can specify the new shape you want for the array. The total number of elements must remain the same.

`reshape(no_of_rows,no_of_elemets_in_eachrow)`

```python
import numpy as np
# 1-D array
arr1 = np.array([1, 2, 3, 4, 5, 6, 7, 8])
print("Original arr1:", arr1)  # Output: [1 2 3 4 5 6 7 8]
# Reshape to 2-D array
arr2 = arr1.reshape((2, 4))
print("Reshaped arr1 to 2x4:\n", arr2)
# Output:
# [[1 2 3 4]
#  [5 6 7 8]]

# Reshape to 3-D array
arr3 = arr1.reshape((2, 2, 2))
print("Reshaped arr1 to 2x2x2:\n", arr3)
# Output:
# [[[1 2]
#   [3 4]]
#
#  [[5 6]
#   [7 8]]]
```

### Broadcasting in NumPy:

Broadcasting error occurs when you try to perform operations on two arrays with incompatible shapes, meaning the shapes do not satisfy the broadcasting rules. Specifically, broadcasting requires that the dimensions of the arrays are either equal or one of them is 1. If these conditions are not met, a broadcasting error will occur.

```python
import numpy as np
m = np.array([1,2,3])
n = np.array([1,2,3,4])
print(m + n)     # here will give broadcast error
```

**Correct usages:**

```python
import numpy as np
arr1 = np.array([[1, 2, 3], [4, 5, 6]])
arr2 = np.array([1, 2, 3])
result = arr1 + arr2
print(result)            # Output:
                         # [[ 2  4  6]
                         #  [ 5  7  9]]
```

### Slicing in NumPy:

Slicing in NumPy allows you to extract a subset of elements from an array.

The basic slicing syntax is `array[start:stop:step]`, where:

`start` is the index of the first element to include (inclusive).

`stop` is the index of the first element to exclude (exclusive).

`step` is the step size (default is 1).

### Examples of 1-D Array slicing:

```python
import numpy as np
arr = np.array([1, 2, 3, 4, 5])
print(arr[1:4])  # Output: [2 3 4]
print(arr[::2])  # Output: [1 3 5]
print(arr[::-1])  # Output: [5 4 3 2 1]
print(arr[0:4])  #Output: [1,2,3,4]
print(arr[1::])  #Output: [2,3,4,5]
print(arr[:3:])  #Output: [1,2,3]
```

### Example of 2-D Array slicing:

```python
import numpy as np
arr = np.array([[1,2,3,4],[5,6,7,8],[9,10,11,12]])
print(arr[1,0:3])   #Output: [5,6,7]
print(arr[2,1:3])   #Output: [10,11]
print(arr[:3,1:3])
```

## Iteration over Array:

There are various ways to iterate over array. using for and using NumPy functions.

### Iterating Over 1D Arrays

For 1D arrays, you can simply use a `for` loop:

```python
python
```

```python
import numpy as np

arr = np.array([1, 2, 3, 4, 5])

for element in arr:
    print(element)
# Output:
# 1
# 2
# 3
# 4
# 5
```

### Iterating Over 2D Arrays

For 2D arrays, you can iterate over rows or elements:

```python
import numpy as np

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

for row in arr:
    print(row)
# Output:
# [1 2 3]
# [4 5 6]            
```

In above code we get each rows , to iterate the each elements over 2-D array you can use nested loop or `flat` or `nditer`

```python
import numpy as np

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

for element in arr.flat:
    print(element)
# Output:
# 1
# 2
# 3
# 4
# 5
# 6
```

Using nested Loops

```python
import numpy as np

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

for row in arr:
    for element in row:
        print(element)
# Output:
# 1
# 2
# 3
# 4
# 5
# 6
```

Using `nditer`

```python
import numpy as np

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

for element in np.nditer(arr):
    print(element)
# Output:
# 1
# 2
# 3
# 4
# 5
# 6
```

### Ndenumerate function:

`ndenumerate` in NumPy allows you to iterate over an array and get both the index and the value of each element.

```python
import numpy as np
arr = np.array([1, 2, 3, 4, 5])
for index, value in np.ndenumerate(arr):
    print(f"Index: {index}, Value: {value}")
# Output:
# Index: (0,), Value: 1
# Index: (1,), Value: 2
# Index: (2,), Value: 3
# Index: (3,), Value: 4
# Index: (4,), Value: 5
```

### Copy V/S View:

### View

A view is a new array object that looks at the same data of the original array. Any modification to the view will affect the original array and vice versa.

```python
import numpy as np
arr = np.array([10, 20, 30, 40, 50])  # Original array
# Creating a view using the view() method
vw = arr.view()
print("Original Array:", arr)  # Output: [10 20 30 40 50]
print("View Array:", vw)       # Output: [10 20 30 40 50]
# Modifying the view
vw[1] = 99
print("Original Array:", arr)  # Output: [10 99 30 40 50]
print("View Array:", vw)       # Output: [10 99 30 40 50]
```

### Copy

A copy is a new array object with its own data. Modifying the copy will not affect the original array and vice versa.

```python
import numpy as np
arr = np.array([1, 2, 3, 4, 5])
copy_arr = arr.copy()
print("Original Array:", arr)  # Output: [1 2 3 4 5]
print("Copy Array:", copy_arr)  # Output: [1 2 3 4 5]
# Modifying the copy
copy_arr[0] = 99
print("Original Array:", arr)  # Output: [1 2 3 4 5]
print("Copy Array:", copy_arr)  # Output: [99 2 3 4 5]
```
