<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:webfeeds="http://webfeeds.org/rss/1.0">
  <channel>
    <title>staticnotes.org</title>
    <link>https://staticnotes.org/</link>
    <webfeeds:icon>https://staticnotes.org/logo.png</webfeeds:icon>
    <webfeeds:logo>https://staticnotes.org/logo.png</webfeeds:logo>
    <description>All recent content posts from Rob on staticnotes.org</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-US</language>
    <copyright>staticnotes.org (CC BY-NC-SA 4.0)</copyright><atom:link href="https://staticnotes.org/index.xml" rel="self" type="application/rss+xml" /><image>
      <url>https://staticnotes.org/logo.png</url>
      <title>staticnotes.org</title>
      <link>https://staticnotes.org/</link>
    </image>
   
    
    <item>
      <title>Encrypted cloud backups with restic</title>
      <link>https://staticnotes.org/posts/cloud-backups-with-restic/</link>
      <pubDate>Mon, 31 Aug 2026 00:00:00 +0100</pubDate>
      
      <guid>https://staticnotes.org/posts/cloud-backups-with-restic/</guid>
    <description>&lt;p&gt;My current backup strategy is pretty inconsistent. I run a weekly system backup of my MacBook onto an external hardrive &lt;code&gt;sysbackup&lt;/code&gt; using &lt;a href=&#34;https://bombich.com/&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;CarbonCopyCloner&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;. I also have another external hardrive &lt;code&gt;photos1&lt;/code&gt; to store files that don&amp;rsquo;t fit onto my MacBook and which isn&amp;rsquo;t backed up at all. Unfortunately, this includes most of my photos and videos. Storing those photos in the cloud would be a convenient option. However, I am not comfortable uploading &lt;em&gt;important&lt;/em&gt; personal documents and photos to the cloud.&lt;span class=&#34;sidenote-number&#34;&gt;&lt;small class=&#34;sidenote&#34;&gt; Even if Google Photos reminds me daily that I should.&lt;/small&gt;&lt;/span&gt;&lt;/p&gt;&#xA;&lt;p&gt;This status quo is eventually going to fail, e.g. if the &lt;code&gt;photos1&lt;/code&gt; drive breaks or both my MacBook and the &lt;code&gt;sysbackup&lt;/code&gt; drive get destroyed at the same time (burglary, fire, flooding, ransomware attack). Recently, my photo hardrive made unusually loud noises when plugged in, which prompted me to finally adopt a more solid backup approach.&lt;/p&gt;&#xA;&lt;p&gt;Generally for backups the 3-2-1 rule is recommended (3 copies, 2 different storage media, 1 off-site backup). To accomplish this I will periodically clone the external hardrive &lt;code&gt;photos1&lt;/code&gt; to a new external hardrive &lt;code&gt;photos2&lt;/code&gt; and also set up the off-site backup. This post is mostly about how I set up the cloud backup, but I will summarize the overall approach at the end as well.&lt;/p&gt;&#xA;&lt;h2 id=&#34;requirements-for-my-cloud-backup&#34; class=&#34;content-heading&#34;&gt;Requirements for my cloud backup&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;To find the right approach I wrote down my requirements:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;reliable and cheap cloud storage&lt;/li&gt;&#xA;&lt;li&gt;backups encrypted locally before upload&lt;/li&gt;&#xA;&lt;li&gt;open-source software to prevent vendor lock-in or software disappearing in 5 years.&lt;/li&gt;&#xA;&lt;li&gt;simple to use for someone comfortable with the command line&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;In the end I settled on &lt;a href=&#34;https://restic.net/&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;&lt;code&gt;restic&lt;/code&gt;&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt; which works on Linux, macOS, Windows, encrypts data locally by default, and doesn&amp;rsquo;t need to run on a server.&lt;span class=&#34;sidenote-number&#34;&gt;&lt;small class=&#34;sidenote&#34;&gt; I also looked into &lt;a href=&#34;https://www.borgbackup.org/&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;BorgBackup&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt; which probably would have worked as well. However, that requires running borg on the server.&lt;/small&gt;&lt;/span&gt; Moreover, the commands and basic configuration are straightforward.&lt;/p&gt;&#xA;&lt;h2 id=&#34;getting-cheap-space-in-the-cloud&#34; class=&#34;content-heading&#34;&gt;Getting cheap space in the cloud&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;I already have a Hetzner account to host my Nextcloud instance. Luckily, they offer very cheap storage. I got the &lt;em&gt;Hetzner 1TB Storage Box&lt;/em&gt; for 3.83€/month. This means my data is stored in their datacenter in Falkenstein, Germany. By activating automatic snapshots of the storage box in the Hetzner console, I get another backup layer.&lt;span class=&#34;sidenote-number&#34;&gt;&lt;small class=&#34;sidenote&#34;&gt; &lt;a href=&#34;https://www.youtube.com/watch?v=MQeJYEN_lrg&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;Here&#xA;    &#xA;&#xA;        &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.9em; margin-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 448 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M549.7 124.1c-6.3-23.7-24.8-42.3-48.3-48.6C458.8 64 288 64 288 64S117.2 64 74.6 75.5c-23.5 6.3-42 24.9-48.3 48.6-11.4 42.9-11.4 132.3-11.4 132.3s0 89.4 11.4 132.3c6.3 23.7 24.8 41.5 48.3 47.8C117.2 448 288 448 288 448s170.8 0 213.4-11.5c23.5-6.3 42-24.2 48.3-47.8 11.4-42.9 11.4-132.3 11.4-132.3s0-89.4-11.4-132.3zm-317.5 213.5V175.2l142.7 81.2-142.7 81.2z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;&#xA;    &#xA;    &#xA;&lt;/a&gt; you can see the inside of their datacenter. &lt;/small&gt;&lt;/span&gt;&lt;/p&gt;&#xA;&lt;p&gt;To set up the Hetzner Storage Box to work with restic, I had to enable &lt;code&gt;External Reachability&lt;/code&gt;, enable &lt;code&gt;SSH&lt;/code&gt;, and set a password in the Hetzner console. The communication between my local machine and the storage box will be via SSH and the file upload happens over &lt;a href=&#34;https://en.wikipedia.org/wiki/SSH_File_Transfer_Protocol&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;SFTP&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;                style=&#34;height: 0.7em; width: 0.7em; margin-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;                class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;                viewBox=&#34;0 0 640 512&#34;&gt;&#xA;                &lt;path fill=&#34;currentColor&#34;&#xA;                    d=&#34;M640 51.2l-.3 12.2c-28.1 .8-45 15.8-55.8 40.3-25 57.8-103.3 240-155.3 358.6H415l-81.9-193.1c-32.5 63.6-68.3 130-99.2 193.1-.3 .3-15 0-15-.3C172 352.3 122.8 243.4 75.8 133.4 64.4 106.7 26.4 63.4 .2 63.7c0-3.1-.3-10-.3-14.2h161.9v13.9c-19.2 1.1-52.8 13.3-43.3 34.2 21.9 49.7 103.6 240.3 125.6 288.6 15-29.7 57.8-109.2 75.3-142.8-13.9-28.3-58.6-133.9-72.8-160-9.7-17.8-36.1-19.4-55.8-19.7V49.8l142.5 .3v13.1c-19.4 .6-38.1 7.8-29.4 26.1 18.9 40 30.6 68.1 48.1 104.7 5.6-10.8 34.7-69.4 48.1-100.8 8.9-20.6-3.9-28.6-38.6-29.4 .3-3.6 0-10.3 .3-13.6 44.4-.3 111.1-.3 123.1-.6v13.6c-22.5 .8-45.8 12.8-58.1 31.7l-59.2 122.8c6.4 16.1 63.3 142.8 69.2 156.7L559.2 91.8c-8.6-23.1-36.4-28.1-47.2-28.3V49.6l127.8 1.1 .2 .5z&#34;&gt;&#xA;                &lt;/path&gt;&#xA;            &lt;/svg&gt;&#xA;        &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt; which is based on SSH.&lt;/p&gt;&#xA;&lt;p&gt;First I create a new pair of SSH keys&lt;span class=&#34;sidenote-number&#34;&gt;&lt;small class=&#34;sidenote&#34;&gt; I have more detailed notes on setting up SSH keys &lt;a href=&#34;https://staticnotes.org/til/2025/11/how-to-set-up-multiple-ssh-keys/&#34; &#xA;&gt;here&#xA;&lt;/a&gt;.&lt;/small&gt;&lt;/span&gt; and then upload the public key to the storage box. In short:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Create new SSH keys:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;terminal&#34;&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-terminal&#34; data-lang=&#34;terminal&#34;&gt;$ ssh-keygen -t ed25519 -f ~/.ssh/storagebox&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Add a host entry for the storagebox to your &lt;code&gt;~/.ssh/config&lt;/code&gt; file:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&lt;pre&gt;&lt;code&gt;Host storagebox&#xA;    HostName uXXXXXX.your-storagebox.de&#xA;    User uXXXXXX&#xA;    Port 23&#xA;    IdentityFile ~/.ssh/storagebox&#xA;    WarnWeakCrypto no-pq-kex&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Copy the public SSH key to the storagebox:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;terminal&#34;&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-terminal&#34; data-lang=&#34;terminal&#34;&gt;$ ssh-copy-id -p 23 -s -i ~/.ssh/storagebox.pub uXXXXXX@uXXXXXX.your-storagebox.de&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;I should now be able to interact via sftp without providing a separate password. I can test that it works with:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;terminal&#34;&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-terminal&#34; data-lang=&#34;terminal&#34;&gt;$ sftp storagebox&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;h2 id=&#34;installing-and-configuring-restic&#34; class=&#34;content-heading&#34;&gt;Installing and configuring &lt;code&gt;restic&lt;/code&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;I installed restic via homebrew with &lt;code&gt;brew install restic&lt;/code&gt;. Next, I create a restic password that restic uses to encrypt my data locally before it is uploaded. To not forget it, I stored it in my password manager and also wrote it down in my notes. I also store this password in my Apple Keychain with:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;terminal&#34;&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-terminal&#34; data-lang=&#34;terminal&#34;&gt;$ security add-generic-password -a &amp;#34;$USER&amp;#34; -s restic-password -w&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;I then configured two important environment variables in a new restic config file &lt;code&gt;~/.config/restic/env&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&lt;pre&gt;&lt;code&gt;export RESTIC_REPOSITORY=&amp;#34;sftp:storagebox:/home/restic-backup&amp;#34;&#xA;export RESTIC_PASSWORD_COMMAND=&amp;#34;security find-generic-password -a yourusername -s restic-password -w&amp;#34;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;&lt;code&gt;RESTIC_REPOSITORY&lt;/code&gt; is the director on the storage box where the backups are stored. This is called the &lt;em&gt;restic repository&lt;/em&gt;. &lt;code&gt;RESTIC_PASSWORD_COMMAND&lt;/code&gt; tells &lt;code&gt;restic&lt;/code&gt; which command to run to get the restic password from the Apple Keychain (all of this avoids storing it in plain text in an environment variable).&lt;/p&gt;&#xA;&lt;p&gt;Next I created the restic repository on the storage box with:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;terminal&#34;&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-terminal&#34; data-lang=&#34;terminal&#34;&gt;$ restic -r sftp:storagebox:/home/restic-backup init&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;This is all that is needed for the initial configuration. I can now start making backups.&lt;/p&gt;&#xA;&lt;h2 id=&#34;basic-restic-backup-commands&#34; class=&#34;content-heading&#34;&gt;Basic restic backup commands&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;To backup the folder &lt;code&gt;~/important_docs/&lt;/code&gt; to the restic repository on the storage box I run:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;terminal&#34;&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-terminal&#34; data-lang=&#34;terminal&#34;&gt;$ restic backup ~/important_docs --verbose&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;Restic calls the content of a directory at a specific point in time a &lt;em&gt;snapshot&lt;/em&gt;. This snapshot is now available in the repository and I can restore the whole snapshot or selected files of the directory back to my local machine.&lt;/p&gt;&#xA;&lt;p&gt;If I run the above backup command again, restic will create a second snapshot, but not upload any files as none have changed. This deduplication makes sure that the data is stored efficiently.&lt;/p&gt;&#xA;&lt;h2 id=&#34;inspecting-the-storage-box&#34; class=&#34;content-heading&#34;&gt;Inspecting the storage box&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;To inspect the files in my storage box I use &lt;a href=&#34;https://cyberduck.io/&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;Cyberduck&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;. Create a new SFTP connection with the storage box details:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&lt;pre&gt;&lt;code&gt;Server: uXXXXXX.your-storagebox.de&#xA;Port: 22&#xA;Username: uXXXXXX&#xA;SSH Private Key: ~/.ssh/storagebox&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;This opens the file viewer:&#xA;&#xA;&#xA;&lt;figure&gt;&#xA;  &lt;div class=&#34;image-wrapper&#34;&gt;&#xA;  &lt;img src=&#34;https://staticnotes.org/posts/cloud-backups-with-restic/cyberduck_files.png&#34; alt=&#34;Cyberduck&#34; loading=&#34;lazy&#34; /&gt;&#xA;  &lt;figcaption&gt;Figure 1. Restic repository files visible in my storage box.&lt;/figcaption&gt;&#xA;  &lt;/div&gt;&#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;I can see that (encrypted) restic files have been uploaded to the subfolder &lt;code&gt;restic-backup&lt;/code&gt;.&lt;span class=&#34;sidenote-number&#34;&gt;&lt;small class=&#34;sidenote&#34;&gt; Of course I can also store other things in the storage box, e.g. I have a separate &lt;code&gt;sync&lt;/code&gt; folder that I use with &lt;code&gt;rsync&lt;/code&gt;.&lt;/small&gt;&lt;/span&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;working-with-snapshots&#34; class=&#34;content-heading&#34;&gt;Working with snapshots&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Here are useful commands to inspect and manage snapshots taken in the past:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;To list all available snapshots in the repository: &lt;code&gt;restic snapshots&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;To list all files in a specific snapshot with id &lt;code&gt;073a90db&lt;/code&gt;: &lt;code&gt;restic ls 073a90db&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;To view the size of the restic repository, run  &lt;code&gt;restic stats --mode raw-data&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;restoring-backups&#34; class=&#34;content-heading&#34;&gt;Restoring backups&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;It is worth understanding how to restore a backed-up directory. Let&amp;rsquo;s say I want to restore snapshot &lt;code&gt;a798b76e&lt;/code&gt;. If I run&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;terminal&#34;&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-terminal&#34; data-lang=&#34;terminal&#34;&gt;$ restic restore a798b76e --target ~/restore-test&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;restic will reproduce all directories and files that are part of that snapshot in the provided target folder &lt;code&gt;restore-test&lt;/code&gt;. If I just want to restore a single file from a snapshot I can use the &lt;code&gt;--include&lt;/code&gt; flag, e.g.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;terminal&#34;&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-terminal&#34; data-lang=&#34;terminal&#34;&gt;$ restic restore a798b76e --include photo_of_grandma.jpg --target ~/restore-test&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;This will restore &lt;code&gt;photo_of_grandma.jpg&lt;/code&gt; from snapshot &lt;code&gt;a798b76e&lt;/code&gt; and make it available in the folder &lt;code&gt;~/restore-test&lt;/code&gt;. Important: To restore you will implicitely or explicitely need my restic password. Make sure you have it available.&lt;/p&gt;&#xA;&lt;h2 id=&#34;managing-the-repositorys-size-and-integrity&#34; class=&#34;content-heading&#34;&gt;Managing the repository&amp;rsquo;s size and integrity&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Two additional commands help me manage the repository and its size. To make sure that the data on the storage box hasn&amp;rsquo;t been corrupted I periodically run:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;terminal&#34;&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-terminal&#34; data-lang=&#34;terminal&#34;&gt;$ restic check&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;This performs a structural integrity check of the data in the repository. Do the available files and the index match? Are there files not in the index? Check that it can decrypt snapshot metadata. This check is fast because it doesn&amp;rsquo;t actually read any data. If I want to also check that files are correct I need to append &lt;code&gt;--read-data&lt;/code&gt; for a full repository read. This will likely take some time. Alternatively I can only check a subset of data with &lt;code&gt;--read-data-subset=5%&lt;/code&gt; or &lt;code&gt;--read-data-subset=500M&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;The other important command is &lt;code&gt;forget&lt;/code&gt;. My storage box only has a size of 1TB, so if I keep accumulating snapshots, I will eventually run out of space. &lt;code&gt;forget&lt;/code&gt; and &lt;code&gt;prune&lt;/code&gt; can be used to delete snapshots.&lt;/p&gt;&#xA;&lt;p&gt;To manually delete a snapshot with id &lt;code&gt;a798b76e&lt;/code&gt; I run:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;terminal&#34;&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-terminal&#34; data-lang=&#34;terminal&#34;&gt;$ restic forget a798b76e&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;This only deletes the snapshot. It doesn&amp;rsquo;t actually delete the data that was exclusive to that snapshot (not part of another snapshot). To delete this data I need to follow up with &lt;code&gt;prune&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;terminal&#34;&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-terminal&#34; data-lang=&#34;terminal&#34;&gt;$ restic prune&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;or do it as part of the forget command:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;terminal&#34;&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-terminal&#34; data-lang=&#34;terminal&#34;&gt;$ restic forget a798b76e --prune&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;The restic docs recommend running a &lt;code&gt;restic check&lt;/code&gt; after pruning operations to flag any issues.&lt;/p&gt;&#xA;&lt;p&gt;Instead of deleting a specific snapshot, I want to specify a deletion policy, e.g. &amp;ldquo;keep one backup for the last 6 month, one for the last 4 weeks, and daily for the current week.&amp;rdquo; Restic uses the &lt;code&gt;--keep-*&lt;/code&gt; flag to specify such a policy.&lt;span class=&#34;sidenote-number&#34;&gt;&lt;small class=&#34;sidenote&#34;&gt; Use the &lt;code&gt;--dry-run&lt;/code&gt; flag when trying this out the first time.&lt;/small&gt;&lt;/span&gt; Some example policies:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;code&gt;--keep-last n&lt;/code&gt; keep the &lt;code&gt;n&lt;/code&gt; last (most recent) snapshots.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;code&gt;--keep-daily n&lt;/code&gt; for the last &lt;code&gt;n&lt;/code&gt; days which have one or more snapshots, keep only the most recent one for each day.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;code&gt;--keep-weekly n&lt;/code&gt; for the last &lt;code&gt;n&lt;/code&gt; weeks which have one or more snapshots, keep only the most recent one for each week.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;These can be combined (union of snapshots). So for my desired policy I could use:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;terminal&#34;&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-terminal&#34; data-lang=&#34;terminal&#34;&gt;$ restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune --verbose&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;Note that one snapshot could fulfil multiple conditions.&lt;/p&gt;&#xA;&lt;h2 id=&#34;scheduling-and-profiles-with-resticprofile&#34; class=&#34;content-heading&#34;&gt;Scheduling and profiles with &lt;code&gt;resticprofile&lt;/code&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;So far I only have a set of CLI commands that I would have to remember to run periodically.&#xA;To actually make &lt;code&gt;restic&lt;/code&gt; work, I want &lt;em&gt;scheduling&lt;/em&gt; and &lt;em&gt;profiles&lt;/em&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Scheduling to reliably run the backup daily and profiles to have different command configurations for different backup sources, e.g. I want daily backups of core folders on my MacBook and only weekly backups for my external hard drive &lt;code&gt;photos1&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;I am using &lt;a href=&#34;https://creativeprojects.github.io/resticprofile/&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;resticprofile&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt; for both which I install via homebrew&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;terminal&#34;&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-terminal&#34; data-lang=&#34;terminal&#34;&gt;$ brew tap creativeprojects/tap&#xA;$ brew install resticprofile&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;With &lt;code&gt;resticprofile&lt;/code&gt; I can use a &lt;code&gt;yml&lt;/code&gt; or &lt;code&gt;toml&lt;/code&gt; configuration file to specify commands and configurations for specific paths. I created two profiles here &lt;code&gt;~/.config/resticprofile/profiles.yml&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;yml&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;version&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;1&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# default profile set up for files on my MacBook&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;default&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;repository&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;sftp:storagebox:/home/restic-backup&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;password-command&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;security find-generic-password -a yourusername -s restic-password -w&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;initialize&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;force-inactive-lock&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;retry-lock&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;10m&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;backup&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;source&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;s2&#34;&gt;&amp;#34;~/core_data_from_my_macbook&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;exclude&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;s2&#34;&gt;&amp;#34;~/stuff_I_want_to_exclude/&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;exclude-file&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;s2&#34;&gt;&amp;#34;~/.config/restic/excludes.txt&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;verbose&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;schedule&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;11:00&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;forget&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# path: true reuses the source list from backup command&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;keep-daily&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;m&#34;&gt;7&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;keep-weekly&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;m&#34;&gt;4&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;keep-monthly&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;m&#34;&gt;6&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;prune&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;schedule&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;12:00&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;check&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;read-data-subset&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;5%&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;schedule&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;Mon 12:30&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# for `photos1` external hard drive&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;photos&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;repository&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;sftp:storagebox:/home/restic-backup&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;password-command&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;security find-generic-password -a yourusername -s restic-password -w&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;initialize&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;backup&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;source&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;s2&#34;&gt;&amp;#34;/Volumes/photos1/Photos&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;forget&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;prune&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;keep-last&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;m&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;The first profile &lt;code&gt;default&lt;/code&gt; configures my MacBook backups. It specifies which folders to backup and is configured to run daily at 11am. At 12pm &lt;code&gt;restic&lt;/code&gt; executes my forget policy on the specified paths and at 12:30 &lt;code&gt;restic&lt;/code&gt; runs an integrity check.&lt;/p&gt;&#xA;&lt;p&gt;I have set up a second profile &lt;code&gt;photos&lt;/code&gt; to configure backups from my external hard drive to the same remote restic repository. The backup command is not scheduled. I only run it after I have copied new photos onto the harddrive, which only really happens every few weeks. The forget policy ensures the last 5 snapshots are kept.&lt;/p&gt;&#xA;&lt;p&gt;To schedule this configuration, I run &lt;code&gt;resticprofile schedule&lt;/code&gt;. On macOS this schedules the jobs in the &lt;code&gt;launchd&lt;/code&gt; service manager.&lt;span class=&#34;sidenote-number&#34;&gt;&lt;small class=&#34;sidenote&#34;&gt; The first time this runs, macOS will ask you to confirm permissions.&lt;/small&gt;&lt;/span&gt; I verified the schedule is set with &lt;code&gt;launchctl list | grep -i restic&lt;/code&gt;. Moreover, I can show all configured profiles with&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;terminal&#34;&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-terminal&#34; data-lang=&#34;terminal&#34;&gt;$ resticprofile profiles&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;I run the backup command for the &lt;em&gt;photos profile&lt;/em&gt; manually with&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;terminal&#34;&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-terminal&#34; data-lang=&#34;terminal&#34;&gt;$ resticprofile photos.backup&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;h2 id=&#34;conclusion&#34; class=&#34;content-heading&#34;&gt;Conclusion&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;My final backup approach is show in Figure 2. My MacBook is backed up daily to the remote restic repository on the Hetzner Storage Box and weekly to an external hard drive. My photos hard drive is backed up weekly to the restic repository and monthly to a second external hard drive.&#xA;&#xA;&#xA;&lt;figure&gt;&#xA;  &lt;div class=&#34;image-wrapper&#34;&gt;&#xA;  &lt;img src=&#34;https://staticnotes.org/posts/cloud-backups-with-restic/backup_devices.jpg&#34; alt=&#34;Backup approach&#34; loading=&#34;lazy&#34; /&gt;&#xA;  &lt;figcaption&gt;Figure 2. Final backup approach to restic cloud repository and external hard drives.&lt;/figcaption&gt;&#xA;  &lt;/div&gt;&#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;I have been running this setup for a couple of weeks and so far I am happy with it. The important files on my MacBook are about 15GB which took 20&amp;ndash;30min for the first uploaded snapshot and only a few seconds since. The &lt;code&gt;photos1&lt;/code&gt; hard drive has about 250GB of data which took 4&amp;ndash;5h for the initial upload.&lt;/p&gt;&#xA;&lt;p&gt;It&amp;rsquo;s also nice that I can change storage vendor seamlessly, e.g. I can switch to AWS S3 or Backblaze B2 by modifying the config file and setting up new SSH keys.&lt;/p&gt;&#xA;&lt;h2 id=&#34;appendix&#34; class=&#34;content-heading&#34;&gt;Appendix&#xA;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;launchd-schedules-with-sleeping-macbook-closed-lid&#34; class=&#34;content-heading&#34;&gt;Launchd schedules with sleeping MacBook (closed lid)&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;I noticed that some scheduled backups failed when my MacBook was asleep (lid closed). I think that it&amp;rsquo;s because launchd resumes scheduled jobs once the MacBook awakes but a network connection isn&amp;rsquo;t always immediately available. I fixed this by checking for a network connection befor running the backup command using the &lt;code&gt;run-before&lt;/code&gt; setting. I added the following line for &lt;code&gt;backup&lt;/code&gt; &lt;code&gt;forget&lt;/code&gt; and &lt;code&gt;check&lt;/code&gt; in my &lt;code&gt;profiles.yml&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;yml&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c&#34;&gt;# [...]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;backup&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# before running backup try 30x to reach the storage box, this should give time for network access to be available after sleep&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;run-before&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;s2&#34;&gt;&amp;#34;for i in $(seq 1 30); do nc -z -G 5 uXXXXXX.your-storagebox.de 23 &amp;amp;&amp;amp; exit 0; sleep 10; done; exit 1&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# [...]      &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;h3 id=&#34;locks&#34; class=&#34;content-heading&#34;&gt;Locks&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;Certain restic commands put a lock onto the repository to prevent multiple clients from working on the same files. I can list all locks with&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;terminal&#34;&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-terminal&#34; data-lang=&#34;terminal&#34;&gt;$ restic list locks&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;Sometimes failed commands leave the repository locked. If you are sure it&amp;rsquo;s safe to unlock it, run:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;terminal&#34;&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-terminal&#34; data-lang=&#34;terminal&#34;&gt;$ restic unlock&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;A common failure mode is for example for the &lt;code&gt;check&lt;/code&gt; command to run and fail and leave an exclusive lock behind on the repository, which then makes subsequent backups fail. To avoid this issue I added the following settings:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;yml&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c&#34;&gt;# [...]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;default&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;repository&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;sftp:storagebox:/home/restic-backup&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# remove stale restic locks older than 1h&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;force-inactive-lock&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# when restic finds a blocking lock, try locking again in 10min&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;retry-lock&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;10m&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# [...]      &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;h3 id=&#34;exclusion-file&#34; class=&#34;content-heading&#34;&gt;Exclusion file&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;My profile uses an exclusion file &lt;code&gt;~/.config/restic/excludes.txt&lt;/code&gt; to ignore certain types of files I don&amp;rsquo;t want to back up, e.g. build artefacts or temporary files:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;txt&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;# Python&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;.venv&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;venv&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;__pycache__&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;*.pyc&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;.pytest_cache&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;.mypy_cache&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;.ruff_cache&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;.tox&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;*.egg-info&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;# Node / JS&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;node_modules&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;.next&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;.nuxt&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;.parcel-cache&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;# Editors &amp;amp; tooling&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;.idea&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;.vscode&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;*.swp&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;*~&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;# Caches &amp;amp; logs&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;.cache&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;*.log&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;*.tmp&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;.terraform&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;# OS junk&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;.DS_Store&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;._*&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;.Spotlight-V100&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;.fseventsd&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;.Trashes&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;.TemporaryItems&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;.DocumentRevisions-V100&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;Additionally I use the flag &lt;code&gt;exclude-caches: true&lt;/code&gt;, which ignores all folders that have a &lt;code&gt;CACHEDIR.TAG&lt;/code&gt; file, e.g. Rust cache directories.&lt;/p&gt;&#xA;</description>
    </item>
    
    <item>
      <title>TIL how to trigger notifications in Thunderbird for specific emails</title>
      <link>https://staticnotes.org/til/2026/8/thunderbird-notifications/</link>
      <pubDate>Sun, 16 Aug 2026 00:00:00 +0100</pubDate>
      
      <guid>https://staticnotes.org/til/2026/8/thunderbird-notifications/</guid>
    <description>&lt;p&gt;Some training sessions in my triathlon club are very popular. The weekly swim sessions in the winter and some of the longer rides on summer weekends are limited to a few people per group. There are usually five groups for different fitness levels.&#xA;People in my club are really fast at signing up, but there are always some who drop out a day or a few hours before the training session. If I didn&amp;rsquo;t sign up quickly enough I usually try to grab one of those last-minute spots. But even for those you have to be quick. I usually have between 1-5 min to react after a drop out before it gets snatched by somebody else.&lt;/p&gt;&#xA;&lt;h2 id=&#34;problem&#34; class=&#34;content-heading&#34;&gt;Problem&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;The notifications for available spots usually come during work hours via email from the booking system. I want to set up the email client, Thunderbird, on my MacBook to display a notification as soon as such an email arrives.&lt;/p&gt;&#xA;&lt;h2 id=&#34;solution&#34; class=&#34;content-heading&#34;&gt;Solution&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;There is no built-in feature in Thunderbird to get notified for specific emails. But I managed to set it up using the Thunderbird Extension &lt;code&gt;FiltaQuilla&lt;/code&gt; and an osascript one-liner.&lt;/p&gt;&#xA;&lt;h3 id=&#34;filtaquilla-extension&#34; class=&#34;content-heading&#34;&gt;FiltaQuilla extension&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;In Thunderbird install the &lt;a href=&#34;https://services.addons.thunderbird.net/en-US/thunderbird/addon/filtaquilla/&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;FiltaQuilla&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt; extension. This adds a number of new email filter actions to Thunderbird. After installation, navigate to the &lt;code&gt;FiltaQuilla&lt;/code&gt; preferences and check the &lt;code&gt;Run Program&lt;/code&gt; filter action. Then restart Thunderbird.&lt;/p&gt;&#xA;&lt;h3 id=&#34;notification-display-script&#34; class=&#34;content-heading&#34;&gt;Notification display script&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;Create the script that later runs when a certain filter rule fires. I created &lt;code&gt;~/scripts/thunderbird_alert.sh&lt;/code&gt; with the following content&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;bash&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#!/bin/bash&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;&lt;/span&gt;osascript -e &lt;span class=&#34;s1&#34;&gt;&amp;#39;display notification &amp;#34;A new spot opened up. Check your inbox.&amp;#34; with title &amp;#34;Mail from Triathlon Club&amp;#34; sound name &amp;#34;Glass&amp;#34;&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;Then add execution rights to the script with &lt;code&gt;chmod +x ~/scripts/thunderbird_alert.sh&lt;/code&gt;. I tested the notification by running the script: &lt;code&gt;~/scripts/thunderbird_alert.sh&lt;/code&gt;. You should see it appear in the top right corner of your screen. Mine looks like this:&#xA;&#xA;&#xA;&lt;figure&gt;&#xA;  &lt;div class=&#34;image-wrapper&#34;&gt;&#xA;  &lt;img src=&#34;https://staticnotes.org/til/2026/8/thunderbird-notifications/triathlon_notification.jpg&#34; alt=&#34;Triathlon spot notification&#34; loading=&#34;lazy&#34; /&gt;&#xA;  &lt;figcaption&gt;Figure 1. Notification about new spots for Triathlon training sessions.&lt;/figcaption&gt;&#xA;  &lt;/div&gt;&#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;h3 id=&#34;set-up-filter-rule&#34; class=&#34;content-heading&#34;&gt;Set up filter rule&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;Back in Thunderbird, go to &lt;code&gt;Tools&lt;/code&gt; -&amp;gt; &lt;code&gt;Message Filters&lt;/code&gt; -&amp;gt; &lt;code&gt;New...&lt;/code&gt;. Give the new rule a name, then set match on &lt;code&gt;From&lt;/code&gt; &lt;code&gt;is&lt;/code&gt; &lt;code&gt;no_reply@triathlon.club&lt;/code&gt;. For the action select &lt;code&gt;Run Program&lt;/code&gt; and paste the path to the notification script &lt;code&gt;/Users/myname/scripts/thunderbird_alert.sh&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;That&amp;rsquo;s it. Every time a new email with that address arrives in Thunderbird, the filter rule fires the script and I see the alert that a spot is available. Compared to previous weeks my booking rate for popular sessions has gone up a lot.&lt;/p&gt;&#xA;</description>
    </item>
    
    <item>
      <title>Visualising London rental bike location data</title>
      <link>https://staticnotes.org/posts/london-bike-data/</link>
      <pubDate>Wed, 15 Jul 2026 12:00:00 +0000</pubDate>
      
      <guid>https://staticnotes.org/posts/london-bike-data/</guid>
    <description>&lt;p&gt;London has a great rental bike scheme that makes it easy to get around the city. Rather than free-floating you can rent bikes from fixed bike stations like the one shown below. If you get the yearly membership you even get a key that you can use to rent the bike without an app.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;Image&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;filename&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;map_images/station_photo.jpg&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;width&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;400px&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;&#xA;&#xA;&lt;img src=&#34;https://staticnotes.org/posts/london-bike-data/output_1_0.jpg&#34; alt=&#34;jpeg&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;Moreover, Transport for London makes the live rental station data available on &lt;a href=&#34;https://tfl.gov.uk/tfl/syndication/feeds/cycle-hire/livecyclehireupdates.xml&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;this endpoint&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;. I collected hourly snapshots of bike availability data over a month so I can play around with it with DuckDB&amp;rsquo;s new spatial features and QGIS.&lt;/p&gt;&#xA;&lt;h2 id=&#34;collecting-the-data&#34; class=&#34;content-heading&#34;&gt;Collecting the data&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;I created a simple script &lt;code&gt;fetch_bike_data.sh&lt;/code&gt; and a cronjob on a Hetzner VPS to request and store the data every hour. Here is the script:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;bash&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#!/bin/bash&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;DATE&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;\\&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;((&lt;/span&gt;date +&lt;span class=&#34;s2&#34;&gt;&amp;#34;%Y.%m.%d.%H.%M.%S&amp;#34;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;DIR&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;/home/projects/bike&lt;span class=&#34;se&#34;&gt;\_&lt;/span&gt;data&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;mkdir -p &lt;span class=&#34;se&#34;&gt;\\&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;DIR&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;curl -o santander.&lt;span class=&#34;nv&#34;&gt;$DATE&lt;/span&gt;.xml https://tfl.gov.uk/tfl/syndication/feeds/cycle-hire/livecyclehireupdates.xml&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;After adding permissions to the script with &lt;code&gt;chmod +x fetch_bike_data.sh&lt;/code&gt; I modify crontab:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;sh&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;crontab -e&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;and add the following line to run this script:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;sh&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;m&#34;&gt;0&lt;/span&gt; * * * * /home/projects/bike_data/fetch_bike_data.sh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;Each request is stored to an XML-file on disk which has the following format:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;xml&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;&amp;lt;stations&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;lastUpdate=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;1784031061002&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;version=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;2.0&amp;#34;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;lt;station&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;lt;id&amp;gt;&lt;/span&gt;1&lt;span class=&#34;nt&#34;&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;River Street , Clerkenwell&lt;span class=&#34;nt&#34;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;lt;terminalName&amp;gt;&lt;/span&gt;001023&lt;span class=&#34;nt&#34;&gt;&amp;lt;/terminalName&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;lt;lat&amp;gt;&lt;/span&gt;51.52916347&lt;span class=&#34;nt&#34;&gt;&amp;lt;/lat&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;lt;long&amp;gt;&lt;/span&gt;-0.109970527&lt;span class=&#34;nt&#34;&gt;&amp;lt;/long&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;lt;installed&amp;gt;&lt;/span&gt;true&lt;span class=&#34;nt&#34;&gt;&amp;lt;/installed&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;lt;locked&amp;gt;&lt;/span&gt;false&lt;span class=&#34;nt&#34;&gt;&amp;lt;/locked&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;lt;installDate&amp;gt;&lt;/span&gt;1278947280000&lt;span class=&#34;nt&#34;&gt;&amp;lt;/installDate&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;lt;removalDate/&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;lt;temporary&amp;gt;&lt;/span&gt;false&lt;span class=&#34;nt&#34;&gt;&amp;lt;/temporary&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;lt;nbBikes&amp;gt;&lt;/span&gt;0&lt;span class=&#34;nt&#34;&gt;&amp;lt;/nbBikes&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;lt;nbStandardBikes&amp;gt;&lt;/span&gt;0&lt;span class=&#34;nt&#34;&gt;&amp;lt;/nbStandardBikes&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;lt;nbEBikes&amp;gt;&lt;/span&gt;0&lt;span class=&#34;nt&#34;&gt;&amp;lt;/nbEBikes&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;lt;nbEmptyDocks&amp;gt;&lt;/span&gt;19&lt;span class=&#34;nt&#34;&gt;&amp;lt;/nbEmptyDocks&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;lt;nbDocks&amp;gt;&lt;/span&gt;19&lt;span class=&#34;nt&#34;&gt;&amp;lt;/nbDocks&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;lt;/station&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;lt;station&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;lt;id&amp;gt;&lt;/span&gt;2&lt;span class=&#34;nt&#34;&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;Phillimore Gardens, Kensington&lt;span class=&#34;nt&#34;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;lt;terminalName&amp;gt;&lt;/span&gt;001018&lt;span class=&#34;nt&#34;&gt;&amp;lt;/terminalName&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;lt;lat&amp;gt;&lt;/span&gt;51.49960695&lt;span class=&#34;nt&#34;&gt;&amp;lt;/lat&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;lt;long&amp;gt;&lt;/span&gt;-0.197574246&lt;span class=&#34;nt&#34;&gt;&amp;lt;/long&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;lt;installed&amp;gt;&lt;/span&gt;true&lt;span class=&#34;nt&#34;&gt;&amp;lt;/installed&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;lt;locked&amp;gt;&lt;/span&gt;false&lt;span class=&#34;nt&#34;&gt;&amp;lt;/locked&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;lt;installDate&amp;gt;&lt;/span&gt;1278585780000&lt;span class=&#34;nt&#34;&gt;&amp;lt;/installDate&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;lt;removalDate/&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;lt;temporary&amp;gt;&lt;/span&gt;false&lt;span class=&#34;nt&#34;&gt;&amp;lt;/temporary&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;lt;nbBikes&amp;gt;&lt;/span&gt;6&lt;span class=&#34;nt&#34;&gt;&amp;lt;/nbBikes&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;lt;nbStandardBikes&amp;gt;&lt;/span&gt;6&lt;span class=&#34;nt&#34;&gt;&amp;lt;/nbStandardBikes&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;lt;nbEBikes&amp;gt;&lt;/span&gt;0&lt;span class=&#34;nt&#34;&gt;&amp;lt;/nbEBikes&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;lt;nbEmptyDocks&amp;gt;&lt;/span&gt;28&lt;span class=&#34;nt&#34;&gt;&amp;lt;/nbEmptyDocks&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;lt;nbDocks&amp;gt;&lt;/span&gt;37&lt;span class=&#34;nt&#34;&gt;&amp;lt;/nbDocks&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;lt;/station&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;[...]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;As you can see the raw data contains per-station information about its name, location, and bike availability.&lt;/p&gt;&#xA;&lt;h2 id=&#34;convert-to-duckdb-table&#34; class=&#34;content-heading&#34;&gt;Convert to DuckDB table&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;I load and combine all the XML-files into a DuckDB database &lt;code&gt;london_bike_data&lt;/code&gt; so that I can use SQL to analyse it. Before I do that I install the extensions &lt;code&gt;spatial&lt;/code&gt; and &lt;code&gt;h3&lt;/code&gt; for the geospatial features and &lt;code&gt;webbed&lt;/code&gt; to read xml files.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;duckdb&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;IPython.display&lt;/span&gt; &lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Image&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Connect to DuckDB&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;conn&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;duckdb&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;connect&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;london_bike_data.duckdb&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;conn&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;execute&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;INSTALL spatial;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;conn&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;execute&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;INSTALL h3 FROM community;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;conn&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;execute&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;INSTALL webbed FROM community;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;conn&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;execute&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;LOAD spatial;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;conn&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;execute&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;LOAD h3;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;conn&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;execute&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;LOAD webbed;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;The next query loads all collected xml-files into one DuckDB table and enforces the schema, adds metadata, and handles missing data issues. Note the Hetzner instance was based in Helsinki which I account for when converting the filename into a timestamp with the correct time zone.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;conn&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;execute&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&amp;#34;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;    CREATE OR REPLACE TABLE londonbikestations AS&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;     SELECT&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;            strptime(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;            regexp_extract(filename, &amp;#39;santander&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;\\&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;.(&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;\\&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{4}&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;\\&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;\\&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{2}&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;\\&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;\\&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{2}&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;\\&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;\\&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{2}&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;\\&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;\\&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{2}&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;\\&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;\\&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{2}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;\\&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;.xml&amp;#39;, 1),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;            &amp;#39;%Y.%m.&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;%d&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;.%H.%M.%S&amp;#39;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;        ) AT TIME ZONE &amp;#39;Europe/Helsinki&amp;#39; AT TIME ZONE &amp;#39;Europe/London&amp;#39; AS downloaded_at,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                 id::INTEGER AS id,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                 name,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                 terminalName AS terminal_name,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                 lat::DOUBLE AS lat,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                 long::DOUBLE AS lng,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                 installed::BOOLEAN AS installed,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                 locked::BOOLEAN AS locked,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                 temporary::BOOLEAN AS temporary,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                 nbBikes::INTEGER AS nb_bikes,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                 nbStandardBikes::INTEGER AS nb_standard_bikes,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                 nbEBikes::INTEGER AS nb_ebikes,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                 nbEmptyDocks::INTEGER AS nb_empty_docks,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                 nbDocks::INTEGER AS nb_docks,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                epoch_ms(installDate::BIGINT) AS install_date,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                CASE WHEN removalDate = &amp;#39;&amp;#39; THEN NULL ELSE epoch_ms(removalDate::BIGINT) END AS removal_date&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;             FROM read_xml(&amp;#39;data/santander.*.xml&amp;#39;, all_varchar=true, filename=true)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;conn&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sql&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;select count(*) from londonbikestations&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;pre&gt;&lt;code&gt;┌──────────────┐&#xA;│ count_star() │&#xA;│    int64     │&#xA;├──────────────┤&#xA;│       707197 │&#xA;└──────────────┘&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The query below shows the station data that is available. The table contains the station &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt; and coordinates (&lt;code&gt;lat&lt;/code&gt;, &lt;code&gt;lng&lt;/code&gt;) and when it was installed and/or removed. For every station I have the capacity &lt;code&gt;nb_docks&lt;/code&gt; and the current number of rentable bikes &lt;code&gt;nb_bikes&lt;/code&gt; (split into standard and ebikes).&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;conn&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sql&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;describe table londonbikestations&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;pre&gt;&lt;code&gt;┌───────────────────┬─────────────┬─────────┬─────────┬─────────┬─────────┐&#xA;│    column_name    │ column_type │  null   │   key   │ default │  extra  │&#xA;│      varchar      │   varchar   │ varchar │ varchar │ varchar │ varchar │&#xA;├───────────────────┼─────────────┼─────────┼─────────┼─────────┼─────────┤&#xA;│ downloaded_at     │ TIMESTAMP   │ YES     │ NULL    │ NULL    │ NULL    │&#xA;│ id                │ INTEGER     │ YES     │ NULL    │ NULL    │ NULL    │&#xA;│ name              │ VARCHAR     │ YES     │ NULL    │ NULL    │ NULL    │&#xA;│ terminal_name     │ VARCHAR     │ YES     │ NULL    │ NULL    │ NULL    │&#xA;│ lat               │ DOUBLE      │ YES     │ NULL    │ NULL    │ NULL    │&#xA;│ lng               │ DOUBLE      │ YES     │ NULL    │ NULL    │ NULL    │&#xA;│ installed         │ BOOLEAN     │ YES     │ NULL    │ NULL    │ NULL    │&#xA;│ locked            │ BOOLEAN     │ YES     │ NULL    │ NULL    │ NULL    │&#xA;│ temporary         │ BOOLEAN     │ YES     │ NULL    │ NULL    │ NULL    │&#xA;│ nb_bikes          │ INTEGER     │ YES     │ NULL    │ NULL    │ NULL    │&#xA;│ nb_standard_bikes │ INTEGER     │ YES     │ NULL    │ NULL    │ NULL    │&#xA;│ nb_ebikes         │ INTEGER     │ YES     │ NULL    │ NULL    │ NULL    │&#xA;│ nb_empty_docks    │ INTEGER     │ YES     │ NULL    │ NULL    │ NULL    │&#xA;│ nb_docks          │ INTEGER     │ YES     │ NULL    │ NULL    │ NULL    │&#xA;│ install_date      │ TIMESTAMP   │ YES     │ NULL    │ NULL    │ NULL    │&#xA;│ removal_date      │ TIMESTAMP   │ YES     │ NULL    │ NULL    │ NULL    │&#xA;└───────────────────┴─────────────┴─────────┴─────────┴─────────┴─────────┘&#xA;  16 rows                                                       6 columns&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The &lt;code&gt;removal_date&lt;/code&gt; column seems not trustworthy since there is definitely activity (changes in &lt;code&gt;nb_bikes&lt;/code&gt;) happening at stations that have a removal date, e.g.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;conn&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sql&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;select downloaded_at, name, terminal_name, nb_bikes, removal_date from londonbikestations where terminal_name = &amp;#39;000968&amp;#39; order by downloaded_at&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;pre&gt;&lt;code&gt;┌─────────────────────┬──────────────────────────┬───────────────┬──────────┬─────────────────────┐&#xA;│    downloaded_at    │           name           │ terminal_name │ nb_bikes │    removal_date     │&#xA;│      timestamp      │         varchar          │    varchar    │  int32   │      timestamp      │&#xA;├─────────────────────┼──────────────────────────┼───────────────┼──────────┼─────────────────────┤&#xA;│ 2026-05-08 22:00:01 │ Warwick Row, Westminster │ 000968        │        6 │ 2018-10-18 06:56:00 │&#xA;│ 2026-05-08 23:00:01 │ Warwick Row, Westminster │ 000968        │        6 │ 2018-10-18 06:56:00 │&#xA;│ 2026-05-09 00:00:01 │ Warwick Row, Westminster │ 000968        │        6 │ 2018-10-18 06:56:00 │&#xA;│ 2026-05-09 01:00:01 │ Warwick Row, Westminster │ 000968        │        6 │ 2018-10-18 06:56:00 │&#xA;│ 2026-05-09 02:00:01 │ Warwick Row, Westminster │ 000968        │        6 │ 2018-10-18 06:56:00 │&#xA;│ 2026-05-09 03:00:01 │ Warwick Row, Westminster │ 000968        │        6 │ 2018-10-18 06:56:00 │&#xA;│ 2026-05-09 04:00:01 │ Warwick Row, Westminster │ 000968        │        6 │ 2018-10-18 06:56:00 │&#xA;│ 2026-05-09 05:00:01 │ Warwick Row, Westminster │ 000968        │        6 │ 2018-10-18 06:56:00 │&#xA;│ 2026-05-09 06:00:01 │ Warwick Row, Westminster │ 000968        │        6 │ 2018-10-18 06:56:00 │&#xA;│ 2026-05-09 07:00:01 │ Warwick Row, Westminster │ 000968        │        6 │ 2018-10-18 06:56:00 │&#xA;│          ·          │            ·             │   ·           │        · │          ·          │&#xA;│          ·          │            ·             │   ·           │        · │          ·          │&#xA;│          ·          │            ·             │   ·           │        · │          ·          │&#xA;│ 2026-06-14 12:00:01 │ Warwick Row, Westminster │ 000968        │        3 │ 2018-10-18 06:56:00 │&#xA;│ 2026-06-14 13:00:01 │ Warwick Row, Westminster │ 000968        │        5 │ 2018-10-18 06:56:00 │&#xA;│ 2026-06-14 14:00:01 │ Warwick Row, Westminster │ 000968        │        9 │ 2018-10-18 06:56:00 │&#xA;│ 2026-06-14 15:00:01 │ Warwick Row, Westminster │ 000968        │        4 │ 2018-10-18 06:56:00 │&#xA;│ 2026-06-14 16:00:01 │ Warwick Row, Westminster │ 000968        │        2 │ 2018-10-18 06:56:00 │&#xA;│ 2026-06-14 17:00:01 │ Warwick Row, Westminster │ 000968        │        7 │ 2018-10-18 06:56:00 │&#xA;│ 2026-06-14 18:00:01 │ Warwick Row, Westminster │ 000968        │        9 │ 2018-10-18 06:56:00 │&#xA;│ 2026-06-14 19:00:01 │ Warwick Row, Westminster │ 000968        │       10 │ 2018-10-18 06:56:00 │&#xA;│ 2026-06-14 20:00:01 │ Warwick Row, Westminster │ 000968        │       11 │ 2018-10-18 06:56:00 │&#xA;│ 2026-06-14 21:00:01 │ Warwick Row, Westminster │ 000968        │        8 │ 2018-10-18 06:56:00 │&#xA;└─────────────────────┴──────────────────────────┴───────────────┴──────────┴─────────────────────┘&#xA;  887 rows (20 shown)                                                                   5 columns&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;I have confirmed that this station still exists (&lt;a href=&#34;https://santandercycles.tfl.gov.uk/map&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;here&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;) so I will just ignore this column.&lt;/p&gt;&#xA;&lt;p&gt;I want to get an overview of the total capacity across all stations. The exact number fluctuates slightly between snapshots but the capacity seems to be around 20.9k. The maximum number of &lt;em&gt;docked&lt;/em&gt; bikes was 7708 standard bikes and 1131 eBikes. The &lt;a href=&#34;https://www.santander.co.uk/about-santander/media-centre/press-releases/santander-renews-sponsorship-of-londons-cycle-hire&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;official numbers&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt; given by TfL are 10,000 standard bikes and 2,000 eBikes.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;conn&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sql&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;select downloaded_at, sum(nb_standard_bikes) as sum_docked_standard_bikes, sum(nb_ebikes) as sum_docked_ebikes, sum(nb_docks) as sum_station_capacity from londonbikestations group by downloaded_at order by sum_docked_standard_bikes&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;pre&gt;&lt;code&gt;┌─────────────────────┬───────────────────────────┬───────────────────┬──────────────────────┐&#xA;│    downloaded_at    │ sum_docked_standard_bikes │ sum_docked_ebikes │ sum_station_capacity │&#xA;│      timestamp      │          int128           │      int128       │        int128        │&#xA;├─────────────────────┼───────────────────────────┼───────────────────┼──────────────────────┤&#xA;│ 2026-06-04 08:00:01 │                      6008 │               795 │                20963 │&#xA;│ 2026-06-01 08:00:01 │                      6047 │               585 │                20934 │&#xA;│ 2026-06-03 08:00:01 │                      6056 │               948 │                20951 │&#xA;│ 2026-06-02 08:00:01 │                      6082 │               927 │                20921 │&#xA;│ 2026-06-04 17:00:01 │                      6122 │              1185 │                20963 │&#xA;│ 2026-06-09 08:00:02 │                      6127 │               830 │                20962 │&#xA;│ 2026-05-28 08:00:01 │                      6191 │               692 │                20950 │&#xA;│ 2026-06-01 17:00:01 │                      6201 │              1086 │                20892 │&#xA;│ 2026-05-27 17:00:01 │                      6203 │               829 │                20950 │&#xA;│ 2026-06-04 10:00:01 │                      6210 │               983 │                20963 │&#xA;│          ·          │                        ·  │                ·  │                  ·   │&#xA;│          ·          │                        ·  │                ·  │                  ·   │&#xA;│          ·          │                        ·  │                ·  │                  ·   │&#xA;│ 2026-05-16 06:00:01 │                      7668 │              1081 │                20969 │&#xA;│ 2026-05-15 01:00:01 │                      7671 │              1182 │                20949 │&#xA;│ 2026-05-15 03:00:01 │                      7674 │              1169 │                20949 │&#xA;│ 2026-05-16 01:00:01 │                      7678 │              1172 │                20942 │&#xA;│ 2026-05-15 02:00:01 │                      7678 │              1178 │                20949 │&#xA;│ 2026-05-16 00:00:01 │                      7679 │              1186 │                20942 │&#xA;│ 2026-05-16 02:00:01 │                      7699 │              1157 │                20942 │&#xA;│ 2026-05-16 05:00:01 │                      7703 │              1106 │                20969 │&#xA;│ 2026-05-16 03:00:01 │                      7705 │              1143 │                20942 │&#xA;│ 2026-05-16 04:00:01 │                      7708 │              1131 │                20942 │&#xA;└─────────────────────┴───────────────────────────┴───────────────────┴──────────────────────┘&#xA;  887 rows (20 shown)                                                              4 columns&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The largest station is Jubilee Plaza in Canary Wharf.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;conn&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sql&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;select name, nb_bikes, nb_docks from londonbikestations order by nb_docks desc limit 1&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;pre&gt;&lt;code&gt;┌─────────────────────────────┬──────────┬──────────┐&#xA;│            name             │ nb_bikes │ nb_docks │&#xA;│           varchar           │  int32   │  int32   │&#xA;├─────────────────────────────┼──────────┼──────────┤&#xA;│ Jubilee Plaza, Canary Wharf │       42 │       63 │&#xA;└─────────────────────────────┴──────────┴──────────┘&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;h2 id=&#34;busiest-stations&#34; class=&#34;content-heading&#34;&gt;Busiest stations&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;To approximate the busiest stations I sum the absolute hour-to-hour change in &lt;code&gt;nb_bikes&lt;/code&gt; per station. This approximates total churn (bikes leaving and arriving) since simultaneous arrivals and departures within the same hour cancel out.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;conn&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;execute&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&amp;#34;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;    CREATE OR REPLACE TABLE busy_stations AS   &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;    WITH hourly_changes AS (&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;        SELECT&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;            terminal_name,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;            ST_POINT(lng, lat) AS geometry,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;            name,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;            downloaded_at,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;            nb_bikes,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;            abs(nb_bikes - lag(nb_bikes) OVER w) AS abs_change&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;        FROM londonbikestations&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;        WINDOW w AS (PARTITION BY terminal_name ORDER BY downloaded_at)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;    )&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;    SELECT&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;        terminal_name,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;        geometry,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;        any_value(name) AS name,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;        sum(abs_change) AS total_churn,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;        count(*) AS nb_snapshots&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;    FROM hourly_changes&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;    GROUP BY terminal_name, geometry&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;    ORDER BY total_churn DESC&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;conn&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sql&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&amp;#34;select name, total_churn from busy_stations limit 20&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;pre&gt;&lt;code&gt;┌──────────────────────────────────────────┬─────────────┐&#xA;│                   name                   │ total_churn │&#xA;│                 varchar                  │   int128    │&#xA;├──────────────────────────────────────────┼─────────────┤&#xA;│ Hop Exchange, The Borough                │        3218 │&#xA;│ Soho Square , Soho                       │        3160 │&#xA;│ Argyle Street, Kings Cross               │        2930 │&#xA;│ Waterloo Station 2, Waterloo             │        2753 │&#xA;│ Hyde Park Corner, Hyde Park              │        2605 │&#xA;│ Waterloo Station 1, Waterloo             │        2554 │&#xA;│ Moorfields, Moorgate                     │        2527 │&#xA;│ Brushfield Street, Liverpool Street      │        2514 │&#xA;│ Waterloo Station 3, Waterloo             │        2389 │&#xA;│ St. James&#39;s Square, St. James&#39;s          │        2360 │&#xA;│ Jubilee Plaza, Canary Wharf              │        2289 │&#xA;│ Eagle Wharf Road, Hoxton                 │        2263 │&#xA;│ Cheapside, Bank                          │        2202 │&#xA;│ Imperial College, Knightsbridge          │        2181 │&#xA;│ Worship Street, Shoreditch               │        2167 │&#xA;│ Natural History Museum, South Kensington │        2142 │&#xA;│ Whitehall Place, Strand                  │        2042 │&#xA;│ Queen&#39;s Gate (North), Kensington         │        2019 │&#xA;│ Malet Street, Bloomsbury                 │        2013 │&#xA;│ Exhibition Road, Knightsbridge           │        1987 │&#xA;└──────────────────────────────────────────┴─────────────┘&#xA;  20 rows                                      2 columns&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;None of these stations are surprising. As we can see later on the map all of these stations are in the city centre or close to commuter train stations.&lt;/p&gt;&#xA;&lt;h2 id=&#34;busiest-hours-of-the-day&#34; class=&#34;content-heading&#34;&gt;Busiest hours of the day&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Using the same total-churn metric I group the data by hour of day to see when bike activity peaks.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;conn&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sql&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&amp;#34;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;    WITH hourly_changes AS (&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;        SELECT&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;            terminal_name,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;            downloaded_at,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;            nb_bikes,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;            abs(nb_bikes - lag(nb_bikes) OVER w) AS abs_change&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;        FROM londonbikestations&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;        WINDOW w AS (PARTITION BY terminal_name ORDER BY downloaded_at)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;    )&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;    SELECT&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;        hour(downloaded_at) AS hour_of_day,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;        sum(abs_change) AS total_churn,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;        count(*) AS nb_snapshots&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;    FROM hourly_changes&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;    GROUP BY hour_of_day&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;    ORDER BY total_churn&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;pre&gt;&lt;code&gt;┌─────────────┬─────────────┬──────────────┐&#xA;│ hour_of_day │ total_churn │ nb_snapshots │&#xA;│    int64    │   int128    │    int64     │&#xA;├─────────────┼─────────────┼──────────────┤&#xA;│           3 │        3911 │        29501 │&#xA;│           4 │        4111 │        29501 │&#xA;│           2 │        5398 │        29501 │&#xA;│           5 │        7441 │        29502 │&#xA;│           1 │        8518 │        29501 │&#xA;│           0 │       13462 │        29501 │&#xA;│           6 │       20333 │        29502 │&#xA;│          23 │       21172 │        29500 │&#xA;│          22 │       29554 │        29500 │&#xA;│          21 │       33808 │        28702 │&#xA;│           · │         ·   │          ·   │&#xA;│           · │         ·   │          ·   │&#xA;│           · │         ·   │          ·   │&#xA;│          13 │       40336 │        29497 │&#xA;│          20 │       40433 │        29499 │&#xA;│          10 │       41575 │        29495 │&#xA;│           7 │       45237 │        29503 │&#xA;│          19 │       47598 │        28707 │&#xA;│          16 │       54813 │        29498 │&#xA;│           9 │       57626 │        29498 │&#xA;│          17 │       64213 │        29498 │&#xA;│          18 │       64873 │        30300 │&#xA;│           8 │       78166 │        29503 │&#xA;└─────────────┴─────────────┴──────────────┘&#xA;  24 rows (20 shown)             3 columns&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;We see the highest activity from 7:00-8:00, 8:00-9:00, 16:00-17:00, 17:00-18:00 which are peak commuter hours and the lowest activity in the early morning.&lt;/p&gt;&#xA;&lt;h2 id=&#34;storing-geometric-data-as-parquet-files&#34; class=&#34;content-heading&#34;&gt;Storing geometric data as parquet files&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Before I can use QGIS for map-based visualisations I need to store the relevant data in a compatible format.&lt;/p&gt;&#xA;&lt;p&gt;I want to show the number of bikes available across the city at different times of the day (I picked Wednesday 27.5.2026). To aggregate the number of available bikes spatially, I group by &lt;a href=&#34;https://www.uber.com/gb/en/blog/h3/&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;H3 cells&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;. If the coordinates of the bike station fall into the cell I sum all available bikes.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;conn&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;execute&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&amp;#34;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;    CREATE OR REPLACE TABLE bike_station_geo AS&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;        SELECT *, ST_POINT(lng, lat) AS geometry FROM londonbikestations where downloaded_at::date = &amp;#39;2026-05-27&amp;#39;   &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;conn&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;execute&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&amp;#34;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;    CREATE OR REPLACE TABLE h3_cell_covering AS&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;        SELECT&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;            downloaded_at,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;            H3_LATLNG_TO_CELL(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                ST_Y(geometry),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                ST_X(geometry),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;                7&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;            ) AS hexagon,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;            sum(nb_bikes) AS nb_bikes&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;        FROM bike_station_geo&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;        GROUP BY 1, 2&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;I then store the h3 bike counts table as a parquet file. This involves conversion of the hexagon data into WKB(well-known-binary) that GeoParquet uses to store geometries. Note: By default the geometry in DuckDB is assumed to be &lt;code&gt;EPSG:4326&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;conn&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;execute&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&amp;#34;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;COPY (&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;      SELECT geometry: ST_ASWKB(H3_CELL_TO_BOUNDARY_WKT(hexagon)::geometry),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;              date_trunc(&amp;#39;hour&amp;#39;, downloaded_at) AS downloaded_at,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;             nb_bikes&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;      FROM   h3_cell_covering&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;  ) TO &amp;#39;available_bikes_per_h3_7_cell.parquet&amp;#39;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;  &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;I also want to visualise the location of the busy stations on the map, so I store the busy stations as a separate file.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;conn&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;execute&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&amp;#34;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;COPY (&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;      SELECT *  FROM busy_stations&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;  ) TO &amp;#39;busy_stations.parquet&amp;#39;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;  &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;h2 id=&#34;visualising-availability-using-qgis&#34; class=&#34;content-heading&#34;&gt;Visualising availability using QGIS&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/QGIS&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;QGIS&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;                style=&#34;height: 0.7em; width: 0.7em; margin-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;                class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;                viewBox=&#34;0 0 640 512&#34;&gt;&#xA;                &lt;path fill=&#34;currentColor&#34;&#xA;                    d=&#34;M640 51.2l-.3 12.2c-28.1 .8-45 15.8-55.8 40.3-25 57.8-103.3 240-155.3 358.6H415l-81.9-193.1c-32.5 63.6-68.3 130-99.2 193.1-.3 .3-15 0-15-.3C172 352.3 122.8 243.4 75.8 133.4 64.4 106.7 26.4 63.4 .2 63.7c0-3.1-.3-10-.3-14.2h161.9v13.9c-19.2 1.1-52.8 13.3-43.3 34.2 21.9 49.7 103.6 240.3 125.6 288.6 15-29.7 57.8-109.2 75.3-142.8-13.9-28.3-58.6-133.9-72.8-160-9.7-17.8-36.1-19.4-55.8-19.7V49.8l142.5 .3v13.1c-19.4 .6-38.1 7.8-29.4 26.1 18.9 40 30.6 68.1 48.1 104.7 5.6-10.8 34.7-69.4 48.1-100.8 8.9-20.6-3.9-28.6-38.6-29.4 .3-3.6 0-10.3 .3-13.6 44.4-.3 111.1-.3 123.1-.6v13.6c-22.5 .8-45.8 12.8-58.1 31.7l-59.2 122.8c6.4 16.1 63.3 142.8 69.2 156.7L559.2 91.8c-8.6-23.1-36.4-28.1-47.2-28.3V49.6l127.8 1.1 .2 .5z&#34;&gt;&#xA;                &lt;/path&gt;&#xA;            &lt;/svg&gt;&#xA;        &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt; (3.44.10) is an open-source geographic information system. It allows combining, viewing, and analysing tabular data with geographic and mapping data.&lt;/p&gt;&#xA;&lt;p&gt;I want to display our parquet data over an actual map of London. To get this map, I install the plugin &lt;code&gt;NextGIS MapServices&lt;/code&gt; and then restart QGIS to make it available. I use OpenStreetMap as the base map. In the toolbar click on &lt;code&gt;QuickMapService&lt;/code&gt;(Globe icon) and add the OpenstreetMap OSM Standard. Next, in &lt;code&gt;Project Properties&lt;/code&gt; (Cmd+Shift+P)-&amp;gt; &lt;code&gt;CRS&lt;/code&gt; change the Projection or Coordinate Reference System to &lt;code&gt;EPSG:3857 (Pseudo-Mercator)&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;h3 id=&#34;visualising-busy-stations&#34; class=&#34;content-heading&#34;&gt;Visualising busy stations&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;Drag and drop the &lt;code&gt;busy_stations.parquet&lt;/code&gt; file into the Layers field (bottom left). Ensure under &lt;code&gt;Layer Properties&lt;/code&gt; &amp;ndash;&amp;gt; &lt;code&gt;Assigned Coordinate Reference System&lt;/code&gt; that &lt;code&gt;EPSG:4326 - WGS 84&lt;/code&gt; is selected. This allows QGIS to correctly reproject the geography data from the file onto the map.&lt;/p&gt;&#xA;&lt;p&gt;Under &lt;code&gt;Layer Properties&lt;/code&gt; &amp;ndash;&amp;gt; &lt;code&gt;Symbology&lt;/code&gt; I can change the style of the location marker to show all the stations. You can see all the coordinates of stations in the dataset below.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;Image&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;filename&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;map_images/station_locations.jpg&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;&#xA;&#xA;&lt;img src=&#34;https://staticnotes.org/posts/london-bike-data/output_29_0.jpg&#34; alt=&#34;jpeg&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;To only show the top 20 busiest stations as calculated in the previous section I add a filter on the busy stations layer. (Right click on layer &amp;ndash;&amp;gt; &lt;code&gt;Filter&lt;/code&gt;, then add &lt;code&gt;total_churn &amp;gt; 1900&lt;/code&gt;). I also want to show the station names, so I go to &lt;code&gt;Layer Properties&lt;/code&gt; &amp;ndash;&amp;gt; &lt;code&gt;Label&lt;/code&gt; &amp;ndash;&amp;gt; &lt;code&gt;Single labels&lt;/code&gt; and select for &lt;code&gt;value&lt;/code&gt; the Expression Builder and then use the expression: &lt;code&gt;&amp;quot;name&amp;quot; || &#39; (&#39; || to_string(&amp;quot;total_churn&amp;quot;) || &#39;)&#39;&lt;/code&gt; to concatenate the churn value to the station name in brackets.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;Image&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;filename&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;map_images/busy_stations.jpg&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;&#xA;&#xA;&lt;img src=&#34;https://staticnotes.org/posts/london-bike-data/output_31_0.jpg&#34; alt=&#34;jpeg&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;Unsurprisingly the busiest stations are in the centre where many Londoners work (Bank, Canary Wharf, Soho, Westminster) or close to major train stations (Liverpool Street, King&amp;rsquo;s Cross, Waterloo, Hop Exchange).&lt;/p&gt;&#xA;&lt;h3 id=&#34;rental-bike-availability-during-commute-hours&#34; class=&#34;content-heading&#34;&gt;Rental bike availability during commute hours&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;I also want to visualise how the rental bikes move before and after morning rush hour. My expectation is that fewer bikes are initially in the city centre (zone 1) and more bikes in the outer residential areas (zones 2 and 3). Then around 11am we should see a concentration of bikes in zone 1.&lt;/p&gt;&#xA;&lt;p&gt;I drag and drop the relevant data file &lt;code&gt;available_bikes_per_h3_7_cell.parquet&lt;/code&gt; to create a new Layer. Again, I first ensure that the projection for this dataset is set to &lt;code&gt;EPSG:4326 - WGS 84&lt;/code&gt;. The hexagons should now appear on top of the map. Remember that the file has availability data for every hour of the day, so I add a Layer filter: &lt;code&gt;&amp;quot;downloaded_at&amp;quot; = &#39;2026-05-27T06:00:00.000&#39;&lt;/code&gt; to only use data from one point in time before the start of the rush hour.&lt;/p&gt;&#xA;&lt;p&gt;To style the hex fields according to the number of available bikes in the hexagon area I make the following changes in the Layer properties:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;Symbology&lt;/code&gt; &amp;ndash;&amp;gt; &lt;code&gt;Graduated&lt;/code&gt; &amp;ndash;&amp;gt; Value: &lt;code&gt;nb_bikes&lt;/code&gt;, Color Ramp: &lt;code&gt;RdYlGr&lt;/code&gt;, Mode: &lt;code&gt;Equal Interval&lt;/code&gt;, Opacity: &lt;code&gt;60%&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;Labels&lt;/code&gt; &amp;ndash;&amp;gt; &lt;code&gt;Single Labels&lt;/code&gt; &amp;ndash;&amp;gt; Value: &lt;code&gt;nb_bikes&lt;/code&gt;, 18Pt, Bold.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;I have filtered the layer once for 6am and once for 11am and here are the two outputs:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;display&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Image&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;filename&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;map_images/bike_availibility_6am.jpg&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;&#xA;&#xA;&lt;img src=&#34;https://staticnotes.org/posts/london-bike-data/output_34_0.jpg&#34; alt=&#34;jpeg&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;display&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Image&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;filename&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;map_images/bike_availibility_11am.jpg&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;&#xA;&#xA;&lt;img src=&#34;https://staticnotes.org/posts/london-bike-data/output_35_0.jpg&#34; alt=&#34;jpeg&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;As expected you can see that a lot of the residential districts along the water, West Kensington, Fulham, Wandsworth, Battersea, Elephant and Castle and in the east around Limehouse, Mile End, Bethnal Green see an outflow of available bikes. The central districts around the City of London, Mayfair, King&amp;rsquo;s Cross and Westminster accumulate a lot of the bikes by 11am.&lt;/p&gt;&#xA;&lt;h2 id=&#34;conclusion&#34; class=&#34;content-heading&#34;&gt;Conclusion&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;It turns out to be pretty easy to collect, explore and visualise the London bike station data. DuckDB makes it really easy to ingest hundreds of XML files and the H3 and spatial extensions allow easy geographical aggregation. This is my first time using QGIS. It took 20min to get up to speed with it, but it&amp;rsquo;s really cool how it fuses mapping data and geographical and analytical data. I will try to use it in other projects as well.&lt;/p&gt;&#xA;&lt;h2 id=&#34;references&#34; class=&#34;content-heading&#34;&gt;References&#xA;&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;I was inspired by &lt;a href=&#34;https://tech.marksblogg.com/ebike-fleet-monitoring.html&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;Mark&#xA;Litwintschik&amp;rsquo;s ebike fleet monitoring&#xA;blog post&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;A useful QGIS Tutorial is available &lt;a href=&#34;https://www.youtube.com/watch?v=SovdBaus7pM&amp;amp;t=4127s&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;here&#xA;    &#xA;&#xA;        &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.9em; margin-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 448 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M549.7 124.1c-6.3-23.7-24.8-42.3-48.3-48.6C458.8 64 288 64 288 64S117.2 64 74.6 75.5c-23.5 6.3-42 24.9-48.3 48.6-11.4 42.9-11.4 132.3-11.4 132.3s0 89.4 11.4 132.3c6.3 23.7 24.8 41.5 48.3 47.8C117.2 448 288 448 288 448s170.8 0 213.4-11.5c23.5-6.3 42-24.2 48.3-47.8 11.4-42.9 11.4-132.3 11.4-132.3s0-89.4-11.4-132.3zm-317.5 213.5V175.2l142.7 81.2-142.7 81.2z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;&#xA;    &#xA;    &#xA;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;jupyter-notebook&#34; class=&#34;content-heading&#34;&gt;Jupyter Notebook&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;You can find the Jupyter notebook and the datasets for this post &lt;a href=&#34;https://gitlab.com/frankRi89/blog/-/tree/main/notebooks/london-bike-data&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;here&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;.&lt;/p&gt;&#xA;</description>
    </item>
    
    <item>
      <title>TIL how to profile memory in Python</title>
      <link>https://staticnotes.org/til/2026/4/memory-profiling-in-python/</link>
      <pubDate>Tue, 21 Apr 2026 00:00:00 +0100</pubDate>
      
      <guid>https://staticnotes.org/til/2026/4/memory-profiling-in-python/</guid>
    <description>&lt;p&gt;In my job I often work on pipelines that process a large amount of data in batches. Sometimes the batches are defined outside the task and sometimes the task itself processes its data in batches.&lt;/p&gt;&#xA;&lt;h3 id=&#34;problem&#34; class=&#34;content-heading&#34;&gt;Problem&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;A common question is how to choose the batch size that is used to break up the data to be processed into smaller manageable parts. In many cases increasing batch size means decreasing the time for the whole dataset to be processed&lt;span class=&#34;sidenote-number&#34;&gt;&lt;small class=&#34;sidenote&#34;&gt;This is of course not always the case, but often holds true in practice.&lt;/small&gt;&lt;/span&gt;. At the same time it increases the memory usage of the task at any point in time.&lt;/p&gt;&#xA;&lt;p&gt;When these tasks run in containers in Kubernetes they tend to have a fixed amount of memory available. Increasing the batch size too much means that the container will run out of memory and the task will fail.&lt;/p&gt;&#xA;&lt;p&gt;So the question that often comes up is &amp;ldquo;How do I choose the largest batch that will safely fit on the container?&amp;rdquo;&lt;/p&gt;&#xA;&lt;h3 id=&#34;solution&#34; class=&#34;content-heading&#34;&gt;Solution&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;Our data transformations are written in Python. I can use the &lt;code&gt;memory_profiler&lt;/code&gt; package to sample memory consumption while the task runs and stash the data in a file.&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Install the package (in this case in a &lt;code&gt;poetry&lt;/code&gt; project):&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;bash&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ poetry add --group dev memory-profiler&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;(Ideally:) Make your Python code locally runnable.&lt;/li&gt;&#xA;&lt;li&gt;Prepend your Python run with &lt;code&gt;mprof run&lt;/code&gt;:&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;bash&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ poetry run mprof run dataprocessing_task.py&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;This will record memory usage over time and stash it as a .dat file in the current directory.&lt;/li&gt;&#xA;&lt;li&gt;You can then plot the memory usage over time with&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;bash&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ poetry run mprof plot&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;which plots the last found file with &lt;code&gt;matplotlib&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;The &lt;code&gt;.dat&lt;/code&gt; files are pretty simple:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;txt&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;CMDLINE /Users/dir/.venv/bin/python dataprocessing_task.py&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;MEM 0.187500 1776791347.5081&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;MEM 40.484375 1776791347.6133&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;MEM 59.218750 1776791347.7185&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;MEM 89.812500 1776791347.8203&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;MEM 114.078125 1776791347.9246&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;MEM 121.906250 1776791348.0287&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;MEM 133.640625 1776791348.1339&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;MEM 140.921875 1776791348.2365&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;MEM 151.546875 1776791348.3406&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;MEM 156.515625 1776791348.4457&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;MEM 167.468750 1776791348.5508&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;MEM 178.625000 1776791348.6560&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;MEM 194.421875 1776791348.7611&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;MEM 211.218750 1776791348.8663&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;MEM 225.031250 1776791348.9714&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;MEM 236.187500 1776791349.0765&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;MEM 252.156250 1776791349.1817&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;where the first column is the memory usage in MiB and the second column is a unix timestamp.&lt;/p&gt;&#xA;&lt;p&gt;In my use case I wanted to compare memory consumption of two different batch sizes (30k and 300k). So I profiled the task twice and stored both &lt;code&gt;.dat&lt;/code&gt; files.&lt;/p&gt;&#xA;&lt;p&gt;I then use this claude-generated Python script to visualise and compare memory consumption of the batch size options:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;glob&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;sys&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;datetime&lt;/span&gt; &lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;datetime&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;pathlib&lt;/span&gt; &lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Path&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;matplotlib.pyplot&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;as&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;plt&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;load_mprof&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;str&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;tuple&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;list&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;datetime&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;],&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;list&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;float&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]]:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&amp;#34;Parse an mprof .dat file. Returns (timestamps, memory_in_MiB).&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;times&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;mems&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[],&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;with&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;open&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;as&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;line&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;n&#34;&gt;parts&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;line&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;split&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;not&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;parts&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;or&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;parts&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;MEM&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                &lt;span class=&#34;k&#34;&gt;continue&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;n&#34;&gt;mems&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;append&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;float&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;parts&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]))&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;n&#34;&gt;times&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;append&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;datetime&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;fromtimestamp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;float&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;parts&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;])))&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;times&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;mems&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;main&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;paths&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;list&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;str&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;])&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;None&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;not&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;paths&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;paths&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;sorted&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;glob&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;glob&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;mprofile_*.dat&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;not&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;paths&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;raise&lt;/span&gt; &lt;span class=&#34;ne&#34;&gt;SystemExit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;No .dat files found.&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;fig&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ax&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;plt&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;subplots&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;figsize&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;10&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;path&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;paths&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;times&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;mems&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;load_mprof&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;not&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;times&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;nb&#34;&gt;print&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sa&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;skip &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;path&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;: no MEM samples&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;k&#34;&gt;continue&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;c1&#34;&gt;# plot seconds since start of this run, so runs align on x-axis&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;t0&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;times&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;xs&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;t&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;t0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;total_seconds&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;t&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;times&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;ax&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;plot&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;xs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;mems&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;label&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;stem&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;ax&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;set_xlabel&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;Time since run start (s)&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;ax&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;set_ylabel&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;Memory (MiB)&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;ax&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;set_title&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;Memory usage over time&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;ax&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;legend&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;ax&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;grid&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;True&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;alpha&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mf&#34;&gt;0.3&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;plt&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;tight_layout&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;plt&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;show&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;vm&#34;&gt;__name__&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;main&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sys&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;argv&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:])&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;Here is the plot:&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&lt;figure&gt;&#xA;  &lt;div class=&#34;image-wrapper&#34;&gt;&#xA;  &lt;img src=&#34;https://staticnotes.org/til/2026/4/memory-profiling-in-python/memory_usage_example.jpg&#34; alt=&#34;Memory usage of Python task at different batch sizes&#34; loading=&#34;lazy&#34; /&gt;&#xA;  &lt;figcaption&gt;Figure 1. Memory usage of python task over time for different batch sizes.&lt;/figcaption&gt;&#xA;  &lt;/div&gt;&#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;In this case my container had 6GB of memory available for the Python task, so I could safely increase the batch size to 300k and reduce the runtime of the task by 5x.&lt;/p&gt;&#xA;</description>
    </item>
    
    <item>
      <title>UK house price changes in real terms</title>
      <link>https://staticnotes.org/posts/uk-house-prices/</link>
      <pubDate>Sun, 11 Jan 2026 00:00:00 +0000</pubDate>
      
      <guid>https://staticnotes.org/posts/uk-house-prices/</guid>
    <description>&lt;p&gt;This post is a response to a newspaper article I read this morning. Every month the FT is reporting on how the housing market in the UK is performing as people love to talk about it:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;UK house prices rise less than expected in 2025 as growth slows&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;UK house prices rose by 0.6 per cent in 2025 after a slowdown at the end of the year, according to lender Nationwide. [&amp;hellip;] Prices fell 0.4 per cent between November and December to an average of £271,068. Both figures were below analysts’ expectations of a 1.2 per cent annual rise and a 0.1 per cent month-on-month expansion.    &lt;em&gt;FT Weekend (02.01.2026)&lt;/em&gt;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;I am asking myself how useful this statistic is for the average reader, especially for a reader who wants to buy or sell a property.&lt;/p&gt;&#xA;&lt;p&gt;Two issues are immediately obvious:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;The average yearly house price change across all of the UK might not agree with the house price performance in the areas I live in or I want to move to. There are also differences by house type, e.g. flat vs. detached house vs. terraced house. Different types of property might even perform in opposite directions.&lt;/li&gt;&#xA;&lt;li&gt;British people often view property, at least partially, as an investment opportunity. However, in those cases it would help to at least report the real (inflation-adjusted) house price change, not just the nominal change. An increase of 0.6% in nominal house prices sounds different when one doesn&amp;rsquo;t mention that inflation in 2025 was around 3.6% (UK CPI). Meanwhile other forms of investments did quite well. The FTSE 100 nominally returned 21.4% (which to be fair was a positive outlier this year compared to an average of 6% over the last 10 years).&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;Fortunately, it is not that difficult to compute real house prices more closely to my situation. The raw data is openly available via API:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;The HM Land Registry registers the ownership of property in the UK and provides aggregate monthly data of the average transactions by region and property type.&lt;/li&gt;&#xA;&lt;li&gt;The Office for National Statistics (ONS) provides monthly inflation data (CPI) for the UK.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;So, in this post I want to compute and visualise how real flat and detached house prices in specific areas performed both nominally and in real terms.&lt;/p&gt;&#xA;&lt;h2 id=&#34;querying-the-housing-price-data&#34; class=&#34;content-heading&#34;&gt;Querying the housing price data&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;I am using the UK House Price Index of the Land Registry API:&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;http://landregistry.data.gov.uk/data/ukhpi/region&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;http://landregistry.data.gov.uk/data/ukhpi/region&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;This allows me to query the monthly average house price value for different regions and property types. Important to note here is that this data is based on the successful property transactions in a region which we use here as an approximation of the average value of similar properties in the region (that were or weren&amp;rsquo;t sold).&lt;/p&gt;&#xA;&lt;p&gt;I am based in London and interested in how the housing market performs close to me or areas that I am interested in living in. So I will query the data for all London boroughs as well as some South England regions around London: Kent, Surrey, Buckinghamshire, and Oxfordshire.&lt;/p&gt;&#xA;&lt;p&gt;I then resample the monthly data to quarterly data by taking the mean across the 3 months of a quarter.&lt;/p&gt;&#xA;&lt;p&gt;I then store all the data in parquet files. You can find them &lt;a href=&#34;https://gitlab.com/frankRi89/blog/-/tree/601de34ababe1dc8d3a4aaaeb30a6bbb740de3b4/notebooks/uk-house-prices&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;here&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;h2 id=&#34;inspecting-the-data&#34; class=&#34;content-heading&#34;&gt;Inspecting the data&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s have a look at how the data is stored in the file:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;pandas&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;as&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;pd&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;warnings&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;matplotlib.pyplot&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;as&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;plt&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;pd&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;set_option&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;display.float_format&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;lambda&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;x&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;%.2f&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;%&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;x&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;warnings&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;simplefilter&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;always&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;category&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;ne&#34;&gt;UserWarning&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;Here I am loading the file for the different counties that I am interested in:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;df_regions&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;pd&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;read_parquet&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;./data/uk_house_prices.parquet&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;df_regions&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;is_london_borough&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;False&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;df_regions&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sample&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ignore_index&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;True&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;div&gt;&#xA;&lt;style scoped&gt;&#xA;    .dataframe tbody tr th:only-of-type {&#xA;        vertical-align: middle;&#xA;    }&#xA;&lt;pre&gt;&lt;code&gt;.dataframe tbody tr th {&#xA;    vertical-align: top;&#xA;}&#xA;&#xA;.dataframe thead th {&#xA;    text-align: right;&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&lt;/style&gt;&lt;/p&gt;&#xA;&lt;table border=&#34;1&#34; class=&#34;dataframe&#34;&gt;&#xA;  &lt;thead&gt;&#xA;    &lt;tr style=&#34;text-align: right;&#34;&gt;&#xA;      &lt;th&gt;&lt;/th&gt;&#xA;      &lt;th&gt;Quarter&lt;/th&gt;&#xA;      &lt;th&gt;Region&lt;/th&gt;&#xA;      &lt;th&gt;Price&lt;/th&gt;&#xA;      &lt;th&gt;PropertyType&lt;/th&gt;&#xA;      &lt;th&gt;is_london_borough&lt;/th&gt;&#xA;    &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;0&lt;/th&gt;&#xA;      &lt;td&gt;2023Q4&lt;/td&gt;&#xA;      &lt;td&gt;Surrey&lt;/td&gt;&#xA;      &lt;td&gt;517233.67&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;False&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;1&lt;/th&gt;&#xA;      &lt;td&gt;2011Q3&lt;/td&gt;&#xA;      &lt;td&gt;Kent&lt;/td&gt;&#xA;      &lt;td&gt;192827.67&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;False&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;2&lt;/th&gt;&#xA;      &lt;td&gt;2014Q2&lt;/td&gt;&#xA;      &lt;td&gt;Oxfordshire&lt;/td&gt;&#xA;      &lt;td&gt;277195.00&lt;/td&gt;&#xA;      &lt;td&gt;Semi_detached&lt;/td&gt;&#xA;      &lt;td&gt;False&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;3&lt;/th&gt;&#xA;      &lt;td&gt;2013Q4&lt;/td&gt;&#xA;      &lt;td&gt;London&lt;/td&gt;&#xA;      &lt;td&gt;415838.00&lt;/td&gt;&#xA;      &lt;td&gt;Semi_detached&lt;/td&gt;&#xA;      &lt;td&gt;False&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;4&lt;/th&gt;&#xA;      &lt;td&gt;2021Q3&lt;/td&gt;&#xA;      &lt;td&gt;Buckinghamshire&lt;/td&gt;&#xA;      &lt;td&gt;431147.67&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;False&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;and here I am loading the file for the London boroughs:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;df_boroughs&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;pd&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;read_parquet&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;./data/london_borough_prices.parquet&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;df_boroughs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;is_london_borough&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;True&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;df_boroughs&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sample&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ignore_index&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;True&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;div&gt;&#xA;&lt;style scoped&gt;&#xA;    .dataframe tbody tr th:only-of-type {&#xA;        vertical-align: middle;&#xA;    }&#xA;&lt;pre&gt;&lt;code&gt;.dataframe tbody tr th {&#xA;    vertical-align: top;&#xA;}&#xA;&#xA;.dataframe thead th {&#xA;    text-align: right;&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&lt;/style&gt;&lt;/p&gt;&#xA;&lt;table border=&#34;1&#34; class=&#34;dataframe&#34;&gt;&#xA;  &lt;thead&gt;&#xA;    &lt;tr style=&#34;text-align: right;&#34;&gt;&#xA;      &lt;th&gt;&lt;/th&gt;&#xA;      &lt;th&gt;Quarter&lt;/th&gt;&#xA;      &lt;th&gt;Region&lt;/th&gt;&#xA;      &lt;th&gt;Price&lt;/th&gt;&#xA;      &lt;th&gt;PropertyType&lt;/th&gt;&#xA;      &lt;th&gt;is_london_borough&lt;/th&gt;&#xA;    &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;0&lt;/th&gt;&#xA;      &lt;td&gt;2020Q4&lt;/td&gt;&#xA;      &lt;td&gt;Hillingdon&lt;/td&gt;&#xA;      &lt;td&gt;406610.67&lt;/td&gt;&#xA;      &lt;td&gt;Terraced&lt;/td&gt;&#xA;      &lt;td&gt;True&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;1&lt;/th&gt;&#xA;      &lt;td&gt;2024Q4&lt;/td&gt;&#xA;      &lt;td&gt;Redbridge&lt;/td&gt;&#xA;      &lt;td&gt;490087.67&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;True&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;2&lt;/th&gt;&#xA;      &lt;td&gt;2017Q2&lt;/td&gt;&#xA;      &lt;td&gt;Lewisham&lt;/td&gt;&#xA;      &lt;td&gt;910228.33&lt;/td&gt;&#xA;      &lt;td&gt;Detached&lt;/td&gt;&#xA;      &lt;td&gt;True&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;3&lt;/th&gt;&#xA;      &lt;td&gt;2011Q3&lt;/td&gt;&#xA;      &lt;td&gt;Islington&lt;/td&gt;&#xA;      &lt;td&gt;369223.67&lt;/td&gt;&#xA;      &lt;td&gt;Flat&lt;/td&gt;&#xA;      &lt;td&gt;True&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;4&lt;/th&gt;&#xA;      &lt;td&gt;2010Q3&lt;/td&gt;&#xA;      &lt;td&gt;Enfield&lt;/td&gt;&#xA;      &lt;td&gt;661067.33&lt;/td&gt;&#xA;      &lt;td&gt;Detached&lt;/td&gt;&#xA;      &lt;td&gt;True&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;You can see that the dataset shows the average quarterly housing transaction values by region. The column &lt;code&gt;PropertyType&lt;/code&gt; shows what type of property was sold (Flat, Terraced, Detached, Semi_detached).&lt;/p&gt;&#xA;&lt;h2 id=&#34;computing-real-house-prices&#34; class=&#34;content-heading&#34;&gt;Computing real house prices&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Next we adjust the nominal house prices from the &lt;em&gt;Land Registry&lt;/em&gt; for inflation and compute the real house prices in terms of &amp;ldquo;2025 pounds&amp;rdquo;.&#xA;I have queried the Office for National Statistics API to get the UK&amp;rsquo;s quarterly CPI data and stored it in a parquet file. I am loading this data here:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;cpi_df&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;pd&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;read_parquet&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;./data/cpi_data.parquet&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;cpi_df&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;tail&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;div&gt;&#xA;&lt;style scoped&gt;&#xA;    .dataframe tbody tr th:only-of-type {&#xA;        vertical-align: middle;&#xA;    }&#xA;&lt;pre&gt;&lt;code&gt;.dataframe tbody tr th {&#xA;    vertical-align: top;&#xA;}&#xA;&#xA;.dataframe thead th {&#xA;    text-align: right;&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&lt;/style&gt;&lt;/p&gt;&#xA;&lt;table border=&#34;1&#34; class=&#34;dataframe&#34;&gt;&#xA;  &lt;thead&gt;&#xA;    &lt;tr style=&#34;text-align: right;&#34;&gt;&#xA;      &lt;th&gt;&lt;/th&gt;&#xA;      &lt;th&gt;Quarter&lt;/th&gt;&#xA;      &lt;th&gt;CPI&lt;/th&gt;&#xA;    &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;146&lt;/th&gt;&#xA;      &lt;td&gt;2024Q3&lt;/td&gt;&#xA;      &lt;td&gt;134.10&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;147&lt;/th&gt;&#xA;      &lt;td&gt;2024Q4&lt;/td&gt;&#xA;      &lt;td&gt;135.20&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;148&lt;/th&gt;&#xA;      &lt;td&gt;2025Q1&lt;/td&gt;&#xA;      &lt;td&gt;136.00&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;149&lt;/th&gt;&#xA;      &lt;td&gt;2025Q2&lt;/td&gt;&#xA;      &lt;td&gt;138.50&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;150&lt;/th&gt;&#xA;      &lt;td&gt;2025Q3&lt;/td&gt;&#xA;      &lt;td&gt;139.20&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;With the consumer price index, we can compute the real price of housing. This real price answers the question: &amp;ldquo;How much was the property worth in last quarter&amp;rsquo;s pounds?&amp;rdquo; If you experienced inflation, your money is worth less, and you will need to pay more for the same house in today&amp;rsquo;s money terms. If you experienced deflation, your money is worth more, and you will need to pay less for a house in today&amp;rsquo;s terms (all else equal).&lt;/p&gt;&#xA;&lt;p&gt;The formula to compute the real house price in terms of the current CPI:&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;real_price_current = nominal_price_t * (CPI_current / CPI_t)&lt;/code&gt;&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Merging the dataframes here to do real price calculation in one step&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;df&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;pd&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;concat&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;([&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;df_regions&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;df_boroughs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;])&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Get the most recent CPI value&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;current_cpi&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;cpi_df&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;CPI&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;iloc&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;latest_quarter&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;cpi_df&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;Quarter&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;iloc&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Merge CPI df with house prices df&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;df&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;df&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;merge&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;cpi_df&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;on&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;Quarter&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;how&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;left&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# For quarters without CPI data yet, use the most recent CPI&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;df&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;CPI&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;df&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;CPI&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;fillna&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;current_cpi&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Calculate real prices using above formula &lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;df&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;RealPrice&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;df&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;Price&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;current_cpi&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;/&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;df&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;CPI&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;])&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;df&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sample&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ignore_index&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;True&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;div&gt;&#xA;&lt;style scoped&gt;&#xA;    .dataframe tbody tr th:only-of-type {&#xA;        vertical-align: middle;&#xA;    }&#xA;&lt;pre&gt;&lt;code&gt;.dataframe tbody tr th {&#xA;    vertical-align: top;&#xA;}&#xA;&#xA;.dataframe thead th {&#xA;    text-align: right;&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&lt;/style&gt;&lt;/p&gt;&#xA;&lt;table border=&#34;1&#34; class=&#34;dataframe&#34;&gt;&#xA;  &lt;thead&gt;&#xA;    &lt;tr style=&#34;text-align: right;&#34;&gt;&#xA;      &lt;th&gt;&lt;/th&gt;&#xA;      &lt;th&gt;Quarter&lt;/th&gt;&#xA;      &lt;th&gt;Region&lt;/th&gt;&#xA;      &lt;th&gt;Price&lt;/th&gt;&#xA;      &lt;th&gt;PropertyType&lt;/th&gt;&#xA;      &lt;th&gt;is_london_borough&lt;/th&gt;&#xA;      &lt;th&gt;CPI&lt;/th&gt;&#xA;      &lt;th&gt;RealPrice&lt;/th&gt;&#xA;    &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;0&lt;/th&gt;&#xA;      &lt;td&gt;2012Q3&lt;/td&gt;&#xA;      &lt;td&gt;City Of Westminster&lt;/td&gt;&#xA;      &lt;td&gt;2138137.67&lt;/td&gt;&#xA;      &lt;td&gt;Semi_detached&lt;/td&gt;&#xA;      &lt;td&gt;True&lt;/td&gt;&#xA;      &lt;td&gt;96.10&lt;/td&gt;&#xA;      &lt;td&gt;3097073.50&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;1&lt;/th&gt;&#xA;      &lt;td&gt;2011Q3&lt;/td&gt;&#xA;      &lt;td&gt;Croydon&lt;/td&gt;&#xA;      &lt;td&gt;280930.00&lt;/td&gt;&#xA;      &lt;td&gt;Semi_detached&lt;/td&gt;&#xA;      &lt;td&gt;True&lt;/td&gt;&#xA;      &lt;td&gt;93.80&lt;/td&gt;&#xA;      &lt;td&gt;416902.52&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;2&lt;/th&gt;&#xA;      &lt;td&gt;2016Q3&lt;/td&gt;&#xA;      &lt;td&gt;Bromley&lt;/td&gt;&#xA;      &lt;td&gt;898828.67&lt;/td&gt;&#xA;      &lt;td&gt;Detached&lt;/td&gt;&#xA;      &lt;td&gt;True&lt;/td&gt;&#xA;      &lt;td&gt;100.90&lt;/td&gt;&#xA;      &lt;td&gt;1240009.42&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;3&lt;/th&gt;&#xA;      &lt;td&gt;2017Q4&lt;/td&gt;&#xA;      &lt;td&gt;Ealing&lt;/td&gt;&#xA;      &lt;td&gt;1160573.67&lt;/td&gt;&#xA;      &lt;td&gt;Detached&lt;/td&gt;&#xA;      &lt;td&gt;True&lt;/td&gt;&#xA;      &lt;td&gt;104.60&lt;/td&gt;&#xA;      &lt;td&gt;1544472.80&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;4&lt;/th&gt;&#xA;      &lt;td&gt;2025Q2&lt;/td&gt;&#xA;      &lt;td&gt;City Of London&lt;/td&gt;&#xA;      &lt;td&gt;792392.33&lt;/td&gt;&#xA;      &lt;td&gt;Flat&lt;/td&gt;&#xA;      &lt;td&gt;True&lt;/td&gt;&#xA;      &lt;td&gt;138.50&lt;/td&gt;&#xA;      &lt;td&gt;796397.20&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;As you can see we now have the column &lt;code&gt;RealPrice&lt;/code&gt; which gives us the mean house price transaction per quarter and region in real terms. Now we can visualise the data to finally see what happened.&lt;/p&gt;&#xA;&lt;h2 id=&#34;visualising-nominal-and-real-house-prices-over-time&#34; class=&#34;content-heading&#34;&gt;Visualising nominal and real house prices over time&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;My goal is to plot the nominal and real house prices over time. To keep things visually simpler on the charts I am only selecting a few London boroughs that I am interested in and only look at data since 2015. For London boroughs I am looking at Flats and Terraced houses and for the counties I am only looking at detached houses.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;df&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;df&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;df&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;Quarter&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;2015Q1&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;copy&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;SELECTED_BOROUGHS&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s1&#34;&gt;&amp;#39;Ealing&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s1&#34;&gt;&amp;#39;Hackney&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s1&#34;&gt;&amp;#39;Hammersmith And Fulham&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s1&#34;&gt;&amp;#39;Hounslow&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s1&#34;&gt;&amp;#39;Islington&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s1&#34;&gt;&amp;#39;Richmond Upon Thames&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s1&#34;&gt;&amp;#39;Wandsworth&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;df_boroughs&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;df&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;df&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;Region&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;isin&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;SELECTED_BOROUGHS&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;df&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;PropertyType&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;isin&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;([&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;Flat&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;Terraced&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]))]&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;copy&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;df_regions&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;df&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;df&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;is_london_borough&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;False&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;df&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;PropertyType&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;Detached&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)]&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;copy&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;In the file &lt;code&gt;utils.py&lt;/code&gt; I added some code to create the plots and tables. You can safely skip this if not interested. Importing them here in the notebook:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;utils&lt;/span&gt; &lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;create_table&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;plot_prices_by_type&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;h3 id=&#34;detached-houses-in-south-england-counties&#34; class=&#34;content-heading&#34;&gt;Detached Houses in South England Counties&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;I am first looking at the house price performance of detached houses in five different English counties. I am plotting both the nominal prices (dashed line) and the real prices (solid line).&lt;/p&gt;&#xA;&lt;p&gt;I am also creating a table with the real price changes vs. 1 year ago, 2 years ago, 5 years ago, 10 years ago.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;fig&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;axes&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;plt&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;subplots&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;figsize&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;14&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;8&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;plot_prices_by_type&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;df_regions&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;Detached&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;axes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;Price&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;Detached Houses in South England counties&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;&#xA;&#xA;&lt;img src=&#34;https://staticnotes.org/posts/uk-house-prices/output_21_0.png&#34; alt=&#34;png&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;You can see that within the last 10 years most counties saw a decent increase in nominal terms and a decline in real house prices for detached properties. The only exception is Kent with a small real increase of 0.9%. London and Surrey both lost around 10% in real terms.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;regions&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;sorted&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;df&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;Region&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;unique&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;())&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;gt_regions_flat&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;create_table&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;df_regions&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;Detached&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;s1&#34;&gt;&amp;#39;South England Counties&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;regions&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;gt_regions_flat&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;div id=&#34;vuiiiwlslp&#34; style=&#34;padding-left:0px;padding-right:0px;padding-top:10px;padding-bottom:10px;overflow-x:auto;overflow-y:auto;width:auto;height:auto;&#34;&gt;&#xA;&lt;style&gt;&#xA;#vuiiiwlslp table {&#xA;          font-family: -apple-system, BlinkMacSystemFont, &#39;Segoe UI&#39;, Roboto, Oxygen, Ubuntu, Cantarell, &#39;Helvetica Neue&#39;, &#39;Fira Sans&#39;, &#39;Droid Sans&#39;, Arial, sans-serif;&#xA;          -webkit-font-smoothing: antialiased;&#xA;          -moz-osx-font-smoothing: grayscale;&#xA;        }&#xA;&lt;p&gt;#vuiiiwlslp thead, tbody, tfoot, tr, td, th { border-style: none !important; }&#xA;tr { background-color: transparent !important; }&#xA;#vuiiiwlslp p { margin: 0 !important; padding: 0 !important; }&#xA;#vuiiiwlslp .gt_table { display: table !important; border-collapse: collapse !important; line-height: normal !important; margin-left: auto !important; margin-right: auto !important; color: #333333 !important; font-size: 12px !important; font-weight: normal !important; font-style: normal !important; background-color: #FFFFFF !important; width: auto !important; border-top-style: solid !important; border-top-width: 2px !important; border-top-color: #A8A8A8 !important; border-right-style: none !important; border-right-width: 2px !important; border-right-color: #D3D3D3 !important; border-bottom-style: solid !important; border-bottom-width: 2px !important; border-bottom-color: #A8A8A8 !important; border-left-style: none !important; border-left-width: 2px !important; border-left-color: #D3D3D3 !important; }&#xA;#vuiiiwlslp .gt_caption { padding-top: 4px !important; padding-bottom: 4px !important; }&#xA;#vuiiiwlslp .gt_title { color: #333333 !important; font-size: 125% !important; font-weight: initial !important; padding-top: 4px !important; padding-bottom: 4px !important; padding-left: 5px !important; padding-right: 5px !important; border-bottom-color: #FFFFFF !important; border-bottom-width: 0 !important; }&#xA;#vuiiiwlslp .gt_subtitle { color: #333333 !important; font-size: 85% !important; font-weight: initial !important; padding-top: 3px !important; padding-bottom: 5px !important; padding-left: 5px !important; padding-right: 5px !important; border-top-color: #FFFFFF !important; border-top-width: 0 !important; }&#xA;#vuiiiwlslp .gt_heading { background-color: #FFFFFF !important; text-align: center !important; border-bottom-color: #FFFFFF !important; border-left-style: none !important; border-left-width: 1px !important; border-left-color: #D3D3D3 !important; border-right-style: none !important; border-right-width: 1px !important; border-right-color: #D3D3D3 !important; }&#xA;#vuiiiwlslp .gt_bottom_border { border-bottom-style: solid !important; border-bottom-width: 2px !important; border-bottom-color: #D3D3D3 !important; }&#xA;#vuiiiwlslp .gt_col_headings { border-top-style: solid !important; border-top-width: 2px !important; border-top-color: #D3D3D3 !important; border-bottom-style: solid !important; border-bottom-width: 2px !important; border-bottom-color: #D3D3D3 !important; border-left-style: none !important; border-left-width: 1px !important; border-left-color: #D3D3D3 !important; border-right-style: none !important; border-right-width: 1px !important; border-right-color: #D3D3D3 !important; }&#xA;#vuiiiwlslp .gt_col_heading { color: #333333 !important; background-color: #FFFFFF !important; font-size: 100% !important; font-weight: normal !important; text-transform: inherit !important; border-left-style: none !important; border-left-width: 1px !important; border-left-color: #D3D3D3 !important; border-right-style: none !important; border-right-width: 1px !important; border-right-color: #D3D3D3 !important; vertical-align: bottom !important; padding-top: 5px !important; padding-bottom: 5px !important; padding-left: 5px !important; padding-right: 5px !important; overflow-x: hidden !important; }&#xA;#vuiiiwlslp .gt_column_spanner_outer { color: #333333 !important; background-color: #FFFFFF !important; font-size: 100% !important; font-weight: normal !important; text-transform: inherit !important; padding-top: 0 !important; padding-bottom: 0 !important; padding-left: 4px !important; padding-right: 4px !important; }&#xA;#vuiiiwlslp .gt_column_spanner_outer:first-child { padding-left: 0 !important; }&#xA;#vuiiiwlslp .gt_column_spanner_outer:last-child { padding-right: 0 !important; }&#xA;#vuiiiwlslp .gt_column_spanner { border-bottom-style: solid !important; border-bottom-width: 2px !important; border-bottom-color: #D3D3D3 !important; vertical-align: bottom !important; padding-top: 5px !important; padding-bottom: 5px !important; overflow-x: hidden !important; display: inline-block !important; width: 100% !important; }&#xA;#vuiiiwlslp .gt_spanner_row { border-bottom-style: hidden !important; }&#xA;#vuiiiwlslp .gt_group_heading { padding-top: 8px !important; padding-bottom: 8px !important; padding-left: 5px !important; padding-right: 5px !important; color: #333333 !important; background-color: #FFFFFF !important; font-size: 100% !important; font-weight: initial !important; text-transform: inherit !important; border-top-style: solid !important; border-top-width: 2px !important; border-top-color: #D3D3D3 !important; border-bottom-style: solid !important; border-bottom-width: 2px !important; border-bottom-color: #D3D3D3 !important; border-left-style: none !important; border-left-width: 1px !important; border-left-color: #D3D3D3 !important; border-right-style: none !important; border-right-width: 1px !important; border-right-color: #D3D3D3 !important; vertical-align: middle !important; text-align: left !important; }&#xA;#vuiiiwlslp .gt_empty_group_heading { padding: 0.5px !important; color: #333333 !important; background-color: #FFFFFF !important; font-size: 100% !important; font-weight: initial !important; border-top-style: solid !important; border-top-width: 2px !important; border-top-color: #D3D3D3 !important; border-bottom-style: solid !important; border-bottom-width: 2px !important; border-bottom-color: #D3D3D3 !important; vertical-align: middle !important; }&#xA;#vuiiiwlslp .gt_from_md&amp;gt; :first-child { margin-top: 0 !important; }&#xA;#vuiiiwlslp .gt_from_md&amp;gt; :last-child { margin-bottom: 0 !important; }&#xA;#vuiiiwlslp .gt_row { padding-top: 8px !important; padding-bottom: 8px !important; padding-left: 5px !important; padding-right: 5px !important; margin: 10px !important; border-top-style: solid !important; border-top-width: 1px !important; border-top-color: #D3D3D3 !important; border-left-style: none !important; border-left-width: 1px !important; border-left-color: #D3D3D3 !important; border-right-style: none !important; border-right-width: 1px !important; border-right-color: #D3D3D3 !important; vertical-align: middle !important; overflow-x: hidden !important; }&#xA;#vuiiiwlslp .gt_stub { color: #333333 !important; background-color: #FFFFFF !important; font-size: 100% !important; font-weight: initial !important; text-transform: inherit !important; border-right-style: solid !important; border-right-width: 2px !important; border-right-color: #D3D3D3 !important; padding-left: 5px !important; padding-right: 5px !important; }&#xA;#vuiiiwlslp .gt_stub_row_group { color: #333333 !important; background-color: #FFFFFF !important; font-size: 100% !important; font-weight: initial !important; text-transform: inherit !important; border-right-style: solid !important; border-right-width: 2px !important; border-right-color: #D3D3D3 !important; padding-left: 5px !important; padding-right: 5px !important; vertical-align: top !important; }&#xA;#vuiiiwlslp .gt_row_group_first td { border-top-width: 2px !important; }&#xA;#vuiiiwlslp .gt_row_group_first th { border-top-width: 2px !important; }&#xA;#vuiiiwlslp .gt_striped { color: #333333 !important; background-color: #F4F4F4 !important; }&#xA;#vuiiiwlslp .gt_table_body { border-top-style: solid !important; border-top-width: 2px !important; border-top-color: #D3D3D3 !important; border-bottom-style: solid !important; border-bottom-width: 2px !important; border-bottom-color: #D3D3D3 !important; }&#xA;#vuiiiwlslp .gt_grand_summary_row { color: #333333 !important; background-color: #FFFFFF !important; text-transform: inherit !important; padding-top: 8px !important; padding-bottom: 8px !important; padding-left: 5px !important; padding-right: 5px !important; }&#xA;#vuiiiwlslp .gt_first_grand_summary_row_bottom { border-top-style: double !important; border-top-width: 6px !important; border-top-color: #D3D3D3 !important; }&#xA;#vuiiiwlslp .gt_last_grand_summary_row_top { border-bottom-style: double !important; border-bottom-width: 6px !important; border-bottom-color: #D3D3D3 !important; }&#xA;#vuiiiwlslp .gt_sourcenotes { color: #333333 !important; background-color: #FFFFFF !important; border-bottom-style: none !important; border-bottom-width: 2px !important; border-bottom-color: #D3D3D3 !important; border-left-style: none !important; border-left-width: 2px !important; border-left-color: #D3D3D3 !important; border-right-style: none !important; border-right-width: 2px !important; border-right-color: #D3D3D3 !important; }&#xA;#vuiiiwlslp .gt_sourcenote { font-size: 90% !important; padding-top: 4px !important; padding-bottom: 4px !important; padding-left: 5px !important; padding-right: 5px !important; text-align: left !important; }&#xA;#vuiiiwlslp .gt_left { text-align: left !important; }&#xA;#vuiiiwlslp .gt_center { text-align: center !important; }&#xA;#vuiiiwlslp .gt_right { text-align: right !important; font-variant-numeric: tabular-nums !important; }&#xA;#vuiiiwlslp .gt_font_normal { font-weight: normal !important; }&#xA;#vuiiiwlslp .gt_font_bold { font-weight: bold !important; }&#xA;#vuiiiwlslp .gt_font_italic { font-style: italic !important; }&#xA;#vuiiiwlslp .gt_super { font-size: 65% !important; }&#xA;#vuiiiwlslp .gt_footnote_marks { font-size: 75% !important; vertical-align: 0.4em !important; position: initial !important; }&#xA;#vuiiiwlslp .gt_asterisk { font-size: 100% !important; vertical-align: 0 !important; }&lt;/p&gt;&#xA;&lt;p&gt;&lt;/style&gt;&lt;/p&gt;&#xA;&lt;table class=&#34;gt_table&#34; data-quarto-disable-processing=&#34;false&#34; data-quarto-bootstrap=&#34;false&#34;&gt;&#xA;&lt;thead&gt;&#xA;  &lt;tr class=&#34;gt_heading&#34;&gt;&#xA;    &lt;td colspan=&#34;11&#34; class=&#34;gt_heading gt_title gt_font_normal&#34;&gt;South England Counties&lt;/td&gt;&#xA;  &lt;/tr&gt;&#xA;  &lt;tr class=&#34;gt_heading&#34;&gt;&#xA;    &lt;td colspan=&#34;11&#34; class=&#34;gt_heading gt_subtitle gt_font_normal gt_bottom_border&#34;&gt;Detached properties - Real prices (CPI-adjusted) - Q4 2025 vs 1, 2, 5, 10 years ago&lt;/td&gt;&#xA;  &lt;/tr&gt;&#xA;&lt;tr class=&#34;gt_col_headings gt_spanner_row&#34;&gt;&#xA;  &lt;th class=&#34;gt_col_heading gt_columns_bottom_border gt_left&#34; rowspan=&#34;2&#34; colspan=&#34;1&#34; scope=&#34;col&#34; id=&#34;&#34;&gt;&lt;/th&gt;&#xA;  &lt;th class=&#34;gt_center gt_columns_top_border gt_column_spanner_outer&#34; rowspan=&#34;1&#34; colspan=&#34;2&#34; scope=&#34;colgroup&#34; id=&#34;2025-(Current)&#34;&gt;&#xA;    &lt;span class=&#34;gt_column_spanner&#34;&gt;2025 (Current)&lt;/span&gt;&#xA;  &lt;/th&gt;&#xA;  &lt;th class=&#34;gt_center gt_columns_top_border gt_column_spanner_outer&#34; rowspan=&#34;1&#34; colspan=&#34;2&#34; scope=&#34;colgroup&#34; id=&#34;2024-(1y-ago)&#34;&gt;&#xA;    &lt;span class=&#34;gt_column_spanner&#34;&gt;2024 (1y ago)&lt;/span&gt;&#xA;  &lt;/th&gt;&#xA;  &lt;th class=&#34;gt_center gt_columns_top_border gt_column_spanner_outer&#34; rowspan=&#34;1&#34; colspan=&#34;2&#34; scope=&#34;colgroup&#34; id=&#34;2023-(2y-ago)&#34;&gt;&#xA;    &lt;span class=&#34;gt_column_spanner&#34;&gt;2023 (2y ago)&lt;/span&gt;&#xA;  &lt;/th&gt;&#xA;  &lt;th class=&#34;gt_center gt_columns_top_border gt_column_spanner_outer&#34; rowspan=&#34;1&#34; colspan=&#34;2&#34; scope=&#34;colgroup&#34; id=&#34;2020-(5y-ago)&#34;&gt;&#xA;    &lt;span class=&#34;gt_column_spanner&#34;&gt;2020 (5y ago)&lt;/span&gt;&#xA;  &lt;/th&gt;&#xA;  &lt;th class=&#34;gt_center gt_columns_top_border gt_column_spanner_outer&#34; rowspan=&#34;1&#34; colspan=&#34;2&#34; scope=&#34;colgroup&#34; id=&#34;2015-(10y-ago)&#34;&gt;&#xA;    &lt;span class=&#34;gt_column_spanner&#34;&gt;2015 (10y ago)&lt;/span&gt;&#xA;  &lt;/th&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr class=&#34;gt_col_headings&#34;&gt;&#xA;  &lt;th class=&#34;gt_col_heading gt_columns_bottom_border gt_right&#34; rowspan=&#34;1&#34; colspan=&#34;1&#34; scope=&#34;col&#34; id=&#34;2025-(Current)_Real&#34;&gt;Price (£)&lt;/th&gt;&#xA;  &lt;th class=&#34;gt_col_heading gt_columns_bottom_border gt_right&#34; rowspan=&#34;1&#34; colspan=&#34;1&#34; scope=&#34;col&#34; id=&#34;2025-(Current)_RealChg&#34;&gt;%&lt;/th&gt;&#xA;  &lt;th class=&#34;gt_col_heading gt_columns_bottom_border gt_right&#34; rowspan=&#34;1&#34; colspan=&#34;1&#34; scope=&#34;col&#34; id=&#34;2024-(1y-ago)_Real&#34;&gt;Price (£)&lt;/th&gt;&#xA;  &lt;th class=&#34;gt_col_heading gt_columns_bottom_border gt_right&#34; rowspan=&#34;1&#34; colspan=&#34;1&#34; scope=&#34;col&#34; id=&#34;2024-(1y-ago)_RealChg&#34;&gt;%&lt;/th&gt;&#xA;  &lt;th class=&#34;gt_col_heading gt_columns_bottom_border gt_right&#34; rowspan=&#34;1&#34; colspan=&#34;1&#34; scope=&#34;col&#34; id=&#34;2023-(2y-ago)_Real&#34;&gt;Price (£)&lt;/th&gt;&#xA;  &lt;th class=&#34;gt_col_heading gt_columns_bottom_border gt_right&#34; rowspan=&#34;1&#34; colspan=&#34;1&#34; scope=&#34;col&#34; id=&#34;2023-(2y-ago)_RealChg&#34;&gt;%&lt;/th&gt;&#xA;  &lt;th class=&#34;gt_col_heading gt_columns_bottom_border gt_right&#34; rowspan=&#34;1&#34; colspan=&#34;1&#34; scope=&#34;col&#34; id=&#34;2020-(5y-ago)_Real&#34;&gt;Price (£)&lt;/th&gt;&#xA;  &lt;th class=&#34;gt_col_heading gt_columns_bottom_border gt_right&#34; rowspan=&#34;1&#34; colspan=&#34;1&#34; scope=&#34;col&#34; id=&#34;2020-(5y-ago)_RealChg&#34;&gt;%&lt;/th&gt;&#xA;  &lt;th class=&#34;gt_col_heading gt_columns_bottom_border gt_right&#34; rowspan=&#34;1&#34; colspan=&#34;1&#34; scope=&#34;col&#34; id=&#34;2015-(10y-ago)_Real&#34;&gt;Price (£)&lt;/th&gt;&#xA;  &lt;th class=&#34;gt_col_heading gt_columns_bottom_border gt_right&#34; rowspan=&#34;1&#34; colspan=&#34;1&#34; scope=&#34;col&#34; id=&#34;2015-(10y-ago)_RealChg&#34;&gt;%&lt;/th&gt;&#xA;&lt;/tr&gt;&#xA;&lt;/thead&gt;&#xA;&lt;tbody class=&#34;gt_table_body&#34;&gt;&#xA;  &lt;tr&gt;&#xA;    &lt;th class=&#34;gt_row gt_left gt_stub&#34;&gt;Buckinghamshire&lt;/th&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;853,221&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #efc5c3;&#34; class=&#34;gt_row gt_right&#34;&gt;0.0&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;883,178&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #ecb4b1;&#34; class=&#34;gt_row gt_right&#34;&gt;−3.4&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;894,115&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #ebaeab;&#34; class=&#34;gt_row gt_right&#34;&gt;−4.6&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;974,110&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #e58882;&#34; class=&#34;gt_row gt_right&#34;&gt;−12.4&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;924,706&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #e99f9b;&#34; class=&#34;gt_row gt_right&#34;&gt;−7.7&lt;/td&gt;&#xA;  &lt;/tr&gt;&#xA;  &lt;tr&gt;&#xA;    &lt;th class=&#34;gt_row gt_left gt_stub&#34;&gt;Kent&lt;/th&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;605,363&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #efc5c3;&#34; class=&#34;gt_row gt_right&#34;&gt;0.0&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;620,638&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #edb9b6;&#34; class=&#34;gt_row gt_right&#34;&gt;−2.5&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;633,552&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #ebafac;&#34; class=&#34;gt_row gt_right&#34;&gt;−4.4&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;692,871&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #e58681;&#34; class=&#34;gt_row gt_right&#34;&gt;−12.6&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;599,898&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #f0cac8;&#34; class=&#34;gt_row gt_right&#34;&gt;0.9&lt;/td&gt;&#xA;  &lt;/tr&gt;&#xA;  &lt;tr&gt;&#xA;    &lt;th class=&#34;gt_row gt_left gt_stub&#34;&gt;London&lt;/th&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;1,132,911&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #efc5c3;&#34; class=&#34;gt_row gt_right&#34;&gt;0.0&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;1,178,785&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #ecb2af;&#34; class=&#34;gt_row gt_right&#34;&gt;−3.9&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;1,179,862&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #ecb1ae;&#34; class=&#34;gt_row gt_right&#34;&gt;−4.0&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;1,331,199&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #e37b76;&#34; class=&#34;gt_row gt_right&#34;&gt;−14.9&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;1,265,328&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #e7918d;&#34; class=&#34;gt_row gt_right&#34;&gt;−10.5&lt;/td&gt;&#xA;  &lt;/tr&gt;&#xA;  &lt;tr&gt;&#xA;    &lt;th class=&#34;gt_row gt_left gt_stub&#34;&gt;Oxfordshire&lt;/th&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;680,124&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #efc5c3;&#34; class=&#34;gt_row gt_right&#34;&gt;0.0&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;675,726&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #f0c8c6;&#34; class=&#34;gt_row gt_right&#34;&gt;0.7&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;676,613&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #efc8c6;&#34; class=&#34;gt_row gt_right&#34;&gt;0.5&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;757,910&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #e7928e;&#34; class=&#34;gt_row gt_right&#34;&gt;−10.3&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;723,218&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #eaa8a4;&#34; class=&#34;gt_row gt_right&#34;&gt;−6.0&lt;/td&gt;&#xA;  &lt;/tr&gt;&#xA;  &lt;tr&gt;&#xA;    &lt;th class=&#34;gt_row gt_left gt_stub&#34;&gt;Surrey&lt;/th&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;975,634&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #efc5c3;&#34; class=&#34;gt_row gt_right&#34;&gt;0.0&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;1,007,309&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #ecb6b3;&#34; class=&#34;gt_row gt_right&#34;&gt;−3.1&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;1,023,827&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #ebaeab;&#34; class=&#34;gt_row gt_right&#34;&gt;−4.7&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;1,118,116&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #e58681;&#34; class=&#34;gt_row gt_right&#34;&gt;−12.7&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;1,083,197&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #e7948f;&#34; class=&#34;gt_row gt_right&#34;&gt;−9.9&lt;/td&gt;&#xA;  &lt;/tr&gt;&#xA;&lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;h3 id=&#34;flats-in-london-boroughs&#34; class=&#34;content-heading&#34;&gt;Flats in London Boroughs&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;Next, I am looking at flats in London. Again I am plotting nominal and real quarterly prices for selected London boroughs and create a table with the corresponding numbers.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;fig&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;axes&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;plt&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;subplots&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;figsize&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;14&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;8&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;plot_prices_by_type&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;df_boroughs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;Flat&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;axes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;Price&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;Flats in selected London boroughs&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;&#xA;&#xA;&lt;img src=&#34;https://staticnotes.org/posts/uk-house-prices/output_25_0.png&#34; alt=&#34;png&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;Both the chart and the table show somewhat stable or slightly increasing nominal prices, but drastic reductions in real prices. Both Hammersmith/Fulham and Wandsworth are down about 30% in real terms since 2015. Moreover compared to last year and two years ago we still see price declines in real terms.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;regions&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;sorted&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;df&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;Region&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;unique&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;())&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;gt_regions_flat&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;create_table&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;df_boroughs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;Flat&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;s1&#34;&gt;&amp;#39;Selected London Boroughs&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;regions&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;gt_regions_flat&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;div id=&#34;zldtaermwk&#34; style=&#34;padding-left:0px;padding-right:0px;padding-top:10px;padding-bottom:10px;overflow-x:auto;overflow-y:auto;width:auto;height:auto;&#34;&gt;&#xA;&lt;style&gt;&#xA;#zldtaermwk table {&#xA;          font-family: -apple-system, BlinkMacSystemFont, &#39;Segoe UI&#39;, Roboto, Oxygen, Ubuntu, Cantarell, &#39;Helvetica Neue&#39;, &#39;Fira Sans&#39;, &#39;Droid Sans&#39;, Arial, sans-serif;&#xA;          -webkit-font-smoothing: antialiased;&#xA;          -moz-osx-font-smoothing: grayscale;&#xA;        }&#xA;&lt;p&gt;#zldtaermwk thead, tbody, tfoot, tr, td, th { border-style: none !important; }&#xA;tr { background-color: transparent !important; }&#xA;#zldtaermwk p { margin: 0 !important; padding: 0 !important; }&#xA;#zldtaermwk .gt_table { display: table !important; border-collapse: collapse !important; line-height: normal !important; margin-left: auto !important; margin-right: auto !important; color: #333333 !important; font-size: 12px !important; font-weight: normal !important; font-style: normal !important; background-color: #FFFFFF !important; width: auto !important; border-top-style: solid !important; border-top-width: 2px !important; border-top-color: #A8A8A8 !important; border-right-style: none !important; border-right-width: 2px !important; border-right-color: #D3D3D3 !important; border-bottom-style: solid !important; border-bottom-width: 2px !important; border-bottom-color: #A8A8A8 !important; border-left-style: none !important; border-left-width: 2px !important; border-left-color: #D3D3D3 !important; }&#xA;#zldtaermwk .gt_caption { padding-top: 4px !important; padding-bottom: 4px !important; }&#xA;#zldtaermwk .gt_title { color: #333333 !important; font-size: 125% !important; font-weight: initial !important; padding-top: 4px !important; padding-bottom: 4px !important; padding-left: 5px !important; padding-right: 5px !important; border-bottom-color: #FFFFFF !important; border-bottom-width: 0 !important; }&#xA;#zldtaermwk .gt_subtitle { color: #333333 !important; font-size: 85% !important; font-weight: initial !important; padding-top: 3px !important; padding-bottom: 5px !important; padding-left: 5px !important; padding-right: 5px !important; border-top-color: #FFFFFF !important; border-top-width: 0 !important; }&#xA;#zldtaermwk .gt_heading { background-color: #FFFFFF !important; text-align: center !important; border-bottom-color: #FFFFFF !important; border-left-style: none !important; border-left-width: 1px !important; border-left-color: #D3D3D3 !important; border-right-style: none !important; border-right-width: 1px !important; border-right-color: #D3D3D3 !important; }&#xA;#zldtaermwk .gt_bottom_border { border-bottom-style: solid !important; border-bottom-width: 2px !important; border-bottom-color: #D3D3D3 !important; }&#xA;#zldtaermwk .gt_col_headings { border-top-style: solid !important; border-top-width: 2px !important; border-top-color: #D3D3D3 !important; border-bottom-style: solid !important; border-bottom-width: 2px !important; border-bottom-color: #D3D3D3 !important; border-left-style: none !important; border-left-width: 1px !important; border-left-color: #D3D3D3 !important; border-right-style: none !important; border-right-width: 1px !important; border-right-color: #D3D3D3 !important; }&#xA;#zldtaermwk .gt_col_heading { color: #333333 !important; background-color: #FFFFFF !important; font-size: 100% !important; font-weight: normal !important; text-transform: inherit !important; border-left-style: none !important; border-left-width: 1px !important; border-left-color: #D3D3D3 !important; border-right-style: none !important; border-right-width: 1px !important; border-right-color: #D3D3D3 !important; vertical-align: bottom !important; padding-top: 5px !important; padding-bottom: 5px !important; padding-left: 5px !important; padding-right: 5px !important; overflow-x: hidden !important; }&#xA;#zldtaermwk .gt_column_spanner_outer { color: #333333 !important; background-color: #FFFFFF !important; font-size: 100% !important; font-weight: normal !important; text-transform: inherit !important; padding-top: 0 !important; padding-bottom: 0 !important; padding-left: 4px !important; padding-right: 4px !important; }&#xA;#zldtaermwk .gt_column_spanner_outer:first-child { padding-left: 0 !important; }&#xA;#zldtaermwk .gt_column_spanner_outer:last-child { padding-right: 0 !important; }&#xA;#zldtaermwk .gt_column_spanner { border-bottom-style: solid !important; border-bottom-width: 2px !important; border-bottom-color: #D3D3D3 !important; vertical-align: bottom !important; padding-top: 5px !important; padding-bottom: 5px !important; overflow-x: hidden !important; display: inline-block !important; width: 100% !important; }&#xA;#zldtaermwk .gt_spanner_row { border-bottom-style: hidden !important; }&#xA;#zldtaermwk .gt_group_heading { padding-top: 8px !important; padding-bottom: 8px !important; padding-left: 5px !important; padding-right: 5px !important; color: #333333 !important; background-color: #FFFFFF !important; font-size: 100% !important; font-weight: initial !important; text-transform: inherit !important; border-top-style: solid !important; border-top-width: 2px !important; border-top-color: #D3D3D3 !important; border-bottom-style: solid !important; border-bottom-width: 2px !important; border-bottom-color: #D3D3D3 !important; border-left-style: none !important; border-left-width: 1px !important; border-left-color: #D3D3D3 !important; border-right-style: none !important; border-right-width: 1px !important; border-right-color: #D3D3D3 !important; vertical-align: middle !important; text-align: left !important; }&#xA;#zldtaermwk .gt_empty_group_heading { padding: 0.5px !important; color: #333333 !important; background-color: #FFFFFF !important; font-size: 100% !important; font-weight: initial !important; border-top-style: solid !important; border-top-width: 2px !important; border-top-color: #D3D3D3 !important; border-bottom-style: solid !important; border-bottom-width: 2px !important; border-bottom-color: #D3D3D3 !important; vertical-align: middle !important; }&#xA;#zldtaermwk .gt_from_md&amp;gt; :first-child { margin-top: 0 !important; }&#xA;#zldtaermwk .gt_from_md&amp;gt; :last-child { margin-bottom: 0 !important; }&#xA;#zldtaermwk .gt_row { padding-top: 8px !important; padding-bottom: 8px !important; padding-left: 5px !important; padding-right: 5px !important; margin: 10px !important; border-top-style: solid !important; border-top-width: 1px !important; border-top-color: #D3D3D3 !important; border-left-style: none !important; border-left-width: 1px !important; border-left-color: #D3D3D3 !important; border-right-style: none !important; border-right-width: 1px !important; border-right-color: #D3D3D3 !important; vertical-align: middle !important; overflow-x: hidden !important; }&#xA;#zldtaermwk .gt_stub { color: #333333 !important; background-color: #FFFFFF !important; font-size: 100% !important; font-weight: initial !important; text-transform: inherit !important; border-right-style: solid !important; border-right-width: 2px !important; border-right-color: #D3D3D3 !important; padding-left: 5px !important; padding-right: 5px !important; }&#xA;#zldtaermwk .gt_stub_row_group { color: #333333 !important; background-color: #FFFFFF !important; font-size: 100% !important; font-weight: initial !important; text-transform: inherit !important; border-right-style: solid !important; border-right-width: 2px !important; border-right-color: #D3D3D3 !important; padding-left: 5px !important; padding-right: 5px !important; vertical-align: top !important; }&#xA;#zldtaermwk .gt_row_group_first td { border-top-width: 2px !important; }&#xA;#zldtaermwk .gt_row_group_first th { border-top-width: 2px !important; }&#xA;#zldtaermwk .gt_striped { color: #333333 !important; background-color: #F4F4F4 !important; }&#xA;#zldtaermwk .gt_table_body { border-top-style: solid !important; border-top-width: 2px !important; border-top-color: #D3D3D3 !important; border-bottom-style: solid !important; border-bottom-width: 2px !important; border-bottom-color: #D3D3D3 !important; }&#xA;#zldtaermwk .gt_grand_summary_row { color: #333333 !important; background-color: #FFFFFF !important; text-transform: inherit !important; padding-top: 8px !important; padding-bottom: 8px !important; padding-left: 5px !important; padding-right: 5px !important; }&#xA;#zldtaermwk .gt_first_grand_summary_row_bottom { border-top-style: double !important; border-top-width: 6px !important; border-top-color: #D3D3D3 !important; }&#xA;#zldtaermwk .gt_last_grand_summary_row_top { border-bottom-style: double !important; border-bottom-width: 6px !important; border-bottom-color: #D3D3D3 !important; }&#xA;#zldtaermwk .gt_sourcenotes { color: #333333 !important; background-color: #FFFFFF !important; border-bottom-style: none !important; border-bottom-width: 2px !important; border-bottom-color: #D3D3D3 !important; border-left-style: none !important; border-left-width: 2px !important; border-left-color: #D3D3D3 !important; border-right-style: none !important; border-right-width: 2px !important; border-right-color: #D3D3D3 !important; }&#xA;#zldtaermwk .gt_sourcenote { font-size: 90% !important; padding-top: 4px !important; padding-bottom: 4px !important; padding-left: 5px !important; padding-right: 5px !important; text-align: left !important; }&#xA;#zldtaermwk .gt_left { text-align: left !important; }&#xA;#zldtaermwk .gt_center { text-align: center !important; }&#xA;#zldtaermwk .gt_right { text-align: right !important; font-variant-numeric: tabular-nums !important; }&#xA;#zldtaermwk .gt_font_normal { font-weight: normal !important; }&#xA;#zldtaermwk .gt_font_bold { font-weight: bold !important; }&#xA;#zldtaermwk .gt_font_italic { font-style: italic !important; }&#xA;#zldtaermwk .gt_super { font-size: 65% !important; }&#xA;#zldtaermwk .gt_footnote_marks { font-size: 75% !important; vertical-align: 0.4em !important; position: initial !important; }&#xA;#zldtaermwk .gt_asterisk { font-size: 100% !important; vertical-align: 0 !important; }&lt;/p&gt;&#xA;&lt;p&gt;&lt;/style&gt;&lt;/p&gt;&#xA;&lt;table class=&#34;gt_table&#34; data-quarto-disable-processing=&#34;false&#34; data-quarto-bootstrap=&#34;false&#34;&gt;&#xA;&lt;thead&gt;&#xA;  &lt;tr class=&#34;gt_heading&#34;&gt;&#xA;    &lt;td colspan=&#34;11&#34; class=&#34;gt_heading gt_title gt_font_normal&#34;&gt;Selected London Boroughs&lt;/td&gt;&#xA;  &lt;/tr&gt;&#xA;  &lt;tr class=&#34;gt_heading&#34;&gt;&#xA;    &lt;td colspan=&#34;11&#34; class=&#34;gt_heading gt_subtitle gt_font_normal gt_bottom_border&#34;&gt;Flat properties - Real prices (CPI-adjusted) - Q4 2025 vs 1, 2, 5, 10 years ago&lt;/td&gt;&#xA;  &lt;/tr&gt;&#xA;&lt;tr class=&#34;gt_col_headings gt_spanner_row&#34;&gt;&#xA;  &lt;th class=&#34;gt_col_heading gt_columns_bottom_border gt_left&#34; rowspan=&#34;2&#34; colspan=&#34;1&#34; scope=&#34;col&#34; id=&#34;&#34;&gt;&lt;/th&gt;&#xA;  &lt;th class=&#34;gt_center gt_columns_top_border gt_column_spanner_outer&#34; rowspan=&#34;1&#34; colspan=&#34;2&#34; scope=&#34;colgroup&#34; id=&#34;2025-(Current)&#34;&gt;&#xA;    &lt;span class=&#34;gt_column_spanner&#34;&gt;2025 (Current)&lt;/span&gt;&#xA;  &lt;/th&gt;&#xA;  &lt;th class=&#34;gt_center gt_columns_top_border gt_column_spanner_outer&#34; rowspan=&#34;1&#34; colspan=&#34;2&#34; scope=&#34;colgroup&#34; id=&#34;2024-(1y-ago)&#34;&gt;&#xA;    &lt;span class=&#34;gt_column_spanner&#34;&gt;2024 (1y ago)&lt;/span&gt;&#xA;  &lt;/th&gt;&#xA;  &lt;th class=&#34;gt_center gt_columns_top_border gt_column_spanner_outer&#34; rowspan=&#34;1&#34; colspan=&#34;2&#34; scope=&#34;colgroup&#34; id=&#34;2023-(2y-ago)&#34;&gt;&#xA;    &lt;span class=&#34;gt_column_spanner&#34;&gt;2023 (2y ago)&lt;/span&gt;&#xA;  &lt;/th&gt;&#xA;  &lt;th class=&#34;gt_center gt_columns_top_border gt_column_spanner_outer&#34; rowspan=&#34;1&#34; colspan=&#34;2&#34; scope=&#34;colgroup&#34; id=&#34;2020-(5y-ago)&#34;&gt;&#xA;    &lt;span class=&#34;gt_column_spanner&#34;&gt;2020 (5y ago)&lt;/span&gt;&#xA;  &lt;/th&gt;&#xA;  &lt;th class=&#34;gt_center gt_columns_top_border gt_column_spanner_outer&#34; rowspan=&#34;1&#34; colspan=&#34;2&#34; scope=&#34;colgroup&#34; id=&#34;2015-(10y-ago)&#34;&gt;&#xA;    &lt;span class=&#34;gt_column_spanner&#34;&gt;2015 (10y ago)&lt;/span&gt;&#xA;  &lt;/th&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr class=&#34;gt_col_headings&#34;&gt;&#xA;  &lt;th class=&#34;gt_col_heading gt_columns_bottom_border gt_right&#34; rowspan=&#34;1&#34; colspan=&#34;1&#34; scope=&#34;col&#34; id=&#34;2025-(Current)_Real&#34;&gt;Price (£)&lt;/th&gt;&#xA;  &lt;th class=&#34;gt_col_heading gt_columns_bottom_border gt_right&#34; rowspan=&#34;1&#34; colspan=&#34;1&#34; scope=&#34;col&#34; id=&#34;2025-(Current)_RealChg&#34;&gt;%&lt;/th&gt;&#xA;  &lt;th class=&#34;gt_col_heading gt_columns_bottom_border gt_right&#34; rowspan=&#34;1&#34; colspan=&#34;1&#34; scope=&#34;col&#34; id=&#34;2024-(1y-ago)_Real&#34;&gt;Price (£)&lt;/th&gt;&#xA;  &lt;th class=&#34;gt_col_heading gt_columns_bottom_border gt_right&#34; rowspan=&#34;1&#34; colspan=&#34;1&#34; scope=&#34;col&#34; id=&#34;2024-(1y-ago)_RealChg&#34;&gt;%&lt;/th&gt;&#xA;  &lt;th class=&#34;gt_col_heading gt_columns_bottom_border gt_right&#34; rowspan=&#34;1&#34; colspan=&#34;1&#34; scope=&#34;col&#34; id=&#34;2023-(2y-ago)_Real&#34;&gt;Price (£)&lt;/th&gt;&#xA;  &lt;th class=&#34;gt_col_heading gt_columns_bottom_border gt_right&#34; rowspan=&#34;1&#34; colspan=&#34;1&#34; scope=&#34;col&#34; id=&#34;2023-(2y-ago)_RealChg&#34;&gt;%&lt;/th&gt;&#xA;  &lt;th class=&#34;gt_col_heading gt_columns_bottom_border gt_right&#34; rowspan=&#34;1&#34; colspan=&#34;1&#34; scope=&#34;col&#34; id=&#34;2020-(5y-ago)_Real&#34;&gt;Price (£)&lt;/th&gt;&#xA;  &lt;th class=&#34;gt_col_heading gt_columns_bottom_border gt_right&#34; rowspan=&#34;1&#34; colspan=&#34;1&#34; scope=&#34;col&#34; id=&#34;2020-(5y-ago)_RealChg&#34;&gt;%&lt;/th&gt;&#xA;  &lt;th class=&#34;gt_col_heading gt_columns_bottom_border gt_right&#34; rowspan=&#34;1&#34; colspan=&#34;1&#34; scope=&#34;col&#34; id=&#34;2015-(10y-ago)_Real&#34;&gt;Price (£)&lt;/th&gt;&#xA;  &lt;th class=&#34;gt_col_heading gt_columns_bottom_border gt_right&#34; rowspan=&#34;1&#34; colspan=&#34;1&#34; scope=&#34;col&#34; id=&#34;2015-(10y-ago)_RealChg&#34;&gt;%&lt;/th&gt;&#xA;&lt;/tr&gt;&#xA;&lt;/thead&gt;&#xA;&lt;tbody class=&#34;gt_table_body&#34;&gt;&#xA;  &lt;tr&gt;&#xA;    &lt;th class=&#34;gt_row gt_left gt_stub&#34;&gt;Ealing&lt;/th&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;404,408&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #efc5c3;&#34; class=&#34;gt_row gt_right&#34;&gt;0.0&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;419,269&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #ecb4b1;&#34; class=&#34;gt_row gt_right&#34;&gt;−3.5&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;419,733&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #ecb3b0;&#34; class=&#34;gt_row gt_right&#34;&gt;−3.7&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;489,258&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #e16f69;&#34; class=&#34;gt_row gt_right&#34;&gt;−17.3&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;521,670&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #dd554e;&#34; class=&#34;gt_row gt_right&#34;&gt;−22.5&lt;/td&gt;&#xA;  &lt;/tr&gt;&#xA;  &lt;tr&gt;&#xA;    &lt;th class=&#34;gt_row gt_left gt_stub&#34;&gt;Hackney&lt;/th&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;546,932&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #efc5c3;&#34; class=&#34;gt_row gt_right&#34;&gt;0.0&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;554,931&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #eebebc;&#34; class=&#34;gt_row gt_right&#34;&gt;−1.4&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;583,301&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #eaa6a3;&#34; class=&#34;gt_row gt_right&#34;&gt;−6.2&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;685,095&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #df615a;&#34; class=&#34;gt_row gt_right&#34;&gt;−20.2&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;667,622&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #e16b65;&#34; class=&#34;gt_row gt_right&#34;&gt;−18.1&lt;/td&gt;&#xA;  &lt;/tr&gt;&#xA;  &lt;tr&gt;&#xA;    &lt;th class=&#34;gt_row gt_left gt_stub&#34;&gt;Hammersmith And Fulham&lt;/th&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;577,801&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #efc5c3;&#34; class=&#34;gt_row gt_right&#34;&gt;0.0&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;639,856&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #e79591;&#34; class=&#34;gt_row gt_right&#34;&gt;−9.7&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;707,815&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #e06a63;&#34; class=&#34;gt_row gt_right&#34;&gt;−18.4&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;760,674&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #dc4e46;&#34; class=&#34;gt_row gt_right&#34;&gt;−24.0&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;883,098&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #808080;&#34; class=&#34;gt_row gt_right&#34;&gt;−34.6&lt;/td&gt;&#xA;  &lt;/tr&gt;&#xA;  &lt;tr&gt;&#xA;    &lt;th class=&#34;gt_row gt_left gt_stub&#34;&gt;Hounslow&lt;/th&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;367,011&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #efc5c3;&#34; class=&#34;gt_row gt_right&#34;&gt;0.0&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;375,391&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #edbab7;&#34; class=&#34;gt_row gt_right&#34;&gt;−2.2&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;373,890&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #eebcb9;&#34; class=&#34;gt_row gt_right&#34;&gt;−1.8&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;441,401&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #e2716b;&#34; class=&#34;gt_row gt_right&#34;&gt;−16.9&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;419,450&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #e58782;&#34; class=&#34;gt_row gt_right&#34;&gt;−12.5&lt;/td&gt;&#xA;  &lt;/tr&gt;&#xA;  &lt;tr&gt;&#xA;    &lt;th class=&#34;gt_row gt_left gt_stub&#34;&gt;Islington&lt;/th&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;566,442&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #efc5c3;&#34; class=&#34;gt_row gt_right&#34;&gt;0.0&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;599,386&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #ebaaa6;&#34; class=&#34;gt_row gt_right&#34;&gt;−5.5&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;623,593&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #e89893;&#34; class=&#34;gt_row gt_right&#34;&gt;−9.2&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;725,791&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #dd5851;&#34; class=&#34;gt_row gt_right&#34;&gt;−22.0&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;771,081&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #da4139;&#34; class=&#34;gt_row gt_right&#34;&gt;−26.5&lt;/td&gt;&#xA;  &lt;/tr&gt;&#xA;  &lt;tr&gt;&#xA;    &lt;th class=&#34;gt_row gt_left gt_stub&#34;&gt;Richmond Upon Thames&lt;/th&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;483,018&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #efc5c3;&#34; class=&#34;gt_row gt_right&#34;&gt;0.0&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;524,730&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #e99e9a;&#34; class=&#34;gt_row gt_right&#34;&gt;−7.9&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;520,433&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #e9a19e;&#34; class=&#34;gt_row gt_right&#34;&gt;−7.2&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;601,897&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #df635c;&#34; class=&#34;gt_row gt_right&#34;&gt;−19.8&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;633,108&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #dc4f48;&#34; class=&#34;gt_row gt_right&#34;&gt;−23.7&lt;/td&gt;&#xA;  &lt;/tr&gt;&#xA;  &lt;tr&gt;&#xA;    &lt;th class=&#34;gt_row gt_left gt_stub&#34;&gt;Wandsworth&lt;/th&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;540,034&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #efc5c3;&#34; class=&#34;gt_row gt_right&#34;&gt;0.0&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;568,240&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #ebada9;&#34; class=&#34;gt_row gt_right&#34;&gt;−5.0&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;603,492&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #e7918c;&#34; class=&#34;gt_row gt_right&#34;&gt;−10.5&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;715,552&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #db4b43;&#34; class=&#34;gt_row gt_right&#34;&gt;−24.5&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;761,426&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #FFFFFF; background-color: #d8352c;&#34; class=&#34;gt_row gt_right&#34;&gt;−29.1&lt;/td&gt;&#xA;  &lt;/tr&gt;&#xA;&lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;h3 id=&#34;terraced-houses-in-london-boroughs&#34; class=&#34;content-heading&#34;&gt;Terraced houses in London Boroughs&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;Next, I am looking at terraced houses in the same London boroughs. My expectation is that they did better than flats, especially since they were more popular during the COVID pandemic, where people wanted a garden.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;fig&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;axes&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;plt&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;subplots&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;figsize&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;14&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;8&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;plot_prices_by_type&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;df_boroughs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;Terraced&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;axes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;Price&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;Terraced houses in selected London boroughs&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;&#xA;&#xA;&lt;img src=&#34;https://staticnotes.org/posts/uk-house-prices/output_29_0.png&#34; alt=&#34;png&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;Indeed, terraced houses did better in nominal terms with all of them up. Again in real terms we see price decreases over all considered comparison years (1,2,5,10). However, it is less drastic than for flats in the same locations.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;regions&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;sorted&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;df&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;Region&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;unique&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;())&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;gt_regions_flat&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;create_table&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;df_boroughs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;Terraced&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;s1&#34;&gt;&amp;#39;Selected London Boroughs&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;regions&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;gt_regions_flat&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;div id=&#34;ppuntbiqeb&#34; style=&#34;padding-left:0px;padding-right:0px;padding-top:10px;padding-bottom:10px;overflow-x:auto;overflow-y:auto;width:auto;height:auto;&#34;&gt;&#xA;&lt;style&gt;&#xA;#ppuntbiqeb table {&#xA;          font-family: -apple-system, BlinkMacSystemFont, &#39;Segoe UI&#39;, Roboto, Oxygen, Ubuntu, Cantarell, &#39;Helvetica Neue&#39;, &#39;Fira Sans&#39;, &#39;Droid Sans&#39;, Arial, sans-serif;&#xA;          -webkit-font-smoothing: antialiased;&#xA;          -moz-osx-font-smoothing: grayscale;&#xA;        }&#xA;&lt;p&gt;#ppuntbiqeb thead, tbody, tfoot, tr, td, th { border-style: none !important; }&#xA;tr { background-color: transparent !important; }&#xA;#ppuntbiqeb p { margin: 0 !important; padding: 0 !important; }&#xA;#ppuntbiqeb .gt_table { display: table !important; border-collapse: collapse !important; line-height: normal !important; margin-left: auto !important; margin-right: auto !important; color: #333333 !important; font-size: 12px !important; font-weight: normal !important; font-style: normal !important; background-color: #FFFFFF !important; width: auto !important; border-top-style: solid !important; border-top-width: 2px !important; border-top-color: #A8A8A8 !important; border-right-style: none !important; border-right-width: 2px !important; border-right-color: #D3D3D3 !important; border-bottom-style: solid !important; border-bottom-width: 2px !important; border-bottom-color: #A8A8A8 !important; border-left-style: none !important; border-left-width: 2px !important; border-left-color: #D3D3D3 !important; }&#xA;#ppuntbiqeb .gt_caption { padding-top: 4px !important; padding-bottom: 4px !important; }&#xA;#ppuntbiqeb .gt_title { color: #333333 !important; font-size: 125% !important; font-weight: initial !important; padding-top: 4px !important; padding-bottom: 4px !important; padding-left: 5px !important; padding-right: 5px !important; border-bottom-color: #FFFFFF !important; border-bottom-width: 0 !important; }&#xA;#ppuntbiqeb .gt_subtitle { color: #333333 !important; font-size: 85% !important; font-weight: initial !important; padding-top: 3px !important; padding-bottom: 5px !important; padding-left: 5px !important; padding-right: 5px !important; border-top-color: #FFFFFF !important; border-top-width: 0 !important; }&#xA;#ppuntbiqeb .gt_heading { background-color: #FFFFFF !important; text-align: center !important; border-bottom-color: #FFFFFF !important; border-left-style: none !important; border-left-width: 1px !important; border-left-color: #D3D3D3 !important; border-right-style: none !important; border-right-width: 1px !important; border-right-color: #D3D3D3 !important; }&#xA;#ppuntbiqeb .gt_bottom_border { border-bottom-style: solid !important; border-bottom-width: 2px !important; border-bottom-color: #D3D3D3 !important; }&#xA;#ppuntbiqeb .gt_col_headings { border-top-style: solid !important; border-top-width: 2px !important; border-top-color: #D3D3D3 !important; border-bottom-style: solid !important; border-bottom-width: 2px !important; border-bottom-color: #D3D3D3 !important; border-left-style: none !important; border-left-width: 1px !important; border-left-color: #D3D3D3 !important; border-right-style: none !important; border-right-width: 1px !important; border-right-color: #D3D3D3 !important; }&#xA;#ppuntbiqeb .gt_col_heading { color: #333333 !important; background-color: #FFFFFF !important; font-size: 100% !important; font-weight: normal !important; text-transform: inherit !important; border-left-style: none !important; border-left-width: 1px !important; border-left-color: #D3D3D3 !important; border-right-style: none !important; border-right-width: 1px !important; border-right-color: #D3D3D3 !important; vertical-align: bottom !important; padding-top: 5px !important; padding-bottom: 5px !important; padding-left: 5px !important; padding-right: 5px !important; overflow-x: hidden !important; }&#xA;#ppuntbiqeb .gt_column_spanner_outer { color: #333333 !important; background-color: #FFFFFF !important; font-size: 100% !important; font-weight: normal !important; text-transform: inherit !important; padding-top: 0 !important; padding-bottom: 0 !important; padding-left: 4px !important; padding-right: 4px !important; }&#xA;#ppuntbiqeb .gt_column_spanner_outer:first-child { padding-left: 0 !important; }&#xA;#ppuntbiqeb .gt_column_spanner_outer:last-child { padding-right: 0 !important; }&#xA;#ppuntbiqeb .gt_column_spanner { border-bottom-style: solid !important; border-bottom-width: 2px !important; border-bottom-color: #D3D3D3 !important; vertical-align: bottom !important; padding-top: 5px !important; padding-bottom: 5px !important; overflow-x: hidden !important; display: inline-block !important; width: 100% !important; }&#xA;#ppuntbiqeb .gt_spanner_row { border-bottom-style: hidden !important; }&#xA;#ppuntbiqeb .gt_group_heading { padding-top: 8px !important; padding-bottom: 8px !important; padding-left: 5px !important; padding-right: 5px !important; color: #333333 !important; background-color: #FFFFFF !important; font-size: 100% !important; font-weight: initial !important; text-transform: inherit !important; border-top-style: solid !important; border-top-width: 2px !important; border-top-color: #D3D3D3 !important; border-bottom-style: solid !important; border-bottom-width: 2px !important; border-bottom-color: #D3D3D3 !important; border-left-style: none !important; border-left-width: 1px !important; border-left-color: #D3D3D3 !important; border-right-style: none !important; border-right-width: 1px !important; border-right-color: #D3D3D3 !important; vertical-align: middle !important; text-align: left !important; }&#xA;#ppuntbiqeb .gt_empty_group_heading { padding: 0.5px !important; color: #333333 !important; background-color: #FFFFFF !important; font-size: 100% !important; font-weight: initial !important; border-top-style: solid !important; border-top-width: 2px !important; border-top-color: #D3D3D3 !important; border-bottom-style: solid !important; border-bottom-width: 2px !important; border-bottom-color: #D3D3D3 !important; vertical-align: middle !important; }&#xA;#ppuntbiqeb .gt_from_md&amp;gt; :first-child { margin-top: 0 !important; }&#xA;#ppuntbiqeb .gt_from_md&amp;gt; :last-child { margin-bottom: 0 !important; }&#xA;#ppuntbiqeb .gt_row { padding-top: 8px !important; padding-bottom: 8px !important; padding-left: 5px !important; padding-right: 5px !important; margin: 10px !important; border-top-style: solid !important; border-top-width: 1px !important; border-top-color: #D3D3D3 !important; border-left-style: none !important; border-left-width: 1px !important; border-left-color: #D3D3D3 !important; border-right-style: none !important; border-right-width: 1px !important; border-right-color: #D3D3D3 !important; vertical-align: middle !important; overflow-x: hidden !important; }&#xA;#ppuntbiqeb .gt_stub { color: #333333 !important; background-color: #FFFFFF !important; font-size: 100% !important; font-weight: initial !important; text-transform: inherit !important; border-right-style: solid !important; border-right-width: 2px !important; border-right-color: #D3D3D3 !important; padding-left: 5px !important; padding-right: 5px !important; }&#xA;#ppuntbiqeb .gt_stub_row_group { color: #333333 !important; background-color: #FFFFFF !important; font-size: 100% !important; font-weight: initial !important; text-transform: inherit !important; border-right-style: solid !important; border-right-width: 2px !important; border-right-color: #D3D3D3 !important; padding-left: 5px !important; padding-right: 5px !important; vertical-align: top !important; }&#xA;#ppuntbiqeb .gt_row_group_first td { border-top-width: 2px !important; }&#xA;#ppuntbiqeb .gt_row_group_first th { border-top-width: 2px !important; }&#xA;#ppuntbiqeb .gt_striped { color: #333333 !important; background-color: #F4F4F4 !important; }&#xA;#ppuntbiqeb .gt_table_body { border-top-style: solid !important; border-top-width: 2px !important; border-top-color: #D3D3D3 !important; border-bottom-style: solid !important; border-bottom-width: 2px !important; border-bottom-color: #D3D3D3 !important; }&#xA;#ppuntbiqeb .gt_grand_summary_row { color: #333333 !important; background-color: #FFFFFF !important; text-transform: inherit !important; padding-top: 8px !important; padding-bottom: 8px !important; padding-left: 5px !important; padding-right: 5px !important; }&#xA;#ppuntbiqeb .gt_first_grand_summary_row_bottom { border-top-style: double !important; border-top-width: 6px !important; border-top-color: #D3D3D3 !important; }&#xA;#ppuntbiqeb .gt_last_grand_summary_row_top { border-bottom-style: double !important; border-bottom-width: 6px !important; border-bottom-color: #D3D3D3 !important; }&#xA;#ppuntbiqeb .gt_sourcenotes { color: #333333 !important; background-color: #FFFFFF !important; border-bottom-style: none !important; border-bottom-width: 2px !important; border-bottom-color: #D3D3D3 !important; border-left-style: none !important; border-left-width: 2px !important; border-left-color: #D3D3D3 !important; border-right-style: none !important; border-right-width: 2px !important; border-right-color: #D3D3D3 !important; }&#xA;#ppuntbiqeb .gt_sourcenote { font-size: 90% !important; padding-top: 4px !important; padding-bottom: 4px !important; padding-left: 5px !important; padding-right: 5px !important; text-align: left !important; }&#xA;#ppuntbiqeb .gt_left { text-align: left !important; }&#xA;#ppuntbiqeb .gt_center { text-align: center !important; }&#xA;#ppuntbiqeb .gt_right { text-align: right !important; font-variant-numeric: tabular-nums !important; }&#xA;#ppuntbiqeb .gt_font_normal { font-weight: normal !important; }&#xA;#ppuntbiqeb .gt_font_bold { font-weight: bold !important; }&#xA;#ppuntbiqeb .gt_font_italic { font-style: italic !important; }&#xA;#ppuntbiqeb .gt_super { font-size: 65% !important; }&#xA;#ppuntbiqeb .gt_footnote_marks { font-size: 75% !important; vertical-align: 0.4em !important; position: initial !important; }&#xA;#ppuntbiqeb .gt_asterisk { font-size: 100% !important; vertical-align: 0 !important; }&lt;/p&gt;&#xA;&lt;p&gt;&lt;/style&gt;&lt;/p&gt;&#xA;&lt;table class=&#34;gt_table&#34; data-quarto-disable-processing=&#34;false&#34; data-quarto-bootstrap=&#34;false&#34;&gt;&#xA;&lt;thead&gt;&#xA;  &lt;tr class=&#34;gt_heading&#34;&gt;&#xA;    &lt;td colspan=&#34;11&#34; class=&#34;gt_heading gt_title gt_font_normal&#34;&gt;Selected London Boroughs&lt;/td&gt;&#xA;  &lt;/tr&gt;&#xA;  &lt;tr class=&#34;gt_heading&#34;&gt;&#xA;    &lt;td colspan=&#34;11&#34; class=&#34;gt_heading gt_subtitle gt_font_normal gt_bottom_border&#34;&gt;Terraced properties - Real prices (CPI-adjusted) - Q4 2025 vs 1, 2, 5, 10 years ago&lt;/td&gt;&#xA;  &lt;/tr&gt;&#xA;&lt;tr class=&#34;gt_col_headings gt_spanner_row&#34;&gt;&#xA;  &lt;th class=&#34;gt_col_heading gt_columns_bottom_border gt_left&#34; rowspan=&#34;2&#34; colspan=&#34;1&#34; scope=&#34;col&#34; id=&#34;&#34;&gt;&lt;/th&gt;&#xA;  &lt;th class=&#34;gt_center gt_columns_top_border gt_column_spanner_outer&#34; rowspan=&#34;1&#34; colspan=&#34;2&#34; scope=&#34;colgroup&#34; id=&#34;2025-(Current)&#34;&gt;&#xA;    &lt;span class=&#34;gt_column_spanner&#34;&gt;2025 (Current)&lt;/span&gt;&#xA;  &lt;/th&gt;&#xA;  &lt;th class=&#34;gt_center gt_columns_top_border gt_column_spanner_outer&#34; rowspan=&#34;1&#34; colspan=&#34;2&#34; scope=&#34;colgroup&#34; id=&#34;2024-(1y-ago)&#34;&gt;&#xA;    &lt;span class=&#34;gt_column_spanner&#34;&gt;2024 (1y ago)&lt;/span&gt;&#xA;  &lt;/th&gt;&#xA;  &lt;th class=&#34;gt_center gt_columns_top_border gt_column_spanner_outer&#34; rowspan=&#34;1&#34; colspan=&#34;2&#34; scope=&#34;colgroup&#34; id=&#34;2023-(2y-ago)&#34;&gt;&#xA;    &lt;span class=&#34;gt_column_spanner&#34;&gt;2023 (2y ago)&lt;/span&gt;&#xA;  &lt;/th&gt;&#xA;  &lt;th class=&#34;gt_center gt_columns_top_border gt_column_spanner_outer&#34; rowspan=&#34;1&#34; colspan=&#34;2&#34; scope=&#34;colgroup&#34; id=&#34;2020-(5y-ago)&#34;&gt;&#xA;    &lt;span class=&#34;gt_column_spanner&#34;&gt;2020 (5y ago)&lt;/span&gt;&#xA;  &lt;/th&gt;&#xA;  &lt;th class=&#34;gt_center gt_columns_top_border gt_column_spanner_outer&#34; rowspan=&#34;1&#34; colspan=&#34;2&#34; scope=&#34;colgroup&#34; id=&#34;2015-(10y-ago)&#34;&gt;&#xA;    &lt;span class=&#34;gt_column_spanner&#34;&gt;2015 (10y ago)&lt;/span&gt;&#xA;  &lt;/th&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr class=&#34;gt_col_headings&#34;&gt;&#xA;  &lt;th class=&#34;gt_col_heading gt_columns_bottom_border gt_right&#34; rowspan=&#34;1&#34; colspan=&#34;1&#34; scope=&#34;col&#34; id=&#34;2025-(Current)_Real&#34;&gt;Price (£)&lt;/th&gt;&#xA;  &lt;th class=&#34;gt_col_heading gt_columns_bottom_border gt_right&#34; rowspan=&#34;1&#34; colspan=&#34;1&#34; scope=&#34;col&#34; id=&#34;2025-(Current)_RealChg&#34;&gt;%&lt;/th&gt;&#xA;  &lt;th class=&#34;gt_col_heading gt_columns_bottom_border gt_right&#34; rowspan=&#34;1&#34; colspan=&#34;1&#34; scope=&#34;col&#34; id=&#34;2024-(1y-ago)_Real&#34;&gt;Price (£)&lt;/th&gt;&#xA;  &lt;th class=&#34;gt_col_heading gt_columns_bottom_border gt_right&#34; rowspan=&#34;1&#34; colspan=&#34;1&#34; scope=&#34;col&#34; id=&#34;2024-(1y-ago)_RealChg&#34;&gt;%&lt;/th&gt;&#xA;  &lt;th class=&#34;gt_col_heading gt_columns_bottom_border gt_right&#34; rowspan=&#34;1&#34; colspan=&#34;1&#34; scope=&#34;col&#34; id=&#34;2023-(2y-ago)_Real&#34;&gt;Price (£)&lt;/th&gt;&#xA;  &lt;th class=&#34;gt_col_heading gt_columns_bottom_border gt_right&#34; rowspan=&#34;1&#34; colspan=&#34;1&#34; scope=&#34;col&#34; id=&#34;2023-(2y-ago)_RealChg&#34;&gt;%&lt;/th&gt;&#xA;  &lt;th class=&#34;gt_col_heading gt_columns_bottom_border gt_right&#34; rowspan=&#34;1&#34; colspan=&#34;1&#34; scope=&#34;col&#34; id=&#34;2020-(5y-ago)_Real&#34;&gt;Price (£)&lt;/th&gt;&#xA;  &lt;th class=&#34;gt_col_heading gt_columns_bottom_border gt_right&#34; rowspan=&#34;1&#34; colspan=&#34;1&#34; scope=&#34;col&#34; id=&#34;2020-(5y-ago)_RealChg&#34;&gt;%&lt;/th&gt;&#xA;  &lt;th class=&#34;gt_col_heading gt_columns_bottom_border gt_right&#34; rowspan=&#34;1&#34; colspan=&#34;1&#34; scope=&#34;col&#34; id=&#34;2015-(10y-ago)_Real&#34;&gt;Price (£)&lt;/th&gt;&#xA;  &lt;th class=&#34;gt_col_heading gt_columns_bottom_border gt_right&#34; rowspan=&#34;1&#34; colspan=&#34;1&#34; scope=&#34;col&#34; id=&#34;2015-(10y-ago)_RealChg&#34;&gt;%&lt;/th&gt;&#xA;&lt;/tr&gt;&#xA;&lt;/thead&gt;&#xA;&lt;tbody class=&#34;gt_table_body&#34;&gt;&#xA;  &lt;tr&gt;&#xA;    &lt;th class=&#34;gt_row gt_left gt_stub&#34;&gt;Ealing&lt;/th&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;690,513&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #efc5c3;&#34; class=&#34;gt_row gt_right&#34;&gt;0.0&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;697,849&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #eec0be;&#34; class=&#34;gt_row gt_right&#34;&gt;−1.1&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;690,002&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #efc6c3;&#34; class=&#34;gt_row gt_right&#34;&gt;0.1&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;788,622&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #e58782;&#34; class=&#34;gt_row gt_right&#34;&gt;−12.4&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;800,316&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #e4817c;&#34; class=&#34;gt_row gt_right&#34;&gt;−13.7&lt;/td&gt;&#xA;  &lt;/tr&gt;&#xA;  &lt;tr&gt;&#xA;    &lt;th class=&#34;gt_row gt_left gt_stub&#34;&gt;Hackney&lt;/th&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;972,274&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #efc5c3;&#34; class=&#34;gt_row gt_right&#34;&gt;0.0&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;966,520&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #efc8c6;&#34; class=&#34;gt_row gt_right&#34;&gt;0.6&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;1,006,975&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #ecb4b1;&#34; class=&#34;gt_row gt_right&#34;&gt;−3.4&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;1,158,935&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #e2756f;&#34; class=&#34;gt_row gt_right&#34;&gt;−16.1&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;1,073,030&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #e79792;&#34; class=&#34;gt_row gt_right&#34;&gt;−9.4&lt;/td&gt;&#xA;  &lt;/tr&gt;&#xA;  &lt;tr&gt;&#xA;    &lt;th class=&#34;gt_row gt_left gt_stub&#34;&gt;Hammersmith And Fulham&lt;/th&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;1,111,278&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #efc5c3;&#34; class=&#34;gt_row gt_right&#34;&gt;0.0&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;1,221,472&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #e89894;&#34; class=&#34;gt_row gt_right&#34;&gt;−9.0&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;1,334,009&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #e2726c;&#34; class=&#34;gt_row gt_right&#34;&gt;−16.7&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;1,392,109&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #df615a;&#34; class=&#34;gt_row gt_right&#34;&gt;−20.2&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;1,547,731&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #FFFFFF; background-color: #d83930;&#34; class=&#34;gt_row gt_right&#34;&gt;−28.2&lt;/td&gt;&#xA;  &lt;/tr&gt;&#xA;  &lt;tr&gt;&#xA;    &lt;th class=&#34;gt_row gt_left gt_stub&#34;&gt;Hounslow&lt;/th&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;625,089&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #efc5c3;&#34; class=&#34;gt_row gt_right&#34;&gt;0.0&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;622,316&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #efc7c5;&#34; class=&#34;gt_row gt_right&#34;&gt;0.4&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;612,967&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #f1cfcd;&#34; class=&#34;gt_row gt_right&#34;&gt;2.0&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;704,956&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #e68d88;&#34; class=&#34;gt_row gt_right&#34;&gt;−11.3&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;637,839&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #edbbb9;&#34; class=&#34;gt_row gt_right&#34;&gt;−2.0&lt;/td&gt;&#xA;  &lt;/tr&gt;&#xA;  &lt;tr&gt;&#xA;    &lt;th class=&#34;gt_row gt_left gt_stub&#34;&gt;Islington&lt;/th&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;1,124,856&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #efc5c3;&#34; class=&#34;gt_row gt_right&#34;&gt;0.0&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;1,175,162&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #ecb0ad;&#34; class=&#34;gt_row gt_right&#34;&gt;−4.3&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;1,214,208&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #e9a19d;&#34; class=&#34;gt_row gt_right&#34;&gt;−7.4&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;1,380,766&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #e06963;&#34; class=&#34;gt_row gt_right&#34;&gt;−18.5&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;1,380,624&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #e06963;&#34; class=&#34;gt_row gt_right&#34;&gt;−18.5&lt;/td&gt;&#xA;  &lt;/tr&gt;&#xA;  &lt;tr&gt;&#xA;    &lt;th class=&#34;gt_row gt_left gt_stub&#34;&gt;Richmond Upon Thames&lt;/th&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;890,118&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #efc5c3;&#34; class=&#34;gt_row gt_right&#34;&gt;0.0&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;935,936&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #ebadaa;&#34; class=&#34;gt_row gt_right&#34;&gt;−4.9&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;918,218&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #edb6b3;&#34; class=&#34;gt_row gt_right&#34;&gt;−3.1&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;1,036,584&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #e47f7a;&#34; class=&#34;gt_row gt_right&#34;&gt;−14.1&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;1,033,227&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #e4807b;&#34; class=&#34;gt_row gt_right&#34;&gt;−13.9&lt;/td&gt;&#xA;  &lt;/tr&gt;&#xA;  &lt;tr&gt;&#xA;    &lt;th class=&#34;gt_row gt_left gt_stub&#34;&gt;Wandsworth&lt;/th&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;965,788&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #efc5c3;&#34; class=&#34;gt_row gt_right&#34;&gt;0.0&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;1,000,163&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #ecb4b1;&#34; class=&#34;gt_row gt_right&#34;&gt;−3.4&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;1,050,418&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #e99d99;&#34; class=&#34;gt_row gt_right&#34;&gt;−8.1&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;1,214,261&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #df5f59;&#34; class=&#34;gt_row gt_right&#34;&gt;−20.5&lt;/td&gt;&#xA;    &lt;td class=&#34;gt_row gt_right&#34;&gt;&amp;#163;1,219,378&lt;/td&gt;&#xA;    &lt;td style=&#34;color: #000000; background-color: #de5e57;&#34; class=&#34;gt_row gt_right&#34;&gt;−20.8&lt;/td&gt;&#xA;  &lt;/tr&gt;&#xA;&lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;h1 id=&#34;winners-and-losers-in-real-terms-last-3-years&#34; class=&#34;content-heading&#34;&gt;Winners and Losers in real terms (last 3 years)&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;Lastly I want to see how the housing market in the different London boroughs compares to each other. I am looking at the prices 3 years ago across all property types. (At first I wanted to use 5 years, but I wanted to stay clear of the COVID years which might introduce short-term anomalies into the result). I am comparing real prices even though for a relative comparison between boroughs nominal prices would be fine as well (they all experienced the same inflation).&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;selected&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;df&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;df&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;is_london_borough&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;True&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;df&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;Quarter&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;isin&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;([&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;2025Q4&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;2022Q4&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]))&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;df&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;PropertyType&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;All&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;df_wide&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;selected&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;pivot&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;index&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;Region&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;PropertyType&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;],&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;columns&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;Quarter&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;values&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;RealPrice&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;reset_index&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;df_wide&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;%_real_price_change&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;df_wide&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;2025Q4&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;df_wide&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;2022Q4&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;])&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;/&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;df_wide&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;2022Q4&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;df_wide&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sort_values&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;%_real_price_change&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ascending&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;False&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ignore_index&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;True&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;head&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;10&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;div&gt;&#xA;&lt;style scoped&gt;&#xA;    .dataframe tbody tr th:only-of-type {&#xA;        vertical-align: middle;&#xA;    }&#xA;&lt;pre&gt;&lt;code&gt;.dataframe tbody tr th {&#xA;    vertical-align: top;&#xA;}&#xA;&#xA;.dataframe thead th {&#xA;    text-align: right;&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&lt;/style&gt;&lt;/p&gt;&#xA;&lt;table border=&#34;1&#34; class=&#34;dataframe&#34;&gt;&#xA;  &lt;thead&gt;&#xA;    &lt;tr style=&#34;text-align: right;&#34;&gt;&#xA;      &lt;th&gt;Quarter&lt;/th&gt;&#xA;      &lt;th&gt;Region&lt;/th&gt;&#xA;      &lt;th&gt;PropertyType&lt;/th&gt;&#xA;      &lt;th&gt;2022Q4&lt;/th&gt;&#xA;      &lt;th&gt;2025Q4&lt;/th&gt;&#xA;      &lt;th&gt;%_real_price_change&lt;/th&gt;&#xA;    &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;0&lt;/th&gt;&#xA;      &lt;td&gt;Lewisham&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;529841.12&lt;/td&gt;&#xA;      &lt;td&gt;503542.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.05&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;1&lt;/th&gt;&#xA;      &lt;td&gt;Havering&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;479610.47&lt;/td&gt;&#xA;      &lt;td&gt;450760.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.06&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;2&lt;/th&gt;&#xA;      &lt;td&gt;Southwark&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;647219.21&lt;/td&gt;&#xA;      &lt;td&gt;607299.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.06&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;3&lt;/th&gt;&#xA;      &lt;td&gt;Waltham Forest&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;569427.61&lt;/td&gt;&#xA;      &lt;td&gt;533922.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.06&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;4&lt;/th&gt;&#xA;      &lt;td&gt;Haringey&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;697080.71&lt;/td&gt;&#xA;      &lt;td&gt;649650.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.07&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;5&lt;/th&gt;&#xA;      &lt;td&gt;Hackney&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;681376.13&lt;/td&gt;&#xA;      &lt;td&gt;627538.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.08&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;6&lt;/th&gt;&#xA;      &lt;td&gt;Hounslow&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;588525.22&lt;/td&gt;&#xA;      &lt;td&gt;540799.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.08&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;7&lt;/th&gt;&#xA;      &lt;td&gt;Sutton&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;493375.92&lt;/td&gt;&#xA;      &lt;td&gt;452726.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.08&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;8&lt;/th&gt;&#xA;      &lt;td&gt;Hillingdon&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;526316.99&lt;/td&gt;&#xA;      &lt;td&gt;482852.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.08&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;9&lt;/th&gt;&#xA;      &lt;td&gt;Barking And Dagenham&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;387895.94&lt;/td&gt;&#xA;      &lt;td&gt;354709.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.09&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;You can see that even the best-performing borough Lewisham saw decreasing real house prices of 5% in the last 3 years. Overall a lot of these top 10 boroughs are further outside of London and generally more affordable areas. Both Hackney and Southwark are more central, but have recently undergone gentrification which might have counteracted the price pressure in other central boroughs.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;df_wide&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sort_values&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;%_real_price_change&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ascending&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;False&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ignore_index&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;True&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;tail&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;10&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;div&gt;&#xA;&lt;style scoped&gt;&#xA;    .dataframe tbody tr th:only-of-type {&#xA;        vertical-align: middle;&#xA;    }&#xA;&lt;pre&gt;&lt;code&gt;.dataframe tbody tr th {&#xA;    vertical-align: top;&#xA;}&#xA;&#xA;.dataframe thead th {&#xA;    text-align: right;&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&lt;/style&gt;&lt;/p&gt;&#xA;&lt;table border=&#34;1&#34; class=&#34;dataframe&#34;&gt;&#xA;  &lt;thead&gt;&#xA;    &lt;tr style=&#34;text-align: right;&#34;&gt;&#xA;      &lt;th&gt;Quarter&lt;/th&gt;&#xA;      &lt;th&gt;Region&lt;/th&gt;&#xA;      &lt;th&gt;PropertyType&lt;/th&gt;&#xA;      &lt;th&gt;2022Q4&lt;/th&gt;&#xA;      &lt;th&gt;2025Q4&lt;/th&gt;&#xA;      &lt;th&gt;%_real_price_change&lt;/th&gt;&#xA;    &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;23&lt;/th&gt;&#xA;      &lt;td&gt;Barnet&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;706765.75&lt;/td&gt;&#xA;      &lt;td&gt;606006.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.14&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;24&lt;/th&gt;&#xA;      &lt;td&gt;Camden&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;928639.42&lt;/td&gt;&#xA;      &lt;td&gt;792454.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.15&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;25&lt;/th&gt;&#xA;      &lt;td&gt;Croydon&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;464581.92&lt;/td&gt;&#xA;      &lt;td&gt;393479.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.15&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;26&lt;/th&gt;&#xA;      &lt;td&gt;Lambeth&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;654875.76&lt;/td&gt;&#xA;      &lt;td&gt;550720.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.16&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;27&lt;/th&gt;&#xA;      &lt;td&gt;Hammersmith And Fulham&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;883837.60&lt;/td&gt;&#xA;      &lt;td&gt;741308.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.16&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;28&lt;/th&gt;&#xA;      &lt;td&gt;Wandsworth&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;834257.35&lt;/td&gt;&#xA;      &lt;td&gt;695867.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.17&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;29&lt;/th&gt;&#xA;      &lt;td&gt;Tower Hamlets&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;595217.88&lt;/td&gt;&#xA;      &lt;td&gt;470209.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.21&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;30&lt;/th&gt;&#xA;      &lt;td&gt;Kensington And Chelsea&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;1637183.90&lt;/td&gt;&#xA;      &lt;td&gt;1194726.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.27&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;31&lt;/th&gt;&#xA;      &lt;td&gt;City Of Westminster&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;1273329.16&lt;/td&gt;&#xA;      &lt;td&gt;889935.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.30&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;32&lt;/th&gt;&#xA;      &lt;td&gt;City Of London&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;993657.65&lt;/td&gt;&#xA;      &lt;td&gt;607399.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.39&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;Some of the worst-performing boroughs over the last 3 years are the City of London, Westminster, Kensington &amp;amp; Chelsea, and Hammersmith and Fulham, which are all very expensive &amp;lsquo;prestige&amp;rsquo; areas.&lt;/p&gt;&#xA;&lt;h2 id=&#34;conclusion&#34; class=&#34;content-heading&#34;&gt;Conclusion&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;This analysis shows that a prospective home buyer or seller only gains limited information from a national-level house price statistic. Instead, I showed that there are, unsurprisingly, vast differences in price developments between property types and locations.&lt;/p&gt;&#xA;&lt;p&gt;Moreover, in real terms almost none of the considered regions saw increasing house prices over the last 10 years (notable exception: Kent).&lt;/p&gt;&#xA;&lt;h2 id=&#34;appendix&#34; class=&#34;content-heading&#34;&gt;Appendix&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Since someone asked, here are the real price changes compared to 3 years ago for all London boroughs:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;df_wide&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sort_values&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;%_real_price_change&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ascending&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;False&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ignore_index&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;True&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;div&gt;&#xA;&lt;style scoped&gt;&#xA;    .dataframe tbody tr th:only-of-type {&#xA;        vertical-align: middle;&#xA;    }&#xA;&lt;pre&gt;&lt;code&gt;.dataframe tbody tr th {&#xA;    vertical-align: top;&#xA;}&#xA;&#xA;.dataframe thead th {&#xA;    text-align: right;&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&lt;/style&gt;&lt;/p&gt;&#xA;&lt;table border=&#34;1&#34; class=&#34;dataframe&#34;&gt;&#xA;  &lt;thead&gt;&#xA;    &lt;tr style=&#34;text-align: right;&#34;&gt;&#xA;      &lt;th&gt;Quarter&lt;/th&gt;&#xA;      &lt;th&gt;Region&lt;/th&gt;&#xA;      &lt;th&gt;PropertyType&lt;/th&gt;&#xA;      &lt;th&gt;2022Q4&lt;/th&gt;&#xA;      &lt;th&gt;2025Q4&lt;/th&gt;&#xA;      &lt;th&gt;%_real_price_change&lt;/th&gt;&#xA;    &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;0&lt;/th&gt;&#xA;      &lt;td&gt;Lewisham&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;529841.12&lt;/td&gt;&#xA;      &lt;td&gt;503542.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.05&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;1&lt;/th&gt;&#xA;      &lt;td&gt;Havering&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;479610.47&lt;/td&gt;&#xA;      &lt;td&gt;450760.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.06&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;2&lt;/th&gt;&#xA;      &lt;td&gt;Southwark&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;647219.21&lt;/td&gt;&#xA;      &lt;td&gt;607299.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.06&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;3&lt;/th&gt;&#xA;      &lt;td&gt;Waltham Forest&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;569427.61&lt;/td&gt;&#xA;      &lt;td&gt;533922.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.06&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;4&lt;/th&gt;&#xA;      &lt;td&gt;Haringey&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;697080.71&lt;/td&gt;&#xA;      &lt;td&gt;649650.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.07&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;5&lt;/th&gt;&#xA;      &lt;td&gt;Hackney&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;681376.13&lt;/td&gt;&#xA;      &lt;td&gt;627538.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.08&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;6&lt;/th&gt;&#xA;      &lt;td&gt;Hounslow&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;588525.22&lt;/td&gt;&#xA;      &lt;td&gt;540799.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.08&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;7&lt;/th&gt;&#xA;      &lt;td&gt;Sutton&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;493375.92&lt;/td&gt;&#xA;      &lt;td&gt;452726.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.08&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;8&lt;/th&gt;&#xA;      &lt;td&gt;Hillingdon&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;526316.99&lt;/td&gt;&#xA;      &lt;td&gt;482852.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.08&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;9&lt;/th&gt;&#xA;      &lt;td&gt;Barking And Dagenham&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;387895.94&lt;/td&gt;&#xA;      &lt;td&gt;354709.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.09&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;10&lt;/th&gt;&#xA;      &lt;td&gt;Merton&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;677772.16&lt;/td&gt;&#xA;      &lt;td&gt;618798.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.09&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;11&lt;/th&gt;&#xA;      &lt;td&gt;Harrow&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;590633.55&lt;/td&gt;&#xA;      &lt;td&gt;538433.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.09&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;12&lt;/th&gt;&#xA;      &lt;td&gt;Bromley&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;581127.96&lt;/td&gt;&#xA;      &lt;td&gt;529749.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.09&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;13&lt;/th&gt;&#xA;      &lt;td&gt;Redbridge&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;524996.77&lt;/td&gt;&#xA;      &lt;td&gt;476764.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.09&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;14&lt;/th&gt;&#xA;      &lt;td&gt;Richmond Upon Thames&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;870795.06&lt;/td&gt;&#xA;      &lt;td&gt;785086.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.10&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;15&lt;/th&gt;&#xA;      &lt;td&gt;Enfield&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;533218.77&lt;/td&gt;&#xA;      &lt;td&gt;480183.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.10&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;16&lt;/th&gt;&#xA;      &lt;td&gt;Bexley&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;456886.19&lt;/td&gt;&#xA;      &lt;td&gt;410002.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.10&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;17&lt;/th&gt;&#xA;      &lt;td&gt;Islington&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;757742.76&lt;/td&gt;&#xA;      &lt;td&gt;677305.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.11&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;18&lt;/th&gt;&#xA;      &lt;td&gt;Ealing&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;645069.13&lt;/td&gt;&#xA;      &lt;td&gt;572575.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.11&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;19&lt;/th&gt;&#xA;      &lt;td&gt;Greenwich&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;533193.50&lt;/td&gt;&#xA;      &lt;td&gt;472599.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.11&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;20&lt;/th&gt;&#xA;      &lt;td&gt;Brent&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;639480.63&lt;/td&gt;&#xA;      &lt;td&gt;558093.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.13&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;21&lt;/th&gt;&#xA;      &lt;td&gt;Kingston Upon Thames&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;657636.32&lt;/td&gt;&#xA;      &lt;td&gt;573489.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.13&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;22&lt;/th&gt;&#xA;      &lt;td&gt;Newham&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;471479.67&lt;/td&gt;&#xA;      &lt;td&gt;405808.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.14&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;23&lt;/th&gt;&#xA;      &lt;td&gt;Barnet&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;706765.75&lt;/td&gt;&#xA;      &lt;td&gt;606006.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.14&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;24&lt;/th&gt;&#xA;      &lt;td&gt;Camden&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;928639.42&lt;/td&gt;&#xA;      &lt;td&gt;792454.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.15&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;25&lt;/th&gt;&#xA;      &lt;td&gt;Croydon&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;464581.92&lt;/td&gt;&#xA;      &lt;td&gt;393479.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.15&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;26&lt;/th&gt;&#xA;      &lt;td&gt;Lambeth&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;654875.76&lt;/td&gt;&#xA;      &lt;td&gt;550720.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.16&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;27&lt;/th&gt;&#xA;      &lt;td&gt;Hammersmith And Fulham&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;883837.60&lt;/td&gt;&#xA;      &lt;td&gt;741308.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.16&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;28&lt;/th&gt;&#xA;      &lt;td&gt;Wandsworth&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;834257.35&lt;/td&gt;&#xA;      &lt;td&gt;695867.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.17&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;29&lt;/th&gt;&#xA;      &lt;td&gt;Tower Hamlets&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;595217.88&lt;/td&gt;&#xA;      &lt;td&gt;470209.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.21&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;30&lt;/th&gt;&#xA;      &lt;td&gt;Kensington And Chelsea&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;1637183.90&lt;/td&gt;&#xA;      &lt;td&gt;1194726.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.27&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;31&lt;/th&gt;&#xA;      &lt;td&gt;City Of Westminster&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;1273329.16&lt;/td&gt;&#xA;      &lt;td&gt;889935.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.30&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;32&lt;/th&gt;&#xA;      &lt;td&gt;City Of London&lt;/td&gt;&#xA;      &lt;td&gt;All&lt;/td&gt;&#xA;      &lt;td&gt;993657.65&lt;/td&gt;&#xA;      &lt;td&gt;607399.00&lt;/td&gt;&#xA;      &lt;td&gt;-0.39&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;h2 id=&#34;jupyter-notebook&#34; class=&#34;content-heading&#34;&gt;Jupyter Notebook&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;You can find the Jupyter notebook for this post &lt;a href=&#34;https://gitlab.com/frankRi89/blog/-/tree/main/notebooks/uk-house-prices&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;here&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;.&lt;/p&gt;&#xA;</description>
    </item>
    
    <item>
      <title>TIL how to set up multiple SSH keys (for multiple GitHub accounts)</title>
      <link>https://staticnotes.org/til/2025/11/how-to-set-up-multiple-ssh-keys/</link>
      <pubDate>Fri, 28 Nov 2025 16:20:00 +0000</pubDate>
      
      <guid>https://staticnotes.org/til/2025/11/how-to-set-up-multiple-ssh-keys/</guid>
    <description>&lt;p&gt;Setting up SSH keys is one of these things I need to do every couple of months. Inevitably, I don&amp;rsquo;t remember how to do it and then follow some tutorial, e.g. the &lt;a href=&#34;https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;GitHub Docs&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;. It kind of always works but I haven&amp;rsquo;t fully internalised how to properly manage those different SSH keys.&lt;/p&gt;&#xA;&lt;p&gt;&lt;em&gt;Disclaimer&lt;/em&gt;: This learning was provoked by setting up multiple SSH keys for different GitHub accounts (I know how to do that) and then being able to &lt;code&gt;git clone {private-repository}&lt;/code&gt; a private repository with the correct ssh key (I didn&amp;rsquo;t know how to do that). An example use case could be a personal and professional GitHub account that don&amp;rsquo;t have access to the same private repositories. So ideally when we git clone we want to automatically pick the right ssh key.&lt;/p&gt;&#xA;&lt;h3 id=&#34;1-generate-the-ssh-keys-for-each-account&#34; class=&#34;content-heading&#34;&gt;1. Generate the SSH keys for each account&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s assume we want to generate new separate ssh keys for each account. I like to have them all in my &lt;code&gt;~/.ssh&lt;/code&gt; folder.&#xA;We can use the key generation command&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;bash&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ &lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; ~/.ssh&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ ssh-keygen -t ed25519 -C &lt;span class=&#34;s2&#34;&gt;&amp;#34;your_email or your_name&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;where we specify the encryption type (&lt;code&gt;-t&lt;/code&gt;) as &lt;code&gt;ed25519&lt;/code&gt; which is &lt;a href=&#34;https://en.wikipedia.org/wiki/EdDSA#Ed25519&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;Edwards-curve Digital Signature Algorithm&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;                style=&#34;height: 0.7em; width: 0.7em; margin-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;                class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;                viewBox=&#34;0 0 640 512&#34;&gt;&#xA;                &lt;path fill=&#34;currentColor&#34;&#xA;                    d=&#34;M640 51.2l-.3 12.2c-28.1 .8-45 15.8-55.8 40.3-25 57.8-103.3 240-155.3 358.6H415l-81.9-193.1c-32.5 63.6-68.3 130-99.2 193.1-.3 .3-15 0-15-.3C172 352.3 122.8 243.4 75.8 133.4 64.4 106.7 26.4 63.4 .2 63.7c0-3.1-.3-10-.3-14.2h161.9v13.9c-19.2 1.1-52.8 13.3-43.3 34.2 21.9 49.7 103.6 240.3 125.6 288.6 15-29.7 57.8-109.2 75.3-142.8-13.9-28.3-58.6-133.9-72.8-160-9.7-17.8-36.1-19.4-55.8-19.7V49.8l142.5 .3v13.1c-19.4 .6-38.1 7.8-29.4 26.1 18.9 40 30.6 68.1 48.1 104.7 5.6-10.8 34.7-69.4 48.1-100.8 8.9-20.6-3.9-28.6-38.6-29.4 .3-3.6 0-10.3 .3-13.6 44.4-.3 111.1-.3 123.1-.6v13.6c-22.5 .8-45.8 12.8-58.1 31.7l-59.2 122.8c6.4 16.1 63.3 142.8 69.2 156.7L559.2 91.8c-8.6-23.1-36.4-28.1-47.2-28.3V49.6l127.8 1.1 .2 .5z&#34;&gt;&#xA;                &lt;/path&gt;&#xA;            &lt;/svg&gt;&#xA;        &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt; and generally &lt;a href=&#34;https://www.latacora.com/blog/cryptographic-right-answers/&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;recommended over RSA-based keys&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;. The comment (&lt;code&gt;-C&lt;/code&gt;) argument allows us to optionally add a comment that is added at the back of the key so that you remember who created it. People often use their email address. However, this is not used anywhere to select the key.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;bash&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ Generating public/private ed25519 key pair.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ Enter file in which to save the key &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;/Users/youruser/.ssh/id_ed25519&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;: /Users/youruser/.ssh/github_personal&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;bash&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ Enter passphrase &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;github_personal&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;empty &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; no passphrase&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;: &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;This generates both the private and public key &lt;code&gt;github_personal&lt;/code&gt; (private) and &lt;code&gt;github_personal.pub&lt;/code&gt; (public) in the specified path. If we entered a passphrase the private key will be encrypted as well using &lt;a href=&#34;https://en.wikipedia.org/wiki/Triple_DES&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;3DES&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;                style=&#34;height: 0.7em; width: 0.7em; margin-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;                class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;                viewBox=&#34;0 0 640 512&#34;&gt;&#xA;                &lt;path fill=&#34;currentColor&#34;&#xA;                    d=&#34;M640 51.2l-.3 12.2c-28.1 .8-45 15.8-55.8 40.3-25 57.8-103.3 240-155.3 358.6H415l-81.9-193.1c-32.5 63.6-68.3 130-99.2 193.1-.3 .3-15 0-15-.3C172 352.3 122.8 243.4 75.8 133.4 64.4 106.7 26.4 63.4 .2 63.7c0-3.1-.3-10-.3-14.2h161.9v13.9c-19.2 1.1-52.8 13.3-43.3 34.2 21.9 49.7 103.6 240.3 125.6 288.6 15-29.7 57.8-109.2 75.3-142.8-13.9-28.3-58.6-133.9-72.8-160-9.7-17.8-36.1-19.4-55.8-19.7V49.8l142.5 .3v13.1c-19.4 .6-38.1 7.8-29.4 26.1 18.9 40 30.6 68.1 48.1 104.7 5.6-10.8 34.7-69.4 48.1-100.8 8.9-20.6-3.9-28.6-38.6-29.4 .3-3.6 0-10.3 .3-13.6 44.4-.3 111.1-.3 123.1-.6v13.6c-22.5 .8-45.8 12.8-58.1 31.7l-59.2 122.8c6.4 16.1 63.3 142.8 69.2 156.7L559.2 91.8c-8.6-23.1-36.4-28.1-47.2-28.3V49.6l127.8 1.1 .2 .5z&#34;&gt;&#xA;                &lt;/path&gt;&#xA;            &lt;/svg&gt;&#xA;        &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;. We can check out what the keys look like:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;bash&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ cat github_personal.pub&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDycaSFXnekEe32AZFhhDp3Q14JRXF7bu8CNfGf7Z6hN your_email &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;and&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&lt;pre&gt;&lt;code&gt;$ cat github_personal&#xA;&#xA;-----BEGIN OPENSSH PRIVATE KEY-----&#xA;somegibberylongkeyperhapsfurtherencryptedusingpassphrase&#xA;-----END OPENSSH PRIVATE KEY-----&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s also use the same method to create a keypair for work (&lt;code&gt;github_work&lt;/code&gt;, &lt;code&gt;github_work.pub&lt;/code&gt;). Once we have these keys, we can add them to our two GitHub accounts.&lt;/p&gt;&#xA;&lt;h3 id=&#34;2-associate-the-public-keys-with-the-github-accounts&#34; class=&#34;content-heading&#34;&gt;2. Associate the public keys with the GitHub accounts&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;In both GitHub accounts, navigate to &lt;code&gt;Access:SSH and GPG Keys&lt;/code&gt; -&amp;gt; &lt;code&gt;New SSH key&lt;/code&gt; and add the &lt;strong&gt;public key&lt;/strong&gt; as an &lt;em&gt;Authentication Key&lt;/em&gt; to the account. GitHub now has the ability to verify messages that were signed by the corresponding private key of the pair.&lt;/p&gt;&#xA;&lt;h3 id=&#34;3-set-up-openssh-client-config-file&#34; class=&#34;content-heading&#34;&gt;3. Set up OpenSSH client config file&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;The file &lt;code&gt;~/.ssh/config&lt;/code&gt; allows us to configure &amp;ldquo;Host&amp;rdquo; specifications that we can refer to when using SSH (e.g. when git clone uses the SSH protocol for data transfer). The file looks like this:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&lt;pre&gt;&lt;code&gt;Host hostname1&#xA;    SSH_OPTION value&#xA;    SSH_OPTION value&#xA;&#xA;Host hostname2&#xA;    SSH_OPTION value&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;In our case, we define the two different github identities that we want to be able to select:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&lt;pre&gt;&lt;code&gt;Host github-personal&#xA;    User git&#xA;    PreferredAuthentications publickey&#xA;    Hostname github.com&#xA;    IdentityFile ~/.ssh/github_personal&#xA;&#xA;Host github-work&#xA;    User git&#xA;    PreferredAuthentications publickey&#xA;    Hostname github.com&#xA;    IdentityFile ~/.ssh/github_work&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;This defines which keys to use when we connect using SSH via &lt;code&gt;user@host&lt;/code&gt;. So when we test the config by running &lt;code&gt;ssh -T git@github-personal&lt;/code&gt; SSH will use the SSH key &lt;code&gt;~/.ssh/github_personal&lt;/code&gt; as specified in the file. (Note: We can name the host and user whatever we want.)&lt;/p&gt;&#xA;&lt;h3 id=&#34;4-use-the-right-hostname-when-cloning-repositories&#34; class=&#34;content-heading&#34;&gt;4. Use the right hostname when cloning repositories&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s say we want to git clone a repository that only our work account has access to, then I can use the git clone command &lt;code&gt;git clone {user}@{host}:{repo_path}.git&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;bash&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ git clone git@github-work:/org1/project1.git&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;or more explicitly:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;bash&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ git clone ssh://git@github-work/org1/project.git&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;git/ssh will look into the &lt;code&gt;config&lt;/code&gt; file and select the configured private key for the provided host.&lt;/p&gt;&#xA;&lt;p&gt;GitHub will send a challenge (message) to our machine for authentication. The private key will be used to sign this challenge and our machine then sends the signed message back to the GitHub server. The GitHub server will use the stored public key (from step 2) to verify the signature and allow a connection.&lt;/p&gt;&#xA;&lt;p&gt;Note: If in step 1) we decided to encrypt the private key with a passphrase we will be prompted to provide it. Normally, at that point MacOS will offer us to add this passphrase to our &lt;a href=&#34;https://support.apple.com/en-gb/guide/keychain-access/welcome/mac&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;Keychain&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;.&lt;/p&gt;&#xA;</description>
    </item>
    
    <item>
      <title>TIL how to re-run only failed tests</title>
      <link>https://staticnotes.org/til/2025/11/pytest-rerun-failed/</link>
      <pubDate>Fri, 21 Nov 2025 13:00:00 +0000</pubDate>
      
      <guid>https://staticnotes.org/til/2025/11/pytest-rerun-failed/</guid>
    <description>&lt;p&gt;This is a super small thing I stumbled upon.&#xA;When I implement a new feature, there is inevitably a step where I go through the failing pytest tests and fix or clarify them.&lt;/p&gt;&#xA;&lt;p&gt;Unless I use an IDE, my work loop is like this:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Make code change&lt;/li&gt;&#xA;&lt;li&gt;Run &lt;code&gt;python -m pytest&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;Check which tests are failing.&lt;/li&gt;&#xA;&lt;li&gt;Go back to 1.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;Now, 2. runs all tests which can take quite some time. There is always the option to run a specific test only, but multiple failing tests can be scattered across different files.&lt;/p&gt;&#xA;&lt;p&gt;So instead, what you can do is to run only the tests that failed at your last test run. Simply use:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;bash&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;python -m pytest --last-failed&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;Once you have fixed those tests, it&amp;rsquo;s worth running all tests one more time to check that nothing else was broken by the new changes.&lt;/p&gt;&#xA;&lt;p&gt;Another option is to use the test view of your IDE, if you have access to one in that environment.&lt;/p&gt;&#xA;</description>
    </item>
    
    <item>
      <title>TIL how to investigate missing data easily with DuckDB</title>
      <link>https://staticnotes.org/til/2025/05/duckdb-glob_utility_function/</link>
      <pubDate>Fri, 16 May 2025 14:00:00 +0000</pubDate>
      
      <guid>https://staticnotes.org/til/2025/05/duckdb-glob_utility_function/</guid>
    <description>&lt;p&gt;In data engineering pipelines we commonly store output data in S3 buckets at various stages.&#xA;In this particular case we stored the input data received from customers as csv files in the S3 bucket &lt;code&gt;s3://customer-ingested/customerA.csv&lt;/code&gt; and after some transformations we stored the transformed data as csv files in another S3 bucket &lt;code&gt;s3://customer-transformed/customerA.csv&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;h3 id=&#34;problem&#34; class=&#34;content-heading&#34;&gt;Problem&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;For some of the customers the csv file was missing in the first bucket &lt;code&gt;customer-ingested&lt;/code&gt; while we were sure that all customers had a file in &lt;code&gt;customer-transformed&lt;/code&gt;. However, there was a legitimate reason why the file could be missing which was that in the application the customer had a state of &lt;code&gt;is_deactivated = True&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;The goal of this investigation was to find all the active customers for which we were missing data in the bucket &lt;code&gt;customer-ingested&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;h3 id=&#34;solution&#34; class=&#34;content-heading&#34;&gt;Solution&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;The nice thing about DuckDB in this scenario is that I can combine filesystem data with information from an application table and work in (familiar) SQL land.&lt;/p&gt;&#xA;&lt;p&gt;Here is the query:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;SQL&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-SQL&#34; data-lang=&#34;SQL&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;with&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;transformed&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;as&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;select&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;regexp_extract&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;file&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;/([^/]+)\.csv$&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;AS&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;filenames_transformed&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;from&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;glob&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;s3://customer-transformed/*.csv&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ingested&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;as&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;select&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;regexp_extract&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;file&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;/([^/]+)\.csv$&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;AS&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;filenames_ingested&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;from&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;glob&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;s3://customer-ingested/*.csv&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;customer_status&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;as&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;select&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;customer_name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;is_deactivated&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;from&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;read_parquet&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;s3://application_table_snapshot/customers.parquet&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;select&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;from&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;transformed&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;left&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;join&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ingested&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;on&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;transformed&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;filenames_transformed&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ingested&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;filenames_ingested&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;left&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;join&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;customer_status&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;as&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;cs&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;on&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;transformed&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;filenames_transformed&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;cs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;customer_name&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;where&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;is_deactivated&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;False&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;and&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;filenames_ingested&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;is&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;null&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&lt;pre&gt;&lt;code&gt;┌───────────────────────┬────────────────────┬────────────────────┬────────────────┐&#xA;│ filenames_transformed │ filenames_ingested │   customer_name    │ is_deactivated │&#xA;│        varchar        │      varchar       │      varchar       │    varchar     │&#xA;├───────────────────────┼────────────────────┼────────────────────┼────────────────┤&#xA;│ customerT             │ NULL               │ customerT          │ False          │&#xA;│ customerW             │ NULL               │ customerW          │ False          │&#xA;│ customerZ             │ NULL               │ customerZ          │ False          │&#xA;└───────────────────────┴────────────────────┴────────────────────┴────────────────┘&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;In the CTE, I am creating a table &lt;code&gt;transformed&lt;/code&gt; with all the filenames in the S3 bucket &lt;code&gt;customer-transformed&lt;/code&gt;. I am using DuckDB&amp;rsquo;s utility function &lt;code&gt;glob(search_path)&lt;/code&gt; to retrieve the filenames. I do the same for the bucket &lt;code&gt;customer-ingested&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;To get the customer status I read from a recent replication snapshot of the application database to get &lt;code&gt;customer_status&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Finally, I join the three expressions to identify missing files (missing values) in the column &lt;code&gt;filenames_ingested&lt;/code&gt; for active customers.&lt;/p&gt;&#xA;</description>
    </item>
    
    <item>
      <title>TIL that DuckDB now supports Avro files</title>
      <link>https://staticnotes.org/til/2025/03/duckdb-avro-support/</link>
      <pubDate>Wed, 12 Mar 2025 16:00:00 +0000</pubDate>
      
      <guid>https://staticnotes.org/til/2025/03/duckdb-avro-support/</guid>
    <description>&lt;p&gt;In the data engineering pipelines of my current company we use two main file formats:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Parquet files for analytical workloads (columnar-storage)&lt;/li&gt;&#xA;&lt;li&gt;Avro files for transactional / event-based messages (row-storage)&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;In &lt;a href=&#34;https://staticnotes.org/posts/duckdb-for-data-scientists/&#34; &#xA;&gt;Querying remote S3 files&#xA;&lt;/a&gt; I wrote about how I use DuckDB to query parquet files stored in S3. Recently, I noticed that DuckDB &lt;a href=&#34;https://duckdb.org/2024/12/09/duckdb-avro-extension.html&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;started supporting&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt; reading of &lt;a href=&#34;https://avro.apache.org/&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;Avro&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt; files in the same way.&lt;/p&gt;&#xA;&lt;h3 id=&#34;install-extension&#34; class=&#34;content-heading&#34;&gt;Install extension&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;To install the extension open DuckDB and run:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;sql&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;INSTALL&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;avro&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;FROM&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;community&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;LOAD&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;avro&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;h3 id=&#34;read-local-files&#34; class=&#34;content-heading&#34;&gt;Read local files&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;Local files can be queried with the &lt;code&gt;read_avro&lt;/code&gt; function:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;sql&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;select&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;FROM&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;read_avro&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;my_dataset.avro&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;h3 id=&#34;read-files-stored-in-s3&#34; class=&#34;content-heading&#34;&gt;Read files stored in S3&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;Remote files can be queried by providing the S3 URI (after &lt;a href=&#34;https://staticnotes.org/posts/duckdb-for-data-scientists/#authentication-with-aws&#34; &#xA;&gt;logging into the AWS account&#xA;&lt;/a&gt;):&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;sql&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;select&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;FROM&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;read_avro&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;s3://my_bucket/my_remote_dataset.avro&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;A few limitations of the extension are listed &lt;a href=&#34;https://duckdb.org/2024/12/09/duckdb-avro-extension.html#limitations--next-steps&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;here&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;.&lt;/p&gt;&#xA;</description>
    </item>
    
    <item>
      <title>Building a local data warehouse with DuckDB, dbt, and Superset</title>
      <link>https://staticnotes.org/posts/duckdb-dbt-superset/</link>
      <pubDate>Sun, 09 Mar 2025 15:00:00 +0000</pubDate>
      
      <guid>https://staticnotes.org/posts/duckdb-dbt-superset/</guid>
    <description>&lt;p&gt;In previous blog posts, I described two DuckDB use cases for data scientists and data engineers: &lt;a href=&#34;https://staticnotes.org/posts/duckdb-for-data-scientists/&#34; &#xA;&gt;Querying remote parquet files&#xA;&lt;/a&gt; and &lt;a href=&#34;https://staticnotes.org/posts/duckdb-large-datasets/&#34; &#xA;&gt;processing larger-than-memory datasets&#xA;&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Now I want to explore if I can set up a local and open-source version of the analytics data stack that we use at my company. At work, we are using Snowflake, dbt cloud, and Google&amp;rsquo;s Looker which cost us several thousand EUR per month. I am going to use the following open-source tools in my local setup:&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;&lt;/th&gt;&#xA;          &lt;th&gt;cloud&lt;/th&gt;&#xA;          &lt;th&gt;local&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;analytical database&lt;/td&gt;&#xA;          &lt;td&gt;snowflake&lt;/td&gt;&#xA;          &lt;td&gt;DuckDB&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;data modelling tool&lt;/td&gt;&#xA;          &lt;td&gt;dbt cloud&lt;/td&gt;&#xA;          &lt;td&gt;local dbt&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;dashboard tool&lt;/td&gt;&#xA;          &lt;td&gt;Looker&lt;/td&gt;&#xA;          &lt;td&gt;Apache Superset&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;Figure 1 shows how the components work together.&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&lt;figure&gt;&#xA;  &lt;div class=&#34;image-wrapper&#34;&gt;&#xA;  &lt;img src=&#34;https://staticnotes.org/posts/duckdb-dbt-superset/duckdb_dbt_superset_setup.png&#34; alt=&#34;Local data warehouse&#34; loading=&#34;lazy&#34; /&gt;&#xA;  &lt;figcaption&gt;Figure 1. Components of my local data warehouse stack.&lt;/figcaption&gt;&#xA;  &lt;/div&gt;&#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;I will use a toy visualisation problem to demonstrate the setup. My goal is to load the race time dataset that I used in &lt;a href=&#34;https://staticnotes.org/posts/vatternrundan-results/&#34; &#xA;&gt;Data analysis: Vätternrundan 2024 results&#xA;&lt;/a&gt; into DuckDB. I then use dbt to create data models and Superset to create an interactive dashboard to visualise the data.&lt;/p&gt;&#xA;&lt;h2 id=&#34;loading-the-raw-data-into-duckdb&#34; class=&#34;content-heading&#34;&gt;Loading the raw data into DuckDB&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;I want to use DuckDB for all data processing. Therefore, I will load the raw race time data into DuckDB.&lt;/p&gt;&#xA;&lt;p&gt;I start by installing DuckDB as my analytical database. Since I use homebrew as a package manager, I can run:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;sh&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;brew install duckdb&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;I then create a persistent database (a file on my machine):&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;sh&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;duckdb database.duckdb&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;and create a new DuckDB table from the parquet file that contains the raw data.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;sql&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;CREATE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;TABLE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;vatternrundan&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;AS&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;select&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;startnumber&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;city&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;country&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;result_time&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;start_time&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;from&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;read_parquet&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;results_vatternrundan24.parquet&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;To verify that everything works, I run:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;sql&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;describe&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;table&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;vatternrundan&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;sql&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;┌─────────────┬─────────────┬─────────┬─────────┬─────────┬─────────┐&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;column_name&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;column_type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;null&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;   &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;   &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;key&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;   &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;default&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;extra&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;   &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;varchar&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;   &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;   &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;varchar&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;   &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;varchar&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;varchar&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;varchar&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;varchar&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;├─────────────┼─────────────┼─────────┼─────────┼─────────┼─────────┤&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;startnumber&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;BIGINT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;YES&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;     &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;city&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;VARCHAR&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;     &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;YES&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;     &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;country&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;     &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;VARCHAR&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;     &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;YES&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;     &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;result_time&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;BIGINT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;YES&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;     &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;start_time&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;VARCHAR&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;     &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;YES&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;     &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;│&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;└─────────────┴─────────────┴─────────┴─────────┴─────────┴─────────┘&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;h2 id=&#34;setting-up-dbt-for-data-transformations&#34; class=&#34;content-heading&#34;&gt;Setting up dbt for data transformations&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;I want to use dbt to create data models on top of the raw data. I start by creating a new poetry project to install dependencies:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;sh&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;poetry init&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;poetry add duckdb-dbt&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;Then I initialise the dbt project &lt;code&gt;local_warehouse&lt;/code&gt; with&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;sh&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;poetry run dbt init local_warehouse&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;Next, I configure dbt to use DuckDB as the data processing backend. In my work setup, this would point at a Snowflake instance instead. I point at the local database file in the dbt profiles file &lt;code&gt;profiles.yml&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;yml&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;local_warehouse&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;outputs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;dev&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;duckdb&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;../duckdb/database.duckdb&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;target&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;dev&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;h3 id=&#34;dbt-models&#34; class=&#34;content-heading&#34;&gt;dbt models&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;I define the DuckDB table &lt;code&gt;vatternrundan&lt;/code&gt; as a dbt source in &lt;code&gt;sources.yml&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;yml&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;version&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;m&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;sources&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;vatternrundan_db&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;schema&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;main &lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;tables&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;vatternrundan&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;This means I can now refer to this raw data in dbt.&#xA;Moreover, I want to create two more models. One thin staging layer over the raw data &lt;code&gt;stg_vatternrundan.sql&lt;/code&gt;, and one refined model that aggregates the rider data by country &lt;code&gt;average_speed_by_country.sql&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;The staging model is stored in the dbt project as &lt;code&gt;models/staging/stg_vatternrundan.sql&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;sql&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;select&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;startnumber&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;::&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;int64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;as&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;startnumber&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;city&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;::&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;varchar&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;as&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;city&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;country&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;::&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;varchar&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;as&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;country&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;to_microseconds&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;((&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;result_time&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;/&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1000&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)::&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;int64&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;as&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;result_time&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;from&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;{{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;source&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;vatternrundan_db&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;vatternrundan&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;}}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;As you can see, I refer to the DuckDB source table, define the column types, and do some light conversions.&lt;/p&gt;&#xA;&lt;p&gt;The aggregation model is stored in &lt;code&gt;models/refined/average_speed_by_country.sql&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;sql&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;with&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;speeds&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;as&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;select&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;startnumber&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;country&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;60&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;datepart&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;hours&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;result_time&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;+&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;datepart&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;minutes&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;result_time&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;as&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;result_time_minutes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;315&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;60&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;/&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;result_time_minutes&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;as&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;average_speed&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;from&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;{{&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;ref&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;stg_vatternrundan&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;}}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;select&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;country&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;count&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;as&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;num_riders&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;mean&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;average_speed&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;as&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;average_speed_of_country&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;from&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;speeds&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;group&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;by&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;country&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;order&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;by&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;average_speed_of_country&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;desc&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;I use this dbt model to compute the average rider speed for every country in the dataset.&lt;/p&gt;&#xA;&lt;p&gt;Now that I have defined my dbt models, I configure dbt to run them by adding the following to &lt;code&gt;dbt_project.yml&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;yml&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;models&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;local_warehouse&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;staging&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;+schema&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;staging&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;+materialized&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;table&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;refined&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;+schema&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;refined&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;+materialized&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;table&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;This tells dbt how to materialise the dbt models in DuckDB. So let&amp;rsquo;s run dbt to build the models:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;sh&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;poetry run dbt run --profiles-dir&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;h3 id=&#34;check-tables-were-created&#34; class=&#34;content-heading&#34;&gt;Check tables were created&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;I can verify in DuckDB that these tables have been created&lt;/p&gt;&#xA;&lt;p&gt;Run&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;sh&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;duckdb database.duckdb&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;followed by:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;sql&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;show&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;all&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;tables&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;which should now show:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&lt;pre&gt;&lt;code&gt;database.main_staging.stg_vatternrundan&#xA;database.main_refined.average_speed_by_country&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;h2 id=&#34;superset-for-data-analytics-dashboards&#34; class=&#34;content-heading&#34;&gt;Superset for data analytics dashboards&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;After modelling my data, I want to create a dashboard and visualise the data. I use Apache Superset as my dashboarding tool. Superset works with many analytical databases. However, setting it up to work with DuckDB is a bit clunky and I had to troubleshoot quite a bit.&lt;/p&gt;&#xA;&lt;h3 id=&#34;install-superset-with-duckdb-support&#34; class=&#34;content-heading&#34;&gt;Install Superset with DuckDB support&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;I follow the &lt;a href=&#34;https://superset.apache.org/docs/quickstart/&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;Quickstart Guide&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt; with some modifications. First, I download the git repository and checkout the last tagged commit:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;sh&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;git clone https://github.com/apache/superset&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; superset&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;git checkout tags/4.1.1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;I need to make two modifications before building the container.&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Install &lt;code&gt;duckdb-engine&lt;/code&gt; inside the container. This allows us later to select DuckDB as a database in Superset. To do this, add&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;yml&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;l&#34;&gt;RUN pip install duckdb-engine&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;to the &lt;code&gt;Dockerfile&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;ol start=&#34;2&#34;&gt;&#xA;&lt;li&gt;I need to make the DuckDB database file available in the Docker container. I add its path as a volume. Modify &lt;code&gt;docker-compose-image-tag.yml&lt;/code&gt;:&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;yml&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;superset&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;l&#34;&gt;...]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;volumes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;./docker:/app/docker&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;superset_home:/app/superset_home&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;&amp;lt;local-machine-path-to-duckdb-database&amp;gt;:/app/duckdb  &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;and force the Superset container to build from the Dockerfile and not from the image:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;yml&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;superset&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;l&#34;&gt;...]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;build&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;dockerfile&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;Dockerfile&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c&#34;&gt;#   image: apache/superset: comment out or remove&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;Then build the container with&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;sh&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;docker compose build&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;and start Superset with&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;sh&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;docker compose -f docker-compose-image-tag.yml up&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;Then, I can access Superset with my browser under &lt;code&gt;http://localhost:8088&lt;/code&gt; and log in with username: &lt;code&gt;admin&lt;/code&gt; and password: &lt;code&gt;admin&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;h3 id=&#34;configure-superset&#34; class=&#34;content-heading&#34;&gt;Configure Superset&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;Now, I can add DuckDB as a database in the Superset UI. I navigate to &lt;code&gt;Settings&lt;/code&gt; -&amp;gt; &lt;code&gt;Data&lt;/code&gt; -&amp;gt; &lt;code&gt;Database Connections&lt;/code&gt; &lt;code&gt;+ Database&lt;/code&gt; -&amp;gt; &lt;code&gt;DuckDB&lt;/code&gt;&lt;/p&gt;&#xA;&lt;p&gt;and add the link to the DuckDB database file as the &lt;code&gt;SQLAlchemy URI&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&lt;pre&gt;&lt;code&gt;duckdb:////app/duckdb/database.duckdb?access_mode=READ_ONLY&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;Clicking on &lt;code&gt;Test Connection&lt;/code&gt; should respond with &amp;ldquo;Connection looks good!&amp;rdquo;.&lt;/p&gt;&#xA;&lt;h3 id=&#34;add-datasets-in-superset&#34; class=&#34;content-heading&#34;&gt;Add datasets in Superset&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;In the navbar under &lt;code&gt;Datasets&lt;/code&gt; I can add the DuckDB tables &lt;code&gt;stg_vatternrundan&lt;/code&gt; and &lt;code&gt;average_speed_by_country&lt;/code&gt; as new Superset datasets.&lt;/p&gt;&#xA;&lt;h3 id=&#34;create-dashboard-in-superset&#34; class=&#34;content-heading&#34;&gt;Create dashboard in Superset&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;Creating the dashboard is self-explanatory.&#xA;In the navbar, I select &lt;code&gt;Dashboards&lt;/code&gt; and add a new Dashboard called &amp;ldquo;Vätternrundan Dashboard&amp;rdquo;. Next, I create two charts:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;number of riders per country&lt;/li&gt;&#xA;&lt;li&gt;average speed of riders per country&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Just select the relevant Superset Dataset, build the charts, and add them to the dashboard. Figure 2 shows how I did it.&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&lt;figure&gt;&#xA;  &lt;div class=&#34;image-wrapper&#34;&gt;&#xA;  &lt;img src=&#34;https://staticnotes.org/posts/duckdb-dbt-superset/superset_dashboard.jpg&#34; alt=&#34;Superset Dashboard&#34; loading=&#34;lazy&#34; /&gt;&#xA;  &lt;figcaption&gt;Figure 2. Visualization of Vätternrundan rider speed by country in Apache Superset.&lt;/figcaption&gt;&#xA;  &lt;/div&gt;&#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;h2 id=&#34;conclusion&#34; class=&#34;content-heading&#34;&gt;Conclusion&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;The combination of DuckDB, dbt, and Apache Superset is a local version of &amp;ldquo;the modern datastack&amp;rdquo;. This can be useful for personal projects, and to experiment with data transformations and visualisations.&lt;/p&gt;&#xA;&lt;p&gt;I found that the integration of DuckDB and dbt worked seamlessly. On the other hand, connecting Superset to DuckDB was clunky. Superset would benefit from better DuckDB support out-of-the-box.&lt;/p&gt;&#xA;&lt;h2 id=&#34;troubleshooting&#34; class=&#34;content-heading&#34;&gt;Troubleshooting&#xA;&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;DuckDB only allows one connection with read-write-access, but multiple concurrent read-only connections. Make sure you are not connected to your DuckDB database with more than one client as access mode allows write by default, e.g. Superset and the DuckDB CLI client in your terminal.&lt;/li&gt;&#xA;&lt;li&gt;Ensure to connect Superset to DuckDB in read-only mode, i.e. add &lt;code&gt;access_mode=READ_ONLY&lt;/code&gt; to the SQLAlchemy URI to connect.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;</description>
    </item>
    
    <item>
      <title>Book notes: Poor Charlie’s Almanack</title>
      <link>https://staticnotes.org/posts/poor-charlies-almanack-notes/</link>
      <pubDate>Fri, 21 Feb 2025 00:00:00 +0000</pubDate>
      
      <guid>https://staticnotes.org/posts/poor-charlies-almanack-notes/</guid>
    <description>&lt;p&gt;The book is a collection of speeches that Charlie Munger, partner of Warren Buffett at Berkshire Hathaway, gave over the years (1986 - 2007) at universities and institutions.&lt;/p&gt;&#xA;&lt;p&gt;I found four interesting themes across the speeches:&lt;/p&gt;&#xA;&lt;h2 id=&#34;advocating-interdisciplinary-collaboration-between-university-departments&#34; class=&#34;content-heading&#34;&gt;Advocating interdisciplinary collaboration between university departments&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;He believes that the social sciences, especially economics and psychology, are focusing on too narrow theoretical problems. Academics suffer from a &lt;em&gt;man-with-a-hammer-syndrome&lt;/em&gt;. He suggests that they collaborate more across departments, e.g. economics borrowing from psychology, and within departments, e.g. macroeconomists shouldn&amp;rsquo;t avoid microeconomic explanations. Moreover, the social sciences should try to incorporate findings / models from the hard sciences (physics, chemistry, etc.) and attribute them properly.&lt;/p&gt;&#xA;&lt;h2 id=&#34;fraud-and-advice-for-endowment-funds&#34; class=&#34;content-heading&#34;&gt;Fraud and advice for endowment funds&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;In some of his talks, he uses fictional examples of companies to show the problems with accounting and financial engineering fraud.&lt;/p&gt;&#xA;&lt;p&gt;He thinks that charitable foundations and endowment funds should as much as possible avoid wasteful investment practices. He criticises the trend of university endowment funds to employ layers of analysts and consultants to use funds of funds. These multilayered systems erode a large chunk of the endowment funds&amp;rsquo; returns when compared to simpler alternatives, like unlevered domestic equity indices. He directly criticises the practices of his audience of consultants and fund managers in talk six, but he uses humour and self-deprecation to get away with it.&lt;/p&gt;&#xA;&lt;h2 id=&#34;inverted-advice-for-graduates&#34; class=&#34;content-heading&#34;&gt;(Inverted) Advice for Graduates&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;In his commencement speeches for university graduates, he gives advice on &lt;strong&gt;how to guarantee misery&lt;/strong&gt; in life.&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Be unreliable.&lt;/li&gt;&#xA;&lt;li&gt;Don&amp;rsquo;t learn from others&amp;rsquo; mistakes. Instead, make common mistakes of others again (join a cult, drive while drunk, gamble) and don&amp;rsquo;t learn from people that came before you.&lt;/li&gt;&#xA;&lt;li&gt;Give up after adversity and failures. Just give up when the inevitable hard times occur.&lt;/li&gt;&#xA;&lt;li&gt;Don&amp;rsquo;t ever invert. Don&amp;rsquo;t attempt to learn from thinking about achieving the opposite of your goals. Don&amp;rsquo;t try to be objective.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;mental-checklist-to-analyse-problems&#34; class=&#34;content-heading&#34;&gt;Mental checklist to analyse problems&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;A recurring topic in his speeches is a checklist of mental models and human biases. He developed this checklist over time and iterated through it when analysing a problem or evaluating an investment. Using appropriate checklists and &lt;a href=&#34;https://fs.blog/inversion/&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;the inversion technique&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt; are two main tools that he promotes for better thinking.&lt;/p&gt;&#xA;&lt;p&gt;Here are 25 human biases from talk eleven: &lt;a href=&#34;https://fs.blog/great-talks/psychology-human-misjudgment/&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;The Psychology of Human Misjudgment&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Reward and Punishment Superresponse Tendency&lt;/strong&gt;: If you want to persuade people, appeal to their own interests. Ensure the incentives of people you work with are aligned with the outcome you want. Don&amp;rsquo;t reward them for metrics that they can easily game.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Liking/Loving Tendency&lt;/strong&gt;: People are seeking love and approval from other people. Moreover, we favour people and products that are merely associated with the target of our affection. We can use this to our advantage by liking truly admirable people or ideas.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Disliking/Hating Tendency&lt;/strong&gt;: People can have a tendency to dislike things different from them or products or people that are associated with the object of their dislike.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Doubt-Avoidance Tendency&lt;/strong&gt;: Our brains are conditioned to quickly remove doubt after reaching a first decision. This tendency should be countered by forcing a delay for reflection before an important decision, e.g. jury decisions in court.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Inconsistency-Avoidance Tendency&lt;/strong&gt;: A tendency to stick to previous conclusions, habits, and ideas to avoid change. Practically, this means that it is much easier to prevent a bad habit than to change it. To counter this tendency, we should force the discussion of counterarguments before a decision can be made.&#xA;This tendency can be used to manipulate people (see also &lt;a href=&#34;https://en.wikipedia.org/wiki/Ben_Franklin_effect&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;Ben Franklin effect&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;                style=&#34;height: 0.7em; width: 0.7em; margin-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;                class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;                viewBox=&#34;0 0 640 512&#34;&gt;&#xA;                &lt;path fill=&#34;currentColor&#34;&#xA;                    d=&#34;M640 51.2l-.3 12.2c-28.1 .8-45 15.8-55.8 40.3-25 57.8-103.3 240-155.3 358.6H415l-81.9-193.1c-32.5 63.6-68.3 130-99.2 193.1-.3 .3-15 0-15-.3C172 352.3 122.8 243.4 75.8 133.4 64.4 106.7 26.4 63.4 .2 63.7c0-3.1-.3-10-.3-14.2h161.9v13.9c-19.2 1.1-52.8 13.3-43.3 34.2 21.9 49.7 103.6 240.3 125.6 288.6 15-29.7 57.8-109.2 75.3-142.8-13.9-28.3-58.6-133.9-72.8-160-9.7-17.8-36.1-19.4-55.8-19.7V49.8l142.5 .3v13.1c-19.4 .6-38.1 7.8-29.4 26.1 18.9 40 30.6 68.1 48.1 104.7 5.6-10.8 34.7-69.4 48.1-100.8 8.9-20.6-3.9-28.6-38.6-29.4 .3-3.6 0-10.3 .3-13.6 44.4-.3 111.1-.3 123.1-.6v13.6c-22.5 .8-45.8 12.8-58.1 31.7l-59.2 122.8c6.4 16.1 63.3 142.8 69.2 156.7L559.2 91.8c-8.6-23.1-36.4-28.1-47.2-28.3V49.6l127.8 1.1 .2 .5z&#34;&gt;&#xA;                &lt;/path&gt;&#xA;            &lt;/svg&gt;&#xA;        &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;, &lt;a href=&#34;https://en.wikipedia.org/wiki/Consistency_%28negotiation%29&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;Cialdini&amp;rsquo;s consistency principle&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;                style=&#34;height: 0.7em; width: 0.7em; margin-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;                class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;                viewBox=&#34;0 0 640 512&#34;&gt;&#xA;                &lt;path fill=&#34;currentColor&#34;&#xA;                    d=&#34;M640 51.2l-.3 12.2c-28.1 .8-45 15.8-55.8 40.3-25 57.8-103.3 240-155.3 358.6H415l-81.9-193.1c-32.5 63.6-68.3 130-99.2 193.1-.3 .3-15 0-15-.3C172 352.3 122.8 243.4 75.8 133.4 64.4 106.7 26.4 63.4 .2 63.7c0-3.1-.3-10-.3-14.2h161.9v13.9c-19.2 1.1-52.8 13.3-43.3 34.2 21.9 49.7 103.6 240.3 125.6 288.6 15-29.7 57.8-109.2 75.3-142.8-13.9-28.3-58.6-133.9-72.8-160-9.7-17.8-36.1-19.4-55.8-19.7V49.8l142.5 .3v13.1c-19.4 .6-38.1 7.8-29.4 26.1 18.9 40 30.6 68.1 48.1 104.7 5.6-10.8 34.7-69.4 48.1-100.8 8.9-20.6-3.9-28.6-38.6-29.4 .3-3.6 0-10.3 .3-13.6 44.4-.3 111.1-.3 123.1-.6v13.6c-22.5 .8-45.8 12.8-58.1 31.7l-59.2 122.8c6.4 16.1 63.3 142.8 69.2 156.7L559.2 91.8c-8.6-23.1-36.4-28.1-47.2-28.3V49.6l127.8 1.1 .2 .5z&#34;&gt;&#xA;                &lt;/path&gt;&#xA;            &lt;/svg&gt;&#xA;        &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;).&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Curiosity Tendency&lt;/strong&gt;: Humans have a general tendency to be curious, which can be supercharged with today&amp;rsquo;s access to information. This fortunate tendency should be used to counteract other psychological tendencies.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Kantian Fairness Tendency&lt;/strong&gt;: People have a tendency in direct interactions to behave fairly (following Kant&amp;rsquo;s categorical imperative).&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Envy/Jealousy Tendency&lt;/strong&gt;: People&amp;rsquo;s tendency to envy someone else&amp;rsquo;s status, wealth, or compensation. &amp;ldquo;It is not greed that drives the world but envy.&amp;rdquo; (Buffett)&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Reciprocation Tendency&lt;/strong&gt;: People have a tendency to reciprocate favours and disfavours. This can also be used for manipulation, e.g. a salesman could do you a small favour to get a much better outcome in negotiation.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Influence-from-Mere-Association Tendency&lt;/strong&gt;: Valuing something by the association with another unrelated factor/idea/concept. Examples:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;association of quality with the highest price&lt;/li&gt;&#xA;&lt;li&gt;purchasing of luxury items to boost status&lt;/li&gt;&#xA;&lt;li&gt;advertising of products with unrelated but positive images&lt;/li&gt;&#xA;&lt;li&gt;associating one&amp;rsquo;s ability with past successes and making bad decisions&lt;/li&gt;&#xA;&lt;li&gt;thinking of someone worse because they are a competitor&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Simple Pain-Avoiding Psychological Denial&lt;/strong&gt;: Denying reality because it is too painful to accept, e.g. addiction, bankruptcy.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Denial of a reality that&amp;rsquo;s too painful to accept, e.g. addiction to alcohol.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Excessive Self-Regard Tendency&lt;/strong&gt;: People&amp;rsquo;s tendency to overestimate their abilities, decisions, and possessions. Moreover, their preference of people that are similar to them.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Overoptimism Tendency&lt;/strong&gt;: A tendency to be overly optimistic of the future, especially if one has done well in the past.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Deprival-Superreaction Tendency&lt;/strong&gt;: Reacting more strongly to losses than to gains, e.g. losing $10 is considered worse than gaining $10. Irrational overreaction to threatened loss of status, territory, love, friendship, or property.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Social-Proof Tendency&lt;/strong&gt;: A tendency to act and think the same way as people around you (Group-think).&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Contrast-Misreaction Tendency&lt;/strong&gt;: Making bad decisions by anchoring on an irrelevant comparison. Examples:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Adding $1000 of useless add-ons to a car only because the car costs $65k.&lt;/li&gt;&#xA;&lt;li&gt;A real estate agent presenting 3 terrible and expensive houses, then showing a merely bad house to make it look more desirable.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Stress-Influence Tendency&lt;/strong&gt;: Light stress can increase performance temporarily, while heavy stress can cause dysfunctional thinking and bad decision-making.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Availability-Misweighting Tendency&lt;/strong&gt;: Tendency to overweight or overvalue things (people, decisions, work, ideas) that are close or readily accessible to you. This also holds for metrics that are easy to measure and actions that are easy to take. This can be countered by following checklists of actions and by considering more difficult (or less accessible) alternatives.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Use-It-or-Lose-It Tendency&lt;/strong&gt;: Over time, our skills and knowledge fade. Therefore we should deliberately train and repeat the skills we want to retain. Write them down as a checklist and work through them regularly.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Drug-Misinfluence Tendency&lt;/strong&gt;: Most people can&amp;rsquo;t handle drugs responsibly over a long period of time. Not worth trying to prove that you can.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Senescence-Misinfluence Tendency:&lt;/strong&gt; Older people have a harder time learning new skills. Knowing this, the best counter is to actively maintain the accumulated knowledge, see 19).&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Authority-Misinfluence Tendency&lt;/strong&gt;: Tendency to blindly follow the leader (see also: &lt;a href=&#34;https://psychsafety.com/the-hippo/&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;HiPPO effect&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;).&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Twaddle Tendency&lt;/strong&gt;: Some people waste time talking about things they are not an expert in. Try to separate these people from the experts and follow the latter.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Reason-Respecting Tendency:&lt;/strong&gt; People can learn better when they can think through the reasons behind a directive or action. Therefore, when giving orders explain your reasoning.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Lollapalooza Tendency&lt;/strong&gt;: Often multiple human biases act together to drive a certain behaviour or outcome.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;conclusion&#34; class=&#34;content-heading&#34;&gt;Conclusion&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;I gave only 3/5 stars because the format of reading his talks didn&amp;rsquo;t appeal to me. I was already aware of most of the human biases listed by Munger. Some are discussed in &lt;a href=&#34;https://en.wikipedia.org/wiki/Thinking,_Fast_and_Slow&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;Thinking Fast and Slow (2011)&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;                style=&#34;height: 0.7em; width: 0.7em; margin-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;                class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;                viewBox=&#34;0 0 640 512&#34;&gt;&#xA;                &lt;path fill=&#34;currentColor&#34;&#xA;                    d=&#34;M640 51.2l-.3 12.2c-28.1 .8-45 15.8-55.8 40.3-25 57.8-103.3 240-155.3 358.6H415l-81.9-193.1c-32.5 63.6-68.3 130-99.2 193.1-.3 .3-15 0-15-.3C172 352.3 122.8 243.4 75.8 133.4 64.4 106.7 26.4 63.4 .2 63.7c0-3.1-.3-10-.3-14.2h161.9v13.9c-19.2 1.1-52.8 13.3-43.3 34.2 21.9 49.7 103.6 240.3 125.6 288.6 15-29.7 57.8-109.2 75.3-142.8-13.9-28.3-58.6-133.9-72.8-160-9.7-17.8-36.1-19.4-55.8-19.7V49.8l142.5 .3v13.1c-19.4 .6-38.1 7.8-29.4 26.1 18.9 40 30.6 68.1 48.1 104.7 5.6-10.8 34.7-69.4 48.1-100.8 8.9-20.6-3.9-28.6-38.6-29.4 .3-3.6 0-10.3 .3-13.6 44.4-.3 111.1-.3 123.1-.6v13.6c-22.5 .8-45.8 12.8-58.1 31.7l-59.2 122.8c6.4 16.1 63.3 142.8 69.2 156.7L559.2 91.8c-8.6-23.1-36.4-28.1-47.2-28.3V49.6l127.8 1.1 .2 .5z&#34;&gt;&#xA;                &lt;/path&gt;&#xA;            &lt;/svg&gt;&#xA;        &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt; and &lt;a href=&#34;https://en.wikipedia.org/wiki/Influence:_Science_and_Practice&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;Influence: Science and Practice (2001)&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;                style=&#34;height: 0.7em; width: 0.7em; margin-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;                class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;                viewBox=&#34;0 0 640 512&#34;&gt;&#xA;                &lt;path fill=&#34;currentColor&#34;&#xA;                    d=&#34;M640 51.2l-.3 12.2c-28.1 .8-45 15.8-55.8 40.3-25 57.8-103.3 240-155.3 358.6H415l-81.9-193.1c-32.5 63.6-68.3 130-99.2 193.1-.3 .3-15 0-15-.3C172 352.3 122.8 243.4 75.8 133.4 64.4 106.7 26.4 63.4 .2 63.7c0-3.1-.3-10-.3-14.2h161.9v13.9c-19.2 1.1-52.8 13.3-43.3 34.2 21.9 49.7 103.6 240.3 125.6 288.6 15-29.7 57.8-109.2 75.3-142.8-13.9-28.3-58.6-133.9-72.8-160-9.7-17.8-36.1-19.4-55.8-19.7V49.8l142.5 .3v13.1c-19.4 .6-38.1 7.8-29.4 26.1 18.9 40 30.6 68.1 48.1 104.7 5.6-10.8 34.7-69.4 48.1-100.8 8.9-20.6-3.9-28.6-38.6-29.4 .3-3.6 0-10.3 .3-13.6 44.4-.3 111.1-.3 123.1-.6v13.6c-22.5 .8-45.8 12.8-58.1 31.7l-59.2 122.8c6.4 16.1 63.3 142.8 69.2 156.7L559.2 91.8c-8.6-23.1-36.4-28.1-47.2-28.3V49.6l127.8 1.1 .2 .5z&#34;&gt;&#xA;                &lt;/path&gt;&#xA;            &lt;/svg&gt;&#xA;        &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;. However, what I found impressive is that he talked about and used these biases already 20 years ago, before they were widely discussed. I also liked that in many speeches he directly criticises the audience that invited him to speak. However, he does it in a charming and humorous way.&lt;/p&gt;&#xA;</description>
    </item>
    
    <item>
      <title>TIL how to fix a broken Joplin sync</title>
      <link>https://staticnotes.org/til/broken-joplin-sync/</link>
      <pubDate>Mon, 17 Feb 2025 00:00:00 +0000</pubDate>
      
      <guid>https://staticnotes.org/til/broken-joplin-sync/</guid>
    <description>&lt;p&gt;I use &lt;a href=&#34;https://joplinapp.org/&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;Joplin&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt; for all my note-taking as it&amp;rsquo;s open-source, markdown-based, and has decent Desktop and Android apps. I use note encryption and sync my notes via WebDAV with my remote Nextcloud instance.&lt;/p&gt;&#xA;&lt;h3 id=&#34;problem&#34; class=&#34;content-heading&#34;&gt;Problem&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;After not syncing my notes for several weeks, I encountered a couple of errors both on my Desktop app and on my Android app. I don&amp;rsquo;t have access to the error message anymore, but the errors referred to files and file IDs that caused inconsistencies on the remote server, e.g. broken references:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&lt;pre&gt;&lt;code&gt;f1231gdffgasd1asd2.md TypeError: Cannot read property &amp;#39;type_ of null.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;h3 id=&#34;solution&#34; class=&#34;content-heading&#34;&gt;Solution&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;To solve the issue, I had to delete the offending file on the remote server. However, to not lose it, it&amp;rsquo;s best to make a local copy of the file&amp;rsquo;s content if it is present on one of your local devices.&lt;/p&gt;&#xA;&lt;p&gt;First, I had to determine which file caused the issue, which is a bit harder here as filenames are encrypted. The easiest way to identify the note from the filename is to paste a local markdown reference into a new Joplin note like this:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&lt;pre&gt;&lt;code&gt;[Note I am looking for](:/f1231gdffgasd1asd2)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;and then click the link in the rendered note. This took me to the offending note.&lt;/p&gt;&#xA;&lt;p&gt;Second, copy the content into a new note.&lt;/p&gt;&#xA;&lt;p&gt;Third, I had to delete the file on the remote. I connected on my Mac to the remote WebDAV server via Finder. In Finder, I clicked on &lt;code&gt;Go&lt;/code&gt; -&amp;gt; &lt;code&gt;Connect to Server&lt;/code&gt; -&amp;gt; Add address: &lt;code&gt;https://yourserveraddress.org/remote.php/webdav/joplin&lt;/code&gt; (look up the link in your Joplin settings), and authenticated with my Nextcloud credentials. Then I was able to search for the file &lt;code&gt;f1231gdffgasd1asd2&lt;/code&gt; and delete it.&lt;/p&gt;&#xA;&lt;p&gt;The sync now succeeds.&lt;/p&gt;&#xA;</description>
    </item>
    
    <item>
      <title>TIL how to debug a broken Nextcloud login (again)</title>
      <link>https://staticnotes.org/til/nextcloud-login-debugging-2/</link>
      <pubDate>Sun, 16 Feb 2025 00:00:00 +0000</pubDate>
      
      <guid>https://staticnotes.org/til/nextcloud-login-debugging-2/</guid>
    <description>&lt;p&gt;The symptoms of this problem were the same as the ones I described in &lt;a href=&#34;https://staticnotes.org/til/nextcloud-login-debugging/&#34; &#xA;&gt;TIL how to debug a broken Nextcloud login&#xA;&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;!-- https://staticnotes.org/til/nextcloud-login-debugging/) --&gt;&#xA;&lt;h3 id=&#34;problem&#34; class=&#34;content-heading&#34;&gt;Problem&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;Phone and Mac apps stopped syncing data with the Nextcloud instance. In my browser I could access the login page, but after entering the details I was being sent back to the same login page without any error message.&lt;/p&gt;&#xA;&lt;h3 id=&#34;investigation&#34; class=&#34;content-heading&#34;&gt;Investigation&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;Last time the issue was caused by excessive logging. However, I reduced the &lt;code&gt;loglevel&lt;/code&gt; in the Nextcloud config, so this was not the issue here. I am no expert in web admin, PHP, or Nextcloud, so I used Claude 3.5 to &lt;em&gt;pair&lt;/em&gt; on debugging:&lt;/p&gt;&#xA;&lt;div class=&#34;promptbox&#34;&gt;&#xA;    &lt;div class=&#34;promptbox-title&#34;&gt;&#xA;        Prompt&#xA;    &lt;/div&gt;&#xA;    &lt;div class=&#34;promptbox-content&#34;&gt;&#xA;        I am self-hosting a nextcloud instance on a Hetzner VPC. I currently have the following problem. I can&amp;rsquo;t log in to nextcloud via my account. The screen just reloads and shows me the login page again. It is not a login detail issue. They are correct. Moreover, other nextcloud related syncs (files, calendar) stopped working. Walk me through how I can debug this issue and get nextcloud up and running again.      &lt;/div&gt; &#xA;&lt;/div&gt;&#xA;&lt;p&gt;I can&amp;rsquo;t say that the suggestions worked right away, but they put me on the right path and helped me with some useful debugging commands.&lt;/p&gt;&#xA;&lt;p&gt;The command&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;sh&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;df -h&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;showed me that I didn&amp;rsquo;t have &lt;em&gt;any&lt;/em&gt; free space. While the command&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;sh&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;du -sh /var/www/nextcloud/data/*&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;indicated that in Nextcloud a lot of space was occupied by old file versions and deleted files from the Nextcloud file sync.&lt;/p&gt;&#xA;&lt;h3 id=&#34;solution&#34; class=&#34;content-heading&#34;&gt;Solution&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;You can use the Nextcloud console &lt;code&gt;occ&lt;/code&gt; (ownCloud Console) tool to run maintenance tasks on the Nextcloud instance.&lt;/p&gt;&#xA;&lt;p&gt;The command&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;sh&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo -u www-data php /var/www/nextcloud/occ list&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;lists all available commands.&lt;/p&gt;&#xA;&lt;p&gt;A couple of cleanup commands to remove old file versions and files in the trashbin worked for me:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;bash&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Check Nextcloud trashbin size&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo -u www-data php /var/www/nextcloud/occ trashbin:cleanup --all-users&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Clean up file versions&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo -u www-data php /var/www/nextcloud/occ versions:cleanup&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Clear Nextcloud cache:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo -u www-data php /var/www/nextcloud/occ cache:clear&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;This freed up enough space for Nextcloud to work again.&lt;/p&gt;&#xA;&lt;p&gt;Unfortunately, the &lt;code&gt;occ&lt;/code&gt; commands didn&amp;rsquo;t work right away because there was apparently not enough space to run any of them. Each one returned the same error message:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&lt;pre&gt;&lt;code&gt;fwrite(): write of 4963 bytes failed with errno=28 No space left on device &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;So I first had to free up a few hundred megabytes:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Clear local repository of retrieved package files:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;sh&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo apt-get clean&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Remove old log files:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;sh&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo find /var/log -type f -name &lt;span class=&#34;s2&#34;&gt;&amp;#34;*.gz&amp;#34;&lt;/span&gt; -delete&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo find /var/log -type f -name &lt;span class=&#34;s2&#34;&gt;&amp;#34;*.1&amp;#34;&lt;/span&gt; -delete&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo truncate -s &lt;span class=&#34;m&#34;&gt;0&lt;/span&gt; /var/log/*.log&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Remove temporary files:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;sh&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo rm -rf /tmp/*&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;This cleared enough space to run the &lt;code&gt;occ&lt;/code&gt; commands.&lt;/p&gt;&#xA;</description>
    </item>
    
    <item>
      <title>About this blog and its microfeatures</title>
      <link>https://staticnotes.org/posts/blog-microfeatures/</link>
      <pubDate>Wed, 12 Feb 2025 00:00:00 +0000</pubDate>
      
      <guid>https://staticnotes.org/posts/blog-microfeatures/</guid>
    <description>&lt;p&gt;No blog would be complete without a post explaining the code and frameworks used to generate it. In fact, often tweaking the blog code is more fun than actually writing something.&lt;/p&gt;&#xA;&lt;h2 id=&#34;static-site-generator&#34; class=&#34;content-heading&#34;&gt;Static site generator&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;I try to keep the tech of this blog simple. I am using the &lt;a href=&#34;https://gohugo.io/&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;Hugo&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt; static site generator and chose one of the simplest themes &lt;a href=&#34;https://themes.gohugo.io/themes/hugo-bearcub/&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;Bear Cub&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt; to get started. What I liked about the theme was its small code footprint, no JavaScript/ads/trackers, and it was entirely focused on text. The generated pages are small and load fast. The simple layout made it very easy for me to understand and modify it over time.&lt;/p&gt;&#xA;&lt;p&gt;The source code is hosted on GitLab and the static sites are published to GitLab Pages. This means that to publish a new post, I just have to push a commit with the changes to the remote repository.&lt;/p&gt;&#xA;&lt;h2 id=&#34;supporting-math-code-jupyter-notebooks&#34; class=&#34;content-heading&#34;&gt;Supporting math, code, Jupyter notebooks&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;At the time of starting this blog I worked as a data scientist. I wanted to be able to share reproducible Jupyter notebooks that I could easily convert to HTML and embed in this blog. &lt;span class=&#34;sidenote-number&#34;&gt;&lt;small class=&#34;sidenote&#34;&gt; All notebooks are reproducible unless the data used in the blog post is too large to be uploaded to GitLab. &lt;/small&gt;&lt;/span&gt; I put my notebooks into the &lt;code&gt;/notebooks&lt;/code&gt; folder of the repository and use the script &lt;a href=&#34;https://github.com/vlunot/nb2hugo&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;nb2hugo&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt; to convert the notebooks to markdown files. At the bottom of the post you can usually find the link to the Jupyter notebook, the poetry environment, and the data. I wrote about this &lt;a href=&#34;https://staticnotes.org/posts/hugo-and-jupyter/&#34; &#xA;&gt;here&#xA;&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;The equations on this blog are rendered with &lt;a href=&#34;https://katex.org/&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;KaTeX&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;h2 id=&#34;content-types&#34; class=&#34;content-heading&#34;&gt;Content types&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;The content of this blog has four different types:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;Posts&lt;/strong&gt; are normal blog posts.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;TILs&lt;/strong&gt;, or Today-I-Learned, are notes documenting something small I have learned on a day. The idea is based on &lt;a href=&#34;https://til.simonwillison.net/&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;Simon Willison&amp;rsquo;s TILs&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Links&lt;/strong&gt; appear in &lt;strong&gt;Feeds&lt;/strong&gt; and are links that I want to share, usually with a short comment. They can be thought of as a retweet.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Book&lt;/strong&gt; reviews.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;1-3 are shown in reverse-chronological order in the main page&amp;rsquo;s feed.&lt;/p&gt;&#xA;&lt;h2 id=&#34;microfeatures&#34; class=&#34;content-heading&#34;&gt;Microfeatures&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;I like Daniel Fedorin&amp;rsquo;s post on &lt;a href=&#34;https://danilafe.com/blog/blog_microfeatures/&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;blog microfeatures&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;. In the same spirit, here are the microfeatures that I have added:&lt;/p&gt;&#xA;&lt;h3 id=&#34;recommendations&#34; class=&#34;content-heading&#34;&gt;Recommendations&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;Most pages have post recommendations at the bottom. I compute the (cosine) similarity of posts after summarizing them with a local LLM, computing embeddings, and storing them in a local Chroma vector database. The two most similar posts and their cosine similarity are then displayed. Note that all of this is pre-generated and static. I wrote in more detail about how this works &lt;a href=&#34;https://staticnotes.org/posts/how-recommendations-work/&#34; &#xA;&gt;here&#xA;&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;For this post, they look like this:&lt;/p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;  &lt;div class=&#34;similar-post-section&#34;&gt;&#xA;    Similar posts:&#xA;    &lt;ul class=&#34;similar-posts-list&#34;&gt;&#xA;      &#xA;        &lt;li&gt;&#xA;          &lt;div class=&#34;similar-post-title&#34;&gt;&lt;a  href=&#34;./posts/hugo-and-jupyter/&#34;&gt;How to display Jupyter notebooks on your Hugo blog&lt;/a&gt; &lt;a href=&#34;./posts/how-recommendations-work&#34;&gt;(0.55)&lt;/a&gt;&lt;/div&gt; &#xA;        &lt;/li&gt;&#xA;      &#xA;        &lt;li&gt;&#xA;          &lt;div class=&#34;similar-post-title&#34;&gt;&lt;a  href=&#34;./posts/how-recommendations-work/&#34;&gt;How related posts are computed&lt;/a&gt; &lt;a href=&#34;./posts/how-recommendations-work&#34;&gt;(0.458)&lt;/a&gt;&lt;/div&gt; &#xA;        &lt;/li&gt;&#xA;      &#xA;    &lt;/ul&gt;&#xA;  &lt;/div&gt;&#xA;&#xA;&#xA;&#xA;&lt;h3 id=&#34;sidenotes&#34; class=&#34;content-heading&#34;&gt;Sidenotes&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;I can add sidenotes&lt;span class=&#34;sidenote-number&#34;&gt;&lt;small class=&#34;sidenote&#34;&gt; like this one.&lt;/small&gt;&lt;/span&gt; to a blog post by using the &lt;code&gt;{{% sidenote %}}&lt;/code&gt; shortcode. I really like to use this for comments that are additional to the primary content or later updates to a blog post.&lt;/p&gt;&#xA;&lt;h3 id=&#34;markers-for-external-links&#34; class=&#34;content-heading&#34;&gt;Markers for external links&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;Internal &lt;a href=&#34;https://staticnotes.org/posts/blog-microfeatures/&#34; &#xA;&gt;links&#xA;&lt;/a&gt; on this blog have no marker and open in the same window. &lt;a href=&#34;https://www.google.com/&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;External links&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt; are appended with an icon and open in a new window. Inspired by &lt;a href=&#34;https://gwern.net/&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;gwern.net&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;, I also added specific icons for particular sites: &lt;a href=&#34;https://x.com/jack/status/20&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;tweet&#xA;    &#xA;&#xA;        &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.9em; margin-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 448 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M389.2 48h70.6L305.6 224.2 487 464H345L233.7 318.6 106.5 464H35.8L200.7 275.5 26.8 48H172.4L272.9 180.9 389.2 48zM364.4 421.8h39.1L151.1 88h-42L364.4 421.8z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;        &lt;/span&gt;&#xA;&#xA;        &#xA;    &#xA;&lt;/a&gt; on x.com, &lt;a href=&#34;https://www.goodreads.com/book/show/42844155-harry-potter-and-the-philosopher-s-stone&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;book link&#xA;    &#xA;&#xA;        &#xA;    &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;        style=&#34;height: 0.7em; width: 0.9em; margin-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;        class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;        viewBox=&#34;0 0 448 512&#34;&gt;&#xA;        &lt;path fill=&#34;currentColor&#34;&#xA;            d=&#34;M299.9 191.2c5.1 37.3-4.7 79-35.9 100.7-22.3 15.5-52.8 14.1-70.8 5.7-37.1-17.3-49.5-58.6-46.8-97.2 4.3-60.9 40.9-87.9 75.3-87.5 46.9-.2 71.8 31.8 78.2 78.3zM448 88v336c0 30.9-25.1 56-56 56H56c-30.9 0-56-25.1-56-56V88c0-30.9 25.1-56 56-56h336c30.9 0 56 25.1 56 56zM330 313.2s-.1-34-.1-217.3h-29v40.3c-.8 .3-1.2-.5-1.6-1.2-9.6-20.7-35.9-46.3-76-46-51.9 .4-87.2 31.2-100.6 77.8-4.3 14.9-5.8 30.1-5.5 45.6 1.7 77.9 45.1 117.8 112.4 115.2 28.9-1.1 54.5-17 69-45.2 .5-1 1.1-1.9 1.7-2.9 .2 .1 .4 .1 .6 .2 .3 3.8 .2 30.7 .1 34.5-.2 14.8-2 29.5-7.2 43.5-7.8 21-22.3 34.7-44.5 39.5-17.8 3.9-35.6 3.8-53.2-1.2-21.5-6.1-36.5-19-41.1-41.8-.3-1.6-1.3-1.3-2.3-1.3h-26.8c.8 10.6 3.2 20.3 8.5 29.2 24.2 40.5 82.7 48.5 128.2 37.4 49.9-12.3 67.3-54.9 67.4-106.3z&#34;&gt;&#xA;        &lt;/path&gt;&#xA;    &lt;/svg&gt;&#xA;&lt;/span&gt;&#xA;&#xA;&#xA;&#xA;    &#xA;&lt;/a&gt; on goodreads, &lt;a href=&#34;https://www.youtube.com/watch?v=MiUHjLxm3V0&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;video&#xA;    &#xA;&#xA;        &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.9em; margin-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 448 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M549.7 124.1c-6.3-23.7-24.8-42.3-48.3-48.6C458.8 64 288 64 288 64S117.2 64 74.6 75.5c-23.5 6.3-42 24.9-48.3 48.6-11.4 42.9-11.4 132.3-11.4 132.3s0 89.4 11.4 132.3c6.3 23.7 24.8 41.5 48.3 47.8C117.2 448 288 448 288 448s170.8 0 213.4-11.5c23.5-6.3 42-24.2 48.3-47.8 11.4-42.9 11.4-132.3 11.4-132.3s0-89.4-11.4-132.3zm-317.5 213.5V175.2l142.7 81.2-142.7 81.2z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;&#xA;    &#xA;    &#xA;&lt;/a&gt; on youtube, and &lt;a href=&#34;https://en.wikipedia.org/wiki/Blog&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;article&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;                style=&#34;height: 0.7em; width: 0.7em; margin-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;                class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;                viewBox=&#34;0 0 640 512&#34;&gt;&#xA;                &lt;path fill=&#34;currentColor&#34;&#xA;                    d=&#34;M640 51.2l-.3 12.2c-28.1 .8-45 15.8-55.8 40.3-25 57.8-103.3 240-155.3 358.6H415l-81.9-193.1c-32.5 63.6-68.3 130-99.2 193.1-.3 .3-15 0-15-.3C172 352.3 122.8 243.4 75.8 133.4 64.4 106.7 26.4 63.4 .2 63.7c0-3.1-.3-10-.3-14.2h161.9v13.9c-19.2 1.1-52.8 13.3-43.3 34.2 21.9 49.7 103.6 240.3 125.6 288.6 15-29.7 57.8-109.2 75.3-142.8-13.9-28.3-58.6-133.9-72.8-160-9.7-17.8-36.1-19.4-55.8-19.7V49.8l142.5 .3v13.1c-19.4 .6-38.1 7.8-29.4 26.1 18.9 40 30.6 68.1 48.1 104.7 5.6-10.8 34.7-69.4 48.1-100.8 8.9-20.6-3.9-28.6-38.6-29.4 .3-3.6 0-10.3 .3-13.6 44.4-.3 111.1-.3 123.1-.6v13.6c-22.5 .8-45.8 12.8-58.1 31.7l-59.2 122.8c6.4 16.1 63.3 142.8 69.2 156.7L559.2 91.8c-8.6-23.1-36.4-28.1-47.2-28.3V49.6l127.8 1.1 .2 .5z&#34;&gt;&#xA;                &lt;/path&gt;&#xA;            &lt;/svg&gt;&#xA;        &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt; on wikipedia.&lt;/p&gt;&#xA;&lt;h3 id=&#34;link-blog--retweets--boosts&#34; class=&#34;content-heading&#34;&gt;Link-blog / retweets / boosts&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;The main page of this blog has a section &lt;code&gt;Recent&lt;/code&gt; which displays a feed of the content being posted. It&amp;rsquo;s a mix of posts, TILs and link-blog entries and should mimic a social media profile feed.&#xA;Shared links have a reshare/boost symbol and usually a short comment about them. They also have a &lt;code&gt;via&lt;/code&gt; link to the place where I found this link. This is also inspired by Simon Willison&amp;rsquo;s main page.&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&lt;figure&gt;&#xA;  &lt;div class=&#34;image-wrapper&#34;&gt;&#xA;  &lt;img src=&#34;https://staticnotes.org/posts/blog-microfeatures/linkblogentry.png&#34; alt=&#34;SharedLink&#34; loading=&#34;lazy&#34; /&gt;&#xA;  &lt;figcaption&gt;Figure 1. A shared link.&lt;/figcaption&gt;&#xA;  &lt;/div&gt;&#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;h3 id=&#34;rss&#34; class=&#34;content-heading&#34;&gt;RSS&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;I provide &lt;a href=&#34;https://staticnotes.org/index.xml&#34; &#xA;&gt;RSS feeds&#xA;&lt;/a&gt; for all content types and also separate feeds for &lt;a href=&#34;https://staticnotes.org/posts.xml&#34; &#xA;&gt;Posts&#xA;&lt;/a&gt;, &lt;a href=&#34;https://staticnotes.org/til.xml&#34; &#xA;&gt;TIL&#xA;&lt;/a&gt;, &lt;a href=&#34;https://staticnotes.org/feed.xml&#34; &#xA;&gt;Feed&#xA;&lt;/a&gt;, and &lt;a href=&#34;https://staticnotes.org/books.xml&#34; &#xA;&gt;Books&#xA;&lt;/a&gt; only. I wrote more about this &lt;a href=&#34;https://staticnotes.org/til/hugo-with-separate-rss-feeds/&#34; &#xA;&gt;here&#xA;&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;h3 id=&#34;table-of-contents&#34; class=&#34;content-heading&#34;&gt;Table of Contents&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;Longer blog posts have a table of contents section at the beginning of the post.&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&lt;figure&gt;&#xA;  &lt;div class=&#34;image-wrapper&#34;&gt;&#xA;  &lt;img src=&#34;https://staticnotes.org/posts/blog-microfeatures/tableofcontents.png&#34; alt=&#34;TableOfContents&#34; loading=&#34;lazy&#34; /&gt;&#xA;  &lt;figcaption&gt;Figure 2. Some longer posts show a table of contents at the top.&lt;/figcaption&gt;&#xA;  &lt;/div&gt;&#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;h3 id=&#34;linkable-headers&#34; class=&#34;content-heading&#34;&gt;Linkable Headers&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;The main headers of blog posts have a linkable §-symbol in front of them. This means you can easily link to a specific section of the blog post.&lt;/p&gt;&#xA;&lt;h3 id=&#34;drawings&#34; class=&#34;content-heading&#34;&gt;Drawings&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;I added the ability to provide an optional SVG image that is added to the background next to the post title, e.g. &lt;a href=&#34;https://staticnotes.org/posts/writing-well/&#34; &#xA;&gt;here&#xA;&lt;/a&gt; and &lt;a href=&#34;https://staticnotes.org/posts/reading-and-note-taking/&#34; &#xA;&gt;here&#xA;&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&lt;figure&gt;&#xA;  &lt;div class=&#34;image-wrapper&#34;&gt;&#xA;  &lt;img src=&#34;https://staticnotes.org/posts/blog-microfeatures/title_drawing.png&#34; alt=&#34;TitleDrawing&#34; loading=&#34;lazy&#34; /&gt;&#xA;  &lt;figcaption&gt;Figure 3. Blog posts can have an SVG drawing next to the title.&lt;/figcaption&gt;&#xA;  &lt;/div&gt;&#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;h3 id=&#34;prompts--llm-responses&#34; class=&#34;content-heading&#34;&gt;Prompts / LLM responses&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;When I first wrote about &lt;a href=&#34;https://staticnotes.org/til/hugo-with-separate-rss-feeds/&#34; &#xA;&gt;using an LLM to build something&#xA;&lt;/a&gt; I thought that it would be quite common to share a prompt and the LLM&amp;rsquo;s output in your blog post. So I built a small feature to display them in a chat-like fashion. You can use &lt;code&gt;{{% prompt %}}&lt;/code&gt; to put text into a prompt chat bubble, and &lt;code&gt;{{% llm %}}&lt;/code&gt; for the model response (colored by model). Turns out, this is not something I regularly need and nobody wants to read someone else&amp;rsquo;s prompt or the LLM&amp;rsquo;s answer anymore.&lt;/p&gt;&#xA;&lt;div class=&#34;promptbox&#34;&gt;&#xA;    &lt;div class=&#34;promptbox-title&#34;&gt;&#xA;        Prompt&#xA;    &lt;/div&gt;&#xA;    &lt;div class=&#34;promptbox-content&#34;&gt;&#xA;        How many Rs are in the word strawberry?      &lt;/div&gt; &#xA;&lt;/div&gt;&#xA;&lt;details class=&#34;llmbox llm-anthropic&#34;&gt;&#xA;    &lt;summary class=&#34;llmbox-title&#34;&gt;&#xA;        Model: claude-opus-5-5 - response &#xA;    &lt;/summary&gt;&#xA;    &lt;div class=&#34;llmbox-content&#34;&gt;&#xA;&lt;p&gt;There are 3 Rs in &amp;ldquo;strawberry&amp;rdquo;: strawberry.&#xA;&lt;/div&gt;&lt;/p&gt;&#xA;&lt;/details&gt;&#xA;&lt;details class=&#34;llmbox llm-openai&#34;&gt;&#xA;    &lt;summary class=&#34;llmbox-title&#34;&gt;&#xA;        Model: openai-sol-5-6 - response &#xA;    &lt;/summary&gt;&#xA;    &lt;div class=&#34;llmbox-content&#34;&gt;&#xA;&lt;p&gt;There are 3 Rs in &amp;ldquo;strawberry&amp;rdquo;: strawberry.&#xA;&lt;/div&gt;&lt;/p&gt;&#xA;&lt;/details&gt;&#xA;&lt;details class=&#34;llmbox llm-qwen&#34;&gt;&#xA;    &lt;summary class=&#34;llmbox-title&#34;&gt;&#xA;        Model: qwen-4-3 - response &#xA;    &lt;/summary&gt;&#xA;    &lt;div class=&#34;llmbox-content&#34;&gt;&#xA;&lt;p&gt;There are 3 Rs in &amp;ldquo;strawberry&amp;rdquo;: strawberry.&#xA;&lt;/div&gt;&lt;/p&gt;&#xA;&lt;/details&gt;&#xA;</description>
    </item>
    
    <item>
      <title>How related posts are computed</title>
      <link>https://staticnotes.org/posts/how-recommendations-work/</link>
      <pubDate>Sun, 09 Feb 2025 00:00:00 +0000</pubDate>
      
      <guid>https://staticnotes.org/posts/how-recommendations-work/</guid>
    <description>&lt;p&gt;The number next to related posts at the bottom of each page is the advertised post&amp;rsquo;s &amp;ldquo;similarity&amp;rdquo; to the currently viewed page (from 1.0 to -1.0).&lt;/p&gt;&#xA;&lt;p&gt;I am using the following process to compute related posts locally:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Summarise every post and TIL using local qwen (&lt;code&gt;qwen3.5:9b&lt;/code&gt; via Ollama) with the following prompt:&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;div class=&#34;promptbox&#34;&gt;&#xA;    &lt;div class=&#34;promptbox-title&#34;&gt;&#xA;        Prompt&#xA;    &lt;/div&gt;&#xA;    &lt;div class=&#34;promptbox-content&#34;&gt;&#xA;        &lt;p&gt;You are an analyst and editor with many years of experience in reading and synthesizing content.&lt;/p&gt;&#xA;&lt;p&gt;Here is a blog post:&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;&amp;lt;BLOGPOST&amp;gt;&lt;/code&gt;&lt;/p&gt;&#xA;&lt;p&gt;{ blog_post }&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;&amp;lt;/BLOGPOST&amp;gt;&lt;/code&gt;&lt;/p&gt;&#xA;&lt;p&gt;Please create a comprehensive and concise summary of the blog post. Focus on the main concepts, key details, and central arguments.&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;&amp;lt;INSTRUCTIONS&amp;gt;&lt;/code&gt;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Include any specific technologies, methods, or frameworks mentioned.&lt;/li&gt;&#xA;&lt;li&gt;Don&amp;rsquo;t use more than 7 sentences.&lt;/li&gt;&#xA;&lt;li&gt;Respond in plaintext. Don&amp;rsquo;t add formatting or linebreak characters to your response.&lt;/li&gt;&#xA;&lt;li&gt;Don&amp;rsquo;t repeat the instructions of the task. Respond directly.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&lt;code&gt;&amp;lt;/INSTRUCTIONS&amp;gt;&lt;/code&gt;&lt;/p&gt;&#xA;      &lt;/div&gt; &#xA;&lt;/div&gt;&#xA;&lt;ol start=&#34;2&#34;&gt;&#xA;&lt;li&gt;Embed the summary using a qwen embedding model &lt;code&gt;qwen3-embedding:0.6b&lt;/code&gt; and store the embedding and metadata about the post in a persistent &lt;a href=&#34;https://www.trychroma.com/&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;ChromaDB&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt; vector database (a file on my computer).&lt;/li&gt;&#xA;&lt;li&gt;Compute the &lt;a href=&#34;https://en.wikipedia.org/wiki/Cosine_similarity&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;cosine similarity&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;                style=&#34;height: 0.7em; width: 0.7em; margin-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;                class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;                viewBox=&#34;0 0 640 512&#34;&gt;&#xA;                &lt;path fill=&#34;currentColor&#34;&#xA;                    d=&#34;M640 51.2l-.3 12.2c-28.1 .8-45 15.8-55.8 40.3-25 57.8-103.3 240-155.3 358.6H415l-81.9-193.1c-32.5 63.6-68.3 130-99.2 193.1-.3 .3-15 0-15-.3C172 352.3 122.8 243.4 75.8 133.4 64.4 106.7 26.4 63.4 .2 63.7c0-3.1-.3-10-.3-14.2h161.9v13.9c-19.2 1.1-52.8 13.3-43.3 34.2 21.9 49.7 103.6 240.3 125.6 288.6 15-29.7 57.8-109.2 75.3-142.8-13.9-28.3-58.6-133.9-72.8-160-9.7-17.8-36.1-19.4-55.8-19.7V49.8l142.5 .3v13.1c-19.4 .6-38.1 7.8-29.4 26.1 18.9 40 30.6 68.1 48.1 104.7 5.6-10.8 34.7-69.4 48.1-100.8 8.9-20.6-3.9-28.6-38.6-29.4 .3-3.6 0-10.3 .3-13.6 44.4-.3 111.1-.3 123.1-.6v13.6c-22.5 .8-45.8 12.8-58.1 31.7l-59.2 122.8c6.4 16.1 63.3 142.8 69.2 156.7L559.2 91.8c-8.6-23.1-36.4-28.1-47.2-28.3V49.6l127.8 1.1 .2 .5z&#34;&gt;&#xA;                &lt;/path&gt;&#xA;            &lt;/svg&gt;&#xA;        &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt; between the embeddings for each pair of posts. A score of 1.0 indicates proportional vectors, a score of 0.0 orthogonal vectors, and a score of -1.0 opposite vectors.&lt;/li&gt;&#xA;&lt;li&gt;Write a yaml file that includes for every post a link to the most similar post and their similarity.&lt;/li&gt;&#xA;&lt;li&gt;Use a Hugo partial to include the data in the yaml file about the most relevant posts at the bottom of each page.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h3 id=&#34;noteworthy&#34; class=&#34;content-heading&#34;&gt;Noteworthy&#xA;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;When using Llama, I had to explicitly instruct the model not to restate the task. Otherwise, every summary would have started with &amp;ldquo;Here is your concise blog post summary in not more than 7 sentences.&amp;rdquo; which would create some artificial similarity in the embeddings.&lt;/li&gt;&#xA;&lt;li&gt;Most blog posts are shorter than 15000 characters, or 4000 tokens. I had to work this out to configure the model context correctly.&lt;/li&gt;&#xA;&lt;li&gt;ChromaDB&amp;rsquo;s default embedding model &lt;code&gt;all-MiniLM-L6-v2&lt;/code&gt; has 384 dimensions and a maximum input sequence length of 256 BERT tokens (~100&amp;ndash;150 words). I checked some of the summaries and they were around 160 &amp;ndash; 220 BERT tokens. I have since switched to a qwen embedding model which has much longer input context. A good embedding model leaderboard to help make a choice is available &lt;a href=&#34;https://huggingface.co/spaces/mteb/leaderboard&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;here&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;example&#34; class=&#34;content-heading&#34;&gt;Example&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;For the blog post &lt;a href=&#34;https://staticnotes.org/posts/uk-house-prices/&#34; &#xA;&gt;UK house price changes in real terms&#xA;&lt;/a&gt; the LLM came up with this summary which was then embedded:&lt;/p&gt;&#xA;&lt;details class=&#34;llmbox llm-qwen&#34;&gt;&#xA;    &lt;summary class=&#34;llmbox-title&#34;&gt;&#xA;        Model: qwen3.5:9b - response &#xA;    &lt;/summary&gt;&#xA;    &lt;div class=&#34;llmbox-content&#34;&gt;&#xA;&lt;p&gt;The author critiques standard UK housing market reports for presenting nominal price changes that obscure local realities and ignore inflation, arguing instead for real house prices adjusted using CPI data from ONS alongside Land Registry transaction records. To address this gap, the post demonstrates a Python-based workflow utilizing pandas to merge regional property datasets with quarterly consumer price index figures to calculate purchasing power-adjusted values in 2025 pounds. The analysis focuses on specific areas around London and South England, visualizing trends for detached houses in counties like Surrey and Kent as well as flats within selected boroughs since 2015. Results reveal that while nominal prices may show modest growth or stagnation, real house prices have actually declined significantly over the last decade in many regions due to inflation outpacing asset appreciation. The author provides interactive tables comparing current valuations against historical data points from one, two, five, and ten years prior to highlight these divergences across different property types. By leveraging open APIs for Land Registry and ONS data stored as parquet files, the post offers a reproducible method for investors and buyers to assess true market performance beyond headline statistics. Ultimately, this approach clarifies that British housing has often underperformed relative to other investments like the FTSE  100 when viewed through an inflation-adjusted lens.&#xA;&lt;/div&gt;&lt;/p&gt;&#xA;&lt;/details&gt;&#xA;&lt;h3 id=&#34;embeddings&#34; class=&#34;content-heading&#34;&gt;Embeddings&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;I can visualise the embeddings in two dimensions using &lt;a href=&#34;https://scikit-learn.org/stable/modules/generated/sklearn.manifold.TSNE.html&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;t-SNE&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&lt;figure&gt;&#xA;  &lt;div class=&#34;image-wrapper&#34;&gt;&#xA;  &lt;img src=&#34;https://staticnotes.org/posts/how-recommendations-work/embeddings_visualization.png&#34; alt=&#34;Embeddings in 2D space&#34; loading=&#34;lazy&#34; /&gt;&#xA;  &lt;figcaption&gt;Figure 1. Plot of blog post summary embeddings in t-SNE space.&lt;/figcaption&gt;&#xA;  &lt;/div&gt;&#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;The plot looks somewhat reasonable. The code-heavy Jupyter notebook posts are at the bottom, clustered around &lt;code&gt;duckdb-large-datasets&lt;/code&gt;. The book reviews (&lt;code&gt;4000-weeks&lt;/code&gt;, &lt;code&gt;how-big-things-get-done&lt;/code&gt;, &lt;code&gt;how-to-win-friends&lt;/code&gt;) are fairly close together. SQL related posts are clustered at the top. On the other hand, I would have expected &lt;code&gt;reading-and-note-taking&lt;/code&gt; to be closer to &lt;code&gt;writing-well&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;h3 id=&#34;code&#34; class=&#34;content-heading&#34;&gt;Code&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;I am using &lt;a href=&#34;https://gitlab.com/frankRi89/blog/-/blob/04aca65a2c58058e76df07f121a232885549ce1e/code/posts-similarity/src/similarity_generator.py&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;this langchain script&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt; to compute the recommendations.&lt;/p&gt;&#xA;&lt;h3 id=&#34;update&#34; class=&#34;content-heading&#34;&gt;Update&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;A previous version of the code and blog post used Llama 3.1 to summarise the text and ChromaDB&amp;rsquo;s default embedding model &lt;code&gt;all-MiniLM-L6-v2&lt;/code&gt;.&lt;/p&gt;&#xA;</description>
    </item>
    
    <item>
      <title>Book notes: How Big Things Get Done by Bent Flyvbjerg and Dan Gardner</title>
      <link>https://staticnotes.org/posts/how-big-things-get-done/</link>
      <pubDate>Sun, 02 Feb 2025 00:00:00 +0000</pubDate>
      
      <guid>https://staticnotes.org/posts/how-big-things-get-done/</guid>
    <description>&lt;p&gt;&lt;a href=&#34;https://itu.dk/flyvbjerg&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;Bent Flyvbjerg&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt; is an economics professor at IT University of Copenhagen. He maintains a database of megaprojects (power plants, opera halls, tunnels, airports) and their planned and realised timelines and budgets. He researches the reasons why modern megaprojects often fail to deliver on time and on budget. It has recently become quite popular to &lt;a href=&#34;https://patrickcollison.com/fast&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;discuss&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt; the apparent decrease in speed with which large-scale projects are realised. Examples are easy to find, e.g. the delayed &lt;a href=&#34;https://en.wikipedia.org/wiki/High_Speed_2&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;High Speed Rail 2 (HS2)&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;                style=&#34;height: 0.7em; width: 0.7em; margin-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;                class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;                viewBox=&#34;0 0 640 512&#34;&gt;&#xA;                &lt;path fill=&#34;currentColor&#34;&#xA;                    d=&#34;M640 51.2l-.3 12.2c-28.1 .8-45 15.8-55.8 40.3-25 57.8-103.3 240-155.3 358.6H415l-81.9-193.1c-32.5 63.6-68.3 130-99.2 193.1-.3 .3-15 0-15-.3C172 352.3 122.8 243.4 75.8 133.4 64.4 106.7 26.4 63.4 .2 63.7c0-3.1-.3-10-.3-14.2h161.9v13.9c-19.2 1.1-52.8 13.3-43.3 34.2 21.9 49.7 103.6 240.3 125.6 288.6 15-29.7 57.8-109.2 75.3-142.8-13.9-28.3-58.6-133.9-72.8-160-9.7-17.8-36.1-19.4-55.8-19.7V49.8l142.5 .3v13.1c-19.4 .6-38.1 7.8-29.4 26.1 18.9 40 30.6 68.1 48.1 104.7 5.6-10.8 34.7-69.4 48.1-100.8 8.9-20.6-3.9-28.6-38.6-29.4 .3-3.6 0-10.3 .3-13.6 44.4-.3 111.1-.3 123.1-.6v13.6c-22.5 .8-45.8 12.8-58.1 31.7l-59.2 122.8c6.4 16.1 63.3 142.8 69.2 156.7L559.2 91.8c-8.6-23.1-36.4-28.1-47.2-28.3V49.6l127.8 1.1 .2 .5z&#34;&gt;&#xA;                &lt;/path&gt;&#xA;            &lt;/svg&gt;&#xA;        &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt; project or the time and cost overruns of the &lt;a href=&#34;https://en.wikipedia.org/wiki/Construction_of_Berlin_Brandenburg_Airport&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;Berlin Brandenburg Airport (BER)&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;                style=&#34;height: 0.7em; width: 0.7em; margin-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;                class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;                viewBox=&#34;0 0 640 512&#34;&gt;&#xA;                &lt;path fill=&#34;currentColor&#34;&#xA;                    d=&#34;M640 51.2l-.3 12.2c-28.1 .8-45 15.8-55.8 40.3-25 57.8-103.3 240-155.3 358.6H415l-81.9-193.1c-32.5 63.6-68.3 130-99.2 193.1-.3 .3-15 0-15-.3C172 352.3 122.8 243.4 75.8 133.4 64.4 106.7 26.4 63.4 .2 63.7c0-3.1-.3-10-.3-14.2h161.9v13.9c-19.2 1.1-52.8 13.3-43.3 34.2 21.9 49.7 103.6 240.3 125.6 288.6 15-29.7 57.8-109.2 75.3-142.8-13.9-28.3-58.6-133.9-72.8-160-9.7-17.8-36.1-19.4-55.8-19.7V49.8l142.5 .3v13.1c-19.4 .6-38.1 7.8-29.4 26.1 18.9 40 30.6 68.1 48.1 104.7 5.6-10.8 34.7-69.4 48.1-100.8 8.9-20.6-3.9-28.6-38.6-29.4 .3-3.6 0-10.3 .3-13.6 44.4-.3 111.1-.3 123.1-.6v13.6c-22.5 .8-45.8 12.8-58.1 31.7l-59.2 122.8c6.4 16.1 63.3 142.8 69.2 156.7L559.2 91.8c-8.6-23.1-36.4-28.1-47.2-28.3V49.6l127.8 1.1 .2 .5z&#34;&gt;&#xA;                &lt;/path&gt;&#xA;            &lt;/svg&gt;&#xA;        &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;. Counter-examples are less common (or less newsworthy), e.g. the 2023 Notre-Dame Reconstruction.&lt;/p&gt;&#xA;&lt;p&gt;In their book Flyvbjerg/Gardner explain the common reasons for cost and time overruns of megaprojects and how to mitigate them. I think we can apply the majority of their learnings for smaller home (kitchen renovation) and work projects (cloud migration) as well.&lt;/p&gt;&#xA;&lt;h2 id=&#34;planning-is-cheap-building-is-expensive&#34; class=&#34;content-heading&#34;&gt;Planning is cheap, building is expensive&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;In large construction projects, planning is a lot cheaper than building. Once a badly planned project is underway, unforeseen problems will be discovered. Delays tend to cause further delays. The total project time can be in itself a source of additional risk and cost.&lt;/p&gt;&#xA;&lt;p&gt;When politicians plan prestigious infrastructure projects, they have a bias for strategic misrepresentation of the cost and time to get support for it. Therefore, the government and the public need to scrutinise the project&amp;rsquo;s goal and its planning to ensure that the plan is realistic and enough alternatives have been considered. Only get involved in such a project if it has the people and funds, including contingencies, to succeed.&lt;/p&gt;&#xA;&lt;p&gt;Since planning is a lot cheaper than building for large projects, Flyvbjerg/Gardner advise investing significant time into the planning phase:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;understand the objective behind the proposed project (assume an outside view)&lt;/li&gt;&#xA;&lt;li&gt;explore alternatives and don&amp;rsquo;t commit to the first available solution (resist quick action bias)&lt;/li&gt;&#xA;&lt;li&gt;don&amp;rsquo;t forecast using the best-case scenario, instead use similar past projects to anchor your estimate&lt;/li&gt;&#xA;&lt;li&gt;experiment and iterate in the planning phase using digital modelling and simulations&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;understand-the-why&#34; class=&#34;content-heading&#34;&gt;Understand the why&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;&lt;em&gt;The project is not a goal in itself, it is how the goal is achieved&lt;/em&gt;. We want to talk to the stakeholder to understand the goal. Is the project even the right approach? Good planning needs to explore the problem before jumping to the solution. This includes considering alternatives.&lt;/p&gt;&#xA;&lt;p&gt;There is a good example in the book about a bridge project. The original goal of the project is to connect an island with the mainland. By jumping to the bridge idea, the stakeholders ignored alternatives, e.g. a tunnel, ferries, a helipad. Or maybe a physical connection is not required. If it&amp;rsquo;s about improved communication, maybe a high-speed broadband connection achieves the goal.&lt;/p&gt;&#xA;&lt;p&gt;Another framing of this idea in product development is to work backwards from the customer. Understand their needs and problems, before coming up with any solution.&lt;/p&gt;&#xA;&lt;h3 id=&#34;reduce-uncertainty-via-experimentation&#34; class=&#34;content-heading&#34;&gt;Reduce uncertainty via experimentation&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;A crucial part of the planning phase is to experiment with the solution. Ideally, we can simulate and iterate on the project, e.g. use 3D models of the building, low fidelity designs to show customers, or low effort versions of the animation movie we are trying to produce.&lt;/p&gt;&#xA;&lt;p&gt;The simulation ensures that the majority of aspects of our project are scrutinised before the building phase begins. We can assume that the project will run into problems, so we want most of the problems to occur during the &lt;em&gt;cheap&lt;/em&gt; planning phase.&lt;/p&gt;&#xA;&lt;h3 id=&#34;experience-in-people-and-technology&#34; class=&#34;content-heading&#34;&gt;Experience in people and technology&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;To maximise the chance of a successful project we should maximise &lt;em&gt;experience&lt;/em&gt;. Flyvbjerg/Gardner consider both experience in &lt;em&gt;key people&lt;/em&gt; and in &lt;em&gt;technology&lt;/em&gt;.&#xA;We should try to ensure that key people have experience in similar projects. Ask: &amp;ldquo;Have they done it before?&amp;rdquo;.&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;&amp;ldquo;Technology is &amp;lsquo;frozen experience&amp;rsquo;.&amp;rdquo;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;All things being equal, we should use tried and tested off-the-shelf technology instead of shiny new technology. If we can, we should use existing designs and operational processes.&lt;span class=&#34;sidenote-number&#34;&gt;&lt;small class=&#34;sidenote&#34;&gt;This is the same idea behind McKinley&amp;rsquo;s advice to &lt;a href=&#34;https://mcfunley.com/choose-boring-technology&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;Choose Boring Technology&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;.&lt;/small&gt;&lt;/span&gt;&lt;/p&gt;&#xA;&lt;p&gt;The construction of the Empire State Building is used throughout the book as a positive example. One reason for this was the architect William Lamb who insisted on using only proven technology, and who created a design that allowed repeatable non-custom work steps. Moreover, the construction company Starrett Brothers and Eken had built several similar skyscrapers on time and budget before.&lt;/p&gt;&#xA;&lt;h3 id=&#34;reference-class-forecasting&#34; class=&#34;content-heading&#34;&gt;Reference-class forecasting&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;After the project is planned we need to forecast the project duration and its cost. Unfortunately, planners often forecast too optimistically. Instead of deriving it from the project alone, the authors suggest using &lt;em&gt;reference-class forecasting&lt;/em&gt;. The idea is to find a set of comparable projects, and to anchor our forecast based on their outcomes. Then adjust from that anchor.&lt;/p&gt;&#xA;&lt;p&gt;However, good forecasting cannot protect the project from fat-tailed risks. Since those risks can kill our project, we need to identify the (known) high risk events and try to mitigate them.&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;&amp;ldquo;Successful project leaders focus every day on not losing, while keeping a keen eye on the [&amp;hellip;] goal they are trying to achieve.&amp;rdquo;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;h2 id=&#34;after-the-planning-is-done-build-quickly&#34; class=&#34;content-heading&#34;&gt;After the planning is done, build quickly&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;The project duration is in itself a source of budget and time risk.&#xA;After planning, simulating, and forecasting, we therefore need to act fast once the building phase starts. To do this we should ideally use an experienced team and a modular building pattern.&lt;/p&gt;&#xA;&lt;h3 id=&#34;build-with-an-experienced-team&#34; class=&#34;content-heading&#34;&gt;Build with an experienced team&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;If possible, hire, what the authors call, a masterbuilder. That is someone who has experience in similar projects and can pick the right team. If such a team doesn&amp;rsquo;t exist, you or them need to create it.&lt;/p&gt;&#xA;&lt;p&gt;Moreover, we need to ensure that the incentives of participating contractors are aligned with ours by sharing risks and rewards, e.g. pay a bonus for early completion. Don&amp;rsquo;t always pick the contractor that submits the lowest bid, because the lowest bid doesn&amp;rsquo;t necessarily lead to the lowest cost. Moreover, we will try to choose companies that we have successfully collaborated with before.&lt;/p&gt;&#xA;&lt;h3 id=&#34;whats-our-lego&#34; class=&#34;content-heading&#34;&gt;What&amp;rsquo;s our Lego?&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;Nuclear power plant constructions belong to the class of projects that most likely exceed cost and time budgets. Flyvbjerg/Gardner argue that you are building one large thing that has few repeatable, standardised parts. Solar power plants are the opposite. The core ingredient, the solar modules, can be produced rapidly and repeatedly in factories, and then assembled in a modular fashion.&lt;/p&gt;&#xA;&lt;p&gt;When possible we should try to build modules. We can then produce and assemble the modules in a repeatable process. This delivers value in stages. After we have completed a module, we can use the learnings to iterate on the module&amp;rsquo;s design and assembly process. So we should always ask ourselves:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;&amp;ldquo;What&amp;rsquo;s our basic building block, the thing we will repeatedly make, becoming smarter and better each time. What&amp;rsquo;s our Lego?&amp;rdquo;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;h2 id=&#34;additional-thoughts-about-the-book&#34; class=&#34;content-heading&#34;&gt;Additional thoughts about the book&#xA;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;incentives-and-identity&#34; class=&#34;content-heading&#34;&gt;Incentives and identity&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;There is a section in the book that discusses why the Heathrow Terminal 5 project was completed on time. The authors argue that a large part was the alignment of incentives between the project managers, the contractors, and the workers. Contractors were aligned via contractual bonuses and established working relationships. Workers were treated well, their feedback was actively encouraged, and they felt like they were contributing to a historic project in their country. Not part of the book, but the Notre-Dame reconstruction after the fire, was also accomplished on time. I believe that the history of the building, and the feeling of being part of a national project are very powerful motivators that aligned the participants of the project.&lt;/p&gt;&#xA;&lt;h3 id=&#34;cost-overruns-by-project-type&#34; class=&#34;content-heading&#34;&gt;Cost overruns by project type&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;I found the table in the book that shows the mean cost overrun by category interesting.&lt;/p&gt;&#xA;&lt;p&gt;The table shows the mean (base rate) cost overrun for each project type, the fatness of the distribution (% of projects in the upper tail), and the cost overrun for projects in that tail.&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th style=&#34;text-align: center&#34;&gt;Project type&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: center&#34;&gt;Mean cost overrun (%)&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: center&#34;&gt;% of projects in tail (&amp;gt;= 50% overrun)&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: center&#34;&gt;Mean overrun of projects in tail (%)&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;Nuclear storage&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;238&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;48&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;427&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;Olympic Games&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;157&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;76&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;200&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;Nuclear power&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;120&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;55&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;204&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;IT&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;73&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;18&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;447&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;&amp;hellip;&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;&amp;hellip;&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;&amp;hellip;&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;&amp;hellip;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;Buildings&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;62&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;39&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;206&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;Rail&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;39&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;28&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;116&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;Airport&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;39&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;43&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;88&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;Tunnels&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;37&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;28&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;103&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;&amp;hellip;&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;&amp;hellip;&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;&amp;hellip;&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;&amp;hellip;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;Wind energy&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;13&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;7&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;97&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;Energy transmission&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;8&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;4&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;166&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;Solar power&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;1&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;2&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;50&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;It makes sense that (non-standard, non-modular) nuclear projects have the highest base rate for cost increase. On the opposite side are wind energy and solar power projects, that can be pre-produced in factories and assembled on-site. Organising Olympic Games suffers from the fact that they are highly complex, and usually held in a city that hasn&amp;rsquo;t hosted them before (inexperience). As a tech worker, I am intrigued to see IT projects at the top of the list. Moreover, when IT projects overrun they incur a 447% cost overrun, the highest among all project types.&lt;/p&gt;&#xA;&lt;h3 id=&#34;learnings-for-software-projects&#34; class=&#34;content-heading&#34;&gt;Learnings for software projects&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;How applicable is the advice for day-to-day data and software projects? I think much of the advice applies. I found that chunking larger projects into small value-delivering modules is a great way to ensure continuous progress (especially in large refactoring projects).&lt;/p&gt;&#xA;&lt;p&gt;The emphasis on planning vs. building is probably not as relevant in normal day-to-day software projects. There is less of a cost difference between planning and building a feature (in both cases mostly the software engineers&amp;rsquo; time) compared to construction projects.&lt;/p&gt;&#xA;</description>
    </item>
    
    <item>
      <title>LLM prompt heuristics that definitely maybe work</title>
      <link>https://staticnotes.org/posts/prompt-heuristics/</link>
      <pubDate>Fri, 11 Oct 2024 14:56:00 +0000</pubDate>
      
      <guid>https://staticnotes.org/posts/prompt-heuristics/</guid>
    <description>&lt;p&gt;Effective prompt writing for large language models continues to be a dark art. Having read the prompt engineering blogs from Meta, Anthropic, and OpenAI and watched some of Anthropic&amp;rsquo;s prompt discussions online, it does feel more like design than engineering. Even employees from the same lab don&amp;rsquo;t agree what tricks actually work. If you have time watch &lt;a href=&#34;https://www.youtube.com/watch?v=hkhDdcM5V94&amp;amp;pp=ygUqcHJvbXB0IGVuZ2luZWVyaW5nIG1hc3RlcmNsYXNzIGFpIGVuZ2luZWVy&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;this video&#xA;    &#xA;&#xA;        &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.9em; margin-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 448 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M549.7 124.1c-6.3-23.7-24.8-42.3-48.3-48.6C458.8 64 288 64 288 64S117.2 64 74.6 75.5c-23.5 6.3-42 24.9-48.3 48.6-11.4 42.9-11.4 132.3-11.4 132.3s0 89.4 11.4 132.3c6.3 23.7 24.8 41.5 48.3 47.8C117.2 448 288 448 288 448s170.8 0 213.4-11.5c23.5-6.3 42-24.2 48.3-47.8 11.4-42.9 11.4-132.3 11.4-132.3s0-89.4-11.4-132.3zm-317.5 213.5V175.2l142.7 81.2-142.7 81.2z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;&#xA;    &#xA;    &#xA;&lt;/a&gt; of one of Anthropic&amp;rsquo;s prompt engineers running a prompting masterclass. While giving good heuristics to the audience he is careful not to make any definite statements.&lt;/p&gt;&#xA;&lt;p&gt;I don&amp;rsquo;t believe it is a good investment to try to become a world-class prompter. All heuristics are highly dependent on the model architecture, training data, and training procedure. This means that with every iteration of the models, heuristics could become obsolete or harmful to performance.&lt;/p&gt;&#xA;&lt;p&gt;However, the current attention-based architecture doesn&amp;rsquo;t seem to be going anywhere&#xA;soon. Therefore, it is reasonable to expect that we can continue to use the prompt context to help the model to move the embeddings of the user task into directions that contain a lot of nuance and information about the domain of the task.&lt;/p&gt;&#xA;&lt;p&gt;Therefore, I am collecting the advice into a few guidelines that I can use with the current models. I will caveat this by saying that in 90% of my use-cases the model response is parsed by a human: me. Therefore, I&#xA;am not as worried about hallucinations as someone that puts the model outputs in front of their customers or inside data parsing pipelines.&lt;/p&gt;&#xA;&lt;p&gt;Here is what seems to work in October 2024 with Claude Sonnet 3.5, GPT-4o, and&#xA;Llama 3.2.&lt;/p&gt;&#xA;&lt;h3 id=&#34;prompt-specificity&#34; class=&#34;content-heading&#34;&gt;Prompt specificity&#xA;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Make the prompt as specific to the task as you can. This is probably the&#xA;biggest return on your time. Good advice given in &lt;a href=&#34;https://www.youtube.com/watch?v=T9aRN5JkmL8&amp;amp;t=1769s&amp;amp;pp=ygUccHJvbXB0IGVuZ2luZWVyaW5nIGFudGhyb3BpYw%3D%3D&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;this discussion&#xA;    &#xA;&#xA;        &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.9em; margin-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 448 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M549.7 124.1c-6.3-23.7-24.8-42.3-48.3-48.6C458.8 64 288 64 288 64S117.2 64 74.6 75.5c-23.5 6.3-42 24.9-48.3 48.6-11.4 42.9-11.4 132.3-11.4 132.3s0 89.4 11.4 132.3c6.3 23.7 24.8 41.5 48.3 47.8C117.2 448 288 448 288 448s170.8 0 213.4-11.5c23.5-6.3 42-24.2 48.3-47.8 11.4-42.9 11.4-132.3 11.4-132.3s0-89.4-11.4-132.3zm-317.5 213.5V175.2l142.7 81.2-142.7 81.2z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;&#xA;    &#xA;    &#xA;&lt;/a&gt; is to&#xA;imagine printing out the prompt and giving it to a new hire at your company. Then see if they could solve the task. This forces you to give all necessary context and constraints of the task.&lt;/li&gt;&#xA;&lt;li&gt;Components of a role prompt: The role, its place in an organisation, its&#xA;perspective and the perspective of the person or organisation being addressed.&lt;/li&gt;&#xA;&lt;li&gt;No lazy role prompts. Make the role and the context clear.&#xA;&lt;div class=&#34;code-block&#34;&gt;&lt;pre&gt;&lt;code&gt;Bad: You are a cab driver.&#xA;Good: You are a cab driver, driving people as your full-time job in London for twenty years and are knowledgeable about the city, its roads, and its sights. &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;If the model is used inside a product, tell it about it:&#xA;&lt;div class=&#34;code-block&#34;&gt;&lt;pre&gt;&lt;code&gt;Bad: You are an assistant writing document summaries.&#xA;Good: You are an assistant used in a product for law firms that summarizes&#xA;legal documents.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;order-of-prompt-components&#34; class=&#34;content-heading&#34;&gt;Order of prompt components&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;Order the components of your prompts in the following order:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Specific role and context description (put into system prompt if part of interactive&#xA;use of the model)&lt;/li&gt;&#xA;&lt;li&gt;Input data, e.g. documents, code snippets, CSV files&lt;/li&gt;&#xA;&lt;li&gt;Task description&lt;/li&gt;&#xA;&lt;li&gt;(Optional:) Good examples and bad examples&lt;/li&gt;&#xA;&lt;li&gt;Task constraints&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h3 id=&#34;use-xml-tags-for-prompt-components&#34; class=&#34;content-heading&#34;&gt;Use XML tags for prompt components&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;Use XML tags to separate different components of a prompt.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&lt;pre&gt;&lt;code&gt;You are an experienced post-doc at a reputable research institute in the US. You are an expert in the research field of the following paper:&#xA;&#xA;&amp;lt;Research_paper&amp;gt;&#xA;{{research_paper}}  &#xA;&amp;lt;/Research_paper&amp;gt;&#xA;&#xA;Your task is to summarize the findings of the research paper given to you in&#xA;&amp;lt;Research_paper&amp;gt; tags.&#xA;&#xA;&amp;lt;Instructions&amp;gt;&#xA;- List between 1 - 5 most important findings of the paper. Don&amp;#39;t list more&#xA;  than 5 findings.&#xA;- more instructions...&#xA;&amp;lt;/Instructions&amp;gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;h3 id=&#34;using-examples-few-shot-prompting&#34; class=&#34;content-heading&#34;&gt;Using examples (few shot prompting)&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;It can help to provide both good and bad examples for a specific task with an&#xA;explanation why they are good or bad. In the above example we could provide&#xA;an example research paper with a summary of findings we wrote ourselves as a&#xA;good example.&lt;/p&gt;&#xA;&lt;h3 id=&#34;instructions&#34; class=&#34;content-heading&#34;&gt;Instructions&#xA;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Response length limit. Instead of writing &amp;ldquo;Be concise&amp;rdquo;, tell the model what that means for your context, e.g. &amp;ldquo;Answer in no more than 4 sentences.&amp;rdquo;&lt;/li&gt;&#xA;&lt;li&gt;Avoid open-ended instructions.&lt;/li&gt;&#xA;&lt;li&gt;Instruct for style, formatting, and restrictions.&lt;/li&gt;&#xA;&lt;li&gt;Instruct to ask for sources of evidence to reduce hallucinations.&lt;/li&gt;&#xA;&lt;li&gt;Ask it to respond in a chain-of-thought to increase performance.&#xA;&lt;div class=&#34;code-block&#34;&gt;&lt;pre&gt;&lt;code&gt;You are a logician and love to solve logic puzzles. Carefully read the following puzzle.&#xA;&#xA;&amp;lt;PUZZLE&amp;gt;&#xA;Simon is looking at Charlie. Charlie is looking at Sarah. You know that Simon is married and Sarah is not married. Is a married person looking at an unmarried person?&#xA;&amp;lt;/PUZZLE&amp;gt;&#xA;Let&amp;#39;s think step by step before giving an answer.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;grammar-and-style&#34; class=&#34;content-heading&#34;&gt;Grammar and style&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;Avoid typos and wrong punctuation as that &lt;em&gt;can&lt;/em&gt; deteriorate the quality of the response.&lt;/p&gt;&#xA;&lt;h3 id=&#34;parse-able-output&#34; class=&#34;content-heading&#34;&gt;Parse-able output&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;Often LLMs add a preamble at the beginning or an epilogue at the end of a response. If you want to force the model to respond only with valid json, you can:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Use the &amp;ldquo;Prefill Claude&amp;rsquo;s response&amp;rdquo; feature&lt;/li&gt;&#xA;&lt;li&gt;Ask the model to put the json into &lt;JSON&gt;&lt;/JSON&gt; tags and then extract that&#xA;block from the response&lt;/li&gt;&#xA;&lt;li&gt;Prefill the response yourself by adding at the end of your prompt: &lt;code&gt;Here is the JSON: {&lt;/code&gt;. The open bracket conditions the model to start the&#xA;answer with the first JSON key. You then need to prepend the &amp;ldquo;{&amp;rdquo; to the&#xA;response to make it valid.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h3 id=&#34;other-tricks&#34; class=&#34;content-heading&#34;&gt;Other tricks&#xA;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;If your prompt includes logic that could be handled in code, handle it in&#xA;code.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&lt;pre&gt;&lt;code&gt;Bad: You are part of a role-playing game that is used for training of customer support agents in a Fortune 500 company. &#xA;&#xA;You can assume any of the following roles based on user input. &#xA;&#xA;If the user asks for Role1 assume the role of a customer asking questions about the company&amp;#39;s products. If the user asks for Role2, assume the role of a helpful customer support agent.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;Good: Define two different prompts for each role and use code to switch&#xA;prompts based on what role the operator wants the model to assume.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Give the model a way out if it doesn&amp;rsquo;t know the answer.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&lt;pre&gt;&lt;code&gt;Good: {{prompt}}&#xA;If something weird happens and you are unsure about what to do, simply print out&#xA;&amp;#34;UNSURE&amp;#34;.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Use a temperature of 0 for fact-based, less creative tasks.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;iterative-prompt-design&#34; class=&#34;content-heading&#34;&gt;Iterative prompt design&#xA;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;You can use the LLM to generate examples for a task and select the good&#xA;examples. Then use those examples in the prompt that is used &amp;ldquo;in production&amp;rdquo;.&lt;/li&gt;&#xA;&lt;li&gt;If the model responds incorrectly, tell it about the mistake and ask it how&#xA;you should modify the prompt.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;</description>
    </item>
    
    <item>
      <title>TIL (from Claude) how to create separate RSS feeds for my Hugo blog</title>
      <link>https://staticnotes.org/til/hugo-with-separate-rss-feeds/</link>
      <pubDate>Thu, 03 Oct 2024 00:00:00 +0100</pubDate>
      
      <guid>https://staticnotes.org/til/hugo-with-separate-rss-feeds/</guid>
    <description>&lt;p&gt;I really like the idea of providing different content types of my blog in separate RSS feeds. For example a blog could have:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;essays&lt;/li&gt;&#xA;&lt;li&gt;today-I-learned posts&lt;/li&gt;&#xA;&lt;li&gt;link-list or bloglink posts&lt;/li&gt;&#xA;&lt;li&gt;short social media style posts&lt;/li&gt;&#xA;&lt;li&gt;etc.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Providing different RSS feeds for them allows my readers to subscribe to all or a subset of these, depending on their interest. I am using Hugo, as the static site generator for this website. Setting up these different feeds for a framework that I am only partially familiar with is a great use-case to work with an LLM.&lt;/p&gt;&#xA;&lt;p&gt;I knew that I would need to:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Distinguish different content types with a flag&lt;/li&gt;&#xA;&lt;li&gt;Specify different RSS templates that filter on these flags&lt;/li&gt;&#xA;&lt;li&gt;Tell Hugo to generate separate xml files for each content type and use the right templates.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;I just didn&amp;rsquo;t know how to piece these parts together. I first tried googling the answer and read the typical mix of&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;stackoverflow posts that were not solving the exact same problem&lt;/li&gt;&#xA;&lt;li&gt;Hugo forum discussions that did not have a clear solution or where the solution wasn&amp;rsquo;t transferable to my unique setup.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;I think with those first pointers, spending some time on the Hugo documentation, and trial-and-erroring, I could have worked out how to do it.&#xA;I think that these shallow but unknown problems are the ideal use case for an LLM.&lt;/p&gt;&#xA;&lt;p&gt;I think of using LLMs as an interface for search + context, where I don&amp;rsquo;t have to do the laborious work of pre-filtering and connecting the sources. Instead, it helps me get a quick overview and a potential draft of a solution. I can then go deeper and understand the problem better by asking about parts of the solution that I don&amp;rsquo;t understand. I can then mix this with more googling and get to a solution.&lt;/p&gt;&#xA;&lt;h2 id=&#34;my-goal&#34; class=&#34;content-heading&#34;&gt;My Goal:&#xA;&lt;/h2&gt;&#xA;&lt;div class=&#34;promptbox&#34;&gt;&#xA;    &lt;div class=&#34;promptbox-title&#34;&gt;&#xA;        Prompt&#xA;    &lt;/div&gt;&#xA;    &lt;div class=&#34;promptbox-content&#34;&gt;&#xA;        &lt;p&gt;I have a hugo blog with the following folder structure:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&lt;pre&gt;&lt;code&gt;/content/posts/post1.md&#xA;/content/posts/post2.md&#xA;/content/posts/_index.md&#xA;/content/til/_index.md&#xA;/content/til/til1.md&#xA;/content/til/til2.md&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;Markdown pages written in the posts subfolder have type: post in the frontmatter and pages written in the til subfolder have type: til in the frontmatter.&lt;/p&gt;&#xA;&lt;p&gt;Explain to me step by step how I can create three separate rss feeds on my website.&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Only showing content coming from the til subfolder on til.xml&lt;/li&gt;&#xA;&lt;li&gt;Only showing content coming from the post subfolder posts.xml&lt;/li&gt;&#xA;&lt;li&gt;One showing everything on index.xml&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;      &lt;/div&gt; &#xA;&lt;/div&gt;&#xA;&lt;h2 id=&#34;solution&#34; class=&#34;content-heading&#34;&gt;Solution&#xA;&lt;/h2&gt;&#xA;&lt;details class=&#34;llmbox llm-anthropic&#34;&gt;&#xA;    &lt;summary class=&#34;llmbox-title&#34;&gt;&#xA;        Model: claude-3-5-sonnet-20241022 - response &#xA;    &lt;/summary&gt;&#xA;    &lt;div class=&#34;llmbox-content&#34;&gt;&#xA;&lt;p&gt;I&amp;rsquo;d be happy to explain how you can create three separate RSS feeds for your Hugo blog with the structure you&amp;rsquo;ve described. Let&amp;rsquo;s go through this step-by-step:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Create RSS template files:&#xA;First, you&amp;rsquo;ll need to create three separate RSS template files in your Hugo project&amp;rsquo;s layouts folder.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;Now, let me explain each step in more detail:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;We create three separate RSS template files in the &lt;code&gt;layouts/_default/&lt;/code&gt; directory. Each file corresponds to one of the RSS feeds you want to create.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;In each RSS template, we use Hugo&amp;rsquo;s templating language to generate the RSS feed. The main difference between the templates is in the range statement:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;For &lt;code&gt;til.xml&lt;/code&gt;, we use &lt;code&gt;{{ range where .Site.RegularPages &amp;quot;Type&amp;quot; &amp;quot;til&amp;quot; }}&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;For &lt;code&gt;posts.xml&lt;/code&gt;, we use &lt;code&gt;{{ range where .Site.RegularPages &amp;quot;Type&amp;quot; &amp;quot;post&amp;quot; }}&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;For &lt;code&gt;index.xml&lt;/code&gt;, we use &lt;code&gt;{{ range .Site.RegularPages }}&lt;/code&gt; to include all content&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;We update the Hugo configuration file to define new output formats for TIL and Posts, and to include these new formats in the home page outputs.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;We add links to the new RSS feeds in the site&amp;rsquo;s HTML, typically in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; section or footer.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Finally, we build the site to generate the new RSS feed files.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;After following these steps, your Hugo site will generate three separate RSS feeds:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;til.xml&lt;/code&gt; will only include content from the &lt;code&gt;til&lt;/code&gt; subfolder&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;posts.xml&lt;/code&gt; will only include content from the &lt;code&gt;posts&lt;/code&gt; subfolder&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;index.xml&lt;/code&gt; will include all content&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Would you like me to explain any part of this process in more detail?&#xA;&lt;/div&gt;&lt;/p&gt;&#xA;&lt;/details&gt;&#xA;&lt;p&gt;I followed these steps, except for using my already existing xml template for &lt;code&gt;til.xml&lt;/code&gt;, &lt;code&gt;posts.xml&lt;/code&gt;, &lt;code&gt;index.xml&lt;/code&gt;. I already read in some Hugo discussions that I need to add &lt;code&gt;[outputs]&lt;/code&gt; and &lt;code&gt;[outputFormats]&lt;/code&gt; to my Hugo &lt;code&gt;config.toml&lt;/code&gt; file. However, I didn&amp;rsquo;t really understand the difference between home, page, section and what exactly &lt;code&gt;home = [&amp;quot;HTML&amp;quot;, &amp;quot;RSS&amp;quot;, &amp;quot;JSON&amp;quot;, &amp;quot;TIL&amp;quot;, &amp;quot;POSTS&amp;quot;]&lt;/code&gt; does.&#xA;So I asked for clarification:&lt;/p&gt;&#xA;&lt;div class=&#34;promptbox&#34;&gt;&#xA;    &lt;div class=&#34;promptbox-title&#34;&gt;&#xA;        Prompt&#xA;    &lt;/div&gt;&#xA;    &lt;div class=&#34;promptbox-content&#34;&gt;&#xA;        Can you explain to me the config [outputs] and its purpose as well as [outputFormats]?      &lt;/div&gt; &#xA;&lt;/div&gt;&#xA;&lt;details class=&#34;llmbox llm-anthropic&#34;&gt;&#xA;    &lt;summary class=&#34;llmbox-title&#34;&gt;&#xA;        Model: claude-3-5-sonnet-20241022 - response &#xA;    &lt;/summary&gt;&#xA;    &lt;div class=&#34;llmbox-content&#34;&gt;&#xA;&lt;p&gt;Certainly! I&amp;rsquo;d be happy to explain the &lt;code&gt;[outputs]&lt;/code&gt; and &lt;code&gt;[outputFormats]&lt;/code&gt; configurations in Hugo and their purposes.&lt;/p&gt;&#xA;&lt;p&gt;Now, let me provide a more detailed explanation of these configurations:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;code&gt;[outputs]&lt;/code&gt;: This configuration section determines what types of files Hugo will generate for different types of pages on your site. In our case, we&amp;rsquo;re telling Hugo to generate additional output formats for the home page. The line &lt;code&gt;home = [&amp;quot;HTML&amp;quot;, &amp;quot;RSS&amp;quot;, &amp;quot;TIL&amp;quot;, &amp;quot;POSTS&amp;quot;]&lt;/code&gt; means:&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;For the home page, generate the standard HTML and RSS outputs&lt;/li&gt;&#xA;&lt;li&gt;Also generate our custom TIL and POSTS outputs&#xA;This is crucial for creating our multiple RSS feeds, as it instructs Hugo to actually generate these files when building the site.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;ol start=&#34;2&#34;&gt;&#xA;&lt;li&gt;&lt;code&gt;[outputFormats]&lt;/code&gt;: This section allows you to define new output formats or modify existing ones. In our case, we&amp;rsquo;re defining two new output formats: TIL and POSTS. For each new format, we specify:&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;mediaType&lt;/code&gt;: This tells Hugo what kind of content this is. We&amp;rsquo;re using &amp;ldquo;application/rss+xml&amp;rdquo; because we&amp;rsquo;re creating RSS feeds.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;baseName&lt;/code&gt;: This determines the filename of the output file. For example, &lt;code&gt;baseName = &amp;quot;til&amp;quot;&lt;/code&gt; will result in a file named &lt;code&gt;til.xml&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;By defining these custom output formats, we&amp;rsquo;re giving Hugo the information it needs to generate our specialized RSS feeds.&lt;/p&gt;&#xA;&lt;p&gt;The interaction between these two configuration sections is key:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;[outputFormats]&lt;/code&gt; defines what the TIL and POSTS formats are&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;[outputs]&lt;/code&gt; tells Hugo to actually generate these formats for the home page&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;This setup allows you to have fine-grained control over what files Hugo generates and how they&amp;rsquo;re named, which is exactly what we need to create multiple, specialized RSS feeds.&#xA;Is there any part of this you&amp;rsquo;d like me to elaborate on further?&#xA;&lt;/div&gt;&lt;/p&gt;&#xA;&lt;/details&gt;&#xA;&lt;p&gt;This helped but I still didn&amp;rsquo;t really get why I would have to add this to &lt;code&gt;home&lt;/code&gt; and not &lt;code&gt;page&lt;/code&gt; because I thought that I want to generate rss for page-types.&lt;/p&gt;&#xA;&lt;div class=&#34;promptbox&#34;&gt;&#xA;    &lt;div class=&#34;promptbox-title&#34;&gt;&#xA;        Prompt&#xA;    &lt;/div&gt;&#xA;    &lt;div class=&#34;promptbox-content&#34;&gt;&#xA;        What&amp;rsquo;s the difference between adding TIL and POSTS to home instead of page?      &lt;/div&gt; &#xA;&lt;/div&gt;&#xA;&lt;details class=&#34;llmbox llm-anthropic&#34;&gt;&#xA;    &lt;summary class=&#34;llmbox-title&#34;&gt;&#xA;        Model: claude-3-5-sonnet-20241022 - response &#xA;    &lt;/summary&gt;&#xA;    &lt;div class=&#34;llmbox-content&#34;&gt;&#xA;&lt;p&gt;That&amp;rsquo;s an excellent question! The difference between adding output formats to &lt;code&gt;home&lt;/code&gt; versus &lt;code&gt;page&lt;/code&gt; in Hugo&amp;rsquo;s configuration is quite significant and affects how and where these outputs are generated. Let me explain:&lt;/p&gt;&#xA;&lt;p&gt;Now, let me elaborate on the key points:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Scope and Purpose:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Adding TIL and POSTS to &lt;code&gt;home&lt;/code&gt; creates site-wide RSS feeds that include all relevant content from across your site. This is ideal for your use case of creating separate feeds for TIL entries and blog posts.&lt;/li&gt;&#xA;&lt;li&gt;If you added these to &lt;code&gt;page&lt;/code&gt;, it would create individual RSS feeds for each content page, which is not what you&amp;rsquo;re aiming for.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Number of Files Generated:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;With &lt;code&gt;home&lt;/code&gt;, you get one &lt;code&gt;til.xml&lt;/code&gt; and one &lt;code&gt;posts.xml&lt;/code&gt; file for your entire site.&lt;/li&gt;&#xA;&lt;li&gt;If you used &lt;code&gt;page&lt;/code&gt;, you&amp;rsquo;d get a &lt;code&gt;til.xml&lt;/code&gt; and &lt;code&gt;posts.xml&lt;/code&gt; for every single page on your site, which would be unnecessary and potentially confusing.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Content Aggregation:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;The &lt;code&gt;home&lt;/code&gt; outputs can access and aggregate content from across your entire site, which is perfect for creating comprehensive RSS feeds.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;page&lt;/code&gt; outputs typically only contain information from that specific page.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Location of Generated Files:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;home&lt;/code&gt; outputs are usually placed at the root of your site, making them easy to find and link to (e.g., &lt;code&gt;https://yoursite.com/til.xml&lt;/code&gt;).&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;page&lt;/code&gt; outputs would be scattered throughout your site structure, mirroring your content organization.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Practical Implementation:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;For your RSS feeds, using &lt;code&gt;home&lt;/code&gt; allows you to create the three distinct feeds you want (TIL, posts, and everything) in a clean, organized manner.&lt;/li&gt;&#xA;&lt;li&gt;Using &lt;code&gt;page&lt;/code&gt; for this purpose would be impractical and wouldn&amp;rsquo;t achieve your goal of separate, comprehensive feeds.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;In summary, for your specific need of creating site-wide RSS feeds for different content types, adding TIL and POSTS to the &lt;code&gt;home&lt;/code&gt; outputs is the correct approach. It generates the feeds at the site level, includes all relevant content, and produces a single file for each feed type, which is exactly what you&amp;rsquo;re looking to achieve.&#xA;&lt;/div&gt;&lt;/p&gt;&#xA;&lt;/details&gt;&#xA;&lt;p&gt;I followed the suggested approach and added to my &lt;code&gt;config.toml&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;toml&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-toml&#34; data-lang=&#34;toml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c&#34;&gt;# I want to top-level output html, rss, and my two custom outputs&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;outputs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;home&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;html&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;rss&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;til&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;post&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c&#34;&gt;# Here I define how to process these custom feeds&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;outputFormats&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;til&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;mediaType&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;application/rss+xml&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;baseName&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;til&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;outputFormats&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;post&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;mediaType&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;application/rss+xml&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;baseName&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;posts&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;Then I built the page and Hugo would build three different xml files:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&lt;pre&gt;&lt;code&gt;public/index.xml&#xA;public/posts.xml&#xA;public/til.xml&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;However, the files all had the same content as if they were built from the same template instead of the individual templates that filter by page type.&lt;/p&gt;&#xA;&lt;div class=&#34;promptbox&#34;&gt;&#xA;    &lt;div class=&#34;promptbox-title&#34;&gt;&#xA;        Prompt&#xA;    &lt;/div&gt;&#xA;    &lt;div class=&#34;promptbox-content&#34;&gt;&#xA;        This generates the three xml files but they all seem to use the same template file      &lt;/div&gt; &#xA;&lt;/div&gt;&#xA;&lt;details class=&#34;llmbox llm-anthropic&#34;&gt;&#xA;    &lt;summary class=&#34;llmbox-title&#34;&gt;&#xA;        Model: claude-3-5-sonnet-20241022 - response &#xA;    &lt;/summary&gt;&#xA;    &lt;div class=&#34;llmbox-content&#34;&gt;&#xA;&lt;p&gt;I apologize for the confusion in my previous explanation. You&amp;rsquo;re right to point out that issue. Let&amp;rsquo;s correct this and explain how to properly set up distinct templates for each RSS feed.&lt;/p&gt;&#xA;&lt;p&gt;Now, let me explain the key changes and why they solve the problem:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Template File Naming:&#xA;The crucial change is in how we name and place the template files. Instead of putting them in &lt;code&gt;layouts/_default/&lt;/code&gt;, we&amp;rsquo;re now creating:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;layouts/index.til.xml&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;layouts/index.posts.xml&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;layouts/index.xml&lt;/code&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;This naming convention is important because Hugo uses it to match the output formats we defined in the configuration.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Template Content:&#xA;The content of each template remains largely the same, but we&amp;rsquo;ve made sure that each one is filtering for the correct content type:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;index.til.xml&lt;/code&gt; uses &lt;code&gt;{{ range where .Site.RegularPages &amp;quot;Type&amp;quot; &amp;quot;til&amp;quot; }}&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;index.posts.xml&lt;/code&gt; uses &lt;code&gt;{{ range where .Site.RegularPages &amp;quot;Type&amp;quot; &amp;quot;post&amp;quot; }}&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;index.xml&lt;/code&gt; continues to use &lt;code&gt;{{ range .Site.RegularPages }}&lt;/code&gt; for all content&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Configuration:&#xA;The configuration in &lt;code&gt;config.toml&lt;/code&gt; remains the same as before. The &lt;code&gt;[outputFormats]&lt;/code&gt; and &lt;code&gt;[outputs]&lt;/code&gt; sections are correctly set up to generate these files.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;How It Works:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;When Hugo sees &lt;code&gt;TIL&lt;/code&gt; in the &lt;code&gt;[outputs]&lt;/code&gt; section, it looks for a template named &lt;code&gt;index.til.xml&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;Similarly, for &lt;code&gt;POSTS&lt;/code&gt;, it looks for &lt;code&gt;index.posts.xml&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;The default RSS output uses &lt;code&gt;index.xml&lt;/code&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;This setup ensures that each RSS feed uses its own template, allowing you to customize the content for each feed independently.&lt;/p&gt;&#xA;&lt;p&gt;The reason your previous attempt generated three XML files with the same content was likely because Hugo was falling back to a default RSS template when it couldn&amp;rsquo;t find specific templates for TIL and POSTS.&lt;/p&gt;&#xA;&lt;p&gt;With these changes, you should now have three distinct RSS feeds, each with its appropriate content. Does this explanation help clarify the issue? Would you like me to elaborate on any part of this solution?&#xA;&lt;/div&gt;&lt;/p&gt;&#xA;&lt;/details&gt;&#xA;&lt;p&gt;So I moved the template layout files as suggested to:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&lt;pre&gt;&lt;code&gt;layouts/index.til.xml&#xA;layouts/index.posts.xml&#xA;layouts/index.xml&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;and they were correctly selected.&#xA;You can find the correct feeds here:&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://staticnotes.org/index.xml&#34; &#xA;&gt;https://staticnotes.org/index.xml&#xA;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://staticnotes.org/posts.xml&#34; &#xA;&gt;https://staticnotes.org/posts.xml&#xA;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://staticnotes.org/til.xml&#34; &#xA;&gt;https://staticnotes.org/til.xml&#xA;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;conclusion&#34; class=&#34;content-heading&#34;&gt;Conclusion&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;A common criticism is that LLMs will confidently hallucinate incorrect information. I think this will likely be true for the next couple of years. However, when I use the old-school google + stackoverflow + documentation + online discussion workflow I also might generate an incorrect approach. This can happen because I misunderstand the content or because I can&amp;rsquo;t piece the information from different sources together correctly for my use case.&lt;/p&gt;&#xA;&lt;p&gt;I think viewing an LLM as an interactive interface to search and combine information and context is the right mental model to use them productively.&lt;/p&gt;&#xA;</description>
    </item>
    
    <item>
      <title>Processing 112M rows of steam reviews locally with DuckDB</title>
      <link>https://staticnotes.org/posts/duckdb-large-datasets/</link>
      <pubDate>Sat, 28 Sep 2024 13:43:40 +0000</pubDate>
      
      <guid>https://staticnotes.org/posts/duckdb-large-datasets/</guid>
    <description>&lt;p&gt;In &lt;a href=&#34;https://staticnotes.org/posts/duckdb-for-data-scientists/&#34; &#xA;&gt;DuckDB use cases for data scientists: Querying remote S3 files&#xA;&lt;/a&gt; I wrote about how I use DuckDB as a convenient way to query data from CSV or Parquet files in S3. Another use case for data scientists and data engineers is DuckDB&amp;rsquo;s ability to process larger-than-memory data on your local machine. For us data scientists this fills the gap between&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;small data that you can transform with pandas&lt;/li&gt;&#xA;&lt;li&gt;big data that typically requires a multi-node processing engine like PySpark.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;In this post I am going to run some exploratory queries against this &lt;a href=&#34;https://www.kaggle.com/datasets/kieranpoc/steam-reviews/&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;kaggle Steam review dataset&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;. It contains 112M rows of Steam game reviews and comes as an uncompressed 46GB CSV file (17GB compressed). Let&amp;rsquo;s find out how my MacBook copes with it. Later I am going to run a small comparison against &lt;a href=&#34;https://docs.pola.rs/&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;polars&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;, a DataFrame library, which is often mentioned for out-of-memory processing.&lt;span class=&#34;sidenote-number&#34;&gt;&lt;small class=&#34;sidenote&#34;&gt;I am not comparing against pandas because its inability to work with large datasets was one of the reasons why I explored this topic. However, you could do this &lt;a href=&#34;https://pandas.pydata.org/docs/user_guide/scale.html#use-chunking&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;with chunking&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;.&lt;/small&gt;&lt;/span&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;useful-commands-and-settings&#34; class=&#34;content-heading&#34;&gt;Useful commands and settings&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Before I start, here are some useful DuckDB commands and settings that I often use:&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Command&lt;/th&gt;&#xA;          &lt;th&gt;What is it good for?&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;.timer on&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Prints the execution time after each SQL command&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;FROM duckdb_memory();&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Gives an overview of how much memory is used by DuckDB&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;SET enable_progress_bar = true;&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Prints a progress bar for query runs&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;SET memory_limit = &#39;2GB&#39;;&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Controls how much data DuckDB can keep in RAM.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;.mode line&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Prints query results one at a time (useful if many columns). Default: &lt;code&gt;.mode duckbox&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;SUMMARIZE (select *)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Summarizes the contents of a table.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;.exit&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Stops DuckDB.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;steam-review-dataset&#34; class=&#34;content-heading&#34;&gt;Steam review dataset&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;First, I want to familiarise myself with the dataset. I downloaded the 46GB file &lt;code&gt;all_reviews.csv&lt;/code&gt; to my 2023 MacBook Pro M3 with 18GB RAM. After starting a non-persistent session with the &lt;code&gt;duckdb&lt;/code&gt; command, I use the above commands to activate the timer and line mode. I then take a look at an example row with:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;sql&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;select&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;from&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;read_csv&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;all_reviews.csv&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ignore_errors&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;limit&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&lt;pre&gt;&lt;code&gt;              recommendationid = 148919893&#xA;                         appid = 10&#xA;                          game = Counter-Strike&#xA;                author_steamid = 76561199036724879&#xA;        author_num_games_owned = 0&#xA;            author_num_reviews = 3&#xA;       author_playtime_forever = 197&#xA;author_playtime_last_two_weeks = 197&#xA;     author_playtime_at_review = 197&#xA;            author_last_played = 1698336369&#xA;                      language = russian&#xA;                        review = старость&#xA;             timestamp_created = 1698336397&#xA;             timestamp_updated = 1698336397&#xA;                      voted_up = 1&#xA;                      votes_up = 0&#xA;                   votes_funny = 0&#xA;           weighted_vote_score = 0.0&#xA;                 comment_count = 0&#xA;                steam_purchase = 1&#xA;             received_for_free = 0&#xA;   written_during_early_access = 0&#xA;         hidden_in_steam_china = 1&#xA;          steam_china_location = &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;This gives me an overview of the file columns and an idea of their content.&lt;/p&gt;&#xA;&lt;h2 id=&#34;processing-the-file-directly&#34; class=&#34;content-heading&#34;&gt;Processing the file directly&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Rather than slowly loading all data into DuckDB, I want to test DuckDB&amp;rsquo;s stream processing capabilities. Instead of fully materialising the data in memory, the execution engine reads and processes the data in chunks.&lt;span class=&#34;sidenote-number&#34;&gt;&lt;small class=&#34;sidenote&#34;&gt;This is a useful feature if you want to convert larger-than-memory files from one format to another format, e.g. CSV to Parquet. But that&amp;rsquo;s not what we are here for today.&lt;/small&gt;&lt;/span&gt;&lt;/p&gt;&#xA;&lt;p&gt;I am going to use two aggregation queries for my benchmark:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;The first query counts the number of English reviews per steam account and sorts them from highest to lowest&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;terminal&#34;&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-terminal&#34; data-lang=&#34;terminal&#34;&gt;D select author_steamid, &#xA;COUNT(*) AS num_reviews  &#xA;FROM read_csv(&amp;#39;all_reviews.csv&amp;#39;, ignore_errors = true)  &#xA;WHERE language = &amp;#39;english&amp;#39; &#xA;GROUP BY author_steamid &#xA;ORDER BY num_reviews DESC;&#xA;100% ▕████████████████████████████████████████████████████████████▏ &#xA;┌───────────────────┬─────────────┐&#xA;│  author_steamid   │ num_reviews │&#xA;│       int64       │    int64    │&#xA;├───────────────────┼─────────────┤&#xA;│ 76561198030784015 │        9674 │&#xA;│ 76561198024340430 │        5930 │&#xA;│ 76561198067298289 │        5534 │&#xA;│ 76561198094803808 │        4341 │&#xA;│ 76561198125392509 │        4124 │&#xA;│ 76561198027267313 │        4124 │&#xA;│ 76561197960373660 │        3350 │&#xA;│ 76561197970602587 │        3212 │&#xA;│ 76561197961017729 │        2810 │&#xA;│ 76561198155150242 │        2554 │&#xA;│ 76561198066590240 │        2490 │&#xA;│ 76561198045381877 │        2280 │&#xA;│ 76561198069159152 │        2216 │&#xA;│ 76561197960319772 │        1978 │&#xA;│ 76561198062813911 │        1940 │&#xA;│ 76561198025731804 │        1928 │&#xA;│ 76561198137285867 │        1898 │&#xA;│ 76561198043135631 │        1883 │&#xA;│ 76561198036629241 │        1875 │&#xA;│ 76561198055119582 │        1843 │&#xA;│         ·         │           · │&#xA;│         ·         │           · │&#xA;│         ·         │           · │&#xA;│ 76561198111175247 │           1 │&#xA;├───────────────────┴─────────────┤&#xA;│    15324507 rows (40 shown)     │&#xA;└─────────────────────────────────┘&#xA;Run Time (s): real 22.556 user 166.382276 sys 9.815767&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;This query processes the 112M rows in a surprisingly short 22.5s. The most active Steam user has written 9674 game reviews. You can find them &lt;a href=&#34;https://www.steamidfinder.com/lookup/76561198030784015/&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;here&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;I made the second query intentionally more complex. I filter for reviews that contain the word &lt;code&gt;love&lt;/code&gt; and I also compute the mean of the &lt;code&gt;votes_up&lt;/code&gt; score and the sum of the &lt;code&gt;votes_funny&lt;/code&gt; score for each user. I then filter the aggregation using &lt;code&gt;having&lt;/code&gt; for only users that have a &lt;code&gt;sum_votes_funny&lt;/code&gt; score of more than 100.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;terminal&#34;&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-terminal&#34; data-lang=&#34;terminal&#34;&gt;D SELECT author_steamid, &#xA;    COUNT(*) AS num_reviews,&#xA;    avg(votes_up) as mean_votes_up, &#xA;    sum(votes_funny) as sum_votes_funny,&#xA;    FROM read_csv(&amp;#39;all_reviews.csv&amp;#39;, ignore_errors = true) &#xA;    WHERE language = &amp;#39;english&amp;#39; and review LIKE &amp;#39;%love%&amp;#39;&#xA;    GROUP BY author_steamid &#xA;    having sum_votes_funny &amp;gt; 100 &#xA;    ORDER BY num_reviews DESC; &#xA;100% ▕████████████████████████████████████████████████████████████▏ &#xA;┌───────────────────┬─────────────┬────────────────────┬─────────────────┐&#xA;│  author_steamid   │ num_reviews │   mean_votes_up    │ sum_votes_funny │&#xA;│       int64       │    int64    │       double       │     int128      │&#xA;├───────────────────┼─────────────┼────────────────────┼─────────────────┤&#xA;│ 76561198155150242 │         777 │   1.09009009009009 │             362 │&#xA;│ 76561198042406453 │         665 │ 13.478195488721804 │             341 │&#xA;│ 76561198043135631 │         475 │ 2.9410526315789474 │             116 │&#xA;│ 76561198149437416 │         352 │ 1.2386363636363635 │             256 │&#xA;│ 76561198007343154 │         342 │  4.038011695906433 │             375 │&#xA;│ 76561198066590240 │         273 │  9.293040293040294 │             247 │&#xA;│ 76561197970314107 │         241 │  38.15767634854772 │             319 │&#xA;│ 76561197961017729 │         237 │ 10.278481012658228 │             251 │&#xA;│ 76561197981638563 │         231 │ 26.372294372294373 │             509 │&#xA;│ 76561197972040704 │         223 │  8.560538116591928 │             123 │&#xA;│ 76561197992694498 │         223 │   73.1390134529148 │             759 │&#xA;│ 76561197970761123 │         222 │ 22.603603603603602 │             150 │&#xA;│ 76561198043609914 │         217 │ 18.792626728110598 │             129 │&#xA;│ 76561198053422627 │         202 │  17.04950495049505 │             284 │&#xA;│ 76561198007888370 │         196 │  93.96938775510205 │            3345 │&#xA;│ 76561198040884867 │         191 │  38.41884816753927 │             509 │&#xA;│ 76561198817597644 │         166 │ 22.542168674698797 │             119 │&#xA;│ 76561198356141989 │         166 │ 19.246987951807228 │             216 │&#xA;│ 76561198031599084 │         165 │ 17.163636363636364 │             171 │&#xA;│ 76561198011647032 │         159 │  6.345911949685535 │             192 │&#xA;│         ·         │           · │                ·   │              ·  │&#xA;│         ·         │           · │                ·   │              ·  │&#xA;│         ·         │           · │                ·   │              ·  │&#xA;│ 76561198106232693 │           1 │               96.0 │             155 │&#xA;├───────────────────┴─────────────┴────────────────────┴─────────────────┤&#xA;│ 2383 rows (40 shown)                                         4 columns │&#xA;└────────────────────────────────────────────────────────────────────────┘&#xA;Run Time (s): real 26.648 user 213.432443 sys 7.281861&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;With ~26s this is on par with the previous query. User &lt;code&gt;76561198155150242&lt;/code&gt; used the word &amp;ldquo;love&amp;rdquo; in 777 reviews and collected 362 funny upvotes across their reviews.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;These timings show how incredibly convenient DuckDB is to analyse large datasets. &amp;lt;30s is still in the realm where I can interactively work with the dataset and explore different queries.&lt;span class=&#34;sidenote-number&#34;&gt;&lt;small class=&#34;sidenote&#34;&gt;You can push DuckDB to the limit by using a blocking operator, e.g. a rank() window function. This query will be slow because the entire input needs to be buffered to compute the result.&lt;/small&gt;&lt;/span&gt;&lt;/p&gt;&#xA;&lt;p&gt;When I explore data it is more common that I work in a notebook instead of the CLI. Fortunately, I can run the same queries using the &lt;a href=&#34;https://duckdb.org/docs/guides/python/install&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;DuckDB Python client&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;. In the next section I am going to compare it against polars, another relatively new kid on the block for out-of-memory analytics.&lt;/p&gt;&#xA;&lt;h2 id=&#34;working-in-a-jupyter-notebook&#34; class=&#34;content-heading&#34;&gt;Working in a Jupyter notebook&#xA;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;duckdb-python-client&#34; class=&#34;content-heading&#34;&gt;DuckDB Python client&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;I am going to write the code to run the above queries using the &lt;code&gt;duckdb&lt;/code&gt; Python package and return the results as a &lt;code&gt;pandas&lt;/code&gt; dataframe.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;duckdb&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;pandas&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;as&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;pd&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Connect to DuckDB&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;conn&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;duckdb&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;connect&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;:memory:&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Define the queries&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;query1&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&amp;#34;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;SELECT author_steamid, COUNT(*) AS num_reviews &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;FROM read_csv(&amp;#39;./steam_reviews/all_reviews/all_reviews.csv&amp;#39;, ignore_errors = true) &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;WHERE language = &amp;#39;english&amp;#39; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;GROUP BY author_steamid &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;ORDER BY num_reviews DESC &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;query2&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&amp;#34;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;SELECT author_steamid, &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;COUNT(*) AS num_reviews,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;avg(votes_up) as mean_votes_up, &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;sum(votes_funny) as sum_votes_funny,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;FROM read_csv(&amp;#39;./steam_reviews/all_reviews/all_reviews.csv&amp;#39;, ignore_errors = true) &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;WHERE language = &amp;#39;english&amp;#39; and review LIKE &amp;#39;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;%lo&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;ve%&amp;#39;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;GROUP BY author_steamid &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;having sum_votes_funny &amp;gt; 100 &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;ORDER BY num_reviews DESC &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;%%&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;timeit&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Execute the query&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;result1&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;conn&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;execute&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;query1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Convert the result to a pandas DataFrame&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;df1&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;result1&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;df&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;pre&gt;&lt;code&gt;16.6 s ± 627 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;df1&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;head&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;div&gt;&#xA;&lt;style scoped&gt;&#xA;    .dataframe tbody tr th:only-of-type {&#xA;        vertical-align: middle;&#xA;    }&#xA;&lt;pre&gt;&lt;code&gt;.dataframe tbody tr th {&#xA;    vertical-align: top;&#xA;}&#xA;&#xA;.dataframe thead th {&#xA;    text-align: right;&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&lt;/style&gt;&lt;/p&gt;&#xA;&lt;table border=&#34;1&#34; class=&#34;dataframe&#34;&gt;&#xA;  &lt;thead&gt;&#xA;    &lt;tr style=&#34;text-align: right;&#34;&gt;&#xA;      &lt;th&gt;&lt;/th&gt;&#xA;      &lt;th&gt;author_steamid&lt;/th&gt;&#xA;      &lt;th&gt;num_reviews&lt;/th&gt;&#xA;    &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;0&lt;/th&gt;&#xA;      &lt;td&gt;76561198030784015&lt;/td&gt;&#xA;      &lt;td&gt;9674&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;1&lt;/th&gt;&#xA;      &lt;td&gt;76561198024340430&lt;/td&gt;&#xA;      &lt;td&gt;5930&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;2&lt;/th&gt;&#xA;      &lt;td&gt;76561198067298289&lt;/td&gt;&#xA;      &lt;td&gt;5534&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;3&lt;/th&gt;&#xA;      &lt;td&gt;76561198094803808&lt;/td&gt;&#xA;      &lt;td&gt;4341&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;4&lt;/th&gt;&#xA;      &lt;td&gt;76561198027267313&lt;/td&gt;&#xA;      &lt;td&gt;4124&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;%%&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;timeit&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Execute the query&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;result2&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;conn&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;execute&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;query2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Convert the result to a pandas DataFrame&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;df2&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;result2&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;df&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;pre&gt;&lt;code&gt;19.9 s ± 343 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;df2&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;head&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;div&gt;&#xA;&lt;style scoped&gt;&#xA;    .dataframe tbody tr th:only-of-type {&#xA;        vertical-align: middle;&#xA;    }&#xA;&lt;pre&gt;&lt;code&gt;.dataframe tbody tr th {&#xA;    vertical-align: top;&#xA;}&#xA;&#xA;.dataframe thead th {&#xA;    text-align: right;&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&lt;/style&gt;&lt;/p&gt;&#xA;&lt;table border=&#34;1&#34; class=&#34;dataframe&#34;&gt;&#xA;  &lt;thead&gt;&#xA;    &lt;tr style=&#34;text-align: right;&#34;&gt;&#xA;      &lt;th&gt;&lt;/th&gt;&#xA;      &lt;th&gt;author_steamid&lt;/th&gt;&#xA;      &lt;th&gt;num_reviews&lt;/th&gt;&#xA;      &lt;th&gt;mean_votes_up&lt;/th&gt;&#xA;      &lt;th&gt;sum_votes_funny&lt;/th&gt;&#xA;    &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;0&lt;/th&gt;&#xA;      &lt;td&gt;76561198155150242&lt;/td&gt;&#xA;      &lt;td&gt;777&lt;/td&gt;&#xA;      &lt;td&gt;1.090090&lt;/td&gt;&#xA;      &lt;td&gt;362.0&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;1&lt;/th&gt;&#xA;      &lt;td&gt;76561198042406453&lt;/td&gt;&#xA;      &lt;td&gt;665&lt;/td&gt;&#xA;      &lt;td&gt;13.478195&lt;/td&gt;&#xA;      &lt;td&gt;341.0&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;2&lt;/th&gt;&#xA;      &lt;td&gt;76561198043135631&lt;/td&gt;&#xA;      &lt;td&gt;475&lt;/td&gt;&#xA;      &lt;td&gt;2.941053&lt;/td&gt;&#xA;      &lt;td&gt;116.0&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;3&lt;/th&gt;&#xA;      &lt;td&gt;76561198149437416&lt;/td&gt;&#xA;      &lt;td&gt;352&lt;/td&gt;&#xA;      &lt;td&gt;1.238636&lt;/td&gt;&#xA;      &lt;td&gt;256.0&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;4&lt;/th&gt;&#xA;      &lt;td&gt;76561198007343154&lt;/td&gt;&#xA;      &lt;td&gt;342&lt;/td&gt;&#xA;      &lt;td&gt;4.038012&lt;/td&gt;&#xA;      &lt;td&gt;375.0&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;Not surprisingly this gives the same results as the queries run from the CLI. Having the aggregated results as a pandas dataframe allows me to use it in downstream work. I don&amp;rsquo;t need to learn another syntax.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;## clear memory &lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;gc&lt;/span&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;del&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;result1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;result2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;df1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;df2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;gc&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;collect&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;h3 id=&#34;polars&#34; class=&#34;content-heading&#34;&gt;Polars&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;Since polars is often brought up as a faster and better pandas, I want to do a performance comparison. Polars supports lazy dataframes, which allows me to define operations on the dataframe without loading it fully into memory first.&lt;/p&gt;&#xA;&lt;p&gt;Below are the two equivalent queries written using polars syntax.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt;  &lt;span class=&#34;nn&#34;&gt;polars&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;as&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;pl&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Create a lazy DataFrame&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;df_lazy&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;pl&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;scan_csv&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;./steam_reviews/all_reviews/all_reviews.csv&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ignore_errors&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;True&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;%%&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;time&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Define the query using lazy operations&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;result_polars1&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;df_lazy&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;filter&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;pl&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;col&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;language&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;english&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;group_by&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;author_steamid&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;agg&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;pl&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;alias&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;num_reviews&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sort&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;num_reviews&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;descending&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;True&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Execute the query and collect the results&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;df_polars1&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;result_polars1&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;collect&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;df_polars1&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;head&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;pre&gt;&lt;code&gt;CPU times: user 1min 3s, sys: 22.1 s, total: 1min 26s&#xA;Wall time: 2min 7s&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;div&gt;&lt;style&gt;&#xA;.dataframe &gt; thead &gt; tr,&#xA;.dataframe &gt; tbody &gt; tr {&#xA;  text-align: right;&#xA;  white-space: pre-wrap;&#xA;}&#xA;&lt;/style&gt;&#xA;&lt;small&gt;shape: (5, 2)&lt;/small&gt;&lt;table border=&#34;1&#34; class=&#34;dataframe&#34;&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;author_steamid&lt;/th&gt;&lt;th&gt;num_reviews&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;i64&lt;/td&gt;&lt;td&gt;u32&lt;/td&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;76561198030784015&lt;/td&gt;&lt;td&gt;9822&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;76561198024340430&lt;/td&gt;&lt;td&gt;5983&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;76561198067298289&lt;/td&gt;&lt;td&gt;5577&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;76561198094803808&lt;/td&gt;&lt;td&gt;4408&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;76561198125392509&lt;/td&gt;&lt;td&gt;4203&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;%%&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;time&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;result_polars2&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;df_lazy&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;filter&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;pl&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;col&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;language&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;english&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;pl&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;col&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;review&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;str&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;contains&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;love&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;group_by&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;author_steamid&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;agg&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;([&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;pl&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;alias&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;num_reviews&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;pl&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;col&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;votes_up&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;mean&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;alias&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;mean_votes_up&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;pl&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;col&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;votes_funny&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sum&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;alias&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;sum_votes_funny&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;])&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;filter&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;pl&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;col&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;sum_votes_funny&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;100&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sort&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;num_reviews&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;descending&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;True&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Execute the query and collect the results&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;df_polars2&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;result_polars2&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;collect&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;df_polars2&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;head&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;pre&gt;&lt;code&gt;&amp;lt;timed exec&amp;gt;:9: DeprecationWarning: `pl.count()` is deprecated. Please use `pl.len()` instead.&#xA;&#xA;&#xA;CPU times: user 1min 21s, sys: 42.7 s, total: 2min 4s&#xA;Wall time: 5min 14s&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;div&gt;&lt;style&gt;&#xA;.dataframe &gt; thead &gt; tr,&#xA;.dataframe &gt; tbody &gt; tr {&#xA;  text-align: right;&#xA;  white-space: pre-wrap;&#xA;}&#xA;&lt;/style&gt;&#xA;&lt;small&gt;shape: (5, 4)&lt;/small&gt;&lt;table border=&#34;1&#34; class=&#34;dataframe&#34;&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;author_steamid&lt;/th&gt;&lt;th&gt;num_reviews&lt;/th&gt;&lt;th&gt;mean_votes_up&lt;/th&gt;&lt;th&gt;sum_votes_funny&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;i64&lt;/td&gt;&lt;td&gt;u32&lt;/td&gt;&lt;td&gt;f64&lt;/td&gt;&lt;td&gt;i64&lt;/td&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;76561198155150242&lt;/td&gt;&lt;td&gt;786&lt;/td&gt;&lt;td&gt;1.094148&lt;/td&gt;&lt;td&gt;369&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;76561198042406453&lt;/td&gt;&lt;td&gt;673&lt;/td&gt;&lt;td&gt;13.43685&lt;/td&gt;&lt;td&gt;345&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;76561198043135631&lt;/td&gt;&lt;td&gt;480&lt;/td&gt;&lt;td&gt;2.922917&lt;/td&gt;&lt;td&gt;116&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;76561198149437416&lt;/td&gt;&lt;td&gt;360&lt;/td&gt;&lt;td&gt;1.216667&lt;/td&gt;&lt;td&gt;256&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;76561198007343154&lt;/td&gt;&lt;td&gt;354&lt;/td&gt;&lt;td&gt;4.014124&lt;/td&gt;&lt;td&gt;381&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;&#xA;&lt;p&gt;Now we can compare DuckDB vs. polars execution times for the two queries:&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;wall time&lt;/th&gt;&#xA;          &lt;th&gt;DuckDB&lt;/th&gt;&#xA;          &lt;th&gt;polars&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;query 1&lt;/td&gt;&#xA;          &lt;td&gt;17s&lt;/td&gt;&#xA;          &lt;td&gt;2min 7s&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;query 2&lt;/td&gt;&#xA;          &lt;td&gt;20s&lt;/td&gt;&#xA;          &lt;td&gt;5min 14s&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;conclusion&#34; class=&#34;content-heading&#34;&gt;Conclusion&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;This investigation shows that DuckDB is a powerful and convenient tool to process larger-than-memory datasets on a single machine. As a data scientist this is useful, because I can focus on exploring the data in the early exploration phase. I don&amp;rsquo;t yet have to spend time setting up more complex tools or work on a remote machine. The quick comparison with polars also shows its speed advantages and that it can be used as a drop-in when aggregating data in Jupyter notebooks.&lt;/p&gt;&#xA;</description>
    </item>
    
    <item>
      <title>TIL about the QUALIFY statement in SQL</title>
      <link>https://staticnotes.org/til/sql-qualify/</link>
      <pubDate>Mon, 16 Sep 2024 00:00:00 +0100</pubDate>
      
      <guid>https://staticnotes.org/til/sql-qualify/</guid>
    <description>&lt;p&gt;Today I came across the &lt;code&gt;QUALIFY&lt;/code&gt; clause which is supported in some SQL dialects.&lt;span class=&#34;sidenote-number&#34;&gt;&lt;small class=&#34;sidenote&#34;&gt; It&amp;rsquo;s not part of the SQL standard but supported by the main analytical databases like BigQuery, Snowflake, Oracle, Databricks, DuckDB, etc.&lt;/small&gt;&lt;/span&gt; It&amp;rsquo;s part of &lt;a href=&#34;https://en.wikipedia.org/wiki/Snowflake_Inc.&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;Snowflake&amp;rsquo;s&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;                style=&#34;height: 0.7em; width: 0.7em; margin-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;                class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;                viewBox=&#34;0 0 640 512&#34;&gt;&#xA;                &lt;path fill=&#34;currentColor&#34;&#xA;                    d=&#34;M640 51.2l-.3 12.2c-28.1 .8-45 15.8-55.8 40.3-25 57.8-103.3 240-155.3 358.6H415l-81.9-193.1c-32.5 63.6-68.3 130-99.2 193.1-.3 .3-15 0-15-.3C172 352.3 122.8 243.4 75.8 133.4 64.4 106.7 26.4 63.4 .2 63.7c0-3.1-.3-10-.3-14.2h161.9v13.9c-19.2 1.1-52.8 13.3-43.3 34.2 21.9 49.7 103.6 240.3 125.6 288.6 15-29.7 57.8-109.2 75.3-142.8-13.9-28.3-58.6-133.9-72.8-160-9.7-17.8-36.1-19.4-55.8-19.7V49.8l142.5 .3v13.1c-19.4 .6-38.1 7.8-29.4 26.1 18.9 40 30.6 68.1 48.1 104.7 5.6-10.8 34.7-69.4 48.1-100.8 8.9-20.6-3.9-28.6-38.6-29.4 .3-3.6 0-10.3 .3-13.6 44.4-.3 111.1-.3 123.1-.6v13.6c-22.5 .8-45.8 12.8-58.1 31.7l-59.2 122.8c6.4 16.1 63.3 142.8 69.2 156.7L559.2 91.8c-8.6-23.1-36.4-28.1-47.2-28.3V49.6l127.8 1.1 .2 .5z&#34;&gt;&#xA;                &lt;/path&gt;&#xA;            &lt;/svg&gt;&#xA;        &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt; dialect which is the data warehouse that I use at work.&#xA;The &lt;code&gt;QUALIFY&lt;/code&gt; statement lets me filter the result of a query based on the result of a window function.&lt;/p&gt;&#xA;&lt;p&gt;The order of execution is the following:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&lt;pre&gt;&lt;code&gt;-- SELECT &amp;lt;columns&amp;gt;&#xA;--   FROM &amp;lt;table&amp;gt;&#xA;--   [GROUP BY ...]&#xA;--   [HAVING ...]&#xA;--   QUALIFY &amp;lt;window function predicate&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;&lt;code&gt;QUALIFY&lt;/code&gt; is for window functions what &lt;code&gt;HAVING&lt;/code&gt; is for group-by statements. It allows me to filter based on the outputs of these operations without having to use a subquery or CTE. Therefore I can write more concise SQL.&lt;/p&gt;&#xA;&lt;p&gt;I often deduplicate data using the &lt;code&gt;row_number()&lt;/code&gt; window function. Sometimes I just want the latest event for a particular user in a dataset.&lt;/p&gt;&#xA;&lt;p&gt;An example would be:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;SQL&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-SQL&#34; data-lang=&#34;SQL&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;-- logs with some user events and timestamps&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;with&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;logs_annotated&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;as&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;select&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;logs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;row_number&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;over&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;partition&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;by&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;user_id&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;order&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;by&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;event_timestamp&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;desc&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;as&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;row_number&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;from&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;logs&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;select&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;from&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;logs_annotated&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;where&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;row_number&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;With &lt;code&gt;QUALIFY&lt;/code&gt; I can write one statement instead:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;SQL&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-SQL&#34; data-lang=&#34;SQL&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;select&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;from&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;logs&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;qualify&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;row_number&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;over&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;partition&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;by&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;user_id&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;order&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;by&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;event_timestamp&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;desc&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;This works because the &lt;code&gt;QUALIFY&lt;/code&gt; statement is evaluated after the window function result has been computed and can therefore be used in filtering.&lt;/p&gt;&#xA;</description>
    </item>
    
    <item>
      <title>Interest rate expectations</title>
      <link>https://staticnotes.org/posts/interest-rate-expecations/</link>
      <pubDate>Sat, 14 Sep 2024 00:00:00 +0100</pubDate>
      
      <guid>https://staticnotes.org/posts/interest-rate-expecations/</guid>
    <description>&lt;p&gt;This morning, the top headline on the front page of my weekly newspaper was:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;Bets rise on bumper rate cut by Fed&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;Investors have sharply increased their bets on half percentage point interest rate cut by the Federal reserve next week as the US central bank prepares to lower borrowing cost for the first time in more than four years. Traders in swaps markets are pricing in a 43 percent chance the Fed will opt for a bumper cut in a bid to prevent high rates damaging the economy. -  &lt;em&gt;FT Weekend (14.9.2024)&lt;/em&gt;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;I often read news about expectations on how &lt;em&gt;the Fed&lt;/em&gt; will set interest rates. I have a vague sense that it is related to the price of some interest rate related futures. I just asked myself if I wanted to look up what the market expectation is next week, where would I actually go to look? This detail is regularly glossed over in news articles. It turns out that the maths behind the expectation approximation is fairly simple. Let us work it out.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-federal-funds-rate&#34; class=&#34;content-heading&#34;&gt;The Federal Funds Rate&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Banks are required to put a certain percentage of their deposits into accounts at a Federal Reserve Bank. This is to maintain liquidity to cover depositors&amp;rsquo; withdrawals in the short-term. This reserve requirement is loosely a percentage of the bank&amp;rsquo;s deposits at the end of the day averaged over a two-week period. If at the end of the day a bank has excess reserve balances it can lend it overnight to another bank that is undercapitalised. Banks do that a lot and negotiate an interest rate for this overnight loan. The weighted average of all these deals for the day is the effective federal funds rate (EFFR).&lt;/p&gt;&#xA;&lt;p&gt;The Fed&amp;rsquo;s federal funds rate (FFR) target range is set by the Federal Open Market Committee and is the desired range for the EFFR. While the Fed can&amp;rsquo;t directly impact the EFFR it can influence it via their own deposit interest rates or changes to the monetary supply. This tells us what today&amp;rsquo;s FFR range and EFFR are (both are published by the Fed). But how do we compute the market&amp;rsquo;s expectations on changes to the FFR range?&lt;/p&gt;&#xA;&lt;h2 id=&#34;federal-funds-futures&#34; class=&#34;content-heading&#34;&gt;Federal Funds Futures&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;We can infer the expectations of FFR range changes at the next committee meeting by looking into how FFR future contracts are priced. Those are traded on the Chicago Mercantile Exchange (CME). You can view the monthly future quotes on the &lt;a href=&#34;https://www.cmegroup.com/markets/interest-rates/stirs/30-day-federal-fund.quotes.html&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;CME website&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;These futures are used by banks and fixed-income portfolio managers to hedge against short-term interest rate fluctuations. The 30-day futures are monthly contracts that are settled on the last business day of every month. The contract price payable is the arithmetic mean of the daily EFFR during the contract month as &lt;a href=&#34;https://www.newyorkfed.org/markets/reference-rates/effr&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;reported by&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt; the Federal Reserve Bank of New York, subtracted from 100. For example if the average interest rate was 3.5% for a given month, then the contract price would be 100 - 3.5 = $96.5. Unfortunately, the minimum contract size is the price times 4167, which for this example would be $96.5 * 4167 = $416,796.5. A bit too high for my personal hedging needs.&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-do-the-federal-funds-futures-hold&#34; class=&#34;content-heading&#34;&gt;What do the Federal Funds Future(s) hold?&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Now that we know about the EFFR and Federal Funds futures, we can collect all ingredients to compute the expectations:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;The current FFR range was set in the July meeting to 5.25% - 5.5%.&lt;/li&gt;&#xA;&lt;li&gt;The dates of the next committee meetings are published here on &lt;a href=&#34;https://www.federalreserve.gov/monetarypolicy/fomccalendars.htm&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;federalreserve.gov&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;. The remaining meetings this year are scheduled for: 18.9.2024, 7.11.2024, 18.12.2024.&lt;/li&gt;&#xA;&lt;li&gt;The expected EFFR from the futures contracts for the month of the next committee meeting and the following month. For the remaining year they were on the 14.9.2024:&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;pandas&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;as&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;pd&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;df&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;pd&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;DataFrame&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;s1&#34;&gt;&amp;#39;MONTH&amp;#39;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;09-2024&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;10-2024&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;11-2024&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;12-2024&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;],&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;s1&#34;&gt;&amp;#39;PRICE&amp;#39;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mf&#34;&gt;94.81&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mf&#34;&gt;95.03&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mf&#34;&gt;95.315&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mf&#34;&gt;95.58&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;],&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;s1&#34;&gt;&amp;#39;MEETING&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;18&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;None&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;7&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;18&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;     &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;set_index&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;MONTH&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;display&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;df&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;div&gt;&#xA;&lt;style scoped&gt;&#xA;    .dataframe tbody tr th:only-of-type {&#xA;        vertical-align: middle;&#xA;    }&#xA;&lt;pre&gt;&lt;code&gt;.dataframe tbody tr th {&#xA;    vertical-align: top;&#xA;}&#xA;&#xA;.dataframe thead th {&#xA;    text-align: right;&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&lt;/style&gt;&lt;/p&gt;&#xA;&lt;table border=&#34;1&#34; class=&#34;dataframe&#34;&gt;&#xA;  &lt;thead&gt;&#xA;    &lt;tr style=&#34;text-align: right;&#34;&gt;&#xA;      &lt;th&gt;&lt;/th&gt;&#xA;      &lt;th&gt;PRICE&lt;/th&gt;&#xA;      &lt;th&gt;MEETING&lt;/th&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;MONTH&lt;/th&gt;&#xA;      &lt;th&gt;&lt;/th&gt;&#xA;      &lt;th&gt;&lt;/th&gt;&#xA;    &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;09-2024&lt;/th&gt;&#xA;      &lt;td&gt;94.810&lt;/td&gt;&#xA;      &lt;td&gt;18.0&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;10-2024&lt;/th&gt;&#xA;      &lt;td&gt;95.030&lt;/td&gt;&#xA;      &lt;td&gt;NaN&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;11-2024&lt;/th&gt;&#xA;      &lt;td&gt;95.315&lt;/td&gt;&#xA;      &lt;td&gt;7.0&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;12-2024&lt;/th&gt;&#xA;      &lt;td&gt;95.580&lt;/td&gt;&#xA;      &lt;td&gt;18.0&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;The next month without a committee meeting is October. This means that October&amp;rsquo;s average EFFR can only be impacted by the September committee meeting. This means that the October future contract price of $95.03 is the expected average EFFR for October.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;r_october_avg&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;100&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;df&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;at&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;10-2024&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;PRICE&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;r_september_avg&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;100&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;df&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;at&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;09-2024&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;PRICE&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;It is also the expected EFFR on the last day of September (and the first day of November).&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;r_september_end&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;r_october_avg&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;We are interested in the implied difference in EFFR at the beginning and the end of the month. If there is a difference, then this would imply that the FED committee changed the FFR range.&lt;/p&gt;&#xA;&lt;p&gt;Keep in mind that the contract price is the arithmetic mean of the realised EFFR on every day in September. Assume that the FED committee does lower the price on the 18th of the month, then we will have 18 days at a higher rate and 30-18 = 12 days at a lower rate. All of that is captured in the future price (the average EFFR) for September.&lt;/p&gt;&#xA;&lt;p&gt;Therefore we can use:&lt;/p&gt;&#xA;&lt;p&gt;\[&#xA;EFFR(\text{average of Sep}) = \frac{N}{30} EFFR(\text{start of Sep}) +\frac{30-N}{30} EFFR(\text{end of Sep})&#xA;\]&lt;/p&gt;&#xA;&lt;p&gt;\[&#xA;\Leftrightarrow EFFR(\text{start of Sep}) = \left( EFFR(\text{average of Sep}) - \frac{30-N}{30}\cdot EFFR(\text{end of Sep}))\right) \cdot \frac{30}{N}&#xA;\]&lt;/p&gt;&#xA;&lt;p&gt;where \(N\) is the number of days before the committee meeting in September.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;days_month&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;30&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;days_before_meeting&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;df&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;at&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;09-2024&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;MEETING&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;days_after_meeting&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;days_month&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;days_before_meeting&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;r_september_start&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;r_september_avg&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;days_after_meeting&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;/&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;days_month&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;r_september_end&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;days_month&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;/&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;days_before_meeting&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;print&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sa&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;The expected EFFR for the start of September is &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;r_september_start&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;.2f&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;%.&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;pre&gt;&lt;code&gt;The expected EFFR for the start of September is 5.34%.&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Since today is the 14th of September, we can already look up the realised EFFR up until today. If today were the 25th of August, then we need to calculate the expected \(EFFR(\text{start of Sep})\) with the above formula.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;r_september_change&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;r_september_end&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;r_september_start&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;print&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sa&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;Comparing the expected EFFR at beginning and end of month we have a delta of &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;r_september_change&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;.2f&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;.&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;pre&gt;&lt;code&gt;Comparing the expected EFFR at beginning and end of month we have a delta of -0.37.&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The market&amp;rsquo;s expectation for September is that we will have 37bps lower EFFR at the end of the month. If we buy the futures contract today we will make money if the FED committee cuts the rate in such a way that the realised EFFR will decrease more than 37bps compared to the beginning of the month.&lt;/p&gt;&#xA;&lt;p&gt;Traditionally, the committee changes the FFR range by 25bps (however 50 and 100 basis point changes do occur, e.g. after 9/11, after the 2007-2008 housing market crash and during the COVID-19 pandemic).&lt;/p&gt;&#xA;&lt;p&gt;However, we can see that the market prices in a 37bps lower EFFR. This means we can calculate the probability of a 25bps cut vs. a 50bps cut.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;print&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sa&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;The price implies &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;r_september_change&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;/&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;mf&#34;&gt;0.25&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;.2f&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt; x 25bps rate cuts.&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;pre&gt;&lt;code&gt;The price implies 1.47 x 25bps rate cuts.&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;probability_of_25_bps_cut&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;r_september_change&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;/&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;mf&#34;&gt;0.25&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;%&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;print&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sa&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;This implies a probability of &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;100&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;probability_of_25_bps_cut&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;.2f&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;% that the committee announces a 25bps rate cut.&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;pre&gt;&lt;code&gt;This implies a probability of 53.33% that the committee announces a 25bps rate cut.&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;probability_of_50_bps_cut&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;  &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;r_september_change&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;/&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;mf&#34;&gt;0.25&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;%&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;print&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sa&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;This implies a probability of &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;100&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;probability_of_50_bps_cut&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;.2f&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;% that the committee announces a 50bps rate cut.&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;pre&gt;&lt;code&gt;This implies a probability of 46.67% that the committee announces a 50bps rate cut.&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The calculation confirms the newspaper article&amp;rsquo;s claim that the rate decision is priced as a coin flip between a 25bps and a 50bps rate cut.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Update 18.9.2024&lt;/strong&gt;: The FOMC &lt;a href=&#34;https://www.federalreserve.gov/newsevents/pressreleases/monetary20240918a.htm&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;decided&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt; on a 50bps cut to a target range of 4.75% - 5.0%.&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-about-europe&#34; class=&#34;content-heading&#34;&gt;What about Europe?&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;If you are interested in the expectations for the interest rates set by the Bank of England, you can use &lt;a href=&#34;https://www.ice.com/products/66380299/One-Month-SONIA-Index-Futures&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;One Month SONIA Index Futures&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;. Similarly, for the ECB interest rates expectations you can check &lt;a href=&#34;https://www.ice.com/products/83046794/ECB-Dated-ESTR-Futures&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;ECB Dated ESTR Futures&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;h2 id=&#34;jupyter-notebook&#34; class=&#34;content-heading&#34;&gt;Jupyter Notebook&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;You can find the Jupyter notebook for this post &lt;a href=&#34;https://gitlab.com/frankRi89/blog/-/tree/main/notebooks/interest-rate-expectations&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;here&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;.&lt;/p&gt;&#xA;</description>
    </item>
    
    <item>
      <title>TIL about just and justfiles</title>
      <link>https://staticnotes.org/til/just-command-runner/</link>
      <pubDate>Fri, 13 Sep 2024 00:00:00 +0100</pubDate>
      
      <guid>https://staticnotes.org/til/just-command-runner/</guid>
    <description>&lt;p&gt;&lt;a href=&#34;https://github.com/casey/just&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;just&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt; is &lt;em&gt;just&lt;/em&gt; a command runner that I recently started using to define aliases for commands in my projects, e.g. build, test, or deploy commands.&lt;/p&gt;&#xA;&lt;h3 id=&#34;installation&#34; class=&#34;content-heading&#34;&gt;Installation&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;&lt;code&gt;just&lt;/code&gt; can be installed via homebrew:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;shell&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;brew install just&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;h3 id=&#34;example&#34; class=&#34;content-heading&#34;&gt;Example&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;I want to create alias commands for my &lt;a href=&#34;https://www.getdbt.com/&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;dbt&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt; project to build my dbt models in production vs. development environments. At the moment, I need to run the commands:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;shell&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# prod warehouse&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;poetry run dbt run --target prod&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# dev warehouse&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;poetry run dbt run --target dev&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;To define shorter just commands for this project I add a &lt;code&gt;justfile&lt;/code&gt; into the project folder.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;txt&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;# justfile&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;run-prod *ARGS:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    poetry run dbt run --target prod {{ARGS}}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;run-dev *ARGS:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    poetry run dbt run --target dev {{ARGS}}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;If I am inside the project folder I can now run &lt;code&gt;just run-prod&lt;/code&gt; and &lt;code&gt;just run-dev&lt;/code&gt;. Moreover, I can pass in any dbt keyword arguments, e.g.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;shell&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;just run-dev --select mymodel&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;will resolve to&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;shell&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;poetry run dbt run --target dev --select mymodel&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;h3 id=&#34;is-this-not-just-make&#34; class=&#34;content-heading&#34;&gt;Is this not just &lt;code&gt;make&lt;/code&gt;?&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;&lt;code&gt;just&lt;/code&gt; is inspired by the Unix build tool &lt;a href=&#34;https://en.wikipedia.org/wiki/Make_%28software%29&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;make&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;                style=&#34;height: 0.7em; width: 0.7em; margin-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;                class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;                viewBox=&#34;0 0 640 512&#34;&gt;&#xA;                &lt;path fill=&#34;currentColor&#34;&#xA;                    d=&#34;M640 51.2l-.3 12.2c-28.1 .8-45 15.8-55.8 40.3-25 57.8-103.3 240-155.3 358.6H415l-81.9-193.1c-32.5 63.6-68.3 130-99.2 193.1-.3 .3-15 0-15-.3C172 352.3 122.8 243.4 75.8 133.4 64.4 106.7 26.4 63.4 .2 63.7c0-3.1-.3-10-.3-14.2h161.9v13.9c-19.2 1.1-52.8 13.3-43.3 34.2 21.9 49.7 103.6 240.3 125.6 288.6 15-29.7 57.8-109.2 75.3-142.8-13.9-28.3-58.6-133.9-72.8-160-9.7-17.8-36.1-19.4-55.8-19.7V49.8l142.5 .3v13.1c-19.4 .6-38.1 7.8-29.4 26.1 18.9 40 30.6 68.1 48.1 104.7 5.6-10.8 34.7-69.4 48.1-100.8 8.9-20.6-3.9-28.6-38.6-29.4 .3-3.6 0-10.3 .3-13.6 44.4-.3 111.1-.3 123.1-.6v13.6c-22.5 .8-45.8 12.8-58.1 31.7l-59.2 122.8c6.4 16.1 63.3 142.8 69.2 156.7L559.2 91.8c-8.6-23.1-36.4-28.1-47.2-28.3V49.6l127.8 1.1 .2 .5z&#34;&gt;&#xA;                &lt;/path&gt;&#xA;            &lt;/svg&gt;&#xA;        &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt; from 1978. However, &lt;code&gt;just&lt;/code&gt; is not a build system and focuses solely on command running. This avoids having to deal with things like &lt;code&gt;.PHONY&lt;/code&gt; that can catch you out in &lt;code&gt;make&lt;/code&gt; recipes. I also like two additional features of &lt;code&gt;just&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;I can run my &lt;code&gt;just&lt;/code&gt; commands from any subfolder of the project folder that contains the &lt;code&gt;justfile&lt;/code&gt;. This is useful when I work on this blog. I have to run &lt;code&gt;hugo serve&lt;/code&gt; from the top-level project folder and Jupyter-to-markdown converter &lt;code&gt;nb2hugo&lt;/code&gt; from the &lt;code&gt;notebooks/project_x/&lt;/code&gt; subfolder. With &lt;code&gt;just&lt;/code&gt; command aliases it doesn&amp;rsquo;t matter from which folder I run the command.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;code&gt;just&lt;/code&gt; allows you to load environment variables from an &lt;code&gt;.env&lt;/code&gt; file.&lt;/p&gt;&#xA;&lt;p&gt;Example &lt;code&gt;.env&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;shell&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;DATABASE&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;localhost:1111&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;USERACCOUNT&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;rob1234&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;Add &lt;code&gt;set dotenv-load&lt;/code&gt; to your &lt;code&gt;justfile&lt;/code&gt; and the environment variables will be available in your shell:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;shell&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;set&lt;/span&gt; dotenv-load&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;check-vars:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    @echo &lt;span class=&#34;s2&#34;&gt;&amp;#34;Env vars are DATABASE:&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$DATABASE&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt; and USER:&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$USERACCOUNT&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;                                                                &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;Running &lt;code&gt;just check-vars&lt;/code&gt; will output:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;shell&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Env vars are DATABASE:localhost:1111 and USER:rob1234&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;</description>
    </item>
    
    <item>
      <title>Clearing up confusion around IPython, ipykernel, and Jupyter notebooks</title>
      <link>https://staticnotes.org/posts/jupyter-confusion/</link>
      <pubDate>Mon, 26 Aug 2024 00:00:00 +0100</pubDate>
      
      <guid>https://staticnotes.org/posts/jupyter-confusion/</guid>
    <description>&lt;p&gt;One of my big recurring time sinks while doing data science work used to be trying to get my colleagues&amp;rsquo; Jupyter notebooks to run on my machine. The main contributing factors:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;My team generally uses poetry environments to improve reproducibility, but sometimes dependencies aren&amp;rsquo;t specified&lt;/li&gt;&#xA;&lt;li&gt;I use VS code and the Jupyter extension to edit notebooks in VS code which requires more configuration than running the Jupyter web UI.&lt;/li&gt;&#xA;&lt;li&gt;I lacked a clear understanding of the differences between &lt;code&gt;IPython&lt;/code&gt;, &lt;code&gt;ipykernel&lt;/code&gt;, &lt;code&gt;jupyter&lt;/code&gt; and which Python environments are being used when running a notebook.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;So this is my attempt at a &lt;a href=&#34;https://www.swyx.io/friendcatchers&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;friendcatcher&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;. I hope this saves you a few minutes the next time you run into similar issues.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-different-components&#34; class=&#34;content-heading&#34;&gt;The different components&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s distinguish the components that play a role in running a Jupyter notebook:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Jupyter Notebook platform&lt;/strong&gt;: A web-based interactive computing platform that supports different languages via different kernels, e.g. for Python (ipykernel), Julia (IJulia), R (IRKernel)&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Jupyter notebook&lt;/strong&gt; (extension &lt;code&gt;.ipynb&lt;/code&gt;):  is a document in json format that holds metadata and cell code.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;IPython command shell&lt;/strong&gt;: The shell has two components:&#xA;&lt;ol&gt;&#xA;&lt;li&gt;An interactive Python shell. You can start it with &lt;code&gt;ipython&lt;/code&gt;. It&amp;rsquo;s like the default Python REPL but with enhanced features, e.g. object introspection, tab completion, input history, magic commands, etc.&lt;/li&gt;&#xA;&lt;li&gt;A Jupyter kernel &lt;code&gt;ipykernel&lt;/code&gt;. This is the backend process where user Python code runs and which can be connected to different frontends. One frontend is indeed the IPython shell, another one a Jupyter notebook. You can install &lt;code&gt;ipykernel&lt;/code&gt; as a standalone package into your Python environment.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;jupyter&lt;/code&gt; Python package&lt;/strong&gt;. This is a metapackage which installs the notebook, qtconsole, and ipykernel.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;pre class=&#34;mermaid&#34;&gt;graph TD;&#xA;&#xA;colab(Google Colab UI) &lt;--&gt; ipykernel(ipykernel)&#xA;vs(VS Code UI) &lt;--&gt; ipykernel&#xA;ui(jupyter notebook UI) &lt;--&gt;  server&#xA;server(jupyter server) &lt;--&gt;  ipykernel&#xA;ipykernel &lt;--&gt;  ipython[IPython] &#xA;&lt;/pre&gt;&#xA;&lt;h2 id=&#34;jupyter-kernels-vs-shell-environment&#34; class=&#34;content-heading&#34;&gt;Jupyter kernels vs. shell environment&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;One source of confusion is that a Jupyter kernel can point to a different Python executable than your shell environment.&lt;/p&gt;&#xA;&lt;p&gt;To get an overview of available Jupyter executables you can use:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;List all available Jupyter executables in your system:&lt;/strong&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;bash&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ &lt;span class=&#34;nb&#34;&gt;type&lt;/span&gt; -a jupyter&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;List all available Jupyter kernels:&lt;/strong&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;bash&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ jupyter kernelspec list&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Every Jupyter kernel folder includes a &lt;code&gt;kernel.json&lt;/code&gt; file that links to the Python executable that is being used. Note this can be different from the Python executable referenced by your current shell. Moreover, the shell environment of a Jupyter notebook uses the Python executable used to &lt;em&gt;launch&lt;/em&gt; the notebook.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Print path of currently used Python executable:&lt;/strong&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;bash&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ &lt;span class=&#34;nb&#34;&gt;type&lt;/span&gt; python&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;or in a notebook cell:&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;bash&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;!type python&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Print path of Python executable of current kernel:&lt;/strong&gt;&#xA;In a notebook cell:&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;bash&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;import sys&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sys.executable&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;You can create new kernels using the &lt;code&gt;ipykernel&lt;/code&gt; package:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;bash&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ python -m ipykernel install --user --name envname --display-name &lt;span class=&#34;s2&#34;&gt;&amp;#34;Python (envname)&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;h2 id=&#34;dependency-management&#34; class=&#34;content-heading&#34;&gt;Dependency management&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Since I use VS Code as my frontend I just need to add the &lt;code&gt;ipykernel&lt;/code&gt; package into the virtual environment that I use to manage all other dependencies used to run the notebook. This ensures that the same Python executable is used for the kernel and the shell environment.&lt;span class=&#34;sidenote-number&#34;&gt;&lt;small class=&#34;sidenote&#34;&gt;This is well explained &lt;a href=&#34;https://jakevdp.github.io/blog/2017/12/05/installing-python-packages-from-jupyter/&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;here&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;.&lt;/small&gt;&lt;/span&gt;&lt;/p&gt;&#xA;&lt;p&gt;These are the steps to create a new environment for a Jupyter notebook:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Create project folder:&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;bash&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ mkdir notebook_project&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;Create new virtual environment in the folder, then activate it&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;bash&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ &lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; notebook_project&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ python3 -m venv .venv&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ &lt;span class=&#34;nb&#34;&gt;source&lt;/span&gt; .venv/bin/activate&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;Install &lt;code&gt;ipykernel&lt;/code&gt; (and other dependencies) using pip (make sure the venv is activated):&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;bash&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ python3 -m pip install ipykernel&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ python3 -m pip install pandas&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;Create a new notebook&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;bash&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ touch mynotebook.ipynb&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;Open notebook in VS Code and in the top right corner select &lt;code&gt;Select Kernel&lt;/code&gt; &amp;ndash;&amp;gt; &lt;code&gt;Python Environment&lt;/code&gt; &amp;ndash;&amp;gt; &lt;code&gt;.venv (.venv/bin/python)&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;You should now be able to run the notebook and use the pandas package inside the notebook.&lt;/li&gt;&#xA;&lt;li&gt;To add new dependencies:&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Use the terminal: &lt;code&gt;$ python3 -m pip install &amp;lt;package_name&amp;gt;&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;Install from within a notebook cell:&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;sys&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sys&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;executable&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;m&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;pip&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;install&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;package_name&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;Specify your dependencies in a &lt;code&gt;requirements.txt&lt;/code&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;I use &lt;a href=&#34;https://python-poetry.org/&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;poetry&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt; for virtual environments and dependency management. So in step 2 I would instead use:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;bash&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ &lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; notebook_project&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ poetry init&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;and install packages via:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;bash&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ poetry add ipykernel&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ poetry add pandas&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;If I want to use the default Jupyter UI, I can install the &lt;code&gt;jupyter&lt;/code&gt;  metapackage into my environment and then start the UI with:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;bash&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ poetry add jupyter&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ poetry run jupyter notebook&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;&lt;strong&gt;Links&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://jakevdp.github.io/blog/2017/12/05/installing-python-packages-from-jupyter/&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;https://jakevdp.github.io/blog/2017/12/05/installing-python-packages-from-jupyter/&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://code.visualstudio.com/docs/datascience/jupyter-kernel-management&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;https://code.visualstudio.com/docs/datascience/jupyter-kernel-management&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://docs.jupyter.org/en/latest/projects/architecture/content-architecture.html&#34; &#xA;&#xA;    target=&#34;_blank&#34;&#xA;    &gt;https://docs.jupyter.org/en/latest/projects/architecture/content-architecture.html&#xA;    &#xA;        &lt;span style=&#34;white-space: nowrap&#34;&gt;&amp;thinsp;&lt;svg&#xA;            style=&#34;height: 0.7em; width: 0.7em; padding-left: -0.2em;&#34; focusable=&#34;false&#34; data-prefix=&#34;fas&#34; data-icon=&#34;external-link-alt&#34;&#xA;            class=&#34;svg-inline--fa fa-external-link-alt fa-w-16&#34; role=&#34;img&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;            viewBox=&#34;0 0 512 512&#34;&gt;&#xA;            &lt;path fill=&#34;currentColor&#34;&#xA;                d=&#34;M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z&#34;&gt;&#xA;            &lt;/path&gt;&#xA;        &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;        &#xA;    &#xA;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;</description>
    </item>
    
    <item>
      <title>TIL to create continuously numbered lists with content between items</title>
      <link>https://staticnotes.org/til/markdown-lists/</link>
      <pubDate>Wed, 21 Aug 2024 00:00:00 +0100</pubDate>
      
      <guid>https://staticnotes.org/til/markdown-lists/</guid>
    <description>&lt;p&gt;When writing notes in markdown I commonly encounter the problem that I want to create an automatically numbered list with content between the items. However, this messes up the numbering as it creates two separate numbered lists, e.g.&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;My first point&lt;/li&gt;&#xA;&lt;li&gt;My second point&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;&lt;code&gt;code block or image&lt;/code&gt;&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;My third point&lt;/li&gt;&#xA;&lt;li&gt;My fourth point&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;The solution to this is &lt;em&gt;embarrassingly obvious&lt;/em&gt; once you know it. Just move the image or code block one level down by indenting it with four spaces. This keeps the list intact.&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;My first point&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;My second point&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;code block or image&lt;/code&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;My third point&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;My fourth point&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;</description>
    </item>
    
    <item>
      <title>TIL how to set a lower threshold on a column in SQL</title>
      <link>https://staticnotes.org/til/greatest-sql/</link>
      <pubDate>Wed, 07 Aug 2024 00:00:00 +0100</pubDate>
      
      <guid>https://staticnotes.org/til/greatest-sql/</guid>
    <description>&lt;p&gt;For the last couple of months I have come across multiple instances where I wanted to write an SQL query to apply a lower threshold to all the values in a particular column in one of our Snowflake tables.&lt;/p&gt;&#xA;&lt;p&gt;I used quite clunky workarounds to do that. It turns out you can use the SQL functions &lt;code&gt;GREATEST&lt;/code&gt; or &lt;code&gt;LEAST&lt;/code&gt; for it.&lt;/p&gt;&#xA;&lt;h3 id=&#34;example&#34; class=&#34;content-heading&#34;&gt;Example&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;I am creating a temporary table in the first CTE with a column &lt;code&gt;DATA_COLUMN&lt;/code&gt; that has positive and negative numbers.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34; data-lang=&#34;SQL&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-SQL&#34; data-lang=&#34;SQL&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;with&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;example_table&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;as&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;SELECT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;FROM&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;VALUES&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;100&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;423&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;3&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;001&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;4&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;100&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;12&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;AS&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;t&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;row_nr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;data_column&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;select&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;GREATEST&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;DATA_COLUMN&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;as&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;thresholded&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;from&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;example_table&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;Output:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&lt;pre&gt;&lt;code&gt;0.000&#xA;5.000&#xA;0.000&#xA;100.000&#xA;12.000&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;The function&amp;rsquo;s primary purpose is to take the greatest value among a number of referenced columns, e.g. &lt;code&gt;GREATEST(column1, column2, column3)&lt;/code&gt;.&lt;/p&gt;&#xA;</description>
    </item>
    
  </channel>
</rss>