Indexing & Slicing Arrays

Data Science · Lesson 4 of 32 5 min read Updated

Once your data is in an array, you need to pull out parts of it: one student's marks, one subject for every student, or only the days hotter than 40 °C. NumPy gives you four ways to do this: plain indexes, slices, boolean masks and lists of positions.

In this lesson you will use all four on 1-D and 2-D arrays, and you will learn the one behaviour that surprises almost every beginner: slices are views, not copies.

Indexing and slicing 1-D arrays

A 1-D array works much like a Python list. Positions start at 0, negative positions count from the end, and a slice start:stop:step takes a range where stop is not included.

PythonRun it yourself
import numpy as np

runs = np.array([45, 12, 78, 0, 101, 33, 64])   # one batter, seven matches

print(runs[0], runs[-1])       # first and last
print(runs[2:5])               # positions 2, 3, 4
print(runs[:3])                # first three
print(runs[::2])               # every second match
print(runs[::-1])              # reversed
Output
45 64
[ 78   0 101]
[45 12 78]
[ 45  78 101  64]
[ 64  33 101   0  78  12  45]

Leaving out start means "from the beginning", leaving out stop means "to the end", and a step of -1 walks backwards.

Indexing 2-D arrays

A 2-D array has rows and columns, so you give two positions separated by a comma: array[row, column]. A slice can be used for either part, and a bare : means "all".

PythonRun it yourself
import numpy as np

# rows = students, columns = maths, science, english, hindi
marks = np.array([
    [78, 82, 65, 90],   # Aarav
    [91, 88, 79, 84],   # Priya
    [55, 61, 70, 76],   # Kabir
    [84, 79, 92, 88],   # Meera
])

print(marks[1, 2])        # Priya's english
print(marks[2])           # all of Kabir's marks
print(marks[:, 0])        # maths for every student
print(marks[1:3, 2:])     # Priya and Kabir, english and hindi
print(marks[-1, ::2])     # Meera: maths and english
Output
79
[55 61 70 76]
[78 91 55 84]
[[79 84]
 [70 76]]
[84 92]

Read marks[:, 0] as "every row, column 0". Notice that marks[2] with one index returns a whole row. Taking a single row or column gives a 1-D array; taking a block with slices keeps it 2-D.

Slices are views

When you slice an array, NumPy does not copy the data. It gives you a view: a new array object that looks at the same memory. Changing the view changes the original.

PythonRun it yourself
import numpy as np

stock = np.array([40, 25, 60, 10, 35])

first_three = stock[:3]
first_three[0] = 0          # also changes stock!
print(stock)

safe = stock[:3].copy()
safe[1] = 999               # stock is not affected
print(stock)
print(safe)
Output
[ 0 25 60 10 35]
[ 0 25 60 10 35]
[  0 999  60]

Views make NumPy fast and memory-friendly, because slicing a million-value array costs almost nothing. But if you plan to change a part of an array and keep the original, call .copy().

Boolean masks

A boolean mask is an array of True and False values of the same shape as your data. Comparing an array with a value creates one, and putting the mask inside square brackets keeps only the True positions. This is the NumPy way to filter.

PythonRun it yourself
import numpy as np

temps = np.array([38.5, 41.2, 43.0, 39.8, 44.6, 40.1, 36.9])  # Jaipur, one week in May

hot = temps > 40
print(hot)
print(temps[hot])
print("Days above 40:", hot.sum())

print(temps[(temps > 39) & (temps < 42)])   # and
print(temps[(temps < 37) | (temps > 44)])   # or
print(temps[~hot])                          # not

temps[temps > 43] = 43                      # cap extreme readings
print(temps)
Output
[False  True  True False  True  True False]
[41.2 43.  44.6 40.1]
Days above 40: 4
[41.2 39.8 40.1]
[44.6 36.9]
[38.5 39.8 36.9]
[38.5 41.2 43.  39.8 43.  40.1 36.9]

Since True counts as 1 and False as 0, hot.sum() counts the hot days. To combine conditions, use & (and), | (or) and ~ (not), and wrap each condition in brackets. You can also assign through a mask: the last lines replace every value above 43 with 43.

Fancy indexing

You can also pass a list of positions. This is called fancy indexing, and it always returns a copy, not a view.

PythonRun it yourself
import numpy as np

cities = np.array(["Delhi", "Pune", "Kochi", "Jaipur", "Lucknow"])
sales = np.array([5400, 3100, 2800, 3900, 2600])

print(cities[[0, 3]])

order = np.argsort(sales)[::-1]     # positions from highest to lowest sales
print(order)
print(cities[order])
print(sales[order][:3])

marks = np.array([[78, 82, 65], [91, 88, 79], [55, 61, 70], [84, 79, 92]])
print(marks[marks[:, 0] > 80])      # students with more than 80 in maths
Output
['Delhi' 'Jaipur']
[0 3 1 2 4]
['Delhi' 'Jaipur' 'Pune' 'Kochi' 'Lucknow']
[5400 3900 3100]
[[91 88 79]
 [84 79 92]]

np.argsort() returns the positions that would sort the array. Reversing it with [::-1] gives highest first, and using it as a fancy index sorts a second array, the city names, in the same order. The last line is a pattern you will use constantly: build a mask from one column, then use it to pick whole rows.

Common mistakes

  • Using and / or with arrays. temps > 39 and temps < 42 raises ValueError: The truth value of an array with more than one element is ambiguous. Use & and | with brackets.
  • Forgetting brackets. temps > 39 & temps < 42 is read as temps > (39 & temps) < 42 because & binds tighter than >.
  • Changing a slice by accident. A slice is a view; call .copy() if the original must stay the same.
  • Writing marks[1][2] everywhere. It works, but marks[1, 2] is clearer and faster.

Key points

  • Positions start at 0; slices use start:stop:step and exclude stop.
  • For 2-D arrays write array[row, column]; : selects everything along that axis.
  • Slices are views of the original data; use .copy() to get independent data.
  • A boolean mask such as temps > 40 filters values; combine masks with &, | and ~.
  • Fancy indexing with a list of positions returns a copy; np.argsort() gives sorting positions.

Exercise

A Kochi shop sold these numbers of umbrellas over 10 days: 12, 30, 45, 8, 51, 60, 22, 5, 38, 47. Print the sales from the last three days, the days (positions starting at 1) when more than 40 were sold, and the average of the days with fewer than 25 sales.

Show solution
Python
import numpy as np

umbrellas = np.array([12, 30, 45, 8, 51, 60, 22, 5, 38, 47])
print(umbrellas[-3:])

busy = umbrellas > 40
print("Busy days:", np.arange(1, 11)[busy])
print("Quiet average:", umbrellas[umbrellas < 25].mean())
Output
[ 5 38 47]
Busy days: [ 3  5  6 10]
Quiet average: 11.75

np.arange(1, 11) creates day numbers 1 to 10, and the same mask picks the day numbers where sales were above 40.

Quick Quiz

1. For a 2-D array a, what does a[:, 1] return?

2. b = a[:2] and then b[0] = 0. What happens to a?

3. Which expression keeps values between 10 and 20?

4. What does np.argsort(np.array([30, 10, 20])) return?

Learn Data Science with a mentor

Free lessons get you started. Our Data Science & Machine Learning training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call