## Introduction

Hello Techies,

If you're just starting your journey into the world of data science, machine learning, or scientific computing, you'll undoubtedly come across NumPy. NumPy is a fundamental library in Python that stands for "Numerical Python." It is a powerful tool for working with arrays and matrices, providing high-performance mathematical functions, and facilitating numerical computations. In this blog, we'll take you through the basics of NumPy, its features, and some essential concepts to get you started on your Python data manipulation adventures.

## What is NumPy?

NumPy is an open-source Python library that adds support for large, multi-dimensional arrays and matrices, along with an extensive collection of high-level mathematical functions to operate on these arrays. The library is the backbone of various other Python libraries in the data science ecosystem, such as pandas, SciPy, and scikit-learn.

## Installing NumPy

Before we dive into NumPy, make sure you have it installed on your system. If you don't have it yet, you can install it using **' pip'**, the Python package manager.

```
pip install numpy
```

### Importing NumPy

To use NumPy in your Python script or Jupyter Notebook, you need to import it first.

```
import numpy as np
```

The convention is to import NumPy as **' np'** to save some typing when accessing its functions

### Creating NumPy Arrays

The primary data structure in NumPy is the **' numpy.array'**, which is a multi-dimensional array. You can create a NumPy array from a Python list or tuple.

```
import numpy as np
# Create a 1D NumPy array from a list
arr1d = np.array([1, 2, 3, 4, 5])
# Create a 2D NumPy array from a list of lists
arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
# Create a 3D NumPy array from nested lists
arr3d = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
```

In the first example, **' arr1d'** is a 1D array with five elements. The **' np.array'** the function takes a Python list and converts it into a NumPy array. Similarly, **' arr2d'** and **' arr3d'** are 2D and 3D arrays, respectively.

### NumPy Array Properties

NumPy arrays have several important properties:

**'ndim'**: Number of dimensions (axes) of the array.**'shape'**: Tuple representing the size of each dimension.**'size'**: Total number of elements in the array.**'dtype'**: Data type of the elements in the array.

```
import numpy as np
arr = np.array([[1, 2, 3], [4, 5, 6]])
print("Shape:", arr.shape) # Output: (2, 3)
print("Dimensions:", arr.ndim) # Output: 2
print("Size:", arr.size) # Output: 6
print("Data Type:", arr.dtype) # Output: int64
```

In this example, we have a 2D array **' arr'** with two rows and three columns. The shape of the array is **' (2, 3)'**, indicating that it has two dimensions: the first dimension with size 2 and the second dimension with size 3. The **' size'** property tells us that the array contains a total of 6 elements. The **' dtype'** property shows that the elements in the array are of the data type **' int64'**.

### Array Initialization Functions

NumPy provides several functions to initialize arrays easily.

**'np.zeros'**: Create an array of zeros with a specified shape.**'np.ones'**: Create an array of ones with a specified shape.**'np.full'**: Create an array with a specified shape and fill it with a given value.**'np.arange'**: Create an array with evenly spaced values within a given interval.**'np.linspace'**: Create an array with a specified number of equally spaced values within a given range.**'np.random'**: Generate arrays with random values.

```
import numpy as np
# Create a 1D array of zeros with five elements
zeros_array = np.zeros(5)
# Create a 2D array of ones with a shape of (3, 4)
ones_array = np.ones((3, 4))
# Create a 3D array filled with the value 7 with a shape of (2, 3, 2)
custom_value_array = np.full((2, 3, 2), 7)
# Create a 1D array with values from 0 to 9
range_array = np.arange(10)
# Create a 1D array with 10 equally spaced values between 0 and 1
linspace_array = np.linspace(0, 1, 10)
# Create a 2D array with random values between 0 and 1 with a shape of (2, 3)
random_array = np.random.rand(2, 3)
```

Here's a detailed explanation of the functions used.

**'np.zeros'**: Creates an array filled with zeros. The argument is the desired shape of the array, which can be a single integer for a 1D array or a tuple for a multi-dimensional array.**'np.ones'**: Creates an array filled with ones. Similar to**' np.zeros'**, you pass the desired shape as an argument.**'np.full'**: Creates an array filled with a specified value. The first argument is the desired shape, and the second argument is the value to fill the array with.**'np.arange'**: Creates an array with evenly spaced values within a given interval. The arguments are**' start'**,**' stop'**, and optionally**' step'**. If only one argument is provided, it specifies the stop value, and the start value defaults to 0.**'np.linspace'**: Creates an array with a specified number of equally spaced values within a given range. The arguments are**' start'**,**' stop'**, and**'num'**, where**' num'**is the number of values to generate.**'np.random.rand'**: Generates an array with random values between 0 and 1. The arguments represent the dimensions of the array.

