NumPy Ninja: Unleashing the Power of Python for Data Wizards
Tags: numpy, arrays, data, machinelearning
Published: 2023-12-25 20:27:22

## **Introduction to NUMPY**

**WHAT IS NUMPY?**

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hwb8appstq2vmfacv07a.png)



NumPy is like a superhero for numbers in the world of programming. It stands for "Numerical Python." It's a library, which is like a set of tools or special powers that make it easy to work with numbers in the Python programming language.

**Why use NumPy?**


![?](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dzbecyjehbibl2fru3an.png)



Imagine you have a big box of Lego bricks. Each brick represents a number or a piece of data. NumPy is like a magic glove that helps you organize, manipulate, and play with those Lego bricks in really efficient ways. It's super fast and makes your life easier when dealing with lots of numbers.

![Numpy](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uv76d6p3w3xuklxt7c2w.jpg)


**Importance of NumPy in Data Science:**

- Speed: NumPy is super fast because it's written in a language called C, which is like the Flash of programming languages.
- Efficiency: NumPy uses smart techniques to handle large datasets without making your computer feel like it's lifting a mountain.
- Math Magic: Data science involves a lot of math, and NumPy has tons of functions that make mathematical operations easy, like adding, multiplying, or even doing fancy calculus stuff.
- Compatibility: Many other data science tools and libraries in Python are like friends that understand NumPy's language, so they work well together.

**How impactful is NumPy?**

NumPy is like the secret sauce in the recipe of data science. Without it, things would be much harder and slower. It's used everywhere, from simple calculations to training complex machine learning models.

**How easy is it to use?**


![Numpy works like a magic](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m543y4ef1pkcee41nlix.png)



Imagine you have a magic wand. You say a spell "_**Expecto Patronum**_"(or write a line of code), and NumPy does what you want. It's designed to be user-friendly, with simple functions that do powerful things. With a bit of practice, you'll find yourself saying, "Wow, that was easy!"

**What becomes impossible without NumPy?**

Without NumPy, working with large sets of data would be like trying to build a massive Lego castle without the special glove. It would take forever, and you might even give up because it's just too hard. NumPy makes the impossible possible in the world of data science!


**Let's dive into a simple example to see how NumPy helps a data scientist. **

```
# Without NumPy
math_grades = [90, 85, 88, 92, 78]
physics_grades = [88, 80, 85, 90, 82]

```
**_Lets see what happens without using Numpy_**

```
# Without NumPy
num_students = len(math_grades)
total_math_grades = sum(math_grades)
total_physics_grades = sum(physics_grades)

average_math_grade = total_math_grades / num_students
average_physics_grade = total_physics_grades / num_students

class_average = (average_math_grade + average_physics_grade) / 2

print("Class Average (Without NumPy):", class_average)

```


**_Now see what happens when you use Numpy_**

```



import numpy as np

# With NumPy
math_grades = np.array([90, 85, 88, 92, 78])
physics_grades = np.array([88, 80, 85, 90, 82])

average_math_grade = np.mean(math_grades)
average_physics_grade = np.mean(physics_grades)

class_average = np.mean([average_math_grade, average_physics_grade])

print("Class Average (With NumPy):", class_average)

```

So, now we all have a basic understanding of why we will use Numpy right? So lets get started!


Firstly we will learn about Numpy arrays. In python, we are familiar with Lists right? Array is also another datatype which is present in other languages too like C++,JAVA etc.

There can be many dimensions like 2D,3D,4D etc.

Numpy arrays can be represented in Vectors slash matrices.

Now lets create an array:

```
np.arrange(0,10,1)
```

here arrange function is used to create a numpy array. The syntax will be np.arrange(start,stop,step). It gives an array of length 10 starting from 0 to 9 and the difference between two consecutive numbers will be the step size that is 1.

But there are other ways to create an array too.

```
np.zeros(3) #returns a 1D array of length 3 with all zero elements
np.zeros((5,5))# returns a 2d array of 5 rows and 5 columns
np.zeros((2,3)) 
np.ones((3,4)) # all the elements will be one

```
Imagine you have a toy train, and you want it to travel smoothly from one end of your play table to the other. Now, you don't want it to move too fast or too slow – you want it to make exactly 5 stops along the way.

