NumPy: Getting Started Tutorial

Python was conceived with one target in mind, simplicity. This simplicity has made Python one of the most popular languages today. However, Python developers have to sacrifice performance to make their lives easier. This performance sacrifice considerably impacts numerical and scientific computing, though! Luckily, NumPy is there to save the day!

This article is an introduction to NumPy. After reading it, you’ll know how to install and import NumPy and how to process numeric data with one-dimensional NumPy arrays.

Why NumPy?

Scientists have been interested in Python since the early days, thanks to its ease of use and extensibility. NumPy emerged from the efforts of the scientific Python community to tackle numerical computing weaknesses in Python. The main issues that needed tackling were:

  • Efficient array creation and manipulation
  • Mathematical functions and algorithms to operate on these arrays

NumPy was created in 2005 by merging two numerical packages available at the time: Numeric and Numarray. Since Python is not optimized for speed, most of NumPy’s heavy-lifting code is written in C, with some Fortran code doodling around the edges. It’s what makes NumPy blazing fast!

Since its creation, NumPy has become one of the most reputable third-party modules available for Python. There have even been fierce debates about including it as one of Python’s standard modules. Only the very best of projects earn such honor.

Fast forward to the present, where we’ve seen an explosion of data science and machine learning jobs leveraging Python. NumPy is a cornerstone package offering a solid foundation many other projects rely upon. Many data science and machine learning packages use NumPy under the hood, notably:

  • Pandas
  • scikit-learn
  • Tensorflow

Therefore, mastering NumPy is of paramount importance.

Installing NumPy

NumPy is not part of the default Python distribution, so you’ll need to install it. NumPy is a library that your code imports, so install it in the project’s virtual environment. With the environment active, use pip:

python -m pip install numpy

If you manage your project with uv, add NumPy as a dependency instead:

uv add numpy

If you’re a Conda user, you can use:

conda install numpy

Importing NumPy

Like all packages, you can import parts of NumPy or the entire package. There’s a convention to import the entire package and rename it to np. It’s strongly recommended to use this convention as well. The simple reason for this is that most NumPy users use the package interactively, so they have to type less:

import numpy as npCode language: Python (python)

Throughout this article, we’ll abide by the convention and assume you have NumPy imported as np.

NumPy arrays

At the core of NumPy are arrays. Let’s formally define what an array is:

array
A data structure with elements of the same type whose position can be accessed by one or more indices is an array. Hence, a programming language implementation of a vector, a matrix, or a tensor is an array.

Given this broad definition of arrays, Python has two built-in types that resemble an array: the list and the tuple. We could use a list of lists or a dictionary of lists to store multiple lists, effectively creating multidimensional arrays. However, neither the list, the tuple, nor the dictionary is optimized for numerical purposes. They don’t even adhere to our definition that well since they can store values of different types. E.g., a list can contain a mixed collection of numbers, strings, and any other object type.

NumPy solves many of the Python shortcomings regarding numerical computation through arrays. Especially array creation and manipulation in NumPy is blazing fast and well optimized.

Creating a 1-dimensional array

The easiest way to create an array is to pass a list to NumPy’s main utility to create arrays, np.array:

a = np.array([1, 2, 3])Code language: Python (python)

The array function accepts any Python sequence: a list, a tuple, or even a range. A set is not a sequence, though. Since a set has no order, np.array({1, 2, 3}) doesn’t give you [1 2 3], but a zero-dimensional array holding the whole set as a single object. Convert a set to a list first, like np.array(list(my_set)).

The function accepts several optional keyword arguments, and we will discuss two of them here: copy and dtype.

The copy argument

The copy argument states whether to make a copy of the input object. When copy is True (the default), any changes in the resulting array will not change the input object. If it is False, NumPy won’t make a copy at all, so changes in the array can change the input object. And if NumPy can’t avoid a copy, copy=False raises an error. That’s new in NumPy 2.0. Older versions quietly made a copy anyway.

