Unit 3 • Lesson 1

Why Arrays Beat Lists

Overview

One type and one block of memory buys arithmetic on the whole column.

Intermediate 20–25 min

What You Will Learn in This Lesson

By the end of this lesson, you will be able to:

  • Say what an array gives up compared with a list, and what it buys in return.
  • Predict what * 2 does to a list and to an array.
  • Replace a loop over a column with one expression over an array.
  • Recognise when a list is still the right choice.

Why This Matters

Almost every slow piece of analysis code is a Python loop doing arithmetic one row at a time. Arrays are how that loop disappears — and the rest of this course, including the whole of pandas, is built on top of them. Getting this one idea straight makes the next seven units read as variations on it rather than as new material.

Step 1: A list of numbers is a list of objects

A Python list is a list of references. Each element is a separate object somewhere in memory, carrying its own type tag, and the list holds a pointer to each one. That is what makes a list able to hold a number, a string and another list at once.

1

What an array does instead

An array holds one type in one unbroken block of memory. There are no per-element objects and no type tags, because the type is a property of the array. That is what lets numpy hand the whole block to compiled code and add a million numbers without Python seeing any of them.

import numpy as np

scores = np.array([92, 88, 79])
print(scores)
print(scores.dtype)
print(scores.shape)
2

The bargain, stated plainly

You give up mixed types. You get speed, and an arithmetic that applies to everything at once. Every other feature in this unit follows from that single trade.

import numpy as np

# One type wins. The int is widened, not kept alongside the float.
print(np.array([1, 2, 3.0]).dtype)

# A list has no such rule, because it never made the bargain.
print([1, 2, 3.0])

Mini Practice #1: Watch the dtype settle

Try It Yourself

Print the dtype of each of these three arrays before you run it, then check. The third one is the interesting case.

Press Run to see output

Step 2: Arithmetic applies to the whole array

This is the difference you will feel first, and it is the one that catches people. The same operator means two completely different things depending on what is on the left of it.

1

Multiplying a list repeats it

On a list, * is repetition — it is the same operator that makes "ab" * 3 give "ababab". On an array it is multiplication, applied to every element.

import numpy as np

print([1, 2, 3] * 2)
print(np.array([1, 2, 3]) * 2)
2

The loop you no longer write

Both of these give the same answer. The second one is the one that stays readable when there are three operations rather than one, and it is the one that does not slow down as the column grows.

import numpy as np

scores = [92, 88, 79]

# One row at a time, in Python.
print([s + 5 for s in scores])

# All of it at once, in compiled code.
print(np.array(scores) + 5)

Key idea: A comprehension is not wrong. It is the right tool for a handful of values, and for anything where the elements are not all the same kind of thing. The argument for arrays is about columns of numbers, which is what a data set is made of.

Mini Practice #2: The same sum, two ways

Try It Yourself

Below is a loop that doubles every score. Rewrite the marked line as one expression on an array, and check the two agree.

Press Run to see output

Step 3: When a list is still the right answer

Arrays are not a replacement for lists. They are a replacement for one specific use of lists.

1

Mixed types, or things that are not numbers

A row of a table — a name, a city and a score — is a genuinely mixed thing, and forcing it into an array turns every element into a Python object anyway, losing the whole advantage. Keep those as lists, dicts, or (from unit 4) a DataFrame, which is built to hold a different type per column.

import numpy as np

# Every element becomes an object, and the speed is gone.
mixed = np.array(["Ada", 92, True])
print(mixed.dtype)
print(mixed)
2

Growing a collection item by item

Appending to an array copies the whole block each time, because the block has to stay unbroken. Build with a list and convert once at the end.

import numpy as np

rows = []
for n in range(5):
    rows.append(n * n)

squares = np.array(rows)
print(squares)

Step 4: When to Use It

Columns of numbers

Any calculation applied to every value — scaling, offsets, comparisons — is one expression on an array.

np.array(scores) * 1.1

Data sets with many rows

Arrays stay fast as the column grows, where a Python loop slows down row by row.

When Not to Use It

Do not use an array for mixed values like a name, a score and a flag, or for a collection you build up one item at a time. Use lists, then convert once.

End-of-Lesson Exercises

Exercise 1: Raise every score

Turn scores into a numpy array and print every score raised by 5, using no loop.

np.array(scores) + 5. The addition reaches every element on its own, which is the point of the array.

Press Run to try it, then Check my work to mark it.

Exercise 2: Scale to a percentage

Write as_percent(marks, total) that takes a list of marks and the total they were out of, and returns a numpy array of percentages rounded to one decimal place. Do the arithmetic on the whole array, not a mark at a time.

np.array(marks) / total * 100, then np.round(..., 1). Every step applies to the whole array, so no loop appears anywhere.

Press Run to try it, then Check my work to mark it.