np.linspace is like your magical train conductor tool in the world of Python and NumPy. It helps you create a list of numbers (like train stops) that are evenly spaced, making your train (or calculations) run smoothly.

Here's how you would use np.linspace:


```
import numpy as np

# Let's say you want your train to start at 0 and end at 10, making 5 stops.
train_stops = np.linspace(0, 10, 5)

print(train_stops)

```

**Tips**
How do you simply understand the dimension of an array by seeing it? Look at the brackets. If there is 1 bracket that means the array is 1D array and so on.



**Identity matrix**:

Identity matrix is a 2D square matrix where number of rows is equal to the number of columns. The elements in primary diagonals will be 1

```
np.eye(4)  # identity matrix of 4*4 
```

You can also generate random numbers through numpy libraries with the help of an array. How cool is that ?

- np.random.rand()

This function gives you random numbers between 0 and 1, like picking candies from a jar where each candy is a number.

```
import numpy as np

random_numbers = np.random.rand(3)
print("Random Numbers between 0 and 1:", random_numbers)

```

- np.random.randint()
It's like a magical dice roll. This function gives you random integers within a specified range.



```
random_integer = np.random.randint(1, 7)  # Imagine a 6-sided die
print("Random Integer:", random_integer)

```

- np.random.randn()
This one is like a mysterious recipe that gives you numbers with a mean of 0 and standard deviation of 1. It's like a magic potion, creating numbers with a special pattern.

```
random_standard_numbers = np.random.randn(3)
print("Random Standard Numbers:", random_standard_numbers)

```

- np.random.choice()
Imagine you have a bag of marbles with numbers on them. This function lets you randomly pick one or more marbles from the bag.

```
marbles = np.array([1, 2, 3, 4, 5])
random_pick = np.random.choice(marbles, size=2)
print("Randomly Picked Marbles:", random_pick)

```

- np.random.shuffle()
It's like shuffling a deck of cards. This function rearranges the order of your numbers randomly.



```
deck_of_cards = np.array([1, 2, 3, 4, 5])
np.random.shuffle(deck_of_cards)
print("Shuffled Deck of Cards:", deck_of_cards)

```

![num](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h2njli22q90o8bhu97ns.png)





Remember, these functions are like magic spells for generating randomness in Python with NumPy. They make working with random numbers fun and easy!


**RESHAPE**

Alright, imagine you have a bunch of building blocks, and you want to rearrange them to make a new shape, like turning a flat square into a tall tower. The reshape function in NumPy is like your magical building block organizer. It helps you change the arrangement of your numbers or data without adding or removing any.

Here's how you might use it:

```
import numpy as np

# Imagine you have a flat square of building blocks (numbers 1 to 9)
flat_square = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9])

# Now, you want to reshape it into a 3x3 grid, like making a tower with 3 floors and 3 blocks on each floor.
tower = flat_square.reshape(3, 3)

print("Original Flat Square:")
print(flat_square)

print("\nReshaped Tower:")
print(tower)

```



The reshape function is like telling your magical organizer, "Hey, take these building blocks and arrange them in a way that there are 3 rows and 3 columns." And poof! Your flat square turns into a tower with three floors, each having three blocks.

It's super handy when you want to change the shape of your data without changing the actual data. It's like playing with LEGO blocks and transforming them into different cool structures!

**Caution**: Suppose you have an linear array of 25 elements and now you reshape the array into 2D arrays. okay that's fine. simply write new_arr=arr.reshape(5,5). here number of rows times number of column will be 25 which is equivalent to your previous array element number. but if you give (6 times 5) or anything that is greater than 25 it will throw an error due to insufficiency of elements.

**Let's talk about the max, min, and index-related functions in NumPy, as if we're building with LEGO blocks.**

**np.max() and np.min()**
Imagine you have a stack of LEGO towers, each tower representing a set of numbers. Now, you want to find the tallest tower (max) and the shortest tower (min).




