LSE ME204 · Data Engineering Principles for the Social Sciences
🖥️ Week 01 Day 02 Lecture

Reading Files the Way a Machine Does

1️⃣ How Your Computer Organises Files

10:00 – 10:35

In the first lab yesterday, we had you move through a tree of folders representing different London zones. You did that on the Linux computer you have on Nuvolos, running a Linux Operating System (OS), rather than Windows or Mac. This morning we will cover how that tree works, then open the files inside it.

What an Operating System Does

A computer has four layers:

  • User requests actions from the computer
  • Application Programs define how resources are used (Python, VS Code, browsers)
  • Operating System controls hardware and coordinates resource use
  • Hardware provides the basic computing resources

user User app Application Programs (Python, browsers, etc.) user->app os Operating System app->os hardware Computer Hardware (CPU, memory, storage, etc.) os->hardware

Examples of Operating Systems (older versions of popular logos):

Why Different OSes Exist

Each operating system has its own history and philosophy:

UNIX and Linux

  • UNIX: first major OS, developed at Bell Labs in the 1970s
  • GNU/Linux: free, open-source alternative
  • Android phones run Linux
  • macOS is built on Darwin (a UNIX derivative)

Philosophy: open, portable, simple

Windows

  • Microsoft’s proprietary OS
  • Evolved from MS-DOS to Windows 95 to Windows 10/11
  • Dominant in desktop computing
  • Office suite drove adoption

Philosophy: user-friendly, commercial

💡 Why this matters for you: different OSes organise files differently and use different terminal commands. When a tutorial uses syntax that only works on one operating system, you will know why.

File Systems: UNIX and Linux

The directory structure starts from a single root directory called /:

root / home home root->home user username home->user documents Documents user->documents downloads Downloads user->downloads data data documents->data figures figures documents->figures scripts scripts documents->scripts

💡 macOS uses /Users/ instead of /home/, but the tree structure is the same. On the Linux computer you have on Nuvolos, your starting point is /files/.

File Systems: Windows

Windows uses drive letters (C:, D:) where each drive is a separate filesystem:

C_drive C:\ Users Users C_drive->Users Username Username Users->Username Documents Documents Username->Documents Downloads Downloads Username->Downloads data data Documents->data figures figures Documents->figures scripts scripts Documents->scripts

⚠️ Windows uses \ (backslash) for paths. Mac, Linux, and the Linux environment on Nuvolos use / (forward slash).

Absolute vs Relative Paths

We can refer to a file in two ways:

Absolute path:

  • Full location from the root directory
  • Works from anywhere
  • Examples:
    • Linux: /home/<user>/Documents/data/weather.csv
    • macOS: /Users/<user>/Documents/data/weather.csv
    • Windows: C:\Users\<user>\Documents\data\weather.csv

Relative path:

  • Location relative to where you are now
  • More portable for sharing code
  • Examples:
    • ./data/weather.csv
      (file in a subfolder)
    • ../data/weather.csv
      (go up one level first)

💡 Tips

  • Use pwd in the terminal to check where you are.
  • Always use relative paths in your scripts so they work on any machine.

mkdir: Creating Structure Before You Need It

Before you start working, set up the folders you will need:

Before:

pwd
/files
ls
data/    figures/

After mkdir:

mkdir scripts
ls
data/    figures/    scripts/

💡 This afternoon you will create scripts/ and write your first Python file inside it. On 💻 Week 01 Day 03 Lab you will run mkdir data/weather before saving API data.

Taking Notes in This Course

You can practise mkdir, nano, and Markdown by keeping your own study notes. Pick a layout that works for you:

Option A: by day

study-notes/
├── week01/
   ├── day01.md
   ├── day02.md
   ├── day03.md
   └── day04.md
├── week02/
   ├── day01.md
   ├── ...
   └── day02.md

Option B: flat by session

study-notes/
├── W01D01.md
├── W01D02.md
└── W01D03.md

Option C: by topic

study-notes/
├── terminal.md
├── filesystem.md
├── python.md
├── pandas.md
└── dataviz.md

What a note might look like:

# Filesystem

## Key ideas