### Array Indexing and Slicing

You can access elements of a NumPy array using indexing and slicing, similar to Python lists.

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

In this example, we have a 1D array **' arr'** with five elements. Indexing allows us to access individual elements of the array. The first element of the array has an index of 0, and the last element has an index of -1. Using slicing, we can extract a subset of elements from the array. The slicing notation **' arr[start:stop]'** includes elements from the index **' start'** up to, but not including, index **' stop'**.

### Array Operations

NumPy allows you to perform element-wise operations on arrays without the need for explicit loops.

```
import numpy as np
arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])
# Element-wise addition
result = arr1 + arr2
print(result) # Output: [5, 7, 9]
# Element-wise multiplication
result = arr1 * arr2
print(result) # Output: [4, 10, 18]
# Dot product of two arrays
dot_product = np.dot(arr1, arr2)
print(dot_product) # Output: 32
```

In this example, we have two 1D arrays, **' arr1'** and **' arr2'**. NumPy performs element-wise operations when we add or multiply these arrays. The result of element-wise addition is **' [5, 7, 9]'**, and the result of element-wise multiplication is **' [4, 10, 18]'**.

The **' np.dot'** the function calculates the dot product of two arrays. For 1D arrays, the dot product is the sum of the element-wise multiplication.

### Broadcasting

NumPy allows broadcasting, which is a powerful feature that allows operations between arrays with different shapes. Broadcasting enables you to perform element-wise operations on arrays of different dimensions, making code more concise and efficient.

```
import numpy as np
arr = np.array([[1, 2, 3], [4, 5, 6]])
# Add 5 to each element of arr
result = arr + 5
print(result)
# Output: [[6, 7, 8], [9, 10, 11]]
```

In this example, we add the scalar value 5 to each element of the 2D array **' arr'**. NumPy automatically broadcasts the scalar value to match the shape of the array, performing the addition element-wise.

### Array Shape Manipulation

NumPy provides functions to reshape, flatten, and transpose arrays. These functions help manipulate the structure of arrays to fit different needs.

```
import numpy as np
arr = np.array([[1, 2, 3], [4, 5, 6]])
# Reshape the array to a single row with six columns
reshaped_arr = arr.reshape(1, 6)
print(reshaped_arr)
# Output: [[1, 2, 3, 4, 5, 6]]
# Flatten the array into a 1D array
flattened_arr = arr.flatten()
print(flattened_arr)
# Output: [1, 2, 3, 4, 5, 6]
# Transpose the array (swap rows and columns)
transposed_arr = arr.T
print(transposed_arr)
# Output: [[1, 4], [2, 5], [3, 6]]
```

The **' reshape'** function allows you to change the shape of the array. In this example, we reshape the 2D array **' arr'** into a 1D array with a single row and six columns. The **' flatten'** function converts the array into a 1D array by raveling the elements row by row. The **' T'** attribute transposes the array, swapping rows and columns.

### Aggregation and Statistical Operations

NumPy provides several functions for aggregating data and performing statistical operations on arrays.

```
import numpy as np
arr = np.array([1, 2, 3, 4, 5])
# Calculate the sum of all elements in the array
sum_of_elements = np.sum(arr)
print(sum_of_elements) # Output: 15
# Calculate the mean (average) of the elements in the array
mean_of_elements = np.mean(arr)
print(mean_of_elements) # Output: 3.0
# Find the minimum and maximum values in the array
min_value = np.min(arr)
max_value = np.max(arr)
print(min_value, max_value) # Output: 1 5
# Calculate the standard deviation and variance of the array
std_dev = np.std(arr)
variance = np.var(arr)
print(std_dev, variance) # Output: 1.4142135623730951 2.0
```

In this example, we have a 1D array **' arr'**. We use NumPy functions like **' np.sum'**, **' np.mean'**, **' np.min'**, **' np.max'**, **' np.std'**, and **' np.var'** to calculate various statistics of the array.

### Universal Functions (ufuncs)

NumPy's universal functions, or ufuncs, are functions that operate element-wise on arrays and support broadcasting. They offer a fast and efficient way to apply operations to arrays without the need for explicit loops.

