NumPy


Hello everyone! Today we kick off our deep dive into essential Python libraries—starting with NumPy. Watching this NUMPY TUTORIAL. This continues the same cheat sheet version notes I started in my Basics of Python Lessons!
What Is NumPy?
NumPy is an open‑source library for fast, memory‑efficient n‑dimensional arrays and vectorized math operations, all powered by optimized C code. It also offers built‑in statistical and linear algebra functions.
💡 For mixed‑type data (strings, dates), use Pandas instead.
NumPy Arrays vs. Python List
Speed & Efficiency: C‑backed arrays run math on large datasets much faster than lists.
Vectorization: Apply operations to entire arrays without explicit loops.
Broadcasting: Perform element‑wise math on arrays of different shapes automatically.
NumPy Fundamentals
Creating Arrays: You can create arrays with np. array() and access elements using indexing
import numpy as np
a = np.array([1, 2, 3, 4, 5, 6])
print(a)
# => [1 2 3 4 5 6]
Indexing and Modification
print(a[2]) # => 3
a[3] = 13
print(a) # => [ 1 2 3 13 5 6]
Slicing: Slicing works similarly to Python lists
1. a[3:]
OP => array([13, 5, 6])
a[:2]
OP => arr([1,2])
2. b = a[4:]
OP => arr([5,6]) # you can also assign to other variable & print & play along this way
Array Attributes: NumPy arrays come with built‑in attributes to inspect their structure:
1. # 0-D array(scalar)
a= np.array(23)
print(a.ndim,a.shape,a.size,len(a.shape))
#=> No of Dimensions: 0 #=> Shape:() #=> Len of Shape: 0 #=> Size: 1
- Shape is empty becuase it is non negative number that specify the number of elemnts
along each dimension but in this case we have dimension as 0.
- Len of Shape is always equal to no. of dimensions.
- Size is no of elements.(or product of all elements in shape)
2. # 1-D array
a= np.array([1.2])
print(a.ndim,a.shape,a.size,len(a.shape))
#=> No of Dimensions: 1 #=> Shape- (2,) #=> Len of Shape: 1 #=> Size: 2
3.# 2-D array
a= np.array([[1,2],
[1,2]])
print(a.ndim,a.shape,a.size,len(a.shape))
#=> No of Dimensions: 2 #=> Shape- (2,2) #=> Len of Shape: 2 #=> Size: 4
a= np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
print(a.ndim,a.shape,a.size,len(a.shape))
#=> No of Dimensions: 2 #=> Shape- (3,3) #=> Len of Shape: 2 #=> Size: 12
5. # 3-D array
a= np.array([[[1,2,6,7],
[1,2,6,7]],
[[1,2,6,7],
[1,2,6,7]]])
print(a.ndim,a.shape,a.size,len(a.shape))
#=> No of Dimensions: 3 #=> Shape- (2,2,4) #=> Len of Shape: 2 #=> Size: 12
6. # 4-D array
a= np.array([[[[1,2,6,7],
[1,2,6,7]],
[[1,2,6,7],
[1,2,6,7]]],
[[[1,2,6,7],
[1,2,6,7]],
[[1,2,6,7],
[1,2,6,7]]]])
print(a.ndim,a.shape,a.size,len(a.shape))
#=> No of Dimensions: 4 #=> Shape- (2,2,2,4) #=> Len of Shape: 2 #=> Size: 12
Data Type: dtype()
Data Type (dtype): You can check the data type of array elements
a = np.array([1, 2, 3], dtype=np.int32)
a.d(type) #=> 'int32'
Creating Basic Arrays
np.zeros() and np.ones(): Creating arrays filled with all zeros or ones.
1. a = np.zeros(2)
array([0., 0.])
2. But you can also give shape and define every single thing in it
a = np.zeros((2,3,7,4))
# Here we have 2 dimensions with 3 elements each having 7 columns and 4 rows
3. Similarly for ones instead of 0 it will fill the array with ones
np.ones((2,3,6)) #=> 2×3×4 array of 1.0
# Here we have 2 dimensions each having 6 columns and 3 rows.
np.empty(): Ceates an array with random initial content, which is faster.
1. np.empty(3) #=> array([1.05463191e-311, 1.05658269e-311, 0.00000000e+000]) garbage value
2. To print certain shape of garbage values (uninitialized)
np.empty((3,8))
Why use empty? It’s faster than zeros/ones when you plan to overwrite every element.
np.arange(): Creates arrays with a range of values. Like Python’s
range
, but returns an array:
Format:(first number, last number, step size)
1. np.arange(2,9,2) #=> array([2, 4, 6, 8]) stop is exclusive
2. np.arange(6) #=> array([0, 1, 2, 3, 4, 5]) takes default start 0 and 1 step
np.linspace(): Generates N evenly spaced points including endpoints:
Format(first number, last number , no of elements you want)
np.linspace(0, 10, num=5) #=> array([ 0. , 2.5, 5. , 7.5, 10. ]) stop is exclusive
'''Step size = (end – start) / (num – 1)
Here: (10 – 0) / 4 = 2.5'''
📌 Tip: To convert it into integer we can do:- x = np.ones(2, dtype = np.int64)
Output:- array([1, 1]) Do the same thing everywhere!
Sorting and Concatenating Arrays
np.sort(): Sorts array elements & np.concatenate() : Joins arrays along a specified axis
arr = np.array([2,1,5,3,7,4,6,8])
arr2 = np.array([24,52,43,14,25,56])
1. # Sort
np.sort(arr) # ⇒ [1 2 3 4 5 6 7 8]
np.argsort(arr) # ⇒ [1 0 3 5 2 6 4 7] # Sort and return indices
2. # Partition all elements < kth position come first (unordered)
np.partition(arr, 3) # ⇒ [1 2 3 4 5 7 6 8]
3. # Concatenate
np.concatenate((arr, arr2)) # ⇒ [ 2 1 5 3 7 4 6 8 24 52 43 14 25 56]
4. # 2-D concatenation
axis=0 adds rows; axis=1 adds columns
a = np.array([[1,2],[3,4]])
b = np.array([[5,6]])
np.concatenate((a, b), axis=0) # Shapes must match, except the axis you’re stacking along
=>[[1 2]
[3 4]
[5 6]]
Reshaping and Adding New Axes
np.reshape(): Changes the shape of an array
1. a= np.arange(1,7) #=> array([1, 2, 3, 4, 5, 6])
a.reshape(2,3) #=> array([[1, 2, 3], [4, 5, 6]]) Reshapes the array
2. a= np.arange(6)
np.reshpae(a, (2,3), order='F') #=> array([[0, 2, 4], [1, 3, 5]]) Reshapes column wise
np.reshape(a, (2,3), order='C') #=> array([[0, 1, 2], [3, 4, 5]]) Reshapes row wise
np.newaxis() or np.expand_dims(): Adds a new dimension to an array
a = np.array([1,2,3,4,5,6])
a.shape Shape is #=> (6,)
If we add new axis
1. a2 = a[np.newaxis, :]
a2.shape #=> A new row vector gets added and Shape becomes (1,6)
2. a2 = a[:, np.newaxis,]
a2.shape #=> A new column vector gets added and Shape becomes #=>(6,1)
Equivalent using expand_dims
1. b = np.expand_dims(a, axis =0)
b.shape #=> A new column vector gets added #=>(1,6)
2. c = np.expand_dims(a, axis =1)
c.shape #=> A new row vector gets added #=>(6,1)
Advanced Indexing and Slicing
Condition based Slicing
arr = np.array([12, 89, 45, 31, 789, 200])
mask = arr < 50 # ⇒ [True False True True False False]
arr[mask] # ⇒ [12 45 31]
# 2D mask & nonzero
mat = np.array([[13,62,3,42],
[75,6,7,84],
[93,10,2,21]])
mat < 50 # ⇒ array of True/False
np.nonzero(mat < 50) # ⇒ (row_indices, col_indices) Gets you indices
Creating Arrays from Existing Data
Stacking: np.vstack() (vertical) and np.hstack() (horizontal) & Splitting: np.hsplit()
a = np.array([1,2,3])
b = np.array([4,5,6])
#Stacking
1. Vertical stack (np.vstack)
np.vstack((a, b)) # => [[1,2,3],
# [4,5,6]]
2. Horizontal stack (np.hstack)
np.hstack((a, b)) #=> [1,2,3,4,5,6]
# Splitting
1. x = np.arange(8)
np.hsplit(x, 4) #=> [array([0,1]), array([2,3]), array([4,5]), array([6,7])]
2. np.hsplit(x, (3,4,7)) #=> [array([0,1,2]), array([3]), array([4,5,6]), array([7])]
Views vs Copies
arr= np.arange(6)
1. View: Shares memory with the original array
arr = np.arange(6)
v = arr[1:4]
v[0] = 99 # also changes arr[1] => array([0,99,2,3,4,5])
2. Copy: An independent array
c = arr.copy()
c[0] = –1 # arr unchanged array([0,1,2,3,4,5])
Basic Array Operations
Element‑wise math & Aggregations
1. a + b, a-b, a * b, a / b #both of them should have same shape though!
2. arr= np.arange(1,10).reshape(3,3)
arr.sum() #return sum of all elements
# Axis-specific sums
a.sum(axis=0) # column sums
a.sum(axis=1) # row sums
3. arr.min(), arr.max() # Returns Max and Min Element from array
Return column wise or do the same row wise with:
arr.max(axis = 0) or arr.max(axis = 1)
4. arr.mean(), arr.std(), arr.prod() # Returns mean std and product
-In this also we can find column wise or row wise the same way
Generating Random Numbers
Integers randint() & New Generator(default_rng)
1. np.random.randint(low=0, high=10, size=5) # or you could give shape like(3,2)
# => e.g. [3,7,1,8,0]
2. rng = np.random.default_rng()
rng.random(5) # floats in [0,1)
rng.integers(5, size=3) # array([0, 1, 4], dtype=int64)
Other Useful Operations
1. Unique items
np.unique(arr)
2. Transpose
arr.T # doesnt change the original array
3. Flip
np.flip(arr) # full reverse
np.flip(arr, axis=0) # flip rows
np.flip(arr, axis=1) # flip columns
# Choose particular thing to filp
np.flip(arr[1]) np.flip(arr[:1] #for 2nd row / for 2nd column
4. Flatten vs. Ravel
Flatten- # new array This flattens the 2d or 3d array into 1d!
a1= x.flatten()
a1[0]= 99
print(x)
print(a1)
'''[[1 2 3]
[4 5 6]
[7 8 9]]
[99 1 2 3 4 5 6 7]'''
Ravel- # works like view orignal array also gets changed!
a2= arr.ravel()
a2[0]= 98
print(arr)
print(a2)
'''[[98 2 3]
[4 5 6]
[7 8 9]]
[98 2 3 4 5 6 7 8 9]'''
📌 Tip: Use help(np.function_name) in your notebook to see full docs and parameters.
Thanks for sticking with me through this NumPy deep dive! You now have the fundamentals to create, manipulate, and analyze arrays efficiently—essential groundwork for any ML project.
🔜 What’s next?
In our next lesson, we’ll level up to pandas, the library built on NumPy for working with labeled, tabular data—perfect for real‑world data analysis and machine learning preparation.
See you there! 🚀Stay Tuned and Follow if you like :)!
Subscribe to my newsletter
Read articles from Priyesh Shah directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Priyesh Shah
Priyesh Shah
Hi there👋 I'm Priyesh Shah 💻 B.Tech Computer Engineering student (2028) 🐍 Python, exploring AI/ML and open-source 🚀 Building projects to contribute to GSoC 2026