```
import numpy as np

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

# Finding the tallest tower (max)
tallest_tower = np.max(towers)
print("Tallest Tower:", tallest_tower)

# Finding the shortest tower (min)
shortest_tower = np.min(towers)
print("Shortest Tower:", shortest_tower)

```

It's like looking at your LEGO towers and saying, "Hey, tell me the height of the tallest one and the smallest one." The np.max() and np.min() functions do just that.

**Index-Related Functions**
Now, imagine each LEGO tower has numbered floors, and you want to know on which floor is the tallest and the shortest block. NumPy has functions for that too!

```

# Finding the index of the tallest block
index_of_tallest = np.argmax(towers)
print("Index of Tallest Block:", index_of_tallest)

# Finding the index of the shortest block
index_of_shortest = np.argmin(towers)
print("Index of Shortest Block:", index_of_shortest)
```

Here, it's like saying, "Okay, I found the tallest and shortest towers. Now, tell me on which floor the tallest and the shortest blocks are." The np.argmax() and np.argmin() functions give you the indices (or positions) of the maximum and minimum values in your LEGO towers.

In summary, these functions are your LEGO tower inspectors. They help you find the tallest and shortest towers and tell you on which floor the special blocks are located!

Alright, let's dive into the concepts of checking the shape and datatype of your NumPy arrays, imagining we're playing with toys.

**np.shape**
Think of your NumPy array as a toy box with different toys inside. The np.shape function is like asking, "How many rows and columns does my toy box have?"

```
import numpy as np

# Imagine your toy box is a 2x3 grid of toys.
toy_box = np.array([[1, 2, 3],
                    [4, 5, 6]])

# Checking the shape of the toy box
shape_of_toy_box = np.shape(toy_box)

print("Shape of Toy Box:", shape_of_toy_box)

Shape of Toy Box: (2, 3)
It's like saying, "My toy box has 2 rows and 3 columns of toys
```

**np.dtype**
Now, let's talk about the type of toys in your box. The np.dtype function is like asking, "What kind of toys are in my toy box?"

```
# Checking the datatype of the toys in the toy box
datatype_of_toys = toy_box.dtype

print("Datatype of Toys in Toy Box:", datatype_of_toys)
Datatype of Toys in Toy Box: int64

```

**Alright, let's embark on a magical journey in the world of Harry Potter using NumPy!**


![code](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rq9od5tdbi51qz972d8w.png)



**The Magical Quidditch Match**

Imagine there's a magical Quidditch match happening in the wizarding world. The Quidditch field is represented as a grid, and each cell in the grid corresponds to a magical creature's power level. We want to create a team of wizards and magical creatures to ensure a fair match.


**Step 1: Create the Quidditch Field**

```
import numpy as np

# The Quidditch field is a 5x5 grid
quidditch_field = np.zeros((5, 5))
```

Here, we've created a magical field using np.zeros(). Each cell is initialized to zero, symbolizing the absence of magical creatures

**Step 2: Summon Magical Creatures**

```
# Let's summon magical creatures with random power levels (between 1 and 10)
magical_creatures = np.random.randint(1, 11, size=(5, 5))

```
Now, we have a team of magical creatures with random power levels generated by np.random.randint().

**Step 3: Assign Creatures to the Quidditch Field**

```
# Assign magical creatures to the Quidditch field
quidditch_field = np.maximum(quidditch_field, magical_creatures)

```
We use np.maximum() to ensure that if there's already a magical creature in a cell, we don't replace it with a weaker one.

**Step 4: Check the Team's Power**

## Find the total power of the magical creatures in each row
row_powers = np.sum(quidditch_field, axis=1)

## Find the most powerful row
most_powerful_row_index = np.argmax(row_powers)
most_powerful_row_power = np.max(row_powers)


Here, we use np.sum() and np.argmax() to find the row with the most powerful magical creatures.

**Step 5: Shape and Datatype Check**
## Check the shape and datatype of the Quidditch field
field_shape = np.shape(quidditch_field)
field_datatype = quidditch_field.dtype


