2. Reading Python and Directing AI#

In this lecture we learn to read simple Python code, and to direct an AI tool that writes code for us.

We will not write programs from scratch.

Instead, the aim is to look at short pieces of code, work out what they do, and notice when they do something other than what we asked for.

Here is an example that uses a made-up table of household incomes in four regions.

incomes = {
    'north': [21, 27, 33, 30, 24],
    'south': [15, 23],
    'east': [12, 14, 19, 16, 10, 13],
    'west': [80, 90]
}

threshold = 25


def mean(values):
    return sum(values) / len(values)


below = []

for region, values in incomes.items():
    m = mean(values)
    print(region, m)
    if m < threshold:
        below.append(region)

print('Regions below the threshold:', below)
north 27.0
south 19.0
east 14.0
west 85.0
Regions below the threshold: ['south', 'east']

Don’t worry if this looks unfamiliar.

By the end of the lecture you should be able to read every line.

2.1. Data types#

In this section we look at the basic kinds of data that Python works with: numbers, strings, lists and dictionaries.

2.1.1. Numbers#

A variable is a name for a value, and we create one using =.

x = 10    # This is a comment, which Python ignores
y = 2.5
x * y
25.0

Text after # is a comment, written for human readers.

AI tools add many comments, and these are often the best place to start reading.

Arithmetic mostly works as you would expect, with * for multiplication and / for division.

Powers use **, so that x**2 means \(x^2\).

x**2
100

2.1.2. Strings#

A string is a piece of text, written inside quotes.

region = 'north'
region
'north'

Text that looks like a number is still text.

'10' + '10'
'1010'

Here + joins the two strings instead of adding them.

This is a common problem when data are loaded from a file, and no error message warns you about it.

You will also often see strings starting with f, which insert the value of a variable.

f'The region is {region}'
'The region is north'

2.1.3. Lists#

A list holds several values in order.

values = [21, 27, 33, 30, 24]
len(values)    # The number of elements
5

Elements are selected using square brackets.

Python counts from zero, so the first element has index 0.

values[0]
21

Negative numbers count from the end.

values[-1]
24

We can also select a range of elements, where the last number in the range is not included.

values[0:2]    # Elements 0 and 1, but not 2
[21, 27]

To add an element to the end of a list, we use append.

values.append(40)
values
[21, 27, 33, 30, 24, 40]

2.1.4. Dictionaries#

A dictionary stores values under names, which are called keys.

means = {'north': 27.0, 'south': 19.0}
means['south']
19.0

The same notation adds a new entry, or replaces an existing one.

means['east'] = 14.0
means
{'north': 27.0, 'south': 19.0, 'east': 14.0}

The table in our opening example is a dictionary in which each value is a list.

Exercise 2.1

Without running this code, work out what it prints.

numbers = [4, 8, 15, 16]
numbers.append(23)
print(numbers[0], numbers[-1], len(numbers))

ages = {'Ana': 34, 'Ben': 29}
ages['Ben'] = 30
print(ages['Ben'])

2.2. Conditions#

A comparison gives one of two values, True or False, known as Booleans.

x = 10
x > 5
True
x == 3
False

Notice the difference between the two kinds of equals sign.

A single = gives a value to a variable, while == asks whether two values are equal.

Comparisons can be combined using and and or.

x > 5 and x < 8
False

We can use a comparison to decide what to do next.

m = 19.0

if m < 10:
    print('low')
elif m < 25:
    print('medium')
else:
    print('high')
medium

Python checks each condition in turn, and runs the first block whose condition is true.

Here elif is short for “else if”.

Notice that the blocks are indented.

In Python, indentation is part of the meaning of the code: the indented lines are the ones that belong to the if.

Exercise 2.2

Without running this code, work out what it prints.

How would the output change if z were 12?

z = 7

if z > 10:
    print('large')
    print('checked')
print('done')

2.3. Loops#

A for loop runs a block of code once for each element in a collection.

for v in [21, 27, 33]:
    print(v * 2)
42
54
66

Here v takes each value in turn.

A loop is often used to build up a result.

total = 0

for v in [21, 27, 33]:
    total = total + v

total
81

The function range is often used to run a loop a set number of times.

Like list indices, it starts from zero, so range(3) gives 0, 1 and 2.

for i in range(3):
    print(i)
0
1
2

To loop over a dictionary, we use items, which gives us each key and value together.

means = {'north': 27.0, 'south': 19.0, 'east': 14.0}

for region, m in means.items():
    print(region, m)
north 27.0
south 19.0
east 14.0

AI tools often replace a simple loop with a one-line list comprehension.

[v * 2 for v in [21, 27, 33]]
[42, 54, 66]

Read this as “make a list of v * 2, for each v in the list”.

A comprehension can also include a condition, which keeps only some of the elements.

[v for v in [21, 27, 33] if v > 25]
[27, 33]

Exercise 2.3

Without running this code, work out what it prints.

total = 0

for i in range(4):
    total = total + i

print(total)
print([k * 2 for k in [1, 2, 3, 4] if k > 2])

2.4. Functions#

A function is a named piece of code that takes some inputs and usually returns a result.