Most of the time, what you want is “don’t copy if you don’t have to”. For that, use np.asarray(). It works like np.array(), but it only copies when it needs to.

When using lists to make arrays, NumPy will always copy the object; for example:

lst = [1, 2, 3]
a = np.asarray(lst)
print(a)
# [1 2 3]Code language: Python (python)

If we change the array, the list will stay the same since NumPy copied it:

a[0] = 0
print(lst)
# [1, 2, 3]Code language: Python (python)

If we create the same array but with another NumPy array as input:

a_in = np.array([1, 2, 3])
a = np.asarray(a_in)
print(a)
# [1 2 3]Code language: Python (python)

Let’s see what happens if we change the resulting array:

a[0] = 0
print(a)
# [0 2 3]
print(a_in)
# [0 2 3]Code language: Python (python)

Both arrays changed because np.asarray() didn’t make a copy. In fact, a and a_in are the same array object now.

Here’s the whole example as one script, so you can run it on your own computer:

import numpy as np

lst = [1, 2, 3]
a = np.asarray(lst)
a[0] = 0
print(lst)  # no change to original

b = np.array([1, 2, 3])
a = np.asarray(b)
a[0] = 0
print(b)  # b changed as well!Code language: Python (python)

Output:

[1, 2, 3]
[0 2 3]

If you replace the first np.asarray(lst) with np.array(lst, copy=False), NumPy can’t avoid the copy, so you get an error instead:

ValueError: Unable to avoid copy while creating an array as requested.

The dtype argument

Another commonly used argument is dtype, indicating the data type of the elements of this array explicitly. In the next section, you will learn about the available data types. One of them, the np.int8 type, is the smallest available integer type, taking up way less space (just one byte) than a regular Python integer.

NumPy data types (dtypes)

Another keyword argument of the function np.array is dtype. This argument specifies the data type in the array. Remember, one of the key properties of an array is that all elements have the same type.

NumPy implements its own data types that are optimized for efficient storage and processing. For this, it uses the base class called dtype. Let’s take a look at the most common dtypes:

  • np.int8
  • np.int16
  • np.int32
  • np.int64
  • np.uint8 (and the other unsigned integers)
  • np.float16
  • np.float32
  • np.float64
  • np.float128
  • np.bool_
  • np.str_
  • np.bytes_
  • np.object_

In this article, we’ll focus on numeric types only.

Integers

The integer dtypes, np.int8, np.int16, np.int32, and np.int64 differ only in the size of the number they can store:

  • np.int8 -> -128 to 127
  • np.int16 -> -32,768 to 32,767
  • np.int32 -> -2,147,483,648 to 2,147,483,647
  • np.int64 -> -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807

Each of these has an unsigned version that only stores zero and positive numbers, but goes twice as high. For example, np.uint8 stores 0 to 255, which makes it a popular choice for pixel values in images. You can look up the limits of any integer type with np.iinfo(), like np.iinfo(np.int16).max.

Under normal circumstances using np.int64 is the way to go since it allows us to store the largest numbers. Int64 is the dtype NumPy uses by default. There are benefits, however, to using smaller integers:

  • Reduced memory usage
  • Faster computations

More often than not, memory usage on a modern PC won’t be an issue for relatively small arrays. If you think it will be, try the smaller types. You must ensure that all elements and the results of future operations on those elements will not exceed the maximum size of the chosen type.

Floats

The different NumPy float types allow us to store floats in different precision, dependent on the number of bits we allow the float to use. The larger the number of allowed bits, the more precision our array’s elements will have. E.g., np.float16 will use 16 bits (two bytes), while np.float64 takes up 64 bits (8 bytes).

Increased precision comes at the expense of memory and performance. Still, the rule of thumb is to err on the safe side and use np.float64 by default unless you have a good reason to use something else. E.g., if you can spare some precision, and performance and memory usage are of the essence, use something smaller.