**_Summary_**
In this magical scenario, we created a Quidditch field, summoned magical creatures, assigned them to the field, found the most powerful row, and checked the shape and datatype of our magical world.

Remember, in the wizarding world of NumPy, the arrays are your magical tools, and the functions are your spells!



Alright, let's imagine you have a magical spell book called NumPy, and it helps you find and modify elements in your magical array using square brackets – just like casting spells!

**Finding Elements with Bracket Indexing**

```
import numpy as np

# Imagine you have a magical array with numbers
magical_array = np.array([1, 2, 3, 4, 5])

# Let's use bracket indexing to find the second element (remember, we start counting from 0!)
second_element = magical_array[1]

print("Second Element:", second_element)
Second Element: 2

```
**Slicing with Bracket Indexing**

```
# Now, let's use bracket indexing to get a slice of our magical array
slice_of_array = magical_array[1:4]

print("Slice of Array:", slice_of_array)
Slice of Array: [2 3 4]

```
**Modifying Elements with Bracket Indexing**

**Caution**: If we try to modify the existing array in numpy, due to memory efficiency, the previous array memory gets lost. so in order to modify multiply or specific elements, we have to create a copy array so both of the arrays remain stored.

```

Broadcasting :Numpy arrays differ from a normal Python list because of their ability to broadcast

```
#Setting a value with index range (Broadcasting)
arr[0:5]=100

#Show
arr
```



arr=np.random.randint(1,100,10)
arr_copy=arr.copy()
print(arr)


Output: array([66,  4,  1, 40, 97, 12, 97, 39, 88, 84])
```

```
print(arr_copy[:]=100)

Output: array([100, 100, 100, 100, 100, 100, 100, 100, 100, 100])
```

```
print(arr)

Output: array([66,  4,  1, 40, 97, 12, 97, 39, 88, 84])
```



2D Array Indexing

```
# Imagine you have a magical 2D array
magical_2d_array = np.array([[1, 2, 3],
                             [4, 5, 6],
                             [7, 8, 9]])

# Let's use bracket indexing to find an element in this 2D array
element_in_2d_array = magical_2d_array[1, 2]

print("Element in 2D Array:", element_in_2d_array)
Element in 2D Array: 6

```
bracket indexing is like having a magical wand that helps you point to specific elements or ranges in your arrays, and you can even use it to modify them!

**# 2D array slicing**

```
a=np.random.randint(1,100,50)
a=a.reshape(10,5)
a[4:7,1:4]

print(a)

Output:[[10, 56, 43],
       [ 5, 45, 99],
       [75, 95, 53]]
```
here it means, these are row column representations. 4:7 means from row 4 to row 6, we slice the array and from column 1 to column 3 we slice the array. the elements within this range will show as output.

Sure thing! Let's explore arithmetic operations in NumPy, imagining we're playing with magical numbers.



**NumPy Operations**


```
import numpy as np
arr = np.arange(0,10)  [0,1,2,3,4,5,6,7,8,9]

print(arr+arr)  #[ 0,  2,  4,  6,  8, 10, 12, 14, 16, 18]
print(arr*arr)  #[ 0,  1,  4,  9, 16, 25, 36, 49, 64, 81]
print(arr-arr)  #[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
print(arr**3)   #[ 0,   1,   8,  27,  64, 125, 216, 343, 512, 729]

         
```

But for division there is a caution. in maths, when we divide something by zero we get infinity(error). in python also we get an error. On the bright side, numpy will not throw you an error. rather, it will show the output as a null value and throw you a warning instead. for some negative value divided by zero we will get negative infinity in output with a warning for free.

```
print(arr/arr) 

Output: [ nan,   1.,   1.,   1.,   1.,   1.,   1.,   1.,   1.,   1.]


print(1/arr)

Output: [ inf,  1. ,  0.5,  0.33333333,  0.25 ,0.2 , 0.16666667,  0.14285714,  0.125,  0.11111111]
```



