Unit 1 • Lesson 2

Rows and Columns with Lists

Overview

A table is a list of lists, and a column is what you pull out of it.

Beginner 20–25 min

What You Will Learn in This Lesson

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

  • Store a small table as a list of row lists.
  • Read one row, one cell, and one column out of it.
  • Build a column with a comprehension over the rows.
  • Explain the weakness of position-based rows that the next lesson fixes.

Why This Matters

Every table you will ever analyse — a CSV, a spreadsheet, a DataFrame — is rows and columns. Building one out of plain lists first shows exactly what a table is underneath, so when pandas hands you df["score"] you know it is doing the comprehension in this lesson for you, and why it needed column names to do it.

Step 1: A table is a list of rows

The simplest possible table: an outer list, holding one inner list per row, each inner list holding the values in a fixed order.

1

One index for a row, two for a cell

The first index picks the row. The second picks a position inside that row. Row first, then column — the same order a spreadsheet uses.

table = [
    ["Ada", 92, "maths"],
    ["Grace", 88, "computing"],
    ["Alan", 79, "maths"],
]

print(table[0])      # the first row
print(table[0][1])   # the second cell of the first row
print(table[2][2])   # the third cell of the third row
2

The shape of the table

len(table) is the number of rows. len(table[0]) is the number of columns, assuming every row has the same length — an assumption real files break more often than you would hope.

table = [["Ada", 92, "maths"], ["Grace", 88, "computing"]]

print("rows:", len(table))
print("columns:", len(table[0]))

Mini Practice #1: Find the cell

Try It Yourself

Print Alan’s subject using two indexes, then print the number of columns in the table.

Press Run to see output

Step 2: A column is a comprehension

There is no column object in a list of lists. To work on one, you build a new list out of the same position in every row.

1

Pulling one position out of every row

[row[1] for row in table] reads as “the second value of each row”. That is what a column is.

table = [["Ada", 92], ["Grace", 88], ["Alan", 79]]

scores = [row[1] for row in table]
names = [row[0] for row in table]
print(names)
print(scores)
2

Summarise the column, not the table

Once the column is a list of numbers, every summary from the last lesson works on it.

table = [["Ada", 92], ["Grace", 88], ["Alan", 79]]

scores = [row[1] for row in table]
print(sum(scores) / len(scores))
3

A column with a condition

Adding an if keeps only some rows while taking the column — the names of everyone who studies maths, say.

table = [["Ada", 92, "maths"], ["Grace", 88, "computing"], ["Alan", 79, "maths"]]

maths = [row[0] for row in table if row[2] == "maths"]
print(maths)

Mini Practice #2: Two columns

Try It Yourself

Build names and scores as columns, then print the name of the student with the highest score using scores.index.

Press Run to see output

Step 3: The weakness: positions mean nothing

This representation works, and it has one serious problem.

1

row[1] does not say what it is

You have to remember that position 1 is the score. Six months later, or for someone else reading the code, row[1] is a mystery.

row = ["Ada", 92, "maths"]
print(row[1])   # the score -- but only if you remember
2

A new column moves everything

Insert an age column after the name and every row[1] now reads the age. Nothing raises; the average is just the average age. The next lesson fixes this by giving every value a name.

table = [["Ada", 36, 92], ["Grace", 45, 88]]

# Written before the age column existed. Still runs. Now wrong.
scores = [row[1] for row in table]
print(sum(scores) / len(scores))

Predict, then check: Trace one row at a time

Which names will remain, and in what order? Trace the condition for each row before reading the answer.

table = [["Zoe", 0, "art"], ["Ada", 85, "maths"], ["Kai", 70, "art"]]
names = [row[0] for row in table if row[2] == "art"]
print(names)
Show the output and explanation
['Zoe', 'Kai']

The condition uses column 2, while the result takes column 0. Zoe still belongs to art even though her score is zero. A filter keeps the original order unless you explicitly sort it.

Try one change: Change Zoe’s subject to maths, then add a new art row. Predict the new list.

Go deeper: Trace both indexes separately

A table stored as nested lists has two levels. The first index chooses a row; the second chooses a value within that row. Keep the column layout consistent across every row, because numeric positions carry no field names.

Trace the example

Predict the output before opening the explanation.

rows = [["Ada", 8], ["Grace", 10], ["Lin", 6]]
print(rows[1][0])
scores = [row[1] for row in rows]
print(sum(scores))
Show the output and trace
Grace
24

rows[1] is ["Grace", 10], then [0] selects Grace from that row. The comprehension selects position 1 from every row, producing [8, 10, 6]. Summing this column gives 24.

A mistake to watch for

rows[1] selects a row, not the second column. A shorter row can raise IndexError; a row with swapped fields may instead produce misleading data. Check row lengths and field meaning before extracting a column.

Transfer challenge

Add ["Mo", 12] as a new row. Extract the names into one list and calculate the average score from the score column.

Change the example below to solve the challenge. Predict the result, run your code, and compare. This practice is ungraded.

Press Run to see output
Compare with a worked solution
rows = [["Ada", 8], ["Grace", 10], ["Lin", 6], ["Mo", 12]]
print([row[0] for row in rows])
scores = [row[1] for row in rows]
print(sum(scores) / len(scores))

Run both versions. Explain any difference in their output, then change one input and predict the new result.

Step 4: When to Use It

Small, fixed-shape tables

A handful of rows whose column order you control — test data, a few results typed into a script.

scores = [row[1] for row in table]

Data that arrives as positions

csv.reader and many APIs hand back rows as lists; a comprehension is how you get a column out of them.

When Not to Use It

Do not keep list rows for anything long-lived or shared. The moment a column is added or reordered, every row[1] silently reads the wrong thing — switch to dictionaries.

End-of-Lesson Exercises

Exercise 1: Pull out a column

Pull the second column out of table into a list called scores, then print it.

One value per row, always at the same position. A comprehension over the rows is the shortest way to say that.

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

Exercise 2: A column, filtered

Write names_in(table, subject) returning the names, in table order, of the rows whose third column equals subject. Each row is [name, score, subject].

The subject is at position 2 and the name at position 0: [row[0] for row in table if row[2] == subject].

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