Let’s explore how the float size affects precision:

import numpy as np
a = np.array([1.3738729019013636723763], dtype=np.float16)[0]
print("float16:", a)

a = np.array([1.3738729019013636723763], dtype=np.float32)[0]
print("float32:", a)

a = np.array([1.3738729019013636723763], dtype=np.float64)[0]
print("float64:", a)

# float128 is not available on all platforms
if hasattr(np, "float128"):
    a = np.array([1.3738729019013636723763], dtype=np.float128)[0]
    print("float128:", a)Code language: Python (python)

The output nicely demonstrates how the different types influence the amount of precision we can store:

float16: 1.374 float32: 1.3738729 float64: 1.3738729019013636

There’s even a float128 type on most Linux computers and on Intel Macs. It doesn’t exist on Windows or on Macs with Apple silicon, which is why the example checks for it with hasattr() first. Where it’s available, you get one more line:

float128: 1.3738729019013635746

You only get the float128 line on platforms that support it, like Linux. The exact digits differ per platform: Linux on Intel and AMD uses 80-bit precision behind the name, while Linux on ARM uses real 128-bit precision and prints even more digits.

Using NumPy arrays

We’ll now look closely at how to use NumPy arrays, starting with accessing elements using array indexing.

Getting a single element

We can access and modify single elements:

a = np.array([0.0, 2.0, 3.0, 4.0, 5.0])
print(a[0])
# 0.0

a[0] = 1.0
print(a)
# [1. 2. 3. 4. 5.]Code language: Python (python)

Accessing multiple elements

We can access and modify multiple specific elements in a NumPy array at once. Note that Python lists do not have this feature:

a = np.array([0.0, 2.0, 3.0, 4.0, 5.0])

# Get elements at position 0 and 2
print(a[[0, 2]])
# [0. 3.]

# Change the first two elements
a[[0, 1]] = [0, 3.0]
print(a)
# [0. 3. 3. 4. 5.]Code language: Python (python)

Negative indexing

Negative indices work the same as with lists; they count indices backward. For example, to get elements at the end of the array you can use:

a = np.array([0.0, 2.0, 3.0, 4.0, 5.0])
print(a[-1])
# 5.0

print(a[-2])
# 4.0Code language: Python (python)

Slicing

Slicing works as well, and it behaves exactly like the regular slicing of lists, e.g., the format is a[start: stop: step]. As an example, let’s get the first three elements of an array:

a = np.array([0.0, 2.0, 3.0, 4.0, 5.0])
print(a[0: 3])
# [0. 2. 3.]Code language: Python (python)

Or all the elements except for the last one:

print(a[0: -1])
# [0. 2. 3. 4.]Code language: Python (python)

And like lists, we can also reverse the array this way:

print(a[:: -1])
# [5. 4. 3. 2. 0.]Code language: Python (python)

Append, insert, delete, and sort

NumPy arrays have more in common with lists. Many of the regular operations behave similarly to Python lists, like sorting, deleting, inserting, and appending data. Note that these methods all return a new array instead of modifying the given array.

Append to NumPy array

To append means to add elements to the end. We can append single elements to a NumPy array just like we do with lists:

a = np.array([1.0, 2.0])
a = np.append(a, 3.0)
print(a)
# [1. 2. 3.]Code language: Python (python)

We’re used to using the extend method to append multiple elements to a list. However, NumPy arrays reuse the same append function to add multiple elements:

a = np.array([1.0, 2.0])
a = np.append(a, [4.0, 5.0])
print(a)
# [1. 2. 4. 5.]Code language: Python (python)

Insert into NumPy array

We can insert one or more elements at specific index locations using insert. Each insert below starts from the same array a, so you can compare the results:

a = np.array([1.0, 2.0, 3.0, 4.0, 5.0])

# Insert one element at position 3
print(np.insert(a, 3, values=3.5))
# [1.  2.  3.  3.5 4.  5. ]