**Universal Array Functions**
Numpy comes with many universal array functions, which are essentially just mathematical operations you can use to perform the operation across the array. Let's show some common ones:

```
#Taking Square Roots
np.sqrt(arr)
print(arr)

[ 0.  ,  1. ,  1.41421356,  1.73205081,  2. ,2.23606798,  2.44948974,  2.64575131,  2.82842712,  3.]

```

```
#Calcualting exponential (e^)
np.exp(arr)

#LOg
np.log(arr)
```

___


**<u>Numpy Exercises</u>**

Now that we've learned about NumPy let's test your knowledge. We'll start off with a few simple tasks, and then you'll be asked some more complicated questions.

import numpy as np

**Create an array of 10 zeros**

```

arr=np.zeros(10,'int')
arr
```

**Create an array of 10 ones**

```
arr=np.ones(10,'int')
arr
```
**
Create an array of 10 fives**

```
arr=np.full(5,10)
arr
```

**Create an array of all the even integers from 10 to 50**

```
arr=np.arange(10,51,2)
arr
```


**Create a 3x3 matrix with values ranging from 0 to 8**

```
arr=np.arange(0,9).reshape(3,3)
arr
```


```
**Create a 3x3 identity matrix**
arr
arr=np.eye(3)
arr
```

****Use NumPy to generate a random number between 0 and 1**

```
np.random.rand(1)
```



**Use NumPy to generate an array of 25 random numbers sampled from a standard normal distribution**


```
np.random.randn(25)
```


**Create an array of 20 linearly spaced points between 0 and 1**

```
np.linspace(0,1,20)
```
**
Numpy Indexing and Selection**

```
array([[ 1,  2,  3,  4,  5],
       [ 6,  7,  8,  9, 10],
       [11, 12, 13, 14, 15],
       [16, 17, 18, 19, 20],
       [21, 22, 23, 24, 25]])


mat[2:5,1:5]

array([[12, 13, 14, 15],
       [17, 18, 19, 20],
       [22, 23, 24, 25]])
```


**
Get the sum of all the values in mat**

```
np.sum(mat)
mat.sum()
```

**Get the standard deviation of the values in mat**

```
np.std(mat)
```


**Get the sum of all the columns in mat**

```
mat.sum(axis=0)
```



**Summary: The NumPy Adventure**

In the enchanted land of Python, there exists a magical library called NumPy. It's like a spell book that empowers wizards (programmers) to perform incredible feats with arrays and numbers. As we embarked on this NumPy adventure, we uncovered some of its most powerful spells:

<u>**Creating Arrays:**</u>

```
np.array(): Conjuring arrays from thin air.
np.arange(): Crafting sequences of numbers.
np.zeros() and np.ones(): Summoning arrays filled with mystical zeros and ones.
np.linspace(): Unveiling evenly spaced numbers.
np.identity(): Creating magical identity matrices.
Random Number Magic:

np.random.randint(): Rolling the dice for random integers.
np.random.randn(): Brewing a potion of random standard normal numbers.
np.random.choice(): Drawing numbers from a magical bag.
np.random.shuffle(): Shuffling the deck of numbers.
```

```
Shape Transformation:

reshape(): Rearranging the layout of magical elements.
Exploring and Analyzing:

np.max() and np.min(): Finding the tallest towers and smallest caves.
np.argmax() and np.argmin(): Locating the peaks and valleys in the mystical landscape.
np.shape(): Revealing the dimensions of the magic grid.
dtype: Uncovering the type of magical essence within.
Fancy Indexing:

Using arrays of indices to precisely pick out or modify elements.
Magical Arithmetic:

```



**_Conjuring spells like addition, subtraction, multiplication, and division on arrays.
Our journey through the NumPy realm has equipped us with mighty tools and incantations. Armed with this knowledge, you can now weave your own spells and perform powerful transformations on arrays in Python. May your NumPy adventures continue, and may your code be as magical as the wizarding world itself! 🧙✨
_**



Sayoonara~~
Good night!
Signing off tata!


![end](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s9ig00do3ydcz08m90ja.png)