```
import numpy as np
arr = np.array([1, 2, 3, 4, 5])
# Apply the square root function to each element of the array
result = np.sqrt(arr)
print(result) # Output: [1. 1.41421356 1.73205081 2. 2.23606798]
# Apply the exponential function to each element of the array
result = np.exp(arr)
print(result) # Output: [ 2.71828183 7.3890561 20.08553692 54.59815003 148.4131591 ]
```

In this example, we have a 1D array **' arr'**. The **' np.sqrt'** the function calculates the square root of each element in the array while **' np.exp'** calculating the exponential value (e^x) of each element.

### Array Concatenation and Splitting

NumPy allows you to concatenate and split arrays along different axes, enabling you to combine and divide your data effectively.

```
import numpy as np
arr1 = np.array([[1, 2], [3, 4]])
arr2 = np.array([[5, 6]])
# Concatenate the arrays vertically
vertical_concat = np.concatenate((arr1, arr2), axis=0)
print(vertical_concat)
# Output: [[1 2]
# [3 4]
# [5 6]]
arr3 = np.array([[7], [8]])
# Concatenate the arrays horizontally
horizontal_concat = np.concatenate((arr1, arr3), axis=1)
print(horizontal_concat)
# Output: [[1 2 7]
# [3 4 8]]
# Split the array vertically into two subarrays
split_arr = np.split(vertical_concat, 2, axis=0)
print(split_arr[0])
# Output: [[1 2]
# [3 4]]
print(split_arr[1])
# Output: [[5 6]]
# Split the array horizontally into two subarrays
split_arr = np.split(horizontal_concat, 2, axis=1)
print(split_arr[0])
# Output: [[1 2]
# [3 4]]
print(split_arr[1])
# Output: [[7]
# [8]]
```

In this example, we have three 2D arrays, **' arr1'**,** ' arr2'**, and **' arr3'**. We use the **' np.concatenate'** function to join these arrays either vertically or horizontally. The **' axis'** parameter determines the axis along which the arrays are concatenated.

We also use the **' np.split'** function to split the arrays either vertically or horizontally into subarrays. The first argument is the array to be split; the second argument is the number of equally-sized subarrays and the **' axis'** parameter specifies whether the splitting should be done along rows (axis=0) or columns (axis=1).

### Performance Considerations

NumPy's performance benefits over plain Python lists stem from its underlying C implementation and optimized algorithms. When working with large datasets, NumPy can significantly speed up computations. NumPy arrays are stored in contiguous blocks of memory, which allows for efficient data access and computation.

### File Input/Output

NumPy allows you to save and load NumPy arrays to and from files, such as CSV, text, and binary files.

```
import numpy as np
arr = np.array([[1, 2], [3, 4]])
# Save the array to a CSV file
np.savetxt('data.csv', arr, delimiter=',')
# Load the array from the CSV file
loaded_arr = np.loadtxt('data.csv', delimiter=',')
print(loaded_arr)
# Output: [[1. 2.]
# [3. 4.]]
```

In this example, we have a 2D array **' arr'**, and we use the **' np.savetxt'** function to save the array to a CSV file named **"data.csv"**. The **' delimiter'** parameter specifies the character used to separate values in the file.

The **' np.loadtxt'** function is used to load the array from the CSV file back into a NumPy array. The **' delimiter'** parameter is set to ',' to indicate that the values in the file are separated by commas.

### NumPy for Image Processing

NumPy can be used for basic image processing tasks, such as reading, manipulating, and saving images.

```
import numpy as np
from PIL import Image
# Read an image into a NumPy array
img = Image.open('image.jpg')
img_array = np.array(img)
# Display the shape of the image array
print(img_array.shape) # Output: (height, width, channels)
# Manipulate the image array (e.g., apply filters, change color channels, etc.)
# Save the modified image array as a new image
modified_img = Image.fromarray(modified_img_array)
modified_img.save('modified_image.jpg')
```

In this example, we use the Python Imaging Library (PIL) to read an image named "image.jpg" and convert it into a NumPy array using **' np.array'**. The resulting **' img_array'** is a 3D array, where the first two dimensions represent the height and width of the image, and the third dimension represents the color channels (RGB or grayscale).

You can then manipulate the image array by applying various filters, changing color channels, or performing other image processing operations. After modifying the array, you can convert it back to an image using the**' Image.fromarray'** and save it to a new file.

I hope this article helps you in learning the basics to start development with NumPy in Python.

**Thanks & Regards**

**Jay Pankhaniya**