Files live in a **tree**.
Every absolute path starts from the root (`/` on Linux, `C:\` on Windows).

## Commands I learned

- `pwd` shows where I am
- `ls` lists what is here
- `cd folder/` moves into a folder
- `mkdir name` creates a new folder

## Things I found confusing

Relative paths fail when I am in the wrong folder.

💡 This afternoon, start with mkdir study-notes on the Linux computer on Nuvolos and write your first note in nano.

2️⃣ Reading Files the Way a Machine Does

10:35 – 11:00

You now know how files are organised in a tree. Some files store text you can read; others store data only the right software can decode. In this hour we look inside both.

Markdown: Source vs Rendered

The same file can look completely different depending on the tool you use to open it:

cat shows the raw text:

cat zone1/places/landmark/big-ben/clue.md
# Big Ben

## Where you are

You found [**Big Ben**](https://en.wiki...)

You see the # symbols, the ** markers, and the [link](url) syntax.

glow renders it:

glow zone1/places/landmark/big-ben/clue.md

Now the headings are large, bold text is bold, and links are clickable. The Markdown characters disappear into formatting.

(I particularly prefer to use glow -p <..path..> to be able to paginate through the document)

💡 The file did not change. Only the tool changed. cat shows what is stored. glow interprets the Markdown and renders it.

Markdown Concepts

We use Markdown on these slides, on the course webpages on Moodle, and later in 📚 Jupyter Notebooks (we will learn about them in 🖥️ Week 01 Day 03 Lecture).

If you type this: You get this:
This is a **bold** text.
This is a bold text.
This is an _italic_ text.
This is an italic text.
[This is a link](https://lse.ac.uk/dsi)
This is a link
`print("Hello, World!")`
print(“Hello, World!”)
```python
# This is a code block
print("Hello, World!")
```
# This is a code block
print("Hello, World!")

Markdown Concepts (Headings)

There are also headings in Markdown. They help structure content, not just make text big!

If you type this: You get this:
# Title (H1)
Title (H1)
## Section (H2)
Section (H2)
### Sub-section (H3)
Sub-section (H3)
#### Sub-sub-section (H4)
Sub-sub-section (H4)

⚠️ Do not use # just to make text bigger! It’s not what it represents.
Use it to create hierarchical demarcations of sections instead.

Headings create a hierarchy

A short report. More # means a deeper level. Body text belongs under the heading that owns it.

If you type this:

# London Weather Brief

A short look at June temperatures.

## Data

### Source

Open-Meteo daily series.

### Coverage

1–30 June 2026.

## Findings

Hottest day: 36.0°C.

You get this document:

London Weather Brief
A short look at June temperatures.
Data
Source
Open-Meteo daily series.
Coverage
1–30 June 2026.
Findings
Hottest day: 36.0°C.

💡 The # count is the nesting depth. H2 belongs under H1 and H3 belongs under H2. That is how long documents stay scannable.

CSV: Data as Plain Text

Let’s open a real data file. This is London’s daily temperature for June 2026:

cat data/london_weather_june_2026.csv
date,temperature_2m_max,temperature_2m_min,data_type
2026-06-01,24.4,13.7,historical
2026-06-02,19.8,14.4,historical
2026-06-03,19.1,12.6,historical
...

Each line is one day. The commas separate the values. The first line holds the column names.

💡 CSV stands for comma-separated values. It is plain text you can read in any editor. Python can read it too, once you tell it where the commas and line breaks are.

💡 vim is another popular tool for viewing and editing files in the terminal. It has a steeper learning curve than cat or nano. If you want to try it later, work through vimtutor.

JSON: Same Data, Different Shape

The same weather data, stored as JSON instead of CSV:

Raw text (for example with cat):

{
  "latitude": 51.5085,
  "longitude": -0.1257,
  "daily": {
    "time": ["2026-06-01","2026-06-02"],
    "temperature_2m_max": [24.4, 19.8],
    "data_type": [
      "historical",
      "historical"
    ]
  }
}

In fx (collapsible tree):

fx data/london_weather_june_2026.json

fx lets you collapse and expand sections, so you can explore the structure without scrolling through raw text.

The keys ("time", "temperature_2m_max") name the columns. The values are arrays (lists) of data.

💡 CSV and JSON are both plain text encoding the same data differently. CSV uses commas and line positions. JSON uses braces, keys, and quotes.

🔗 Take a look at fx documentation to see more tricks

Binary Files: What the Computer Sees

Not every file is plain text. Let’s open an image in vim:

vim figures/heatwave_chart.png

The screen fills with gibberish: ^@^@^@^PIHDR, \x89PNG, fragments of binary data that make no sense as text.

Yesterday you saw this chart in the heatwave analysis. It showed a clear trend. Today you see what that same file looks like to the computer when it reads it as text: meaningless characters.

💡 The PNG file stores pixel colours and positions as raw bytes. Only software that knows the PNG format (an image viewer, a browser, Python’s matplotlib) can decode it into the chart you saw.

Plain Text vs Binary

Plain-text files:

  • Human-readable in any text editor
  • CSV, JSON, Markdown, Python scripts (.py)
  • You can view them with cat, edit them with nano or vim
  • Store characters using an encoding (UTF-8)

Binary files:

  • Gibberish when opened as text
  • Images (.png, .jpg), PDFs, databases, executables
  • Need the right application to open
  • Store data as raw bytes

All the plain-text files we use in this course are encoded as UTF-8. We will look at what that means later.

💡 This afternoon you will open a CSV file in the terminal with cat and in Python with open(). You have seen the principle: plain text shows structure, binary does not.

3️⃣ Binary and Memory Foundations

11:00 – 11:30

Allow me to go very low-level here, down to the bits…

Computers Only Understand 0s and 1s

Numbers, text, images, and sounds are all stored as sequences of 0s and 1s in your computer’s memory. Each 0 or 1 is called a bit.

Think of a bit as a tiny box:

\[ \require{color} \fcolorbox{black}{white}{$\phantom{0}$} \phantom{\leftarrow \text{a bit can have a value of $0$}} \]

Computers Only Understand 0s and 1s

Numbers, text, images, and sounds are all stored as sequences of 0s and 1s in your computer’s memory. Each 0 or 1 is called a bit.

Think of a bit as a tiny box:

\[ \require{color} \begin{array}{ccc} \fcolorbox{black}{#eeeeee}{0} & \leftarrow & \text{a bit can have a value of $0$} \end{array} \]

Computers Only Understand 0s and 1s

Numbers, text, images, and sounds are all stored as sequences of 0s and 1s in your computer’s memory. Each 0 or 1 is called a bit.

Think of a bit as a tiny box:

\[ \begin{array}{ccc} \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} & \leftarrow & \text{OR it can have a value of $1$} \end{array} \]

but nothing else!

How Numbers are Stored

With more bits, we can represent more numbers. Here’s how 4 bits can represent 16 different numbers:

\[\begin{array}{ccccccccccccccc} \fcolorbox{black}{#eeeeee}{0} & \fcolorbox{black}{#eeeeee}{0} & \fcolorbox{black}{#eeeeee}{0} & \fcolorbox{black}{#eeeeee}{0} & \rightarrow 0 & & \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} & \fcolorbox{black}{#eeeeee}{0} & \fcolorbox{black}{#eeeeee}{0} & \fcolorbox{black}{#eeeeee}{0} & \rightarrow 8 \\ \fcolorbox{black}{#eeeeee}{0} & \fcolorbox{black}{#eeeeee}{0} & \fcolorbox{black}{#eeeeee}{0} & \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} & \rightarrow 1 & & \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} & \fcolorbox{black}{#eeeeee}{0} & \fcolorbox{black}{#eeeeee}{0} & \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} & \rightarrow 9 \\ \fcolorbox{black}{#eeeeee}{0} & \fcolorbox{black}{#eeeeee}{0} & \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} & \fcolorbox{black}{#eeeeee}{0} & \rightarrow 2 & & \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} & \fcolorbox{black}{#eeeeee}{0} & \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} & \fcolorbox{black}{#eeeeee}{0} & \rightarrow 10 \\ \fcolorbox{black}{#eeeeee}{0} & \fcolorbox{black}{#eeeeee}{0} & \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} & \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} & \rightarrow 3 & & \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} & \fcolorbox{black}{#eeeeee}{0} & \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} & \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} & \rightarrow 11 \\ \fcolorbox{black}{#eeeeee}{0} & \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} & \fcolorbox{black}{#eeeeee}{0} & \fcolorbox{black}{#eeeeee}{0} & \rightarrow 4 & & \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} & \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} & \fcolorbox{black}{#eeeeee}{0} & \fcolorbox{black}{#eeeeee}{0} & \rightarrow 12 \\ \fcolorbox{black}{#eeeeee}{0} & \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} & \fcolorbox{black}{#eeeeee}{0} & \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} & \rightarrow 5 & & \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} & \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} & \fcolorbox{black}{#eeeeee}{0} & \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} & \rightarrow 13 \\ \fcolorbox{black}{#eeeeee}{0} & \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} & \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} & \fcolorbox{black}{#eeeeee}{0} & \rightarrow 6 & & \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} & \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} & \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} & \fcolorbox{black}{#eeeeee}{0} & \rightarrow 14 \\ \fcolorbox{black}{#eeeeee}{0} & \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} & \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} & \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} & \rightarrow 7 & & \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} & \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} & \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} & \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} & \rightarrow 15 \\ \end{array}\]

Bit Position Determines Value

Each position has a different weight. The leftmost bit is worth the most, the rightmost the least:

\[ \begin{array}{cccccc} \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} & \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} & \fcolorbox{black}{#eeeeee}{0} & \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} \\ \downarrow & \downarrow & \downarrow & \downarrow \\ \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} \times 2^3 & \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} \times 2^2 & \fcolorbox{black}{#eeeeee}{0} \times 2^1 & \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} \times 2^0 \\ \downarrow & \downarrow & \downarrow & \downarrow \\ 8 & 4 & 0 & 1 \\ \downarrow & \downarrow & \downarrow & \downarrow \\ 8 & +\quad4 & +\quad0 & +\quad1 & = & 13 \end{array} \]

Negative Numbers in int32

Signed integers use an encoding called two’s complement. The leftmost bit indicates whether the number is negative, but it is part of the complete encoding rather than a separate sign attached to a value.

An int32 uses 32 bits and represents numbers from \(-2{,}147{,}483{,}648\) to \(+2{,}147{,}483{,}647\).

💡 For now, remember the name two’s complement and that the leftmost bit indicates negativity. You do not need to calculate negative binary numbers by hand.

Numbers with Decimal Points

Whole numbers fit neatly into bits, but numbers like 27.3 or 0.1 need more structure. A Python float splits its 64 bits into three parts:

\[ \underbrace{\fcolorbox{#e4002b}{#fce4e4}{$\phantom{0}$}}_{\substack{\textcolor{#e4002b}{1 \text{ bit}} \\ \textcolor{#e4002b}{sign} \\ \textcolor{#e4002b}{+ \text{ or } -}}} \;\bigg|\; \underbrace{\fcolorbox{#3995ba}{#cee8f6}{$\phantom{0}$} \; \fcolorbox{#3995ba}{#cee8f6}{$\phantom{0}$} \; \fcolorbox{#3995ba}{#cee8f6}{$\phantom{0}$} \; \fcolorbox{#3995ba}{#cee8f6}{$\phantom{0}$} \; \fcolorbox{#3995ba}{#cee8f6}{$\phantom{0}$} \; \fcolorbox{#3995ba}{#cee8f6}{$\phantom{0}$} \; \fcolorbox{#3995ba}{#cee8f6}{$\phantom{0}$} \; \fcolorbox{#3995ba}{#cee8f6}{$\phantom{0}$} \; \fcolorbox{#3995ba}{#cee8f6}{$\phantom{0}$} \; \fcolorbox{#3995ba}{#cee8f6}{$\phantom{0}$} \; \fcolorbox{#3995ba}{#cee8f6}{$\phantom{0}$}}_{\substack{\textcolor{#3995ba}{11 \text{ bits}} \\ \textcolor{#3995ba}{exponent} \\ \textcolor{#3995ba}{\text{how big}}}} \;\bigg|\; \underbrace{\fcolorbox{#47315E}{#e8dff0}{$\phantom{0}$} \; \fcolorbox{#47315E}{#e8dff0}{$\phantom{0}$} \; \fcolorbox{#47315E}{#e8dff0}{$\phantom{0}$} \; \fcolorbox{#47315E}{#e8dff0}{$\phantom{0}$} \; \fcolorbox{#47315E}{#e8dff0}{$\phantom{0}$} \; \fcolorbox{#47315E}{#e8dff0}{$\phantom{0}$} \; \fcolorbox{#47315E}{#e8dff0}{$\phantom{0}$} \; \fcolorbox{#47315E}{#e8dff0}{$\phantom{0}$} \; \fcolorbox{#47315E}{#e8dff0}{$\phantom{0}$} \; \fcolorbox{#47315E}{#e8dff0}{$\phantom{0}$} \; \fcolorbox{#47315E}{#e8dff0}{$\cdots$} \; \fcolorbox{#47315E}{#e8dff0}{$\phantom{0}$} \; \fcolorbox{#47315E}{#e8dff0}{$\phantom{0}$} \; \fcolorbox{#47315E}{#e8dff0}{$\phantom{0}$} \; \fcolorbox{#47315E}{#e8dff0}{$\phantom{0}$} \; \fcolorbox{#47315E}{#e8dff0}{$\phantom{0}$} \; \fcolorbox{#47315E}{#e8dff0}{$\phantom{0}$} \; \fcolorbox{#47315E}{#e8dff0}{$\phantom{0}$} \; \fcolorbox{#47315E}{#e8dff0}{$\phantom{0}$} \; \fcolorbox{#47315E}{#e8dff0}{$\phantom{0}$} \; \fcolorbox{#47315E}{#e8dff0}{$\phantom{0}$}}_{\substack{\textcolor{#47315E}{52 \text{ bits}} \\ \textcolor{#47315E}{mantissa} \\ \textcolor{#47315E}{\text{the digits}}}} \]

The sign, exponent, and mantissa work like scientific notation: \(-3.14 \times 10^{2}\) has a sign (\(-\)), digits (\(3.14\)), and a power that shifts the decimal point (\(10^{2}\)).

Python’s usual 64 bit float stores 52 fraction bits and provides about 15-16 significant decimal digits. Values between the available binary representations are rounded to the nearest one.

Fractional Bits in Action: Storing \(5.75\)

After the decimal point, bit positions are worth \(\frac{1}{2}\), \(\frac{1}{4}\), \(\frac{1}{8}\), and so on.
Before it, they work the same as integers (\(2^0\), \(2^1\), \(2^2\), …):

\[ \begin{array}{ccccccccccccc} \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} & \fcolorbox{black}{#eeeeee}{0} & \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} & . & \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} & \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} & \fcolorbox{black}{#eeeeee}{0} & \fcolorbox{black}{#eeeeee}{0} & \fcolorbox{black}{#eeeeee}{0} & \fcolorbox{black}{#eeeeee}{0} & \fcolorbox{black}{#eeeeee}{0} & \fcolorbox{black}{#eeeeee}{0} \\ \downarrow & \downarrow & \downarrow & & \downarrow & \downarrow & \downarrow & \downarrow & \downarrow & \downarrow & \downarrow & \downarrow \\ 2^2 & 2^1 & 2^0 & & \tfrac{1}{2} & \tfrac{1}{4} & \tfrac{1}{8} & \tfrac{1}{16} & \tfrac{1}{32} & \tfrac{1}{64} & \tfrac{1}{128} & \tfrac{1}{256} \\ \downarrow & & \downarrow & & \downarrow & \downarrow & & & & & & \\ 4 & + & 1 & + & 0.5 & + \; 0.25 & & & & & & & = \;\; 5.75 \end{array} \]

Four bits are ON, and \(4 + 1 + 0.5 + 0.25\) gives exactly \(5.75\). No leftover, no rounding.

Not All Numbers Translate Cleanly to Binary

\(0.5\) fits in one bit (\(\frac{1}{2}\), done). But \(0.1\) and \(0.2\) look clean in decimal and turn into repeating patterns in binary, the same way \(\frac{1}{3}\) looks clean as a fraction but repeats forever in decimal (\(0.333\ldots\)):

\[ 0.5 \;=\; 0.\;\fcolorbox{black}{#111111}{$\textcolor{white}{1}$} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#eeeeee}{0} \quad \text{(exact)} \]

\(0.1\) and \(0.2\) in binary:

\[ \begin{array}{rcl} 0.1 & = & 0.\;\underbrace{\fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#111111}{$\textcolor{white}{1}$}}_{\text{start}}\;\underbrace{\fcolorbox{black}{#111111}{$\textcolor{white}{1}$} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#111111}{$\textcolor{white}{1}$}}_{\text{repeats}} \;\cdots \\[1.5em] 0.2 & = & 0.\;\underbrace{\fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} \fcolorbox{black}{#111111}{$\textcolor{white}{1}$}\;\fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} \fcolorbox{black}{#111111}{$\textcolor{white}{1}$}}_{\text{repeats}} \;\cdots \end{array} \]

The stored fraction has finite space, so Python rounds the input to the nearest representable binary value.

\(0.1 + 0.2 \neq 0.3\) ?!?!

Try this in ipython:

>>> 0.1 + 0.2
0.30000000000000004

That is not a bug. Python rounds both \(0.1\) and \(0.2\) to the nearest representable binary floating-point values before adding them.

🤯 This happens in every programming language, not just Python. Click here to see examples in 50+ languages.

\(0.1 + 0.2 \neq 0.3\) ?!?!

Each fractional bit is worth \(\frac{1}{2}\), \(\frac{1}{4}\), \(\frac{1}{8}\), \(\frac{1}{16}\), \(\frac{1}{32}\), \(\frac{1}{64}\), \(\frac{1}{128}\), \(\frac{1}{256}\), and so on. If we keep only 8 bits:

\[ \begin{array}{rcl} 0.1 & \approx & 0.\;\fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} \;=\; \tfrac{1}{16} + \tfrac{1}{32} + \tfrac{1}{256} \;=\; 0.098 \\[0.8em] 0.2 & \approx & 0.\;\fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} \;=\; \tfrac{1}{8} + \tfrac{1}{16} + \tfrac{1}{128} + \tfrac{1}{256} \;=\; 0.199 \end{array} \]

Neither value in this eight bit illustration is exact. Python uses far more precision and rounds to the nearest representable value, so its errors are much smaller.

\(0.1 + 0.2 \neq 0.3\) ?!?!

Now add those stored values, column by column, right to left:

\[ \begin{array}{rrl} & 0.\;& \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#111111}{$\textcolor{white}{1}$}\;\cdots \\ + & 0.\;& \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} \fcolorbox{black}{#111111}{$\textcolor{white}{1}$}\;\cdots \\[0.3em] \hline \\[-0.7em] & 0.\;& \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#eeeeee}{0}\;\fcolorbox{#e4002b}{#fff3cd}{$\textcolor{#e4002b}{?}$} \fcolorbox{#e4002b}{#fff3cd}{$\textcolor{#e4002b}{?}$} \fcolorbox{#e4002b}{#fff3cd}{$\textcolor{#e4002b}{?}$} \fcolorbox{#e4002b}{#fff3cd}{$\textcolor{#e4002b}{?}$}\;\cdots \end{array} \]

The \(\cdots\) show that both binary patterns continue. Python rounds each operand to the nearest representable value, adds them, and rounds the result again. That is why Python prints 0.30000000000000004 rather than exactly 0.3.

Long story short:

Be mindful of the precision you want your numbers to be in!

The ASCII Table: How Text Becomes Binary

In the early days of computing, text was represented using the ASCII table. ASCII assigns a code of 7 bits to each character, normally stored inside a byte of 8 bits. Here are some examples:

The letter ‘A’ is represented by the number 65 encoded in binary as:

\[ \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} \]

The letter ‘a’ (lowercase) is represented by the number 97 encoded in binary as:

\[ \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} \]

The linebreak character ‘\n’ is represented by the number 10 encoded in binary as:

\[ \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} \fcolorbox{black}{#eeeeee}{0} \fcolorbox{black}{#111111}{$\textcolor{white}{1}$} \fcolorbox{black}{#eeeeee}{0} \]

☕ Coffee Break

11:30 – 11:45

After the break:

  • Your first look at Python
  • Variables, lists, and dictionaries
  • Reading a file in Python

4️⃣ First Look at Python

11:45 – 12:45

You have seen plain text in the terminal. Next we use Python in that same terminal to read those files. No code editors and no notebooks yet: everything here is typed or run from the command line.

The Terminal Runs Python Too

You can write Python in a file and run it, or type it line by line in ipython:

Write a script, then run it:

In the terminal: open the editor

nano scripts/hello.py

In the file (nano): what you save

print("Hello from Python")

Close then back in the terminal: run the file

python scripts/hello.py

Output:

Hello from Python

Or type interactively in ipython:

In the terminal: start ipython

ipython

Typed in ipython:

42

Output (echoed back):

42

ipython echoes values back to you automatically. A script only shows what you ask for with print().

💡 This afternoon you will write your own script in nano and run it with python. You will see the difference between ipython and a script for yourself.

Variables

A variable is a name that holds a value. You create one with = (typed in ipython or saved in a .py file):

Typed in ipython (or in a file):

temperature = 27.3
city = "London"
rain_mm = 0

Typed in ipython: check the type

type(temperature)

Output:

float

🗂️ Variables as Memory Addresses

When you create a variable in Python, you bind a name to an object in memory. The variable refers to that object rather than acting as a container that holds the value itself.

temperature = 25.3

When assignment runs:

  1. Python creates a floating-point object for 25.3
  2. The Python runtime manages the object’s storage
  3. The name temperature is bound to that object

When you reassign:

temperature = 28.7

Python does not change the old floating-point object. The assignment binds temperature to another object representing 28.7.

Memory Addresses (simplified):

Address
Variable
Value
0x1000
temperature →
25.3
0x1048
temperature →
28.7

🔍 You can try it yourself: In CPython, id() normally shows an object’s current memory address. Other Python implementations only guarantee an identity that remains unique during that object’s lifetime.

temperature = 25.3
print(id(temperature))  # Normally its current address in CPython

temperature = 28.7
print(id(temperature))  # Usually changes after binding another object

Primitive Types in Python

Four primitive types you will use constantly. Keep this as a reference:

int: whole numbers

Counts, IDs, year, days: 0, 33, -5, 2026

rain_mm = 0
borough_count = 33

Check: type(rain_mm) gives int

float: decimal numbers

Temperatures, rates, means: 27.3, 0.5, -1.2, 19.0

temperature = 27.3
share = 0.42

Check: type(temperature) gives float

str: text

Names, codes, anything in quotes: "London", "E09000001", "27.3"

city = "London"
raw = "27.3"

Check: type(city) gives str

bool: True or False

Yes/no answers: True, False

is_raining = False
has_data = True

Check: type(is_raining) gives bool

Converting between types

When you read a number from a file, Python gives you text (str), not a number. You convert it yourself:

int("27")      # str to int
float("27.3")  # str to float

💡 This will matter this afternoon when you read numbers from a CSV. The values come out as strings, and adding two strings joins them end to end instead of adding numbers.

📊 Data Types and Memory Storage

💡 Although you can’t change the size of the data types in ‘pure Python’, we will enforce this when we start working with numpy and pandas in the next few weeks.

Integer Storage:

  • int32: Uses 32 bits (4 bytes)

    Can store: -2,147,483,648 to 2,147,483,647

  • int64: Uses 64 bits (8 bytes)

    Can store: much larger numbers!

Why this matters:

A DataFrame with 1 million integers:

  • int32: ~4 MB of memory
  • int64: ~8 MB of memory

Choosing the right type saves memory!

Float Precision:

Python floats use 64 bits by default.

This is why sometimes:

0.1 + 0.2 == 0.3  # False!
# Actually gives: 0.30000000000000004

Oddly, the binary representation of numbers in programming isn’t always exact.

🤯 Click here to read more about this.

Lists

A list holds many values in order. You build one with square brackets:

Typed in ipython:

temps = [25.1, 27.3, 22.8, 19.5, 24.0]

You pick a value by its position (counting from zero):

temps[0]       # 25.1  (first)
temps[-1]      # 24.0  (last)
len(temps)     # 5

You can add a value to the end:

temps.append(26.1)

💡 When you split a CSV row on commas, you get a list. Each position in the list is one column value.

Dictionaries

A dictionary maps names to values, like a lookup table:

Typed in ipython:

day = {
    "city": "London",
    "temperature": 27.3,
    "rain_mm": 0
}

You look up a value by its key:

day["temperature"]

Output:

27.3

💡 A JSON object becomes a Python dictionary. The keys ("city", "temperature") are the column names you saw in the JSON file earlier.

📦 Collections Store References Too

Lists and dictionaries store references to objects rather than placing each object’s full contents inside the collection.

temps = [18, 22, 19]

Memory layout:

temps →
[0x2000, 0x2004, 0x2008]
0x2000
18
0x2004
22
0x2008
19

Why this matters:

  1. Lists can resize because they just add more references
  2. When you pass a list to a function, you’re passing the reference, not copying all the data
  3. This is why operations on large datasets can be fast

Collections are containers of references, not containers of actual values.

Why bother? Understanding this memory model helps you understand why some operations are fast (just changing a reference) and others are slow (copying actual data), and why some data structures work better for certain tasks.

for Loops

A loop repeats the same step for each item in a collection:

Typed in ipython:

temps = [25.1, 27.3, 22.8, 19.5, 24.0]

for t in temps:
    print(t)

Output:

25.1
27.3
22.8
19.5
24.0

The indented line runs once for each value in the list. t takes a new value each time.

for Loops: Sum and Average

Loops are not only for printing. You can accumulate a total, then compute a summary:

Typed in ipython:

temps = [25.1, 27.3, 22.8, 19.5, 24.0]
total = 0
for t in temps:
    total = total + t
average = total / len(temps)
total

Output:

119.7
average

Output:

23.94

💡 A loop can build a number (or a list) as it goes. Printing is only one use.

Reading a File in Python: Open, Read, Close

Python reads a file as one long string. First open a connection, read the text, then close it:

Typed in ipython:

f = open('data/london_weather_june_2026.csv', mode='r')
text = f.read()
f.close()
print(text)

Output (print() writes the line breaks as actual new lines):

date,temperature_2m_max,temperature_2m_min,data_type
2026-06-01,24.4,13.7,historical
...

Reading a File: Split into Rows

Once you have the whole file as text, split it on line breaks:

Typed in ipython:

rows = text.split("\n")
first_data_row = rows[1]
print(first_data_row)

Output:

2026-06-01,24.4,13.7,historical

rows[0] is the header. rows[1] is the first day of data.

Reading a File: Split One Row into Fields

Split that row on commas to get one value per column:

Typed in ipython:

fields = first_data_row.split(",")
fields[0]
fields[1]

Output:

'2026-06-01'
'24.4'

Reading a File: Values Are Still Text

Every piece from .split() is a string, even when it looks like a number:

Typed in ipython:

fields[1]            # '24.4'  (text)
float(fields[1])     # 24.4    (a number)

💡 fields[1] is '24.4' (text), not 24.4 (a number). Use float(fields[1]) or int(...) before you do arithmetic.

Where This Is Going

You just read a CSV file by hand: open, read, split on line breaks, split on commas, convert types. It works, but it is a lot of steps.

Python has tools that automate this:

Tool What it does When you will see it
json.load Reads a JSON file into Python dicts and lists 💻 Week 01 Day 03 Lab
pandas Loads data into tables you can filter, group, and plot 🖥️ Week 01 Day 04 Lecture

Today we did it by hand so you understand what those tools are doing for you.

Thanks!

12:45

This afternoon you will put all of this into practice in the lab: reading files in the terminal and in Python.

💬 Questions?

References

These slides use Quarto HTML presentations. For more on Markdown, see the Markdown Guide.

LSE Summer School 2026 | ME204 Week 01 Day 02