NumPy Basics: the ndarray and Array Attributes
NumPy (Numerical Python) is the library that makes Python fast with numbers. It gives Python a new type of object, the ndarray (n-dimensional array): a grid of values that all have the same type. Almost every data library you will use later, such as pandas, Matplotlib, Seaborn, scikit-learn and OpenCV, is built on top of NumPy arrays.
Think of a Python list as a shopping bag: you can throw anything into it (numbers, text, other lists), but you must look at each item one by one. A NumPy array is an egg tray: every slot has the same size and holds the same kind of thing, so you can work on the whole tray at once, very quickly.
About this NumPy series
The series has 17 lessons in five parts. Each lesson explains the code in small steps, in simple words, and shows the real output you will see when you run it. Every lesson ends with exercises and a short summary.
| # | Part | Lesson |
|---|---|---|
| 01 | Fundamentals | NumPy Basics: ndarray and Attributes |
| 02 | Fundamentals | Creating Arrays: zeros, arange, linspace |
| 03 | Fundamentals | Random Numbers and Data Types |
| 04 | Indexing and Reshaping | Indexing and Slicing |
| 05 | Indexing and Reshaping | Boolean Masks, Fancy Indexing, np.where |
| 06 | Indexing and Reshaping | Views vs Copies, reshape, ravel, flatten |
| 07 | Indexing and Reshaping | Transpose, newaxis, expand_dims, squeeze |
| 08 | Indexing and Reshaping | Concatenate, Stack and Split |
| 09 | Computation | Vectorized Arithmetic and Ufuncs |
| 10 | Computation | Broadcasting |
| 11 | Computation | Aggregations, axis and NaN-safe Functions |
| 12 | Computation | Sorting, Searching, unique and Sets |
| 13 | Linear Algebra and Math | Matrix Multiplication and np.linalg |
| 14 | Linear Algebra and Math | Missing Data, Rounding, clip, isclose |
| 15 | Advanced | File I/O and Structured Arrays |
| 16 | Advanced | Performance, Memory Layout and Strides |
| 17 | Advanced | Images as NumPy Arrays |
Setup
You need Python 3.10 or newer. Check your version in a terminal (Command Prompt or PowerShell on Windows):
python --version
Step 1: Create a folder for the project
mkdir NumpyTutorials
cd NumpyTutorials
You will keep all your NumPy practice files in this folder. Each lesson tells you which file to create.
Step 2: Create a virtual environment
A virtual environment is a private copy of Python just for this project. Packages you install in it do not mix with your other projects.
python -m venv venv # creates a folder named "venv"
.\venv\Scripts\Activate.ps1 # Windows PowerShell
venv\Scripts\activate.bat # Windows Command Prompt
source venv/bin/activate # macOS / Linux
Run only the activate line for your system. When it works, you see (venv) at the start of the terminal line.
If PowerShell says "running scripts is disabled on this system", run Set-ExecutionPolicy -Scope CurrentUser RemoteSigned once, then activate again.
Step 3: Create requirements.txt
requirements.txt is a normal text file that lists the packages a project needs, one per line. Instead of remembering every package, anyone can install all of them with one command. Create a file named requirements.txt in the project folder with these two lines:
numpy>=2.0
matplotlib # only lesson 17 (reads and saves the image files)
| Line | Meaning |
|---|---|
numpy>=2.0 | install NumPy, version 2.0 or newer (this series was run with NumPy 2.5) |
matplotlib | install the newest Matplotlib; it is used only in lesson 17 to read and save pictures |
# ... | a comment; pip ignores everything after # |
Step 4: Install the packages
With the virtual environment active, install everything listed in the file:
pip install -r requirements.txt
-r means "read the package names from this file". If you prefer, you can skip the file and name the packages directly; the result is the same:
pip install numpy matplotlib
Step 5: Check that NumPy works
python -c "import numpy; print(numpy.__version__)"
If this prints a version number such as 2.5.4, you are ready.
How to run the examples
Create a new file named ndarray_basics.py in the NumpyTutorials folder you created above. Any name works; this one just matches the topic. Start the file with these lines:
import time
import numpy as np
Then copy the examples of this page into the same file, one after another, in the order shown, and run it with python ndarray_basics.py (with your virtual environment active). Some examples use variables created in earlier ones, so keep everything in the one file. Each Output box shows what that example prints.
Every NumPy program starts with import numpy as np. The short name np is used by everybody, so you will see it in every NumPy example online.
1. Python list vs NumPy array
You make an array with np.array(), usually from a list. At first sight it looks like a list without commas, but it behaves very differently with maths:
prices_list = [100, 200, 300]
prices = np.array([100, 200, 300]) # make an array from a list
print("list :", prices_list)
print("array :", prices)
print("type :", type(prices))
print("list * 2 :", prices_list * 2) # list is repeated
print("array * 2 :", prices * 2) # every element is multiplied
print("list + list:", prices_list + [1, 2, 3]) # lists are joined
print("array + array:", prices + np.array([1, 2, 3])) # elements are added
list * 2repeats the list;array * 2multiplies every element by 2.list + listjoins two lists;array + arrayadds the elements in matching positions.- The type of the array is
numpy.ndarray.
This "do the maths on every element at once" behaviour is called vectorization, and it is the main reason to use NumPy. You never need to write a for loop to add GST to 10,000 prices: prices * 1.18 does it.
2. 1D, 2D and 3D arrays
An array can have any number of dimensions (also called axes). You create them from nested lists:
one_d = np.array([10, 20, 30, 40]) # a row of values
two_d = np.array([[1, 2, 3], # rows and columns (a table)
[4, 5, 6]])
three_d = np.array([[[1, 2], [3, 4]], # several tables stacked
[[5, 6], [7, 8]]])
print("1D array:\n", one_d)
print("2D array:\n", two_d)
print("3D array:\n", three_d)
| Dimensions | Looks like | Real-life example |
|---|---|---|
| 1D | a single row of values | temperatures of 7 days |
| 2D | a table with rows and columns | marks of students (rows) in subjects (columns) |
| 3D | several tables stacked | sales of several stores, each a table of weeks x days; or a colour image |
When NumPy prints an array, every extra [ is one more dimension. The 3D array starts with [[[, and an empty line separates the two tables.
3. Array attributes: shape, ndim, size, dtype
Every array carries information about itself in its attributes. You read them with a dot, without brackets, because they are values, not functions:
marks = np.array([[78, 85, 92, 66],
[88, 79, 95, 70],
[65, 90, 81, 99]]) # 3 students x 4 subjects
print("shape :", marks.shape) # (rows, columns)
print("ndim :", marks.ndim) # number of dimensions
print("size :", marks.size) # total elements = 3 * 4
print("dtype :", marks.dtype) # type of every element
print("itemsize :", marks.itemsize, "bytes per element")
print("nbytes :", marks.nbytes, "bytes in total")
print("len() :", len(marks), "(length of the first dimension)")
| Attribute | Meaning | Here |
|---|---|---|
shape | The size of each dimension, as a tuple | (3, 4): 3 rows, 4 columns |
ndim | Number of dimensions = len(shape) | 2 |
size | Total number of elements = all sizes multiplied | 3 * 4 = 12 |
dtype | The data type of every element | int64 (64-bit whole numbers) |
itemsize | Bytes used by one element | 8 (64 bits = 8 bytes) |
nbytes | Bytes used by all elements = size * itemsize | 12 * 8 = 96 |
shape is the attribute you will check most often. When something goes wrong in NumPy, the first thing to do is print(arr.shape).
4. Comparing attributes
The same attributes for arrays of different dimensions and types, side by side:
arrays = {
"number 5": np.array(5),
"1D": one_d,
"2D": two_d,
"3D": three_d,
"floats": np.array([1.5, 2.5]),
"booleans": np.array([True, False, True]),
"text": np.array(["pen", "book"]),
}
print(f"{'array':<10}{'shape':<12}{'ndim':<6}{'size':<6}dtype")
for name, arr in arrays.items():
print(f"{name:<10}{str(arr.shape):<12}{arr.ndim:<6}{arr.size:<6}{arr.dtype}")
np.array(5)is a 0-dimensional array (a single value): its shape is the empty tuple().- A 1D shape is written
(4,)with a comma, because it is a tuple with one number. - Floats are
float64, True/False values arebool. - Text gets a type like
<U4: Unicode text of at most 4 characters ("book" has 4).
5. All elements get the same type
A list can hold a mix of types. An array cannot: NumPy picks one type that can hold every value, and converts everything to it.
mixed_numbers = np.array([1, 2.5, 3]) # ints and a float
print(mixed_numbers, "->", mixed_numbers.dtype) # all become float
mixed_text = np.array([1, "two", 3.0]) # a string is present
print(mixed_text, "->", mixed_text.dtype) # all become text
flags = np.array([True, 0, 5]) # bool and ints
print(flags, "->", flags.dtype) # all become int
- Ints and a float: everything becomes
float64(1becomes1.). - Anything with text: everything becomes text.
'1'is now a string, so you can no longer do maths with it. Watch out for this when reading data from files. Truewith integers becomes1.
This single type is what makes arrays fast: NumPy knows exactly how many bytes each element uses and where it is in memory. Lesson 03 covers data types in detail.
6. Speed: list loop vs NumPy
Let's square and add up one million numbers, once with a Python loop and once with NumPy:
numbers_list = list(range(1_000_000))
numbers = np.arange(1_000_000)
start = time.perf_counter()
total_list = sum(n * n for n in numbers_list) # Python loop
list_time = time.perf_counter() - start
start = time.perf_counter()
total_numpy = (numbers * numbers).sum() # one NumPy expression
numpy_time = time.perf_counter() - start
print("same result :", total_list == total_numpy)
print(f"list loop : {list_time * 1000:.1f} ms")
print(f"NumPy : {numpy_time * 1000:.1f} ms")
print(f"NumPy is about {list_time / numpy_time:.0f} times faster")
Both give exactly the same answer, but NumPy is many times faster. (Your timings will be different; they depend on your computer.) The Python loop handles one number at a time, and each one is a full Python object. NumPy runs the whole calculation in optimized C code over one block of memory. Lesson 16 looks at speed in detail.
7. Real example: student marks
A 2D array is a natural way to store a table. Here, each row is a student and each column is a subject:
subjects = ["Maths", "Science", "English", "Hindi"]
print("Marks table (rows = students):\n", marks)
print("Total of each student :", marks.sum(axis=1))
print("Average per subject :", marks.mean(axis=0).round(1))
print("Highest mark :", marks.max())
print("Best subject on average:", subjects[marks.mean(axis=0).argmax()])
sum(axis=1)adds across each row: the total of each student.mean(axis=0)goes down each column: the average of each subject.argmax()gives the position of the biggest value, which we use to pick the subject's name.
Don't worry about axis yet; lesson 11 explains it step by step.
Exercises
Try these exercises on your own before moving on:
- Make an array of the temperatures of 7 days and print shape, ndim, size and dtype.
- Make a 2D array of 4 products x 3 months of sales. What is its shape?
- Make np.array([1, 2, "3"]) and print its dtype. Why is it not int?
- Multiply the sales array by 1.18 (add 18% GST). What is the new dtype?
Summary
- NumPy's
ndarrayis a grid of values that all have the same type. - Maths on an array works on every element at once (vectorization), with no loop.
- Arrays can be 1D (a row), 2D (a table), 3D (stacked tables) or more.
shape,ndim,size,dtype,itemsizeandnbytesdescribe an array.- Mixing types converts everything to one type; text "wins" over numbers.
- NumPy is much faster and smaller than Python lists for numbers.