# Insert a list of elements at position 3
print(np.insert(a, 3, values=[100, 200]))
# [  1.   2.   3. 100. 200.   4.   5.]

# Insert 1.5 before index 1 and 3.5 before index 3
print(np.insert(a, [1, 3], values=[1.5, 3.5]))
# [1.  1.5 2.  3.  3.5 4.  5. ]Code language: Python (python)

Delete elements from NumPy array

We can delete one or more elements at once as well:

a = np.array([1.0, 2.0, 3.0, 4.0, 5.0])
a = np.delete(a, -1)
# a is now [1. 2. 3. 4.]

a = np.delete(a, [0, 1])
# a is now [3. 4.]Code language: Python (python)

Sorting NumPy array

There are two ways to sort a NumPy array: in-place sort and creating a new, sorted array. To start with that last one:

a = np.array([1.0, 3.0, 2.0, 4.0, 5.0])
b = np.sort(a)
# b is now [1. 2. 3. 4. 5.]Code language: Python (python)

And to do an in-place sort, do as follows:

a = np.array([1.0, 3.0, 2.0, 4.0, 5.0])
a.sort()
# a is now [1. 2. 3. 4. 5.]Code language: Python (python)

Notice that most methods do not belong to the array class itself, except for the sort method. Hence, we have to call the methods on the np object that accepts the array as an argument. And all these transformations do not happen in place but return a new array (except for sort).

Mathematical array operations

We’ll conclude this article with the most common mathematical operations that one might want to perform with arrays: sum, subtraction, multiplication, and division.

Arrays handle like scalars; operations are carried out element-wise. Hence, arrays can only be added, subtracted, multiplied, or divided by another array of the same size or a scalar.

Let’s define some arrays first, notice that a and b have the same size of 4, b_wrong_size has a different size of 3 elements:

a = np.array([1.0, 2.0, 3.0, 4.0])
b = np.array([2.0, 2.0, 2.0, 2.0])
b_wrong_size = np.array([2.0, 2.0, 2.0])Code language: Python (python)

If we try to operate with arrays of different sizes, a ValueError exception will be raised:

a = np.array([1.0, 2.0, 3.0, 4.0])
b_wrong_size = np.array([2.0, 2.0, 2.0])

# raises ValueError exception
a + b_wrong_size

# ValueError: operands could not be broadcast together with shapes (4,) (3,)Code language: Python (python)

Addition and subtraction

We can add arrays together or add a single value to each element of the array:

a = np.array([1.0, 2.0, 3.0, 4.0])
b = np.array([2.0, 2.0, 2.0, 2.0])

print(a + b)
# [3. 4. 5. 6.]
print(a + 2)
# [3. 4. 5. 6.]

print(a - b)
# [-1.  0.  1.  2.]
print(a - 2)
# [-1.  0.  1.  2.]Code language: Python (python)

Multiplication and division

The same is true for multiplication and division: we can either use a single value or two arrays:

a = np.array([1.0, 2.0, 3.0, 4.0])
b = np.array([2.0, 2.0, 2.0, 2.0])

print(a * b)
# [2. 4. 6. 8.]
print(a * 2)
# [2. 4. 6. 8.]

print(a / b)
# [0.5 1.  1.5 2. ]
print(a / 2)
# [0.5 1.  1.5 2. ]Code language: Python (python)

Learn more

This article is part of a free Python Tutorial. You can browse the tutorial with the navigation buttons at the top and bottom of the article or use the navigation menu. Want to learn more? NumPy pairs nicely with Jupyter Notebooks, so you might want to read up about those.

Conclusion

We’ve looked at one-dimensional array creation, accessing elements, array manipulation, and the most important mathematical operations on arrays. There’s a lot more to learn about NumPy, like multidimensional arrays and broadcasting. I recommend the following resources to learn more:

Related articles