We have already used len, sum and print, which are built into Python.

Inputs are placed in parentheses, and some are given by name.

sorted([3, 1, 2], reverse=True)    # Sort from largest to smallest
[3, 2, 1]

Other functions come from libraries, which we load using import.

import math

math.sqrt(16)
4.0

You will also see forms like import numpy as np, which gives the library a shorter name, so that its functions are written np.something.

2.4.1. Defining functions#

We can define our own functions using def.

def mean(values):
    return sum(values) / len(values)

mean([1, 2, 3])
2.0

The indented lines are the body of the function, and they run only when the function is called.

The line starting with return gives back the result.

An input can have a default value, which is used if we do not supply one.

def scale(v, factor=2):
    return v * factor

print(scale(10))
print(scale(10, factor=3))
20
30

2.4.2. Lambda functions#

A lambda function is a short function written on one line.

This code defines the same function in two ways.

def square(v):
    return v**2

square = lambda v: v**2

square(3)
9

You mainly need to recognize these when one function is given to another, as in this code that sorts regions by their mean income.

means = {'north': 27.0, 'south': 19.0, 'east': 14.0}

sorted(means, key=lambda r: means[r])
['east', 'south', 'north']

Exercise 2.4

Without running this code, work out what it prints.

def f(a, b=2):
    return a * b

print(f(3))
print(f(3, b=5))

2.5. Reading the opening example#

We can now return to the code at the start of the lecture.

incomes = {
    'north': [21, 27, 33, 30, 24],
    'south': [15, 23],
    'east': [12, 14, 19, 16, 10, 13],
    'west': [80, 90]
}

threshold = 25


def mean(values):
    return sum(values) / len(values)


below = []

for region, values in incomes.items():
    m = mean(values)
    print(region, m)
    if m < threshold:
        below.append(region)

print('Regions below the threshold:', below)
north 27.0
south 19.0
east 14.0
west 85.0
Regions below the threshold: ['south', 'east']

Here is what each part does.

  1. incomes is a dictionary, where each key is a region and each value is a list of household incomes.

  2. threshold is a number.

  3. mean is a function that computes the mean of a list.

  4. below starts as an empty list.

  5. The for loop visits each region, computes its mean income and prints it.

  6. The if adds the region to below when its mean is under the threshold.

  7. The last line prints the result.

An AI might write the same steps more compactly, using a list comprehension.

[region for region, values in incomes.items() if mean(values) < threshold]
['south', 'east']

It might also use a lambda function, for example to sort the regions from lowest to highest mean income.

sorted(incomes, key=lambda r: mean(incomes[r]))
['east', 'south', 'north', 'west']

2.6. Directing an AI#

In this section we look at the main types of AI coding tool, a basic workflow for using them, and one way that their code can go wrong without any warning.

2.6.1. Types of tool#

AI coding tools come in three main forms.

  • Assistants inside the notebook, such as the AI in Google Colab and GitHub Copilot, suggest and explain code as you work.

  • Chat apps, such as ChatGPT, Claude and Gemini, answer questions and write code that you then copy and run.

  • End-to-end tools can write code, run it and fix errors with little supervision.

You can use any of these, and the free tier of a standard AI tool is enough for this course.

If you do not have one, the course’s free fallback is the AI built into Colab.

In class we will briefly demonstrate Claude and the AI in Colab.

Before using any of them, read the discussion of confidentiality in Introduction to the Course.

2.6.2. A basic workflow#

Whichever tool you use, the same steps apply.

  1. State the question. Describe the data and say exactly what you want.

  2. Read what comes back. Use what you learned above, and ask the AI to explain any line you cannot follow.

  3. Run it. Start with a small case where you already know the answer.

  4. Check the result. Ask whether it answers the question you actually care about.

The last step is where your own judgment matters most.

2.6.3. Code that runs but answers the wrong question#

Suppose that we ask an AI for the average income in our table.

incomes = {
    'north': [21, 27, 33, 30, 24],
    'south': [15, 23],
    'east': [12, 14, 19, 16, 10, 13],
    'west': [80, 90]
}

The AI returns this code.

region_means = [mean(values) for values in incomes.values()]
sum(region_means) / len(region_means)
36.25

The code runs without error and looks reasonable.

But it computes the mean of the four regional means, which gives the two households in the west the same total weight as the six households in the east.

If we want the average income across all households, we need something different.

total = 0
count = 0

for values in incomes.values():
    total = total + sum(values)
    count = count + len(values)

total / count
28.466666666666665

The two numbers differ because they answer different questions.

No error message can tell you which one you wanted, since that depends on the purpose of the analysis.

This is why you need to state your question precisely, and to understand what the code computes.

We return to this kind of silent failure in Working with Data.

Exercise 2.5

You ask an AI to count the regions with mean income of at least 25, and it returns the code below.

Without running it, find the mistake.

def mean(values):
    return sum(values) / len(values)


table = {'a': [25, 25], 'b': [10, 20], 'c': [30, 40]}

count = 0

for region, values in table.items():
    if mean(values) > 25:
        count = count + 1

print(count)