<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>tommyodland.com</title><link>https://tommyodland.com/</link><description/><atom:link href="https://tommyodland.com/feeds/all.rss.xml" rel="self"/><lastBuildDate>Mon, 22 Jun 2026 00:00:00 +0200</lastBuildDate><item><title>Ten lessons from twenty years of strength training</title><link>https://tommyodland.com/articles/2026/ten-lessons-from-twenty-years-of-strength-training</link><description>&lt;p&gt;I started strength training in 2006.
Over twenty years I&amp;rsquo;ve learned that the principles of strength are simple.
Almost everything you need to know can be summarized in two pages.
Following the ten lessons below will make you stronger than 90-99% of the population.
Here are my two&amp;nbsp;pages:&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="start-easy"&gt;Start&amp;nbsp;easy&lt;/h3&gt;
&lt;p&gt;Start with light weights if you&amp;rsquo;re a beginner.
During the first months, establish a routine of going to the gym and learn to lift correctly.
Don&amp;rsquo;t rush into heavy weights&amp;mdash;you&amp;rsquo;ll have plenty of time for that&amp;nbsp;later.&lt;/p&gt;
&lt;p&gt;The same advice applies if you&amp;rsquo;re getting back to lifting after a break.
When gyms re-opened after &lt;span class="caps"&gt;COVID&lt;/span&gt;-19, I lifted the bar on my first workout.
Then I increased to 40 kg, then 60 kg, 70 kg, etc.
Start too heavy and you might get injured, or experience debilitating soreness.
Either one hinders the steady progress required to build&amp;nbsp;strength.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 400px; width: 95%;"
src="https://tommyodland.com/images/articles/twenty_years_of_strength/2010_EM_squat.JPG"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p style="text-align: center; font-style: italic;"&gt;Squatting 285 kg at 108 kg bodyweight in&amp;nbsp;2010.&lt;/p&gt;

&lt;h3 id="stay-consistent"&gt;Stay&amp;nbsp;consistent&lt;/h3&gt;
&lt;p&gt;Priority number one is to keep training.
Half the game is showing up 2-4 times a week.
If you get injured, come up with a plan to train around it.
Set your ego aside.
Don&amp;rsquo;t risk training through&amp;nbsp;pain.&lt;/p&gt;
&lt;p&gt;You must also avoid burnout.
Hitting the gym six days a week and eating like a bodybuilder can be motivating.
But if you burn out and quit as a result, you&amp;rsquo;ve lost the game.
Slow and steady is usually&amp;nbsp;better.&lt;/p&gt;
&lt;h3 id="push-yourself"&gt;Push&amp;nbsp;yourself&lt;/h3&gt;
&lt;p&gt;Since you are now consistent, the next step is to set goals, make plans and push yourself.
Muscles grow by adapting to stress via progressive overload.
The best way to induce stress is to increase the weight.
If you lift 100 kg today and your goal is to reach 140 kg in one year, then you must increase the weight by around 0.75 kg per week.
This is the outline of a long-term&amp;nbsp;plan.&lt;/p&gt;
&lt;p&gt;There are several ways to drive progress.
Suppose you can comfortably lift 4 sets of 5 repetitions at 80 kg today.
Try 4x5 at 82.5 kg next week, then 4x5 at 85 kg, 4x5 at 87.5 kg, etc. until you plateau.
Don&amp;rsquo;t rush it&amp;mdash;the idea is to start easy and gradually push yourself.
You can alternatively increase repetitions instead of weight: do 4x1 at 90 kg, then 4x2 at 90 kg, etc. until you can do 4x6 at 90 kg&amp;mdash;then increment the weight to 100 kg and start over again with 4x1.
A third option is to combine weights and repetitions, e.g. 4x5 at 80 kg, 5x5 at 80 kg, 4x5 at 82.5 kg, 5x5 at 82.5 kg,&amp;nbsp;etc.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 368px; width: 95%;"
src="https://tommyodland.com/images/articles/twenty_years_of_strength/2022_deadlift_262.gif"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p style="text-align: center; font-style: italic;"&gt;Deadlifting 262.5 kg at 104 kg bodyweight in&amp;nbsp;2022.&lt;/p&gt;

&lt;h3 id="two-steps-forward-one-step-back"&gt;Two steps forward, one step&amp;nbsp;back&lt;/h3&gt;
&lt;p&gt;When your strength plateaus&amp;mdash;which it invariably will&amp;mdash;drop the weight (or repetitions) and start over.
Suppose you lift 4x5 at 80 kg today, and 82.5 kg the next week, then 85 kg, etc. and eventually you fail at 95 kg.
What should you do next?
Try to drop down to 85 kg and start increasing by 2.5 kg per week again.
You&amp;rsquo;ll likely reach 100 kg this&amp;nbsp;time.&lt;/p&gt;
&lt;p&gt;I take a big step back and start light in every new training program (every 8-12 weeks).
Some programs add a small step back every four weeks.
The &amp;#8220;two steps forward, one step back&amp;rsquo;&amp;rsquo; idea can be used at different resolutions in a long-term plan, and can be applied to weight, volume (total number of repetitions) and intensity (% of one-rep&amp;nbsp;max).&lt;/p&gt;
&lt;h3 id="switch-it-up"&gt;Switch it&amp;nbsp;up&lt;/h3&gt;
&lt;p&gt;Introduce slight variations to stay motivated and get unstuck when you stagnate.
If you train 5x5, try increasing the repetitions (e.g., 4x8) or decreasing them (e.g., 5x3).
Switch out barbell exercises for their dumbbell counterparts.
Experiment with unilateral alternatives, for instance heavy lunges instead of squats.
Train with bands or chains for a while.
But don&amp;rsquo;t experiment too much or too often&amp;mdash;stick with something long enough for it to&amp;nbsp;work.&lt;/p&gt;
&lt;h3 id="movements-not-muscles"&gt;Movements, not&amp;nbsp;muscles&lt;/h3&gt;
&lt;p&gt;Want big arms?
Gyms are littered with men who feverishly curl with little to show for it.
But I have yet to see anyone with small arms bench three&amp;nbsp;plates.&lt;/p&gt;
&lt;p&gt;The body responds well to heavy compound exercises: bench press, military press, rows, chin-ups, squats, deadlifts, etc.
These exercises train many muscles simultaneously and let you move serious weight.
I bench four plates, but never train arms&amp;nbsp;directly.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 368px; width: 95%;"
src="https://tommyodland.com/images/articles/twenty_years_of_strength/2022_bench_182.gif"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p style="text-align: center; font-style: italic;"&gt;Benching 182.5 kg at 104 kg bodyweight in&amp;nbsp;2022.&lt;/p&gt;

&lt;h3 id="compete-or-cooperate"&gt;Compete or&amp;nbsp;cooperate&lt;/h3&gt;
&lt;p&gt;Compete with yourself.
Strength is easy to measure, so take advantage of it.
Test your strength, weigh yourself and take some pictures every three months.
Document your journey and make note of what works for&amp;nbsp;you.&lt;/p&gt;
&lt;p&gt;Strength training can be solitary, but it does not have to be.
Train with others, build a community and share information.
Compete with your friends or sign up for a meet.
Read books and articles, participate in online forums, or publish a training&amp;nbsp;log.&lt;/p&gt;
&lt;h3 id="food-makes-you-look-good"&gt;Food makes you look&amp;nbsp;good&lt;/h3&gt;
&lt;p&gt;If your goal is to look good, then diet matters more than training.
I enjoy tweaking my training programs, but it honestly has no effect on how I look&amp;mdash;only my diet&amp;nbsp;does.&lt;/p&gt;
&lt;p&gt;To master your diet, start by writing down everything you eat for a week.
Buy a food scale and weigh your food.
Weigh yourself daily, but use a three-day average to assess&amp;nbsp;progress.&lt;/p&gt;
&lt;p&gt;Once you&amp;rsquo;ve mapped out how you eat today, start adding or subtracting food.
It&amp;rsquo;s easier to control your diet if you eat the same meals every day, so lock them down if you can.
I fix all my meals except one per day, where I only impose a weight limit.
Don&amp;rsquo;t lose more than 0.5-1% of your bodyweight per week.
Don&amp;rsquo;t gain more than 0.25-0.5% per&amp;nbsp;week.&lt;/p&gt;
&lt;p&gt;Healthy foods often have low caloric density, and calories are by far the most important factor.
Eat healthy most of the time.
&amp;#8220;Healthy&amp;rsquo;&amp;rsquo; means food your grandmother or a bodybuilder would approve of.
Eat enough protein.
Don&amp;rsquo;t drink calories.
Don&amp;rsquo;t do anything too extreme, like swearing off entire food groups, avoiding social gatherings or dropping weight sharply.
It rarely works out long&amp;nbsp;term.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 400px; width: 95%;"
src="https://tommyodland.com/images/articles/twenty_years_of_strength/summer_2021_92kg.jpg"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p style="text-align: center; font-style: italic;"&gt;At ~92 kg I look pretty good. At 110 kg? Not so&amp;nbsp;much.&lt;/p&gt;

&lt;h3 id="mute-the-marketers"&gt;Mute the&amp;nbsp;marketers&lt;/h3&gt;
&lt;p&gt;People will try to sell you supplements, clothes, gear and entertainment.
Buy if you like, but it&amp;rsquo;s mostly marketing designed to exploit your&amp;nbsp;psychology.&lt;/p&gt;
&lt;p&gt;For instance, very few supplements have strong scientific backing.
Whey is a cheap source of protein and creatine has documented effects.
That&amp;rsquo;s about&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;As for clothes and gear, buy a pair of Adidas lifting shoes, Rehband knee sleeves and a belt.
They will last a decade or more.
What kind of T-shirt you wear to the gym hardly&amp;nbsp;matters.&lt;/p&gt;
&lt;p&gt;Fitness influencers peddle a mix of minutiae, drama and genuinely good information.
In the end, your attention is sold to their advertisers.
Use social media feeds with caution:
the information does not stick well and comparison is the thief of joy.
For information, read a few high-quality books.
For motivation, make some real-world&amp;nbsp;friends.&lt;/p&gt;
&lt;h3 id="keep-learning"&gt;Keep&amp;nbsp;learning&lt;/h3&gt;
&lt;p&gt;If you enjoyed this article, then 
&lt;a href="https://www.goodreads.com/en/book/show/25241855-the-art-of-lifting"&gt;The Art of Lifting&lt;/a&gt;
and 
&lt;a href="https://www.goodreads.com/book/show/25242034-the-science-of-lifting"&gt;The Science of Lifting&lt;/a&gt;
by Nuckols are short reads in the same spirit.
&lt;a href="https://www.goodreads.com/book/show/25049103-bigger-leaner-stronger"&gt;Bigger Leaner Stronger&lt;/a&gt;
by Matthews is an overall guide to training and nutrition.
The books by &lt;a href="https://www.goodreads.com/author/show/7136135.Eric_Helms"&gt;Eric Helms&lt;/a&gt; also fall into this category.
Rippetoe wrote three books I enjoyed: 
&lt;a href="https://www.goodreads.com/book/show/2098799.Starting_Strength"&gt;Starting Strength&lt;/a&gt;
covers technique on the main lifts, 
&lt;a href="https://www.goodreads.com/book/show/1677880.Practical_Programming_for_Strength_Training"&gt;Practical Programming&lt;/a&gt;
teaches strength program design and 
&lt;a href="https://www.goodreads.com/book/show/2543979.Strong_Enough_Thoughts_from_Thirty_Years_of_Barbell_Training"&gt;Strong Enough?&lt;/a&gt;
is a delightful collection of no-bullshit, down-to-earth essays.
&lt;a href="https://www.goodreads.com/author/show/2943483.Dan_John"&gt;Dan John&lt;/a&gt;
has also written many great essays.
&lt;a href="https://www.goodreads.com/book/show/40121378-atomic-habits"&gt;Atomic Habits&lt;/a&gt;
by Clear covers habit formation, which is a big part of training and&amp;nbsp;lifestyle.&lt;/p&gt;
&lt;p&gt;Unsure where to start?
Set some goals based on the strength standards at
&lt;a href="https://exrx.net/Testing/WeightLifting/StrengthStandards"&gt;ExRx.net&lt;/a&gt;
or
&lt;a href="https://strengthlevel.com/strength-standards"&gt;strengthlevel.com&lt;/a&gt;.
Choose a program like Starting Strength, StrongLifts, 5/3/1, Texas Method, etc.
Good&amp;nbsp;luck!&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Mon, 22 Jun 2026 00:00:00 +0200</pubDate><guid>tag:tommyodland.com,2026-06-22:/articles/2026/ten-lessons-from-twenty-years-of-strength-training</guid><category>articles</category><category>strength</category><category>opinion</category></item><item><title>Optimal bus stop spacing</title><link>https://tommyodland.com/articles/2026/optimal-bus-stop-spacing</link><description>&lt;p&gt;Did you know that building new roads can &lt;em&gt;increase&lt;/em&gt; traffic congestion in a city?
It&amp;rsquo;s called &lt;a href="https://en.wikipedia.org/wiki/Braess%27s_paradox"&gt;Braess&amp;rsquo;s paradox&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In a similar vein&amp;mdash;though perhaps not as counterintuitive&amp;mdash;adding bus stops to a bus route can increase travel time.
That&amp;rsquo;s the main takeaway from the article 
&amp;ldquo;&lt;a href="https://worksinprogress.co/issue/the-united-states-needs-fewer-bus-stops/"&gt;The United States needs fewer bus stops&lt;/a&gt;&amp;rdquo;.
It&amp;rsquo;s great when we can improve a system by subtracting from&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;How can adding a stop increase travel time?
The reason is that travel time is a function of two competing&amp;nbsp;mechanisms:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;If there are many bus stops, you don&amp;rsquo;t have to walk far until you get to&amp;nbsp;one&lt;/li&gt;
&lt;li&gt;However, with many bus stops, the bus will also have to stop many times on your&amp;nbsp;journey&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="simulation"&gt;Simulation&lt;/h2&gt;
&lt;p&gt;Let us simulate this on a 10 kilometer stretch of road.
We assume that population density is uniform along the road, so a journey can be randomly generated by drawing 2 numbers; a start and an&amp;nbsp;end.&lt;/p&gt;
&lt;p&gt;The figure below shows 25 random journeys, along with 6 equidistant bus stops 2000 meters&amp;nbsp;apart.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 620px;
image-rendering: -webkit-optimize-contrast;"
src="https://tommyodland.com/images/articles/bus_stop_spacing/segments_bus_stops.png"&gt;&lt;/p&gt;
&lt;p&gt;The figure above outlines the basic simulation setup.
We&amp;rsquo;ll also make the following&amp;nbsp;assumptions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The bus has infinite passenger capacity and always stops at every bus&amp;nbsp;stop.&lt;/li&gt;
&lt;li&gt;The bus dwells for 45 seconds at each bus&amp;nbsp;stop.&lt;/li&gt;
&lt;li&gt;The bus has a speed of 25 kilometers per hour (slow, but realistic for a&amp;nbsp;city).&lt;/li&gt;
&lt;li&gt;A person&amp;rsquo;s walking speed is 5 kilometers per&amp;nbsp;hour.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Instead of 25 journeys, let us look at one million journeys.
The figure below shows the distribution of travel time when traveling by foot and on the bus&amp;mdash;a fine-grained histogram with a million&amp;nbsp;journeys.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 620px;
image-rendering: -webkit-optimize-contrast;"
src="https://tommyodland.com/images/articles/bus_stop_spacing/journeys_6_bus_stops.png"&gt;&lt;/p&gt;
&lt;p&gt;No journey ever takes more than two hours, since every person walks at a speed of 5 kilometers per hour, and the road is 10 kilometers long.
Most journeys are faster by bus, but this is not the case for very short journeys&amp;mdash;in that case walking is faster.
The average travel time with the bus is 0.35 hours (21 minutes), which is significantly shorter than the average walking time of 0.67&amp;nbsp;hours.&lt;/p&gt;
&lt;p&gt;What if we add more bus stops? Does it help? Let us try with 16 bus&amp;nbsp;stops.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 620px;
image-rendering: -webkit-optimize-contrast;"
src="https://tommyodland.com/images/articles/bus_stop_spacing/journeys_16_bus_stops.png"&gt;&lt;/p&gt;
&lt;p&gt;With 16 bus stops the average spacing goes down to 667 meters.
The average travel time also goes down to 0.26&amp;nbsp;hours.&lt;/p&gt;
&lt;p&gt;What if we keep adding bus stops? Will travel time continue to decrease?
Let&amp;rsquo;s go all the way to 51 bus&amp;nbsp;stops.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 620px;
image-rendering: -webkit-optimize-contrast;"
src="https://tommyodland.com/images/articles/bus_stop_spacing/journeys_51_bus_stops.png"&gt;&lt;/p&gt;
&lt;p&gt;The figure above shows that more bus stops are not better, since with 51 bus stops the travel time is up to 0.36 hours.
In fact, a road with 51 bus stops is &lt;em&gt;worse&lt;/em&gt; than one with 6 bus&amp;nbsp;stops!&lt;/p&gt;
&lt;p&gt;Here is the figure you might have been waiting for; the average travel time as a function of bus&amp;nbsp;stops.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 620px;
image-rendering: -webkit-optimize-contrast;"
src="https://tommyodland.com/images/articles/bus_stop_spacing/travel_time_function_bus_stops.png"&gt;&lt;/p&gt;
&lt;p&gt;The optimal number of bus stops is 16.
Does 16 stops&amp;mdash;a spacing of 667 meters&amp;mdash;seem reasonable?
I think so; you never have to walk for more than 333 meters to catch the bus, and on average you walk 167 meters.
This corresponds to about 4 and 2 minutes of walking,&amp;nbsp;respectively.&lt;/p&gt;
&lt;h2 id="analytical-solution"&gt;Analytical&amp;nbsp;solution&lt;/h2&gt;
&lt;p&gt;The bus stop problem is excellent because it can be approached either by computer simulation or analytical mathematics (deriving equations).
In fact, the two approaches complement each other&amp;nbsp;nicely.&lt;/p&gt;
&lt;p&gt;Let &lt;span class="math"&gt;\(n\)&lt;/span&gt; be the number of bus stops&amp;nbsp;and &lt;span class="math"&gt;\(L\)&lt;/span&gt; be the length of the road (10 kilometers).&amp;nbsp;Then &lt;span class="math"&gt;\(\ell = L / (n-1)\)&lt;/span&gt; is the equidistant spacing between each bus stop.
Furthermore,&amp;nbsp;let &lt;span class="math"&gt;\(v_w\)&lt;/span&gt; be the walking velocity&amp;nbsp;and &lt;span class="math"&gt;\(v_b\)&lt;/span&gt; be the bus velocity.
Finally, we denote the dwell time of the bus, measured in hours,&amp;nbsp;by &lt;span class="math"&gt;\(d\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;The expected total travel time is the sum of three different&amp;nbsp;terms:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Starting at a random location, the average distance to the closest bus stop&amp;nbsp;is &lt;span class="math"&gt;\(\ell / 4\)&lt;/span&gt;. We multiply this by two, since we have to walk both to the nearest bus stop at the start of our journey, and from the final bus stop at the end of our journey. The average time walking to and from the bus is&amp;nbsp;therefore &lt;span class="math"&gt;\(\ell / (2 v_w)\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;The average distance traveled on the bus is&amp;nbsp;roughly &lt;span class="math"&gt;\(L / 3\)&lt;/span&gt;. The total time on the bus, excluding dwelling times, is&amp;nbsp;therefore &lt;span class="math"&gt;\(L / (3 v_b)\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;The number of stops on a journey of&amp;nbsp;length &lt;span class="math"&gt;\(L/3\)&lt;/span&gt; is &lt;span class="math"&gt;\(L / (3 \ell)\)&lt;/span&gt;, so the total time on the bus due to dwelling at bus stops&amp;nbsp;is &lt;span class="math"&gt;\(d L / (3 \ell)\)&lt;/span&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The skeptical reader is encouraged to verify the claims above more thoroughly.
If we put all this together, we obtain the average&amp;nbsp;time &lt;span class="math"&gt;\(T\)&lt;/span&gt;, as a function of bus&amp;nbsp;stops &lt;span class="math"&gt;\(n\)&lt;/span&gt;:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
T(n) &amp;amp;= \frac{\ell}{2 v_w} + \frac{L}{3 v_b} + \frac{d L}{3 \ell} \\
&amp;amp;= \frac{L}{(n-1) 2 v_w} + \frac{L}{3 v_b} + \frac{d (n-1)}{3}
\end{align*}&lt;/div&gt;
&lt;p&gt;
Differentiating and&amp;nbsp;solving &lt;span class="math"&gt;\(T'(n^{\star}) = 0\)&lt;/span&gt;, we&amp;nbsp;obtain&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
n^{\star} = 1 + \sqrt{\frac{3 L}{2 v_w d}}
\end{align*}&lt;/div&gt;
&lt;p&gt;This analytical equation matches the simulation results nicely, as shown in the figure&amp;nbsp;below.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 620px;
image-rendering: -webkit-optimize-contrast;"
src="https://tommyodland.com/images/articles/bus_stop_spacing/travel_time_function_bus_stops_analytical.png"&gt;&lt;/p&gt;
&lt;p&gt;Notice how the optimal number of bus stops grows like the square root&amp;nbsp;of &lt;span class="math"&gt;\(L\)&lt;/span&gt;.
If the road segment becomes four times as long, only twice as many bus stops are needed.
This is because the average journey also becomes longer, due to the assumption that journeys are uniformly drawn from the line&amp;nbsp;segment.&lt;/p&gt;
&lt;p&gt;Why the discrepancy between the simulations and the equation?
It&amp;rsquo;s because bus journeys were simulated by picking the fastest out of four possible journeys.
Given a starting point for a journey, a person can either go left or go right to catch the bus. 
Similarly, a person can either exit the bus before or after the final endpoint of the journey.
This gives four possible journeys, and in each simulation I computed the fastest&amp;nbsp;one.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Sat, 16 May 2026 00:00:00 +0200</pubDate><guid>tag:tommyodland.com,2026-05-16:/articles/2026/optimal-bus-stop-spacing</guid><category>articles</category><category>mathematics</category></item><item><title>Deltidsstillinger</title><link>https://tommyodland.com/articles/2026/deltidsstillinger</link><description>&lt;p&gt;&lt;span class="caps"&gt;NRK&lt;/span&gt; har publisert en sak om &lt;a href="https://www.nrk.no/norge/mote-i-dag_-stenseng-varsler-nye-overtidsregler-for-deltidsansatte-1.17821037"&gt;overtidsregler for deltidsansatte&lt;/a&gt;,
og skriver at det er fastlåste posisjoner mellom arbeidsgivere (&lt;span class="caps"&gt;NHO&lt;/span&gt;, &lt;span class="caps"&gt;KS&lt;/span&gt;, Virke og Spekter) og arbeidstakere (Fagforbundet og &lt;span class="caps"&gt;LO&lt;/span&gt;).
I denne artikkelen presenterer vi et enkelt&amp;nbsp;kompromiss.&lt;/p&gt;
&lt;h2 id="dagens-praksis-med-merarbeid-og-overtid"&gt;Dagens praksis med merarbeid og&amp;nbsp;overtid&lt;/h2&gt;
&lt;p&gt;Anta at Ola jobber 50% stilling (20 timer per uke om vi antar en 40-timers uke) og tjener 300 kroner per&amp;nbsp;time.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 600px;
image-rendering: crisp-edges;"
src="https://tommyodland.com/images/articles/deltidsstillinger/deltidsstillinger_dagens.png"&gt;&lt;/p&gt;
&lt;p&gt;Hvis han jobber 10 timer over avtalt arbeidstid regnes dette som &lt;em&gt;merarbeid&lt;/em&gt;, ikke &lt;em&gt;overtid&lt;/em&gt;.
Han vil få&amp;nbsp;utbetalt &lt;span class="math"&gt;\(300 \times (20 + 10) = 9000\)&lt;/span&gt; kroner.&lt;/p&gt;
&lt;p&gt;Om han derimot jobber 25 timer mer, jobber han 5 timer overtid (over grensa på 40 timer).
Dersom satsen for overtid er 40%, får han&amp;nbsp;utbetalt &lt;span class="math"&gt;\(300 \times 40 + 300 \times 5 \times 1.4 = 14 100\)&lt;/span&gt; kroner.&lt;/p&gt;
&lt;h2 id="eu-domstolens-konklusjon"&gt;&lt;span class="caps"&gt;EU&lt;/span&gt;-domstolens&amp;nbsp;konklusjon&lt;/h2&gt;
&lt;p&gt;I to dommer fra &lt;span class="caps"&gt;EU&lt;/span&gt;-domstolen regnes alt arbeid utover avtalt arbeidstid som overtid, ikke&amp;nbsp;merarbeid.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 600px;
image-rendering: crisp-edges;"
src="https://tommyodland.com/images/articles/deltidsstillinger/deltidsstillinger_EU.png"&gt;&lt;/p&gt;
&lt;p&gt;Med disse reglene vil Ola få følgende&amp;nbsp;utbetalt:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Om han jobber 10 timer over avtalt arbeidstid på 20 timer, får&amp;nbsp;han &lt;span class="math"&gt;\(300 \times 20 + 300 \times 10 \times 1.4 = 10 200\)&lt;/span&gt; kroner.&lt;/li&gt;
&lt;li&gt;Om han jobber 25 timer over avtalt arbeidstid på 20 timer, får&amp;nbsp;han &lt;span class="math"&gt;\(300 \times 20 + 300 \times 25 \times 1.4 = 16 500\)&lt;/span&gt; kroner.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="problemstillingen"&gt;Problemstillingen&lt;/h2&gt;
&lt;p&gt;I dag er det ingen strategisk grunn til at en bedrift bør ansette i 100% stilling.
Det er bedre å ha to ansatte i 50% stilling, fordi da har arbeidsgiver en viss fleksibilitet til å justere opp arbeidsmengden uten å betale ekstra.
Om bedriften trenger 50 arbeidstimer, vil det&amp;nbsp;koste:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Med én&amp;nbsp;ansatt: &lt;span class="math"&gt;\(300 \times 40 + 300 \times 10 \times 1.4 = 16 200\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Med to&amp;nbsp;ansatte: &lt;span class="math"&gt;\(2 \times (300 \times 20 + 300 \times 5) = 15 000\)&lt;/span&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I regimet som &lt;span class="caps"&gt;EU&lt;/span&gt;-domstolen legger opp til, er det likegyldig om man har en eller to ansatte.
Prisen blir&amp;nbsp;uansett:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Med én&amp;nbsp;ansatt: &lt;span class="math"&gt;\(300 \times 40 + 300 \times 10 \times 1.4 = 16 200\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Med to&amp;nbsp;ansatte: &lt;span class="math"&gt;\(2 \times (300 \times 20 + 300 \times 5 \times 1.4) = 16 200\)&lt;/span&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Det er også likegyldig hvem av de ansatte som jobber ekstra.
Dersom Ola har 60% stilling og Kari har 40% stilling, så har det ingen betydning for arbeidsgiver om det er Ola eller Kari som jobber ekstra.
Det er ingen gevinst i å balansere&amp;nbsp;merarbeidet/overtiden.&lt;/p&gt;
&lt;h2 id="et-kompromiss"&gt;Et&amp;nbsp;kompromiss&lt;/h2&gt;
&lt;p&gt;La&amp;nbsp;funksjonen &lt;span class="math"&gt;\(p\)&lt;/span&gt; være overtidsprosent som funksjon av timer&amp;nbsp;jobbet &lt;span class="math"&gt;\(x\)&lt;/span&gt;, og anta at stillingsprosenten (i timer) er gitt&amp;nbsp;ved &lt;span class="math"&gt;\(s\)&lt;/span&gt;.
Et kompromiss er å velge en lineær&amp;nbsp;funksjon &lt;span class="math"&gt;\(p(x) = ax + b\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 600px;
image-rendering: crisp-edges;"
src="https://tommyodland.com/images/articles/deltidsstillinger/deltidsstillinger_kompromiss.png"&gt;
Dersom vi krever&amp;nbsp;at &lt;span class="math"&gt;\(p(s) = 100\)&lt;/span&gt; og &lt;span class="math"&gt;\(p(40) = 140\)&lt;/span&gt;, så får&amp;nbsp;vi&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
p(x;s) = \frac{40}{40 - s} (x - s) + 100
\end{equation*}&lt;/div&gt;
&lt;p&gt;
Når &lt;span class="math"&gt;\(x \leq s\)&lt;/span&gt; definerer&amp;nbsp;vi &lt;span class="math"&gt;\(p(x;s) = 100\)&lt;/span&gt;, og&amp;nbsp;når &lt;span class="math"&gt;\(x \geq 40\)&lt;/span&gt; definerer&amp;nbsp;vi &lt;span class="math"&gt;\(p(x;s) = 140\)&lt;/span&gt;.
Dersom Ola&amp;nbsp;jobber &lt;span class="math"&gt;\(t\)&lt;/span&gt; timer, skal han&amp;nbsp;ha &lt;span class="math"&gt;\(300 \int_0^t (p(x) / 100) \, dx\)&lt;/span&gt; kroner i lønn.
For 30 timer får han 9 300 kroner, sammenlignet med 9 000 i dag og 10 200 under &lt;span class="caps"&gt;EU&lt;/span&gt;-løsningen.&lt;/p&gt;
&lt;p&gt;Partene vil neppe bli enige om et slikt kompromiss i praksis, men det har noen&amp;nbsp;fordeler:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Det koster alltid arbeidsgiver å planlegge dårlig og bruke ansatte utover avtalt stillingsprosent, enten de jobber deltid eller&amp;nbsp;heltid.&lt;/li&gt;
&lt;li&gt;Arbeidsgiver har insentiv til å gi ansatte høyere stillingsprosent, heller enn å konsekvent bruke&amp;nbsp;merarbeid/overtid.&lt;/li&gt;
&lt;li&gt;Arbeidsgiver betaler ikke like mye for å bruke ekstra arbeidskraft fra en ansatt i 50% stilling, som hvis samme arbeidskraft blir hentet fra en ansatt med 100% stilling. Hvem vil du helst bli operert av på sykehuset: fulltidslegen som er på tredje ekstravakt, eller deltidslegen som er på første&amp;nbsp;ekstravakt?&lt;/li&gt;
&lt;li&gt;Dersom arbeidsgiver har to personer ansatt i 50% stilling og trenger to vakter ekstra, lønner det seg å fordele dem likt og gi én vakt til Ola og én til Kari. Med andre ord lønner det seg å balansere overtiden på de ansatte, i motsetning til både dagens regime og &lt;span class="caps"&gt;EU&lt;/span&gt;-domstolens&amp;nbsp;tolkning.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Du kan lese LOs argumenter
&lt;a href="https://www.lo.no/hva-vi-mener/et-likestilt-arbeidsliv/nyheter-om-likestilling/ofte-stilte-sp%C3%B8rsm%C3%A5l-om-overtid-p%C3%A5-deltid/"&gt;her&lt;/a&gt;
og noen motargumenter fra arbeidsgiverne
&lt;a href="https://e24.no/norsk-oekonomi/i/ny5d0o/vil-fagforbundet-svekke-ansattes-motivasjon-til-aa-jobbe-heltid"&gt;her&lt;/a&gt;.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Tue, 14 Apr 2026 00:00:00 +0200</pubDate><guid>tag:tommyodland.com,2026-04-14:/articles/2026/deltidsstillinger</guid><category>articles</category><category>mathematics</category></item><item><title>Continuous interest with continuous deposits</title><link>https://tommyodland.com/articles/2026/continuous-interest-with-continuous-deposits</link><description>&lt;p&gt;Writing a 
&lt;a href="https://tommyodland.com/tools/saving.html"&gt;savings calculator&lt;/a&gt;
is a great programming exercise for beginners.
They&amp;rsquo;re easy to implement in spreadsheets or programming languages like Python, and teach people about the power of compounding interest.
In this article we&amp;rsquo;ll investigate a discretization error that such calculators often make and try to fix&amp;nbsp;it.&lt;/p&gt;
&lt;h2 id="a-simple-savings-calculator"&gt;A simple savings&amp;nbsp;calculator&lt;/h2&gt;
&lt;p&gt;A simple savings calculator might look like this in&amp;nbsp;Python:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;interest_rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;  &lt;span class="c1"&gt;# 10% annual interest&lt;/span&gt;
&lt;span class="n"&gt;saved_per_year&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;  &lt;span class="c1"&gt;# 10 units of money saved per year&lt;/span&gt;
&lt;span class="n"&gt;years&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;

&lt;span class="n"&gt;saved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;year&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;years&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;saved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;saved&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;interest_rate&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;saved_per_year&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Year: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;  Saved: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;saved&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.2f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Running the code produces the following&amp;nbsp;output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Year:  1  Saved: 10.00
Year:  2  Saved: 21.00
Year:  3  Saved: 33.10
...
Year: 18  Saved: 455.99
Year: 19  Saved: 511.59
Year: 20  Saved: 572.75
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;But there&amp;rsquo;s a simplification in this calculation which causes us to &lt;em&gt;underestimate&lt;/em&gt; the total amount saved.
Did you spot&amp;nbsp;it?&lt;/p&gt;
&lt;h2 id="the-discretization-error"&gt;The discretization&amp;nbsp;error&lt;/h2&gt;
&lt;p&gt;The &amp;ldquo;error&amp;rdquo; in the script above is noticeable in the first year of the computation, so let&amp;rsquo;s focus on the first year.
We assumed that one year passes before the amount saved per year is deposited into the account.
In reality, it would likely be deposited monthly as the person saves money throughout the&amp;nbsp;year.&lt;/p&gt;
&lt;p&gt;The bank might pay you the interest owed monthly or yearly, but their internal computation is done &lt;em&gt;&lt;a href="https://money.stackexchange.com/a/10798"&gt;every single day&lt;/a&gt;&lt;/em&gt;.
Therefore depositing as early as possible (when you get your salary) will help accumulate more interest on your deposit throughout the year.
The same applies to mutual&amp;nbsp;funds.&lt;/p&gt;
&lt;p&gt;If the yearly interest rate&amp;nbsp;is &lt;span class="math"&gt;\(r\)&lt;/span&gt;, then the monthly interest rate&amp;nbsp;is &lt;span class="math"&gt;\(r^{1/12}\)&lt;/span&gt; and the yearly interest rate&amp;nbsp;is &lt;span class="math"&gt;\(r^{1/365}\)&lt;/span&gt;.
This is because interest rates compound multiplicatively, so the &lt;a href="https://en.wikipedia.org/wiki/Geometric_mean"&gt;geometric mean&lt;/a&gt;, not the arithmetic mean, is the correct&amp;nbsp;computation.&lt;/p&gt;
&lt;p&gt;Back to our savings calculator: in that first year, if we deposit monthly and compute the interest monthly, we do not end up&amp;nbsp;with &lt;span class="math"&gt;\(10\)&lt;/span&gt; units of money at the end of the first year.
Instead we end up&amp;nbsp;with &lt;span class="math"&gt;\(10.45\)&lt;/span&gt; units of&amp;nbsp;money:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;periods&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;
&lt;span class="n"&gt;interest_rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;periods&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;saved_per_year&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;periods&lt;/span&gt;

&lt;span class="n"&gt;saved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;period&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;periods&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;saved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;saved&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;interest_rate&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;saved_per_year&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Period: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;  Saved: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;saved&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.2f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;...
Period: 12  Saved: 10.45
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;What happens if we compound daily&amp;nbsp;(&lt;span class="math"&gt;\(365\)&lt;/span&gt; times) instead? Or hourly&amp;nbsp;(&lt;span class="math"&gt;\(365 \times 12\)&lt;/span&gt; times)? Or every second&amp;nbsp;(&lt;span class="math"&gt;\(365 \times 12 \times 3600\)&lt;/span&gt; times)?
Here is a figure showing the multiplicative factor that money grows by at the end of one year, as a function of the number of times we compound the interest,&amp;nbsp;using &lt;span class="math"&gt;\(r=1.1\)&lt;/span&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 650px; width: 95%;"
src="https://tommyodland.com/images/articles/continuous_interest_and_deposits/interest_limit.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;For instance, if we&amp;nbsp;compound &lt;span class="math"&gt;\(12\)&lt;/span&gt; times the multiplicative factor&amp;nbsp;is &lt;span class="math"&gt;\(\approx 1.045\)&lt;/span&gt;.
Observe that the factor converges to a finite number instead of blowing up to infinity.
We&amp;rsquo;ll show that, as we compound interest rates and deposit more and more often, the factor converges&amp;nbsp;to&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
(r - 1) / \ln (r),
\end{align*}&lt;/div&gt;
&lt;p&gt;
which for an interest&amp;nbsp;rate &lt;span class="math"&gt;\(r=1.1\)&lt;/span&gt; becomes &lt;span class="math"&gt;\(0.1 / \ln(1.1) = 1.0492\)&lt;/span&gt; (compare with the figure&amp;nbsp;above).&lt;/p&gt;
&lt;h2 id="continuous-deposits"&gt;Continuous&amp;nbsp;deposits&lt;/h2&gt;
&lt;p&gt;Let &lt;span class="math"&gt;\(a\)&lt;/span&gt; be the total amount deposited in a year,&amp;nbsp;let &lt;span class="math"&gt;\(r\)&lt;/span&gt; be the yearly interest rate&amp;nbsp;and &lt;span class="math"&gt;\(n\)&lt;/span&gt; be the number of times interest is&amp;nbsp;compounded.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The naïve savings calculator above&amp;nbsp;used &lt;span class="math"&gt;\(n=1\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;In terms of realism for a savings&amp;nbsp;calculator, &lt;span class="math"&gt;\(n=12\)&lt;/span&gt; is a good choice if salary is paid every month and then&amp;nbsp;deposited.&lt;/li&gt;
&lt;li&gt;From the bank&amp;rsquo;s perspective, whether it&amp;rsquo;s a bank account or a mutual&amp;nbsp;fund, &lt;span class="math"&gt;\(n=365\)&lt;/span&gt; is the upper&amp;nbsp;limit.&lt;/li&gt;
&lt;li&gt;We&amp;rsquo;re interested in what happens&amp;nbsp;as &lt;span class="math"&gt;\(n \to \infty\)&lt;/span&gt;. This is optimistic because it overestimates the discrete reality,&amp;nbsp;but &lt;span class="math"&gt;\(n \to \infty\)&lt;/span&gt; is still a much better approximation&amp;nbsp;of &lt;span class="math"&gt;\(n=12\)&lt;/span&gt; than &lt;span class="math"&gt;\(n=1\)&lt;/span&gt; (again: see the&amp;nbsp;figure).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let &lt;span class="math"&gt;\(a\)&lt;/span&gt; be the yearly added value.
We can look for a pattern as we increase the&amp;nbsp;number &lt;span class="math"&gt;\(n\)&lt;/span&gt;:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\text{n=1} \quad &amp;amp; a \\
\text{n=2} \quad &amp;amp; \left( \frac{a}{2} \right) r^{1/2} + \frac{a}{2} \\
\text{n=3} \quad &amp;amp; \left( \left( \frac{a}{3} \right) r^{1/3} + \frac{a}{3} \right) r^{1/3} + \frac{a}{3} \\
\text{n=4} \quad &amp;amp; \left( \left( \left( \left( \frac{a}{4} \right) r^{1/4} + \frac{a}{4} \right) \right) r^{1/4} + \frac{a}{4} \right) r^{1/4} + \frac{a}{4} \\
\end{align*}&lt;/div&gt;
&lt;p&gt;These are all &lt;a href="https://en.wikipedia.org/wiki/Geometric_series"&gt;geometric series&lt;/a&gt;, and can be written&amp;nbsp;as:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\text{n=1} \quad &amp;amp; a \\
\text{n=2} \quad &amp;amp; \frac{a}{2} \left( r^{1/2} + 1 \right) \\
\text{n=3} \quad &amp;amp; \frac{a}{3} \left( r^{2/3} + r^{1/3} + 1 \right) \\
\text{n=4} \quad &amp;amp; \frac{a}{4} \left( r^{3/4} + r^{2/4} + r^{1/4} + 1 \right) \\
\end{align*}&lt;/div&gt;
&lt;p&gt;The pattern for a&amp;nbsp;general &lt;span class="math"&gt;\(n\)&lt;/span&gt; emerges, and (provided we&amp;nbsp;assume &lt;span class="math"&gt;\(r\neq 1\)&lt;/span&gt;) we can use the formula for a sum of a geometric series to obtain a closed-form&amp;nbsp;expression:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
f_n(a, r) = \frac{a}{n} \sum_{i=0}^{n-1} \left( r^{1/n} \right)^i = \frac{a}{n} \frac{\left( r^{1/n} \right)^n - 1}{r^{1/n} - 1} = \frac{a}{n} \frac{r - 1}{r^{1/n} - 1}
\end{align*}&lt;/div&gt;
&lt;p&gt;What does this converge to&amp;nbsp;as &lt;span class="math"&gt;\(n \to \infty\)&lt;/span&gt;?
It all comes down to what the&amp;nbsp;denominator &lt;span class="math"&gt;\(n (r^{1/n} - 1)\)&lt;/span&gt;, since that&amp;rsquo;s the only part of the equation that depends&amp;nbsp;on &lt;span class="math"&gt;\(n\)&lt;/span&gt;.
The answer&amp;nbsp;is &lt;span class="math"&gt;\(\ln(r)\)&lt;/span&gt;, and there are many ways to show this: L&amp;rsquo;Hôpital&amp;rsquo;s Rule and Taylor Series are both possible methods.
Here&amp;rsquo;s one way that I like: one of the many definitions of the exponential&amp;nbsp;function &lt;span class="math"&gt;\(e^x\)&lt;/span&gt; is&amp;nbsp;that
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
e^x = \lim_{n \to \infty} \left(1+\frac{x}{n}\right)^n
\end{align*}&lt;/div&gt;
&lt;p&gt;
so then,&amp;nbsp;since &lt;span class="math"&gt;\(\ln(x)\)&lt;/span&gt; is the inverse&amp;nbsp;of &lt;span class="math"&gt;\(e^x\)&lt;/span&gt; for&amp;nbsp;positive &lt;span class="math"&gt;\(x\)&lt;/span&gt;,  we must&amp;nbsp;have
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
x = e^{\ln (x)} = \lim_{n \to \infty} \left(1+\frac{\ln (x)}{n}\right)^n
\end{align*}&lt;/div&gt;
&lt;p&gt;
and if we rearrange this expression and&amp;nbsp;substitute &lt;span class="math"&gt;\(x\)&lt;/span&gt; for &lt;span class="math"&gt;\(r\)&lt;/span&gt;, we discover&amp;nbsp;that &lt;span class="math"&gt;\(\lim_{n \to \infty} n (r^{1/n} - 1) = \ln(r)\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;The final result is&amp;nbsp;that
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\lim_{n \to \infty} f_n(a, r) = \lim_{n \to \infty} \frac{a(r - 1)}{n (r^{1/n} - 1)} = \frac{a (r - 1)}{\ln (r)}.
\end{align*}&lt;/div&gt;
&lt;p&gt;We&amp;rsquo;re now in a position to create a more realistic savings calculator, in which money is continually deposited and interest is continually&amp;nbsp;accrued:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;math&lt;/span&gt;

&lt;span class="n"&gt;interest_rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;
&lt;span class="n"&gt;saved_per_year&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="n"&gt;years&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;

&lt;span class="n"&gt;saved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;year&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;years&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;saved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;saved&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;interest_rate&lt;/span&gt;
    &lt;span class="n"&gt;saved&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;saved_per_year&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;interest_rate&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;interest_rate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Year: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;  Saved: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;saved&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.2f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Year:  1  Saved: 10.49
Year:  2  Saved: 22.03
Year:  3  Saved: 34.73
...
Year: 18  Saved: 478.43
Year: 19  Saved: 536.76
Year: 20  Saved: 600.93
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The difference is shown graphically in the animated figure&amp;nbsp;below.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 650px; width: 95%;"
src="https://tommyodland.com/images/articles/continuous_interest_and_deposits/discrete_vs_continuous.gif"
class="img-responsive"&gt;&lt;/p&gt;
&lt;h3 id="solution-by-integration"&gt;Solution by&amp;nbsp;integration&lt;/h3&gt;
&lt;p&gt;We can obtain the same answer as above by realizing that&amp;nbsp;as &lt;span class="math"&gt;\(n\)&lt;/span&gt; becomes large we can replace the sum with a &lt;a href="https://en.wikipedia.org/wiki/Riemann_integral"&gt;Riemann integral&lt;/a&gt;:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
f_n(a, r) = \frac{a}{n} \sum_{i=0}^{n-1} r^{i/n} \approx a \int_{0}^{1} r^{x} \, dx
\end{align*}&lt;/div&gt;
&lt;p&gt;
If you know the indefinite integral&amp;nbsp;of &lt;span class="math"&gt;\(r^x\)&lt;/span&gt;, then it&amp;rsquo;s easy to&amp;nbsp;compute
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\int_{0}^{1} r^{x} \, dx = \int_{0}^{1} \exp(x \ln(r)) \, dx = \frac{1}{\ln(r)} \exp(x \ln(r)) \Biggr|^{x = 0}_{x = 1} = \frac{r-1}{\ln(r)}.
\end{align*}&lt;/div&gt;
&lt;p&gt;
It is comforting that both approaches give identical results.
Let&amp;rsquo;s look at a third approach that also leads to the same answer, but is more general and more powerful: differential&amp;nbsp;equations.&lt;/p&gt;
&lt;h2 id="solution-by-differential-equations"&gt;Solution by differential&amp;nbsp;equations&lt;/h2&gt;
&lt;p&gt;Let us summarize what we have learned so far.
When we account for continuous deposits and continuous interest rates, we do not&amp;nbsp;have &lt;span class="math"&gt;\(a\)&lt;/span&gt; units of money after the first year.
Instead we end up&amp;nbsp;with &lt;span class="math"&gt;\(a (r-1) / \ln(r)\)&lt;/span&gt; units of money.
The&amp;nbsp;factor &lt;span class="math"&gt;\((r-1) / \ln(r)\)&lt;/span&gt; accounts for the difference between discrete yearly deposits and continuous deposits throughout the year.&amp;nbsp;When &lt;span class="math"&gt;\(r=1\)&lt;/span&gt; this factor is undefined, but if we&amp;nbsp;assume &lt;span class="math"&gt;\(r \neq 1\)&lt;/span&gt; then we get this&amp;nbsp;graph:&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 650px; width: 95%;"
src="https://tommyodland.com/images/articles/continuous_interest_and_deposits/corretion_function.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;In the figure above, I also plotted the first-order Taylor expansion&amp;nbsp;around &lt;span class="math"&gt;\(r \approx 1\)&lt;/span&gt;, which&amp;nbsp;is &lt;span class="math"&gt;\(g(r) = 1/2 + r/2\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;The factor that &amp;ldquo;fixes&amp;rdquo; the first year, but can be applied every single year in our savings calculator.
If we re-write the recursive definition into a sum, and again use the sum of a geometric series, we end up with an expression for an arbitrary&amp;nbsp;year &lt;span class="math"&gt;\(n\)&lt;/span&gt;:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\text{n=1} \quad &amp;amp; a \frac{r-1}{\ln(r)} \\
\text{n=2} \quad &amp;amp; \left( a \frac{r-1}{\ln(r)} \right) r + a \frac{r-1}{\ln(r)} \\
\text{n=3} \quad &amp;amp; \left( \left( a \frac{r-1}{\ln(r)} \right) r + a \frac{r-1}{\ln(r)} \right) r + a \frac{r-1}{\ln(r)} \\
\text{n} \quad &amp;amp; a \frac{r-1}{\ln(r)} \left( r^{n-1} + r^{n-2} + \cdots + 1  \right) = a \frac{r-1}{\ln(r)} \frac{r^n - 1}{r-1} = a \frac{r^n - 1}{\ln(r)}
\end{align*}&lt;/div&gt;
&lt;p&gt;Perhaps the most powerful approach to studying continuous deposits and continuous interest rates is by using differential equations.&amp;nbsp;Let &lt;span class="math"&gt;\(M(t)\)&lt;/span&gt; be money as a function of time, which is now a continuous function&amp;nbsp;of &lt;span class="math"&gt;\(t\)&lt;/span&gt;.&amp;nbsp;Let &lt;span class="math"&gt;\(r\)&lt;/span&gt; be the yearly interest rate&amp;nbsp;and &lt;span class="math"&gt;\(t\)&lt;/span&gt; be measured in years.
Before we proceed to the general case, let us study two simple cases&amp;nbsp;first:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Deposits without any&amp;nbsp;interest.&lt;/strong&gt;
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
M(t) = at \qquad M'(t)= a
\end{align*}&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Interest without any&amp;nbsp;deposits.&lt;/strong&gt;
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
M(t) = M_0 r^t \qquad M'(t) = \ln(r)  M_0 r^t = \ln(r) M(t)
\end{align*}&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Both deposits and interest.&lt;/strong&gt;
Above we saw that the rate of change in a bank account is driven&amp;nbsp;by &lt;span class="math"&gt;\(a\)&lt;/span&gt; as well&amp;nbsp;as &lt;span class="math"&gt;\(\ln(r) M(t)\)&lt;/span&gt;.
We sum these to obtain the (first order linear ordinary) differential&amp;nbsp;equation&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
M'(t) = \ln(r) M(t) + a.
\end{align*}&lt;/div&gt;
&lt;p&gt;The solution to this differential equation (verify it!)&amp;nbsp;is&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
M(t) = M_0 r^t + \frac{a (r^t - 1)}{\ln(r)},
\end{align*}&lt;/div&gt;
&lt;p&gt;
where &lt;span class="math"&gt;\(M_0 = M(0)\)&lt;/span&gt; is the initial money in the account.
This matches our previous result perfectly, where we&amp;nbsp;assumed &lt;span class="math"&gt;\(M_0 = 0\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;As a final note,&amp;nbsp;if &lt;span class="math"&gt;\(a\)&lt;/span&gt; is negative and we&amp;rsquo;re depleting the account, then a relevant question is &amp;ldquo;When will we go broke?&amp;rdquo;.
This amounts to&amp;nbsp;solving &lt;span class="math"&gt;\(M(t) = 0\)&lt;/span&gt; for an&amp;nbsp;unknown &lt;span class="math"&gt;\(t\)&lt;/span&gt;.
In the general case,&amp;nbsp;let &lt;span class="math"&gt;\(M(t) = M_t\)&lt;/span&gt;, then we can solve&amp;nbsp;for &lt;span class="math"&gt;\(t\)&lt;/span&gt; to&amp;nbsp;obtain:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
t = \frac{1}{\ln (r)} \ln \left( \frac{M_t \ln(r) + a}{M_0 \ln(r) + a} \right)
\end{align*}&lt;/div&gt;
&lt;p&gt;Finally, here is an updated Python snippet that uses the&amp;nbsp;function &lt;span class="math"&gt;\(M(t)\)&lt;/span&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;math&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;M&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;M0&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;r_power_t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;M0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;r_power_t&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r_power_t&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;interest_rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;
&lt;span class="n"&gt;saved_per_year&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="n"&gt;years&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;year&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;years&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;saved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;interest_rate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;saved_per_year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;M0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Year: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;  Saved: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;saved&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.2f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="summary-and-references"&gt;Summary and&amp;nbsp;references&lt;/h2&gt;
&lt;p&gt;This is an example of how the first thing on​e might implement is not necessarily correct.
I&amp;rsquo;ve taught students about geometric series, Excel and Python using this example a few times, and I was aware of the tension between computing the interest and deposit &lt;em&gt;at the end of the year&lt;/em&gt; (which underestimates) and &lt;em&gt;at the start of the year&lt;/em&gt; (which&amp;nbsp;overestimates).&lt;/p&gt;
&lt;p&gt;For students familiar with calculus and geometric series, working out the limit&amp;nbsp;of &lt;span class="math"&gt;\(n \to \infty\)&lt;/span&gt; is a nice problem.
In the end,&amp;nbsp;assuming &lt;span class="math"&gt;\(n \to \infty\)&lt;/span&gt; is not really correct either&amp;mdash;it probably overestimates slightly compared to the reality (which is&amp;nbsp;likely &lt;span class="math"&gt;\(n=12\)&lt;/span&gt; or so).
In many cases the corrective factor&amp;nbsp;of &lt;span class="math"&gt;\((r - 1) / \ln (r)\)&lt;/span&gt; is likely worth implementing since it&amp;rsquo;s closer to the truth than the&amp;nbsp;naive &lt;span class="math"&gt;\(n=1\)&lt;/span&gt; computation.
We ignored most issues related to numerical stability in this article, but in a proper&amp;nbsp;implementation &lt;span class="math"&gt;\(r^{(1/n)}\)&lt;/span&gt; and &lt;span class="math"&gt;\((r-1) / \ln(r)\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Everything can be summarized by this set of equations: one discrete &lt;a href="https://en.wikipedia.org/wiki/Recurrence_relation#difference_equation"&gt;difference equation&lt;/a&gt; and one continuous differential equation.
The equations and their closed-form solutions&amp;nbsp;are:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\boxed{M_n = r M_{n-1} + a} \Rightarrow M_n &amp;amp;= M_0 r^n + a \frac{r^n - 1}{r - 1} \\
\boxed{M'(t) = \ln(r) M(t) + a} \Rightarrow M(t) &amp;amp;= M_0 r^t + a \frac{r^t - 1}{\ln(r)}
\end{align*}&lt;/div&gt;
&lt;p&gt;Some further&amp;nbsp;reading:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://web.njit.edu/~bose/interest-chapter.pdf"&gt;Modeling Change One Step at a Time&lt;/a&gt;, from &lt;a href="https://www.jstor.org/stable/j.ctt1bw1hh8"&gt;Topics in Mathematical&amp;nbsp;Modeling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://math.libretexts.org/Bookshelves/Differential_Equations/Differential_Equations_(Chasnov)/03:_First-Order_ODEs/3.04:_Applications"&gt;Differential Equations&lt;/a&gt; by&amp;nbsp;Chasnov&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Continuous-repayment_mortgage"&gt;Continuous-repayment mortgage&lt;/a&gt; on&amp;nbsp;Wikipedia&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Compound_interest"&gt;Compound interest&lt;/a&gt; on&amp;nbsp;Wikipedia&lt;/li&gt;
&lt;/ul&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Wed, 25 Mar 2026 00:00:00 +0100</pubDate><guid>tag:tommyodland.com,2026-03-25:/articles/2026/continuous-interest-with-continuous-deposits</guid><category>articles</category><category>mathematics</category></item><item><title>Skatter og bidrag</title><link>https://tommyodland.com/articles/2026/skatter-og-bidrag</link><description>&lt;p&gt;Denne artikkelen er et tankeeksperiment der vi skal endre det norske skatte- og bidragssystemet.
I dag styrer mange mekanismer pengeflyten mellom borgere og staten.
Et forenklet bilde ser slik&amp;nbsp;ut:&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 600px;
image-rendering: crisp-edges;"
src="https://tommyodland.com/images/articles/simple_taxes/simple_taxes_full.png"&gt;&lt;/p&gt;
&lt;p&gt;Kompleksiteten i et slikt system har store kostnader.
Staten må holde styr på alle skattene og bidragsordningene: den må oppdatere lovverk, endre satser, vurdere og innvilge søknader, vedlikeholde digital infrastruktur, osv.
Borgere må forstå systemet, være informert om støtteordningene og navigere søknadsprosesser.
Begge parter kunne vært tjent med en&amp;nbsp;forenkling.&lt;/p&gt;
&lt;p&gt;Vi skal kutte nesten alle skatter og samle dem sammen i en&amp;nbsp;funksjon &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt;.
Deretter skal vi kutte nesten alle støtteordninger og samle dem sammen i en&amp;nbsp;funksjon &lt;span class="math"&gt;\(B(x)\)&lt;/span&gt;.&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
T(x) = \frac{H(e^{-kx} + kx - 1)}{k} \qquad B(x) = be^{-x/b}
\end{align*}&lt;/div&gt;
&lt;p&gt;Det er ikke åpenbart at disse funksjonene er i stand til å modellere store deler av forholdet mellom staten og borgerne.
Vi skal prøve likevel.
Resultatet blir at forholdet mellom staten og individet forenkles til følgende&amp;nbsp;diagram.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 410px;
image-rendering: crisp-edges;"
src="https://tommyodland.com/images/articles/simple_taxes/simple_taxes_simplified.png"&gt;&lt;/p&gt;
&lt;p&gt;Alle skatter og bidrag styres av tre&amp;nbsp;parametre &lt;span class="math"&gt;\(H\)&lt;/span&gt;, &lt;span class="math"&gt;\(k\)&lt;/span&gt; og &lt;span class="math"&gt;\(b\)&lt;/span&gt;, og vi skal estimere disse.
Underveis skal vi gi skattelette til folk flest, &amp;ldquo;ta de rike&amp;rdquo;, forenkle offentlig sektor, sørge for at det alltid lønner seg å jobbe, og kreve formuesskatt fra alle borgere.
Så her er det noe for alle, uansett politisk&amp;nbsp;ståsted.&lt;/p&gt;
&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#inntekt"&gt;Inntekt&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#matematiske-egenskaper"&gt;Matematiske&amp;nbsp;egenskaper&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#hva-kan-vi-forenkle"&gt;Hva kan vi&amp;nbsp;forenkle?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#vanlige-folks-tur"&gt;Vanlige folks&amp;nbsp;tur&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#oppsummering-inntekt"&gt;Oppsummering:&amp;nbsp;Inntekt&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#bidrag-og-sttteordninger"&gt;Bidrag og støtteordninger&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#a-fange-de-fattige"&gt;Å fange de&amp;nbsp;fattige&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#bidrag-bx-med-gode-egenskaper"&gt;Bidrag \(B(x)\) med gode&amp;nbsp;egenskaper&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#hva-kan-vi-forenkle_1"&gt;Hva kan vi&amp;nbsp;forenkle?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#en-realistisk-beregning"&gt;En realistisk beregning&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#oppsummering-bidrag-og-sttteordninger"&gt;Oppsummering: Bidrag og&amp;nbsp;støtteordninger&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#formuesskatt"&gt;Formuesskatt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#oppsummering"&gt;Oppsummering&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#referanser-og-videre-lesing"&gt;Referanser og videre&amp;nbsp;lesing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#appendiks-a-bevis-for-at-bidragsfunksjonen-har-gode-insentiver"&gt;Appendiks A: Bevis for at bidragsfunksjonen har gode&amp;nbsp;insentiver&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#appendiks-b-formuesskatten"&gt;Appendiks B:&amp;nbsp;Formuesskatten&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#appendiks-c-kode"&gt;Appendiks C:&amp;nbsp;Kode&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h1 id="inntekt"&gt;Inntekt&lt;/h1&gt;
&lt;p&gt;For å regne ut skatt på en vanlig borgers inntekt må man ta hensyn til trygdeavgift, minstefradrag, personfradrag, skatt på alminnelig inntekt og trinnskatt.
Dette er et komplisert regnestykke, og i artikkelen &lt;a href="https://tommyodland.com/articles/2024/smooth-taxes-without-brackets"&gt;Smooth taxes without brackets&lt;/a&gt; viste jeg at beregningen kan reduseres til&amp;nbsp;formelen&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
T(x) = 
\frac{H(e^{-kx} + kx - 1)}{k},
\end{align*}&lt;/div&gt;
&lt;p&gt;der &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt; er skatten man betaler om man har en brutto inntekt&amp;nbsp;på &lt;span class="math"&gt;\(x\)&lt;/span&gt; kroner.&amp;nbsp;Parametrene &lt;span class="math"&gt;\(H\)&lt;/span&gt; og &lt;span class="math"&gt;\(k\)&lt;/span&gt; bestemmer hvor aggressiv skatten&amp;nbsp;er.
&lt;span class="math"&gt;\(H\)&lt;/span&gt; er den maksimale marginalskatten&amp;nbsp;ettersom &lt;span class="math"&gt;\(H = \lim_{x \to \infty} T'(x)\)&lt;/span&gt;.
Figuren nedenfor viser at funksjonen er en meget god tilpasning til dagens&amp;nbsp;inntektsskatt.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 750px;
image-rendering: crisp-edges;"
src="https://tommyodland.com/images/articles/simple_taxes/taxes_2025_fit.png"&gt;&lt;/p&gt;
&lt;p&gt;I figuren ovenfor viser vi også inntektsfordelingen, som er hentet fra &lt;a href="https://www.ssb.no/arbeid-og-lonn/lonn-og-arbeidskraftkostnader/artikler/hvor-stort-er-egentlig-lonnsgapet-mellom-kvinner-og-menn"&gt;figur 2 i denne &lt;span class="caps"&gt;SSB&lt;/span&gt;-artikkelen&lt;/a&gt;.
Boks 2.2 i &lt;a href="https://www.regjeringen.no/no/dokumenter/prop.-1-ls-20242025/id3057469/"&gt;Skatter og avgifter 2025&lt;/a&gt; viser statens egen visualisering av marginalskatten.
&lt;a href="https://www.smartepenger.no/105-kalkulator/3798-skatteberegning-2025"&gt;Denne skattekalkulatoren&lt;/a&gt; har 27 felter som kan fylles ut, så den fulle beregningen er enda mer&amp;nbsp;komplisert.&lt;/p&gt;
&lt;h2 id="matematiske-egenskaper"&gt;Matematiske&amp;nbsp;egenskaper&lt;/h2&gt;
&lt;p&gt;Her kommer en rask oppsummering av hvilke spørsmål&amp;nbsp;funksjonen &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt; svarer på og hvilke egenskaper som er hensiktsmessige for en slik skattefunksjon.
Se &lt;a href="https://tommyodland.com/articles/2024/smooth-taxes-without-brackets"&gt;Smooth taxes without brackets&lt;/a&gt; for en grundigere&amp;nbsp;gjennomgang.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Nettoinntekt:&lt;/strong&gt; &lt;span class="math"&gt;\(x - T(x)\)&lt;/span&gt; svarer på spørsmålet &amp;ldquo;Hvor mye penger har jeg igjen etter at jeg har betalt&amp;nbsp;skatt?&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Marginalskatt:&lt;/strong&gt; &lt;span class="math"&gt;\(T'(x)\)&lt;/span&gt; svarer på &amp;ldquo;Hvor stor andel må jeg skatte på neste krone jeg&amp;nbsp;tjener?&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Skatteprosent:&lt;/strong&gt; &lt;span class="math"&gt;\(T(x) / x\)&lt;/span&gt; svarer på &amp;ldquo;Hvilken andel av total inntekt betaler jeg i&amp;nbsp;skatt?&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Eksempel.&lt;/strong&gt;&amp;nbsp;Dersom &lt;span class="math"&gt;\(x=800 \text{ TNOK}\)&lt;/span&gt;, så&amp;nbsp;er:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
T(x) = 227 \text{ TNOK} \quad 
T'(x) = 0.423 \quad
T(x)/x = 0.284
\end{align*}&lt;/div&gt;
&lt;p&gt;Funksjonen &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt; har to viktige egenskaper bakt inn i definisjonen, for all verdier&amp;nbsp;av &lt;span class="math"&gt;\(0 &amp;lt; H \leq 1\)&lt;/span&gt; og &lt;span class="math"&gt;\(k &amp;gt; 0\)&lt;/span&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Gode insentiver.&lt;/strong&gt; En&amp;nbsp;skatt &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt; har gode insentiver dersom&amp;nbsp;marginalskatten &lt;span class="math"&gt;\(T'(x) &amp;lt; 1\)&lt;/span&gt; for&amp;nbsp;alle &lt;span class="math"&gt;\(x\)&lt;/span&gt;, fordi da ender man alltid opp med økt nettoinntekt dersom man tjener&amp;nbsp;mer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Progressiv beskatning.&lt;/strong&gt; En&amp;nbsp;skatt &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt; er progressiv dersom&amp;nbsp;skatteprosenten &lt;span class="math"&gt;\(T(x)/x\)&lt;/span&gt; øker&amp;nbsp;når &lt;span class="math"&gt;\(x\)&lt;/span&gt; øker. Dersom Ola tjener mer enn Kari, så betaler Ola også en større prosentandel av sin inntekt i&amp;nbsp;skatt.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="hva-kan-vi-forenkle"&gt;Hva kan vi&amp;nbsp;forenkle?&lt;/h2&gt;
&lt;p&gt;Fra en borgers perspektiv er kun skattenivået relevant, ikke hva staten kaller de ulike skattene og fradragene eller hva som finansierer hva.
Dersom vi&amp;nbsp;bruker &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt; kan vi ta&amp;nbsp;bort:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Minstefradrag&lt;/li&gt;
&lt;li&gt;Personfradrag&lt;/li&gt;
&lt;li&gt;Skatt på alminnelig&amp;nbsp;inntekt&lt;/li&gt;
&lt;li&gt;Trinnskatt&lt;/li&gt;
&lt;li&gt;Arbeidsgiveravgift (det er i praksis en personskatt på 14.1&amp;nbsp;%)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;La oss også samle all inntekt&amp;nbsp;i &lt;span class="math"&gt;\(x\)&lt;/span&gt;: pensjonsgivende inntekt, utbytte fra selskaper, aksjegevinst, styrehonorarer, osv.
De som eier et selskap kan i dag velge å enten ta ut penger som inntekt eller som utbytte.
Inntekt gir &lt;a href="https://www.azets.com/no-no/innsikt/artikler/lonn-eller-utbytte"&gt;pensjonssparing gjennom folketrygden og rett til sykepenger&lt;/a&gt;, men utbytte har lavere marginalskatt.
Det er ofte optimalt å først maksimere pensjonssparing ved å ta ut lønn opp til 7.1 G og deretter ta utbytte.
Denne typen optimalisering er kun tilgjengelig for et fåtall.
Jo enklere skatten er, desto mindre muligheter er det for&amp;nbsp;optimalisering.&lt;/p&gt;
&lt;p&gt;Om vi er villige til å fjerne noen flere, mer målrettede insentivordninger, kan vi&amp;nbsp;fjerne:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Soner og næringsgrupperinger i arbeidsgiveravgift (&lt;a href="https://www.skatteetaten.no/satser/arbeidsgiveravgift/"&gt;14 koeffisienter&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;span class="caps"&gt;BSU&lt;/span&gt;&amp;nbsp;fradrag&lt;/li&gt;
&lt;li&gt;Fagforeningskontingent&lt;/li&gt;
&lt;li&gt;Foreldrefradrag&lt;/li&gt;
&lt;li&gt;Reiseutgifter&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Noen av disse fradragene bør modelleres som bidrag, og vi kan legge dem tilbake&amp;nbsp;senere.&lt;/p&gt;
&lt;h2 id="vanlige-folks-tur"&gt;Vanlige folks&amp;nbsp;tur&lt;/h2&gt;
&lt;p&gt;Vi kan gi skattelette til store deler av befolkningen om vi velger en høyere verdi for maksimal&amp;nbsp;marginalskatt &lt;span class="math"&gt;\(H\)&lt;/span&gt; og løser&amp;nbsp;for &lt;span class="math"&gt;\(k\)&lt;/span&gt; slik at statens inntekter forblir identiske med dagens nivå&amp;nbsp;(&lt;span class="math"&gt;\(H \approx 0.47\)&lt;/span&gt;).&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 750px;
image-rendering: crisp-edges;"
src="https://tommyodland.com/images/articles/simple_taxes/equivalent_taxes.png"&gt;&lt;/p&gt;
&lt;p&gt;Medianpersonen har en inntekt på omtrent 640 &lt;span class="caps"&gt;TNOK&lt;/span&gt; før skatt.
Går vi fra dagens skatteregime&amp;nbsp;til &lt;span class="math"&gt;\(H=0.55\)&lt;/span&gt;, vil medianpersonen få skattelette på omtrent 3.8 &lt;span class="caps"&gt;TNOK&lt;/span&gt; per år.
Setter vi&amp;nbsp;heller &lt;span class="math"&gt;\(H=0.65\)&lt;/span&gt; får medianpersonen skattelette på 7.4 &lt;span class="caps"&gt;TNOK&lt;/span&gt; per år.
Alle som er under 80 persentilen vil få skattelette, mens de som er over må skatte mer.
I dagens system har &lt;span class="caps"&gt;DNB&lt;/span&gt;-sjefen, som &lt;a href="https://e24.no/boers-og-finans/i/73gvzV/dnb-sjef-kjerstin-braathen-tjente-169-millioner-i-fjor"&gt;tjente 16.9 &lt;span class="caps"&gt;MNOK&lt;/span&gt; i fjor&lt;/a&gt;, samme marginalskatt som en dyktig ingeniør (eller lege, advokat, osv.) som tjener en tiendedel av&amp;nbsp;dette.&lt;/p&gt;
&lt;p&gt;Tabellen sammenligner dagens scenario med to alternativer.
Statens inntekter er&amp;nbsp;identiske.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;                             &lt;/th&gt;
&lt;th&gt;&lt;span class="math"&gt;\(H=0.474\)&lt;/span&gt;  &lt;/th&gt;
&lt;th&gt;&lt;span class="math"&gt;\(H=0.55\)&lt;/span&gt;  &lt;/th&gt;
&lt;th&gt;&lt;span class="math"&gt;\(H=0.65\)&lt;/span&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Skattelette for medianperson&lt;/td&gt;
&lt;td&gt;0 &lt;span class="caps"&gt;NOK&lt;/span&gt;      &lt;/td&gt;
&lt;td&gt; 3800 &lt;span class="caps"&gt;NOK&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;7400 &lt;span class="caps"&gt;NOK&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Andel som får skattelette    &lt;/td&gt;
&lt;td&gt;0 %        &lt;/td&gt;
&lt;td&gt; 79 %     &lt;/td&gt;
&lt;td&gt;80 %     &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Marginalskatt &lt;span class="caps"&gt;DNB&lt;/span&gt;-sjefen    &lt;/td&gt;
&lt;td&gt;47 %      &lt;/td&gt;
&lt;td&gt; 55 %     &lt;/td&gt;
&lt;td&gt;65 %     &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Marginalskatt ingeniør      &lt;/td&gt;
&lt;td&gt;47 %    &lt;/td&gt;
&lt;td&gt; 53 %     &lt;/td&gt;
&lt;td&gt;60 %     &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Skatteprosent &lt;span class="caps"&gt;DNB&lt;/span&gt;-sjefen    &lt;/td&gt;
&lt;td&gt;46 %      &lt;/td&gt;
&lt;td&gt; 53 %     &lt;/td&gt;
&lt;td&gt;63 %     &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Skatteprosent ingeniør      &lt;/td&gt;
&lt;td&gt;37 %      &lt;/td&gt;
&lt;td&gt; 40 %     &lt;/td&gt;
&lt;td&gt;43 %     &lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="oppsummering-inntekt"&gt;Oppsummering:&amp;nbsp;Inntekt&lt;/h2&gt;
&lt;p&gt;Vi samler alt en person tjener i én felles&amp;nbsp;inntekt &lt;span class="math"&gt;\(x\)&lt;/span&gt; og beskatter den&amp;nbsp;med &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt;, enten det er ordinær pensjonsgivende inntekt, utbytte fra selskaper, avkastning på aksjefond, styrehonorar, godtgjørelser eller annet.
Da vil en rik investor med 10 &lt;span class="caps"&gt;MNOK&lt;/span&gt; i aksjegevinst skatte en høyere andel enn en småsparer, noe som ikke er tilfellet i dag.
Mange muligheter for optimalisering forsvinner, og de som får inntekt gjennom utbytte, avkastning og honorarer får samme beregning som folk flest.
Ikke minst blir alt mye enklere.
Vi kan&amp;nbsp;endre &lt;span class="math"&gt;\(H\)&lt;/span&gt; og gi skattelette til 80 % av arbeidsstyrken, men insentivene må også&amp;nbsp;vurderes.&lt;/p&gt;
&lt;p&gt;Vi kan også inkludere arv&amp;nbsp;i &lt;span class="math"&gt;\(x\)&lt;/span&gt;, ettersom det også er inntekt.
Det ville vært en dobbeltbeskatning av &lt;em&gt;penger&lt;/em&gt;, men ikke en dobbeltbeskatning av &lt;em&gt;personer&lt;/em&gt;.
Dersom man arver en villa med nedbetalt lån i Oslo med verdi 20 &lt;span class="caps"&gt;MNOK&lt;/span&gt;, ville man måtte låne 9.1 &lt;span class="caps"&gt;MNOK&lt;/span&gt; i boligen for å betale skatten.
Dette ville utjevnet forskjeller og forhindret opphopning av kapital.
Alternativt kan vi droppe arveskatt og heller endre formuesskatten, noe vi kommer til senere i&amp;nbsp;artikkelen.&lt;/p&gt;
&lt;h1 id="bidrag-og-sttteordninger"&gt;Bidrag og&amp;nbsp;støtteordninger&lt;/h1&gt;
&lt;p&gt;Det eksisterer mange støtteordninger for mennesker som av ulik grunn har lav inntekt, enten i en periode eller permanent.
&lt;span class="caps"&gt;NAV&lt;/span&gt; har &lt;a href="https://www.nav.no/tjenester"&gt;over 200 tjenester&lt;/a&gt;, og noen av disse&amp;nbsp;er:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Uføretrygd&lt;/li&gt;
&lt;li&gt;Sykepenger&lt;/li&gt;
&lt;li&gt;Arbeidsavklaringspenger (&lt;span class="caps"&gt;AAP&lt;/span&gt;)&lt;/li&gt;
&lt;li&gt;Dagpenger&lt;/li&gt;
&lt;li&gt;Tiltakspenger&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I disse fem kategoriene ble det utbetalt &lt;a href="https://www.nav.no/no/lokalt/oslo/nyheter/8-000-flere-fikk-penger-fra-nav-i-2024"&gt;27.3 milliarder &lt;span class="caps"&gt;NOK&lt;/span&gt;&lt;/a&gt; til 166 000 Oslo-borgere i 2024, noe som tilsvarer 164 &lt;span class="caps"&gt;TNOK&lt;/span&gt; per person.
I noen bydeler er andelen som mottar utbetalinger relatert til arbeidsliv og sykdom over 40 %, og på landsbasis er den 36 %.
Andre støtteordninger inkluderer pleiepenger, omsorgspenger, svangerskapspenger, yrkesskadeerstatning, foreldrepenger og økonomisk&amp;nbsp;sosialhjelp.&lt;/p&gt;
&lt;p&gt;Kritikk mot &lt;span class="caps"&gt;NAV&lt;/span&gt; går ofte på at dagens&amp;nbsp;system:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Er krevende å&amp;nbsp;navigere&lt;/li&gt;
&lt;li&gt;Har dårlige&amp;nbsp;insentiver&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Systemet blir lettere å navigere om vi fjerner kompleksitet.
Problemstillingen med insentiver kan forstås ved å se på enkeltsaker som viser såkalte &lt;em&gt;bidragsfeller&lt;/em&gt; (&lt;a href="https://en.wikipedia.org/wiki/Welfare_trap"&gt;welfare traps&lt;/a&gt;).
I noen tilfeller lønner det seg ikke å jobbe fordi da forsvinner&amp;nbsp;bidragene:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.nrk.no/ytring/straffen-for-a-jobbe-1.17128172"&gt;Straffen for å&amp;nbsp;jobbe&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.nrk.no/nordland/ufore-jan-erik-fortviler_-_-taper-nesten-1000-kroner-i-maneden-pa-a-jobbe-1.17346914"&gt;Uføre Jan-Erik fortviler: - Taper nesten 1000 kroner i måneden på å&amp;nbsp;jobbe&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.nrk.no/valg2013/_tjener_-mer-pa-nav-enn-pa-jobb-1.11200902"&gt;&lt;span class="dquo"&gt;&amp;ldquo;&lt;/span&gt;Tjener&amp;rdquo; mer på &lt;span class="caps"&gt;NAV&lt;/span&gt; enn på&amp;nbsp;jobb&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.nrk.no/trondelag/uforetrygd-mer-lonnsomt-enn-jobb-1.8185736"&gt;Uføretrygd mer lønnsomt enn&amp;nbsp;jobb&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;NAVs egen rapport fra 2023 anslår at &lt;a href="https://arbeidogvelferd.nav.no/news/2023/11/1%20av%204%20dagpengemottakere%20kan%20tape%20p%C3%A5%20%C3%A5%20jobbe%20%C3%A9n%20dag"&gt;1 av 4 dagpengemottakere kan tape på å jobbe én dag&lt;/a&gt;.
Mange mennesker lever i økonomiske&amp;nbsp;bidragsfeller.&lt;/p&gt;
&lt;h2 id="a-fange-de-fattige"&gt;Å fange de&amp;nbsp;fattige&lt;/h2&gt;
&lt;p&gt;Det er fort gjort å konstruere en bidragsfelle. 
Intensjonene må være så gode at man ikke tenker nøye nok over insentivene.
Dersom bidraget er betinget på lønn (enten direkte eller indirekte via arbeidsevne) og kuttes skarpt når inntekt øker, så sitter man i fella.
Dersom bidraget derimot senkes sakte, så unngår man&amp;nbsp;feller.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Eksempel - konstant bidragsfelle.&lt;/strong&gt;
Hva om vi gir alle som tjener mindre enn 500 &lt;span class="caps"&gt;TNOK&lt;/span&gt; en utbetaling på 500 &lt;span class="caps"&gt;TNOK&lt;/span&gt;?
Anta at vi har fem personer med ulik inntekt.&amp;nbsp;La &lt;span class="math"&gt;\(B(x)\)&lt;/span&gt; være et bidrag som funksjon av&amp;nbsp;bruttoinntekt &lt;span class="math"&gt;\(x\)&lt;/span&gt;.
Vi&amp;nbsp;setter &lt;span class="math"&gt;\(B(x)\)&lt;/span&gt; lik 500 &lt;span class="caps"&gt;TNOK&lt;/span&gt;&amp;nbsp;dersom &lt;span class="math"&gt;\(x\)&lt;/span&gt; er mindre enn eller lik 500 &lt;span class="caps"&gt;TNOK&lt;/span&gt;, og null hvis ikke.
Visualiseringen viser hva som&amp;nbsp;skjer:&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 750px;
image-rendering: crisp-edges;"
src="https://tommyodland.com/images/articles/simple_taxes/welfare_trap_constant.png"&gt;&lt;/p&gt;
&lt;p&gt;Når man overstiger 500 &lt;span class="caps"&gt;TNOK&lt;/span&gt; i inntekt, så forsvinner hele&amp;nbsp;bidraget &lt;span class="math"&gt;\(B(x)\)&lt;/span&gt; (øverste underfigur til høyre). 
Det rasjonelle er å ligge så nær &lt;span class="caps"&gt;500TNOK&lt;/span&gt; som mulig, men aldri gå over.
Dersom&amp;nbsp;summen &lt;span class="math"&gt;\(x - T(x) + B(x)\)&lt;/span&gt; synker er det ikke noe insentiv for å tjene&amp;nbsp;mer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Eksempel - lineær bidragsfelle.&lt;/strong&gt;
Hva om vi velger en annen funksjon?
Vi kan kompensere de som tjener mindre enn 500 &lt;span class="caps"&gt;TNOK&lt;/span&gt; med en sum slik at alle ender opp med minst 500 &lt;span class="caps"&gt;TNOK&lt;/span&gt;.
Matematisk er&amp;nbsp;funksjonen &lt;span class="math"&gt;\(B(x) = \max(500\text{ TNOK} -x, 0)\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 750px;
image-rendering: crisp-edges;"
src="https://tommyodland.com/images/articles/simple_taxes/welfare_trap_linear.png"&gt;&lt;/p&gt;
&lt;p&gt;For en som har null i inntekt lønner det seg ikke å jobbe i det hele tatt, med mindre personen klarer å tjene ca. 665 &lt;span class="caps"&gt;TNOK&lt;/span&gt;.
Selv da får han ikke betydelig mer enn hva han fikk med null&amp;nbsp;inntekt.&lt;/p&gt;
&lt;h2 id="bidrag-bx-med-gode-egenskaper"&gt;Bidrag &lt;span class="math"&gt;\(B(x)\)&lt;/span&gt; med gode&amp;nbsp;egenskaper&lt;/h2&gt;
&lt;p&gt;Det finnes&amp;nbsp;bidrag &lt;span class="math"&gt;\(B(x)\)&lt;/span&gt; som i kombinasjon&amp;nbsp;med &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt; ikke fører til bidragsfeller.
Funksjoner på&amp;nbsp;formen&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
B(x) = b e^{-x/b} + C \qquad b &amp;lt; \frac{1}{Hk}
\end{align*}&lt;/div&gt;
&lt;p&gt;har gode egenskaper: insentivene er gode og de er billige for&amp;nbsp;staten.&lt;/p&gt;
&lt;p&gt;Summen av skatt og bidrag har gode insentiver dersom det alltid lønner seg å jobbe.
Matematisk er kravet&amp;nbsp;at &lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\underbrace{(x + \epsilon) + B(x + \epsilon) - T(x + \epsilon)}_{\text{netto med $\epsilon$ mer}} &amp;gt; \underbrace{x + B(x) - T(x)}_{\text{netto}}
\end{align*}&lt;/div&gt;
&lt;p&gt;Dersom vi stokker om på likningen og tar&amp;nbsp;grenseverdien &lt;span class="math"&gt;\(\epsilon \to 0\)&lt;/span&gt; får vi&amp;nbsp;kravet
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
T'(x) - B'(x) &amp;lt; 1.
\end{align*}&lt;/div&gt;
&lt;p&gt;Vi deriverer&amp;nbsp;skatten &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt; og&amp;nbsp;substituerer &lt;span class="math"&gt;\(T'(x) = H\left(1 - e^{-kx} \right)\)&lt;/span&gt;.
Da blir kravet&amp;nbsp;at&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
B'(x) &amp;gt; H\left(1 - e^{-kx} \right) - 1.
\end{align*}&lt;/div&gt;
&lt;p&gt;
Denne likningen sier at bidragene ikke kan synke fort mot null når&amp;nbsp;inntekten &lt;span class="math"&gt;\(x\)&lt;/span&gt; øker.
Dette gir intuitivt mening; bidrag som forsvinner fort fører til bidragsfeller.
Alle&amp;nbsp;funksjoner &lt;span class="math"&gt;\(B(x)\)&lt;/span&gt; som oppfyller dette kravet er gyldige, og kvalifisert gjetning leder oss&amp;nbsp;til &lt;span class="math"&gt;\(B(x) = b e^{-x/b} + C\)&lt;/span&gt;, som oppfyller&amp;nbsp;kravet &lt;span class="math"&gt;\(T'(x) - B'(x) &amp;lt; 1\)&lt;/span&gt; når &lt;span class="math"&gt;\(b &amp;lt; 1/(Hk)\)&lt;/span&gt; (se&amp;nbsp;appendiks).&lt;/p&gt;
&lt;p&gt;For eksempel&amp;nbsp;er &lt;span class="math"&gt;\(b=200 \text{ TNOK}\)&lt;/span&gt; og &lt;span class="math"&gt;\(C=0\)&lt;/span&gt; gyldig, og gir følgende omfordeling via skatter og&amp;nbsp;bidrag:&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 750px;
image-rendering: crisp-edges;"
src="https://tommyodland.com/images/articles/simple_taxes/valid_b_200k.png"&gt;&lt;/p&gt;
&lt;p&gt;Konstantleddet &lt;span class="math"&gt;\(C\)&lt;/span&gt; kalles gjerne borgerlønn (&lt;a href="https://en.wikipedia.org/wiki/Universal_basic_income"&gt;universal basic income&lt;/a&gt;),&amp;nbsp;mens &lt;span class="math"&gt;\(b e^{-x/b}\)&lt;/span&gt; er i kombinasjon&amp;nbsp;med &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt; en &lt;a href="https://en.wikipedia.org/wiki/Negative_income_tax"&gt;negativ skatt&lt;/a&gt;.
Problemet&amp;nbsp;med &lt;span class="math"&gt;\(B(x) = C\)&lt;/span&gt; er at det er dyrt for staten, fordi alle får bidrag.
I resten av artikkelen setter&amp;nbsp;vi &lt;span class="math"&gt;\(C=0\)&lt;/span&gt; og antar&amp;nbsp;at &lt;span class="math"&gt;\(B(x) = b e^{-x/b}\)&lt;/span&gt;.
Legg merke til&amp;nbsp;at &lt;span class="math"&gt;\(B(0) = b\)&lt;/span&gt;, altså&amp;nbsp;er &lt;span class="math"&gt;\(b\)&lt;/span&gt; summen som en person uten inntekt vil få&amp;nbsp;utbetalt.&lt;/p&gt;
&lt;p&gt;Verdien &lt;span class="math"&gt;\(b\)&lt;/span&gt; må balansere to hensyn: den må være høy nok til at det går an å leve på, samtidig må den være lav nok til å insentivere til&amp;nbsp;arbeid.&lt;/p&gt;
&lt;h2 id="hva-kan-vi-forenkle_1"&gt;Hva kan vi&amp;nbsp;forenkle?&lt;/h2&gt;
&lt;p&gt;Dersom vi innfører et universelt&amp;nbsp;bidrag &lt;span class="math"&gt;\(B(x) = b e^{-x/b}\)&lt;/span&gt; kan vi fjerne eller kutte kraftig i uføretrygd, sykepenger, arbeidsavklaringspenger, dagpenger, tiltakspenger og økonomisk sosialehjelp.
&lt;span class="caps"&gt;NAV&lt;/span&gt; har &lt;a href="https://www.nav.no/_/en/attachment/inline/e89e93e3-3056-47ce-b374-de424818be8a:88d6d362b3654d28c3661c2c23c520251b9d2e47/%C3%85rsrapport%202024%20NAV.pdf"&gt;12 000 årsverk og 15 mrd. i driftsutgiver&lt;/a&gt;, og deler av dette kan kuttes.
Tiden og ressursene som brukes på å sende inn og behandle søknader forsvinner&amp;nbsp;helt.&lt;/p&gt;
&lt;p&gt;Staten betaler også bidrag til friske mennesker for å søtte en rekke aktiviteter.
Eksempelvis var det &lt;a href="https://www.kulturdirektoratet.no/web/guest/sks/vis-artikkel/-/over-12-000-soknadar-til-statens-kunstnarstipend-no-er-vedtaka-klare"&gt;12 000 søkere&lt;/a&gt; på statens kunstnerstipend i 2024, og i 2025 ble det bevilget nesten &lt;a href="https://kommunikasjon.ntb.no/pressemelding/18456124/1-090-kunstnere-far-statens-kunstnerstipend?publisherId=89220&amp;amp;lang=no"&gt;500 millioner&lt;/a&gt; kroner.
Mange slike ordninger kan fjernes.
Mennesker som ønsker å lage kunst eller gjøre noe helt annet (som kan være vel så samfunnsnyttig men ikke anerkjent av staten), står fritt til å gjøre det&amp;mdash;dersom de er villige til å leve på&amp;nbsp;bidraget &lt;span class="math"&gt;\(B(x)\)&lt;/span&gt;.
Dersom kunsten selger, vil de få mer inntekt, miste bidrag og betale skatt&amp;mdash;men de vil alltid ende opp med mer&amp;nbsp;penger.&lt;/p&gt;
&lt;h1 id="en-realistisk-beregning"&gt;En realistisk&amp;nbsp;beregning&lt;/h1&gt;
&lt;p&gt;I denne seksjonen forsøker vi grovt å regne ut&amp;nbsp;hva &lt;span class="math"&gt;\(B(x)\)&lt;/span&gt; vil koste.
Her er noen store utgiftsposter, hentet fra &lt;a href="https://www.nav.no/_/en/attachment/inline/e89e93e3-3056-47ce-b374-de424818be8a:15067f1398be3d3be4129e606c7d69641c9e3274/%C3%85rsrapport%202024%20NAV.pdf"&gt;NAVs årsrapport 2024&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Uføretrygd: 125 269 &lt;span class="caps"&gt;MNOK&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Sykepengetilfeller: 68 068 &lt;span class="caps"&gt;MNOK&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Arbeidsavklaringspenger: 46 926 &lt;span class="caps"&gt;MNOK&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Dagpenger: 13 300 &lt;span class="caps"&gt;MNOK&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Hjelpemidler: 10 321 &lt;span class="caps"&gt;MNOK&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Til sammen er dette 264 milliarder kroner.
I følge &lt;a href="https://www.ssb.no/arbeid-og-lonn/sysselsetting/statistikk/tilknytning-til-arbeid-utdanning-og-velferdsordninger/artikler/18-prosent-i-yrkesaktiv-alder-er-utenfor"&gt;&lt;span class="caps"&gt;SSB&lt;/span&gt;&lt;/a&gt; er 622 000 personer i yrkesaktiv alder utenfor arbeidslivet (omtrent 18 %).
Fordeler vi 264 milliarder på 622 000 personer kan hver person få 424 &lt;span class="caps"&gt;TNOK&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Ettersom &lt;span class="math"&gt;\(B(x)\)&lt;/span&gt; også deler ut penger til de som ikke har null i inntekt, må vi ta hensyn til de 82 % som er i arbeidslivet.
Jeg fant ingen gode data som viser inntektsfordeling for alle i Norge, men om vi baserer oss på &lt;a href="https://www.ssb.no/arbeid-og-lonn/lonn-og-arbeidskraftkostnader/artikler/hvor-stort-er-egentlig-lonnsgapet-mellom-kvinner-og-menn"&gt;fulltidsekvivalenter&lt;/a&gt; for de 82 % som er i arbeidslivet og kombinerer det med de 18 % som er utenfor, kommer vi frem til at 264 milliarder kan&amp;nbsp;finansiere &lt;span class="math"&gt;\(b = 271 \text{ TNOK}\)&lt;/span&gt;.
At vi bruker fulltidsekvivalenter gjør&amp;nbsp;at &lt;span class="math"&gt;\(b\)&lt;/span&gt; overestimeres.
At vi verken inkluderer flere støtteordninger eller administrative kostnader gjør&amp;nbsp;at &lt;span class="math"&gt;\(b\)&lt;/span&gt; underestimeres.
Disse effektene kansellerer hverandre til en viss grad, og i sum er&amp;nbsp;nok &lt;span class="math"&gt;\(b\)&lt;/span&gt; estimert noelunde&amp;nbsp;riktig.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 750px;
image-rendering: crisp-edges;"
src="https://tommyodland.com/images/articles/simple_taxes/maximal_benefits.png"&gt;&lt;/p&gt;
&lt;p&gt;Men hvor mye bør staten dele ut?
Det finnes ingen klar fasit, men her er to&amp;nbsp;ankerpunkter:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.oslomet.no/om/sifo/referansebudsjettet"&gt;SIFOs referansebudsjett&lt;/a&gt; er på omtrent 190 &lt;span class="caps"&gt;TNOK&lt;/span&gt; i året (eksl. bolig, ferier og helsetjenester).
  Boliginformasjon kan vi finne &lt;a href="https://www.ssb.no/bygg-bolig-og-eiendom/bolig-og-boforhold/statistikk/boforhold-levekarsundersokelsen/artikler/kraftig-okning-i-bokostnader-for-boligeiere"&gt;hos &lt;span class="caps"&gt;SSB&lt;/span&gt;&lt;/a&gt;, som estimerer typisk boligkostnad for eiere til 130 &lt;span class="caps"&gt;TNOK&lt;/span&gt; per år og leietakere til 111 &lt;span class="caps"&gt;TNOK&lt;/span&gt; per år.
  En person trenger derfor omtrent 190 + 120 = 310 &lt;span class="caps"&gt;TNOK&lt;/span&gt; per år for å leve&amp;nbsp;greit.&lt;/li&gt;
&lt;li&gt;Lavinntekstsgrensa måler relativ fattigdom og er definert til å være 60% av medianinntekt etter skatt.
  Grensa er på &lt;a href="https://www.ssb.no/inntekt-og-forbruk/inntekt-og-formue/artikler/hvor-mange-er-fattige-i-norge"&gt;285 &lt;span class="caps"&gt;TNOK&lt;/span&gt;&lt;/a&gt; i 2025.
  
Oppsummert har staten råd&amp;nbsp;til &lt;span class="math"&gt;\(b \approx 271 \text{ TNOK}\)&lt;/span&gt;, og dette nivået samsvarer godt med SIFOs referansebudsjett og&amp;nbsp;fattigdomsgrensen.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="oppsummering-bidrag-og-sttteordninger"&gt;Oppsummering: Bidrag og&amp;nbsp;støtteordninger&lt;/h2&gt;
&lt;p&gt;Bidrag og støtteordninger er i hovedsak til for syke og arbeidsledige.
Dagens systemer kan være vanskelige å navigere, kreve mye dokumentasjon, og kan disinsentivere arbeid.
Staten må bedømme hva som er verdifullt for samfunnet, selv om det kanskje ikke er kommersielt levedyktig.
Stipendordninger kan føre til &lt;a href="https://www.nettavisen.no/okonomi/er-i-kulturradet-fikk-16-5-millioner-i-statsstotte/s/5-95-2611248"&gt;habilitetsutfordringer&lt;/a&gt; og staten må bruke ressurser på å bestemme hvem som får&amp;nbsp;hva.&lt;/p&gt;
&lt;p&gt;Vi innfører en&amp;nbsp;bidragsfunksjon &lt;span class="math"&gt;\(B(x)\)&lt;/span&gt;, som sammen&amp;nbsp;med &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt; gir negativ skatt til de som tjener lite.
Dette krever ingen søknadsprosess eller vurdering fra statens side.
Vi fjerner ikke tiltak som jobbsøkerkurs og økonomisk veiledning, ei heller bidrag til rullestoler, proteser, osv.
Bidragene kunne vært satt til omtrent 300 &lt;span class="caps"&gt;TNOK&lt;/span&gt; per år, men det eksakte beløpet er en subjektiv vurdering.
I hvilken grad barn, studenter og pensjonister omfattes av ordningen kan diskuteres.
Det enkleste er å gi bidrag til disse gruppene og fjerne eksisterende&amp;nbsp;støtteordninger.&lt;/p&gt;
&lt;h1 id="formuesskatt"&gt;Formuesskatt&lt;/h1&gt;
&lt;p&gt;Denne delen av artikkelen er uten sammenheng med resten, som i hovedsak handler om inntekt og bidrag, men la oss se på formueskatt&amp;nbsp;også.&lt;/p&gt;
&lt;p&gt;Den marginale &lt;a href="https://www.skatteetaten.no/satser/formuesskatt/"&gt;formueskatten&lt;/a&gt; har to trinn: ett på 1.76 &lt;span class="caps"&gt;MNOK&lt;/span&gt; (1 %) og ett på 20.7 &lt;span class="caps"&gt;MNOK&lt;/span&gt; (1.1 %).
Den er med andre ord langt mindre progressiv enn inntektsskatt, og den slår inn relativt tidlig.
Effekten er nedjustert av verdsettingsrabatter; eksempelvis har primærbolig en rabatt på hele 75 % mens aksjer har en rabatt på 20&amp;nbsp;%.&lt;/p&gt;
&lt;p&gt;De rike blir rikere på grunn av renters-rente effekten.
Den brede aksjeindeksen S&amp;amp;P 500 har hatt en gjennomsnittlig inflasjonsjustert avkastning på &lt;a href="https://www.inflationtool.com/adjusted-prices/spy"&gt;7.65 % de siste 30 årene&lt;/a&gt;.
En rik person med 100 &lt;span class="caps"&gt;MNOK&lt;/span&gt; investert vil derfor få 7.65 &lt;span class="caps"&gt;MNOK&lt;/span&gt; i avkastning.
Anta at denne avkastningen realiseres og beskattes med 3.46 &lt;span class="caps"&gt;MNOK&lt;/span&gt;&amp;nbsp;(med &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt;) slik at personen står igjen med en gevinst på 4.2 &lt;span class="caps"&gt;MNOK&lt;/span&gt;.
Formuen er nå 100 + 4.2 = 104.2 &lt;span class="caps"&gt;MNOK&lt;/span&gt;, og formueskatten blir 1.1 &lt;span class="caps"&gt;MNOK&lt;/span&gt;, så personen står igjen med 103.1 &lt;span class="caps"&gt;MNOK&lt;/span&gt;.
Ved å sette pengene i en bred indeks som S&amp;amp;P 500, som ikke krever noe arbeid eller oppfølging, kan personen hente ut 3.1 &lt;span class="caps"&gt;MNOK&lt;/span&gt; per år.
Formuen vil ha samme avkastning hvert eneste&amp;nbsp;år.&lt;/p&gt;
&lt;p&gt;Figuren nedenfor viser den marginale formueskatten og simuleringer av ulike formuer i S&amp;amp;P 500.
Formuene vokser lineært på log skala, altså&amp;nbsp;eksponentielt.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 750px;
image-rendering: crisp-edges;"
src="https://tommyodland.com/images/articles/simple_taxes/wealth_tax.png"&gt;&lt;/p&gt;
&lt;p&gt;Dagens marginale formueskatt er i praksis flat, og da vil formuene enten vokse evig (om formueskatten er lav) eller gå mot null (om formuesskatten er høy).
Ved å konstruere en marginal formuesskatt som er mer progressiv kan vi begrense de rikes&amp;nbsp;formuer.&lt;/p&gt;
&lt;p&gt;La &lt;span class="math"&gt;\(y\)&lt;/span&gt; være formue.
Ved å bruke en&amp;nbsp;sigmoid &lt;span class="math"&gt;\(z = 1 / (1 + e^{-z})\)&lt;/span&gt; på log-skala kan vi konstruere en&amp;nbsp;formuesskatt:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
W'(y) = \frac{H}{1 + (y / y_0)^{-k}}
\qquad
W(y) = \frac{H y^{1 + k}}{(k+1) y_0^k} {_2F_1}\left(1, 1 + 1/k, 2 + 1/k, -(y/y_0)^k\right)
\end{align*}&lt;/div&gt;
&lt;p&gt;
Parameteren &lt;span class="math"&gt;\(H\)&lt;/span&gt; er maksimal marginal&amp;nbsp;formuesskatt, &lt;span class="math"&gt;\(y_0\)&lt;/span&gt; er den formuen som&amp;nbsp;gir &lt;span class="math"&gt;\(W'(y_0) = H/2\)&lt;/span&gt; og &lt;span class="math"&gt;\(k &amp;gt; 0\)&lt;/span&gt; bestemmer hvor raskt sigmoid-funksjonen vokser.
Den marginale&amp;nbsp;formueskatten &lt;span class="math"&gt;\(W'(y)\)&lt;/span&gt; er gitt av en relativt uskyldig formel,&amp;nbsp;mens &lt;span class="math"&gt;\(W(y)\)&lt;/span&gt; er uttrykt ved hjelp av en &lt;a href="https://en.wikipedia.org/wiki/Hypergeometric_function"&gt;hypergeometrisk&amp;nbsp;funksjon&lt;/a&gt; &lt;span class="math"&gt;\({_2F_1}\)&lt;/span&gt; (se appendiks).
Man trenger ikke å vite noe om hypergeometriske funksjoner for å bruke eller forstå denne&amp;nbsp;funksjonen.&lt;/p&gt;
&lt;p&gt;Den progressive marginale&amp;nbsp;formuesskatten &lt;span class="math"&gt;\(W'(y)\)&lt;/span&gt; er vist i figuren nedenfor, og sammenlignet med veksten til ulike formuer.
Parametrene er satt ut fra øyemål for å vise effekten på en tydelig måte.
Merk at veksten til ekstremt store formuer begrenses, mens mindre formuer får vokse.
&lt;img
style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 750px;
image-rendering: crisp-edges;"
src="https://tommyodland.com/images/articles/simple_taxes/wealth_tax_progressive.png"&gt;&lt;/p&gt;
&lt;p&gt;Med &lt;span class="math"&gt;\(W(y)\)&lt;/span&gt; betaler alle formuesskatt, men de fleste betaler veldig lite.
En formue på 100 &lt;span class="caps"&gt;TNOK&lt;/span&gt; gir 19 kr i formueskatt, 1 &lt;span class="caps"&gt;MNOK&lt;/span&gt; gir 1200 kr, 10 &lt;span class="caps"&gt;MNOK&lt;/span&gt; gir 64 &lt;span class="caps"&gt;TNOK&lt;/span&gt; og 100 &lt;span class="caps"&gt;MNOK&lt;/span&gt; gir 2.2 &lt;span class="caps"&gt;MNOK&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Alle eksemplene i denne seksjonen antar at formuen er likvid.
Dette er ikke tilfellet dersom formuen består av aksjer i selskaper som ikke er på børs, eller om formuen er i eiendom.
Formuesskatt kan føre til at man mister kontrollen over sitt eget selskap eller mister sin egen bolig.
En bolig gir bruksverdi til eieren på en annen måte enn et selskap, så argumentet for formuesskatt på eiendom er sterkere enn argumentet for formuesskatt på illikvide aksjer.
Dersom vi verken har formueskatt eller arveskatt, kan en rik familie sette formuen i aksjemarkedet og oppnå eksponentiell vekst for all&amp;nbsp;fremtid.&lt;/p&gt;
&lt;h1 id="oppsummering"&gt;Oppsummering&lt;/h1&gt;
&lt;p&gt;I dette tankeeksperimentet endret vi skattesystemet ved å innføre to&amp;nbsp;funksjoner: &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt; for skatt&amp;nbsp;og &lt;span class="math"&gt;\(B(x)\)&lt;/span&gt; for bidrag.
Parametrene&amp;nbsp;(&lt;span class="math"&gt;\(H\)&lt;/span&gt;, &lt;span class="math"&gt;\(k\)&lt;/span&gt; og &lt;span class="math"&gt;\(b\)&lt;/span&gt;) eliminerer kompleksiteten i skatteberegninger, mange av NAVs støtteordninger og en rekke andre overføringsmekanismer.
Den generelle idéen om borgerlønn og negativ inntektsskatt er gammel, mens de spesifikke funksjonene og deres egenskaper er mitt eget bidrag.
Les gjerne &lt;a href="https://civita.no/content/uploads/2018/01/Civitanotat_38_2017.pdf"&gt;Civita-notat nr. 38 om borgerlønn&lt;/a&gt; for en grundig gjennomgang av borgerlønn i norsk&amp;nbsp;sammenheng.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 750px;
image-rendering: crisp-edges;"
src="https://tommyodland.com/images/articles/simple_taxes/redistribution_T_B.png"&gt;&lt;/p&gt;
&lt;p&gt;Økonomiske insentiver som &lt;a href="https://www.skatteetaten.no/satser/finnmarksfradraget/"&gt;Finnmarksfradraget&lt;/a&gt; i skatteberegningen og &lt;a href="https://lanekassen.no/nb-NO/gjeld-og-betaling/finnmark-eller-nord-troms/#samtykke-banner"&gt;sletting av gjeld&lt;/a&gt; for beboere i Finnmark kan modelleres ved å velge litt andre parametre&amp;nbsp;(f.eks. &lt;span class="math"&gt;\(b\)&lt;/span&gt; eller &lt;span class="math"&gt;\(C\)&lt;/span&gt;) i Finnmark.
Her bør staten vise måtehold, ettersom mye av poenget forsvinner dersom man får en eksplosjon i antall parametre.
Vi viste også at en&amp;nbsp;sigmoid-funksjon &lt;span class="math"&gt;\(W(y)\)&lt;/span&gt; som marginal formueskatt begrenser ekstrem&amp;nbsp;formuesvekst.&lt;/p&gt;
&lt;p&gt;Noen av fordelene med et slikt system&amp;nbsp;er:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Å øke maksimal&amp;nbsp;marginalskatt &lt;span class="math"&gt;\(H\)&lt;/span&gt; kan gi skattelette til omtrent 80 % av&amp;nbsp;befolkningen.&lt;/li&gt;
&lt;li&gt;Skatteoptimaliseringsmuligheter som primært kommer de rike til gode&amp;nbsp;fjernes.&lt;/li&gt;
&lt;li&gt;Bidragsfeller som i dag fanger mange &lt;span class="caps"&gt;NAV&lt;/span&gt;-brukere blir&amp;nbsp;eliminert.&lt;/li&gt;
&lt;li&gt;Den administrative besparelsen ville vært stor. Tusenvis av årsverk kuttes og borgere slipper&amp;nbsp;søknadsprosesser.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Det er imidlertid et tankeeksperiment og en øvelse i matematikk, ikke et gjennomtenkt politisk forslag.
Mange av kompleksitetene vi ønsker å fjerne reflekterer legitime forsøk på å balansere konkurrerende samfunnsmål.
Noen vil mene at det er moralsk riktig å gi støtte ut fra hvem som fortjener det eller ikke, selv om det koster å gjennomføre slike vurderinger.
En mer aggressiv formueskatt kan utløse kapitalflukt.
Mange ville brukt økonomisk trygghet til å investere i utdanning, starte bedrifter eller ta seg av familie.
Andre ville potensielt redusert arbeidsinnsatsen eller i verste fall ty til destruktiv atferd.
En implementering ville krevd utredninger av atferdsresponser, overgangsordninger og konsekvenser.
Men hvis det er et land som har råd til å utrede, så er det&amp;nbsp;Norge.&lt;/p&gt;
&lt;h2 id="referanser-og-videre-lesing"&gt;Referanser og videre&amp;nbsp;lesing&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.nav.no/no/nav-og-samfunn/statistikk/flere-statistikkomrader/utbetalinger-til-personer-i-norge-per-fylke-og-kommune"&gt;Utbetalinger til personer i Norge - Statistikk for 2024&lt;/a&gt; - &lt;span class="caps"&gt;NAV&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.nav.no/no/nav-og-samfunn/statistikk/flere-statistikkomrader/utbetalinger-til-personer-i-norge-per-fylke-og-kommune"&gt;Årsrapport 2024&lt;/a&gt; - &lt;span class="caps"&gt;NAV&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.regjeringen.no/no/dokumenter/meld.-st.-1-20242025/id3056833/"&gt;Nasjonalbudsjettet 2025&lt;/a&gt; -&amp;nbsp;Regjeringen&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.nrk.no/ytring/vi-burde-snakke-om-borgerlonn-1.11814756"&gt;Hvorfor snakker vi ikke om borgerlønn?&lt;/a&gt; - &lt;span class="caps"&gt;NRK&lt;/span&gt; Ytring&amp;nbsp;(2014)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://civita.no/content/uploads/2018/01/Civitanotat_38_2017.pdf"&gt;Borgerlønn&lt;/a&gt; - Civita-notat nr. 38 (2017)
 &lt;a href="https://www.smartepenger.no/103-skatt/1890-tips-som-kan-kutte-skatten-din"&gt;50 tips som kan kutte skatten din&lt;/a&gt; -&amp;nbsp;smartepenger.no&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Negative_income_tax"&gt;Negative income tax&lt;/a&gt; -&amp;nbsp;Wikipedia&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.mdpi.com/2071-1050/12/22/9459"&gt;Is There Empirical Evidence on How the Implementation of a Universal Basic Income (&lt;span class="caps"&gt;UBI&lt;/span&gt;) Affects Labour Supply? A Systematic Review&lt;/a&gt; -&amp;nbsp;Artikkel&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="appendiks-a-bevis-for-at-bidragsfunksjonen-har-gode-insentiver"&gt;Appendiks A: Bevis for at bidragsfunksjonen har gode&amp;nbsp;insentiver&lt;/h2&gt;
&lt;p&gt;I dette appendikset skal vi vise at når vi velger&amp;nbsp;funksjoner
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
T(x) = \frac{H(e^{-kx} + kx - 1)}{k} \qquad \text{og}\qquad B(x) = Abe^{-x/b} + C
\end{align*}&lt;/div&gt;
&lt;p&gt;
med &lt;span class="math"&gt;\(0 \leq H &amp;lt; 1\)&lt;/span&gt;, &lt;span class="math"&gt;\(b &amp;gt; 0\)&lt;/span&gt;, &lt;span class="math"&gt;\(k &amp;gt; 0\)&lt;/span&gt; og &lt;span class="math"&gt;\(A &amp;gt; 0\)&lt;/span&gt;, så er har vi alltid gode insentiver&amp;nbsp;fordi
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
T'(x) - B'(x) &amp;lt; 1
\end{align*}&lt;/div&gt;
&lt;p&gt;
for&amp;nbsp;alle &lt;span class="math"&gt;\(x &amp;gt; 0\)&lt;/span&gt; dersom &lt;span class="math"&gt;\(Hkb &amp;lt; A\)&lt;/span&gt; og &lt;span class="math"&gt;\(0 &amp;lt; A \leq 1\)&lt;/span&gt;.
Det naturlige er å&amp;nbsp;velge &lt;span class="math"&gt;\(A=1\)&lt;/span&gt;, fordi da synker bidraget raskest og det blir billigst for&amp;nbsp;staten.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bevis.&lt;/strong&gt;
Vi deriverer funksjonene og setter dem inn i ulikheten.
Da får&amp;nbsp;vi
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
f(x) &amp;amp;= -T'(x) + B(x) + 1 &amp;gt; 0 \\  
f(x) &amp;amp;= H\left(e^{-kx} - 1 \right) - A e^{-x/b} + 1 &amp;gt; 0.
\end{align*}&lt;/div&gt;
&lt;p&gt;
Vi ser også&amp;nbsp;at
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
f'(x) &amp;amp;= -Hk e^{-kx} + \frac{A}{b} e^{-x/b} \\
f'(x) &amp;amp;= 0 \, \Rightarrow \, x (k - \frac{1}{b}) = \ln\left( \frac{Hkb}{A} \right).
\end{align*}&lt;/div&gt;
&lt;p&gt;
Dersom &lt;span class="math"&gt;\(f(x)\)&lt;/span&gt; er positiv og økende&amp;nbsp;når &lt;span class="math"&gt;\(x=0\)&lt;/span&gt;, konvergerer mot et positivt tall og har maksimalt ett ekstremalpunkt&amp;nbsp;når &lt;span class="math"&gt;\(x &amp;gt; 0\)&lt;/span&gt;, så&amp;nbsp;er &lt;span class="math"&gt;\(f(x) &amp;gt; 0\)&lt;/span&gt; for&amp;nbsp;alle &lt;span class="math"&gt;\(x &amp;gt; 0\)&lt;/span&gt; og ulikheten er&amp;nbsp;gyldig.&lt;/p&gt;
&lt;p&gt;Vi observerer&amp;nbsp;at:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="math"&gt;\(f(0) = -A + 1 \geq 0 \Rightarrow A \leq 1\)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(f'(0) = -Hk + A/b &amp;gt; 0 \Rightarrow A &amp;gt; Hkb\)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(\lim_{x \to \infty} f(x) = 1 - H &amp;gt; 0\)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(f(x)\)&lt;/span&gt; har ett ekstremalpunkt&amp;nbsp;fordi &lt;span class="math"&gt;\(f'(x) = 0\)&lt;/span&gt; har én&amp;nbsp;løsning&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Dette gir oss kravene&amp;nbsp;(1) &lt;span class="math"&gt;\(A \leq 1\)&lt;/span&gt; og&amp;nbsp;(2) &lt;span class="math"&gt;\(A &amp;gt; Hkb\)&lt;/span&gt;.
I artikkelen valgte&amp;nbsp;vi &lt;span class="math"&gt;\(A=1\)&lt;/span&gt;, og da blir kravet&amp;nbsp;for &lt;span class="math"&gt;\(b\)&lt;/span&gt; at &lt;span class="math"&gt;\(b &amp;lt; 1/(Hk)\)&lt;/span&gt;. &lt;/p&gt;
&lt;h2 id="appendiks-b-formuesskatten"&gt;Appendiks B:&amp;nbsp;Formuesskatten&lt;/h2&gt;
&lt;p&gt;I dette appendikset skal vi utlede&amp;nbsp;formuesskatten &lt;span class="math"&gt;\(W(y)\)&lt;/span&gt; fra den marginale&amp;nbsp;formuesskatten &lt;span class="math"&gt;\(W'(y)\)&lt;/span&gt;.
Vi definerte den marginale formuesskatten&amp;nbsp;som &lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
W'(y) = \frac{H}{1 + (y / y_0)^{-k}} = \frac{H}{1 + \exp(-k \ln (y / y_0))}.
\end{align*}&lt;/div&gt;
&lt;p&gt;Målet er å evaluere&amp;nbsp;integralet&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\int_0^y W'(z) \, dz = H \int_0^y \frac{1}{1 + \exp(-k \ln (z / y_0))} \, dz
\end{align*}&lt;/div&gt;
&lt;p&gt;for å finne den totale formuesskatten ved et&amp;nbsp;beløp &lt;span class="math"&gt;\(Y\)&lt;/span&gt;.
Først gjør vi&amp;nbsp;substitusjonen &lt;span class="math"&gt;\(u = \ln(z/y_0)\)&lt;/span&gt;, som transformerer integralet&amp;nbsp;til
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
H y_0 \int_{-\infty}^{\ln(y/y_0)} \frac{e^u}{1 + e^{-ku}} \, du = H y_0 \int_{-\infty}^{\ln(y/y_0)} \frac{e^u e^{ku}}{1 + e^{ku}} \, du.
\end{align*}&lt;/div&gt;
&lt;p&gt;
Deretter gjør vi&amp;nbsp;substitusjonen &lt;span class="math"&gt;\(z = e^{ku}\)&lt;/span&gt;, som transformerer integralet&amp;nbsp;til
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\frac{H y_0}{k} \int_{0}^{(y/y_0)^k} \frac{z^{1/k}}{1 + z} \, dz
\end{align*}&lt;/div&gt;
&lt;p&gt;
og dette er et kjent integral som kan formuleres som en &lt;a href="https://en.wikipedia.org/wiki/Hypergeometric_function"&gt;hypergeometrisk funksjon&lt;/a&gt;.
Vi kan bruke&amp;nbsp;identiteten
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\int \frac{x^{1/k}}{1 + x} \, dx = \frac{k x^{1 + 1/k}}{k+1} \, {_2F_1}\left( 1, 1 + 1/k, 2 + 1/k, -x \right)
\end{align*}&lt;/div&gt;
&lt;p&gt;
som gir oss vårt endelig&amp;nbsp;svar:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
W(y) = \int_0^y W'(z) \, dz = \frac{H y^{1 + k}}{(k+1) y_0^k} \, {_2F_1}\left(1, 1 + 1/k, 2 + 1/k, -(y/y_0)^k\right)
\end{align*}&lt;/div&gt;
&lt;h2 id="appendiks-c-kode"&gt;Appendiks C:&amp;nbsp;Kode&lt;/h2&gt;
&lt;p&gt;Her er Python-kode som implementerer hovedidéene i&amp;nbsp;artikkelen.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dataclasses&lt;/span&gt;  &lt;span class="c1"&gt;# Python 3.12&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;abc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ABC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;abstractmethod&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;numpy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;np&lt;/span&gt;  &lt;span class="c1"&gt;# 2.3.3&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;scipy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sp&lt;/span&gt;  &lt;span class="c1"&gt;# 1.16.2&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Transfer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ABC&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Abstract base class for transfer functions (taxes, benefits, etc.).&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

    &lt;span class="nd"&gt;@abstractmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;marginal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Marginal rate at income x.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;pass&lt;/span&gt;

    &lt;span class="nd"&gt;@abstractmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Absolute value at income x.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;pass&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;average&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Average rate: absolute(x) / x.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;


&lt;span class="nd"&gt;@dataclasses&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Tax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Transfer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Class representing a tax function T(x).&lt;/span&gt;

&lt;span class="sd"&gt;    Examples&lt;/span&gt;
&lt;span class="sd"&gt;    --------&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; T = Tax(k=2.787914e-06, H=0.474)&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; T.absolute(500_000)  # Current norwegian tax system: 113643.99&lt;/span&gt;
&lt;span class="sd"&gt;    np.float64(109160.85121307125)&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; T.absolute(1_000_000)  # Current norwegian tax system: 313248.14&lt;/span&gt;
&lt;span class="sd"&gt;    np.float64(314445.0244402999)&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

    &lt;span class="c1"&gt;# Defaults that give best-fit to current tax system in 2025,&lt;/span&gt;
    &lt;span class="c1"&gt;# weighted by income of population.&lt;/span&gt;
    &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;2.8745e-06&lt;/span&gt;
    &lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.4772&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;__post_init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;marginal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Marginal tax rate T&amp;#39;(x).&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;H&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;H&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Absolute value of tax T(x).&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@dataclasses&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Benefit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Transfer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Class representing a benefit function B(x).&lt;/span&gt;

&lt;span class="sd"&gt;    Examples&lt;/span&gt;
&lt;span class="sd"&gt;    --------&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; B = Benefit(300_000)&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; B.absolute(0)&lt;/span&gt;
&lt;span class="sd"&gt;    np.float64(300000.0)&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; B.absolute(300_000)  # exp(-1) * 300_000 = 110363&lt;/span&gt;
&lt;span class="sd"&gt;    np.float64(110363.8323514327)&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;271_000&lt;/span&gt;  &lt;span class="c1"&gt;# Somewhat realistic value&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;__post_init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;marginal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Marginal benefit rate B&amp;#39;(x).&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Absolute value of benefits B(x).&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@dataclasses&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;WealthTax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Transfer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Class representing a wealth tax function W(y).&lt;/span&gt;

&lt;span class="sd"&gt;    Uses a logistic-based wealth tax with hypergeometric function&lt;/span&gt;
&lt;span class="sd"&gt;    for absolute tax calculation.&lt;/span&gt;

&lt;span class="sd"&gt;    Examples&lt;/span&gt;
&lt;span class="sd"&gt;    --------&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; W = WealthTax(y_0=10_000_000, H=0.03, k=1.0)&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; W.marginal(10_000_000)  # At threshold, marginal rate is H/2&lt;/span&gt;
&lt;span class="sd"&gt;    np.float64(0.015)&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; W.average(2_000_000)  # Average tax on 2 MNOK wealth&lt;/span&gt;
&lt;span class="sd"&gt;    np.float64(0.0026517664809068064)&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; W.average(20_000_000)  # Average tax on 20 MNOK wealth&lt;/span&gt;
&lt;span class="sd"&gt;    np.float64(0.013520815669978353)&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

    &lt;span class="n"&gt;y_0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10_000_000&lt;/span&gt;  &lt;span class="c1"&gt;# Wealth threshold&lt;/span&gt;
    &lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.03&lt;/span&gt;  &lt;span class="c1"&gt;# Maximum marginal rate&lt;/span&gt;
    &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;  &lt;span class="c1"&gt;# Steepness parameter&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;__post_init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y_0&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;marginal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Marginal wealth tax rate W&amp;#39;(y).&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="n"&gt;y_0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y_0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;H&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;y_0&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Absolute value of wealth tax W(y).&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="n"&gt;y_0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y_0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;
        &lt;span class="n"&gt;power&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;y_0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;
        &lt;span class="n"&gt;factor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;H&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;power&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;factor&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;special&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hyp2f1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;power&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pytest&lt;/span&gt;

    &lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;--doctest-modules&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vm"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;numerical_derivative&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1e-4&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Test that derivatives are correct for all classes with defaults&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Tax&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Benefit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;WealthTax&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logspace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;analytical&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;marginal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;numerical&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;numerical_derivative&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numerical&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;analytical&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rtol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Fri, 13 Feb 2026 00:00:00 +0100</pubDate><guid>tag:tommyodland.com,2026-02-13:/articles/2026/skatter-og-bidrag</guid><category>articles</category><category>mathematics</category></item><item><title>Practical Linear Algebra with NumPy</title><link>https://tommyodland.com/articles/2026/practical-linear-algebra-with-numpy</link><description>&lt;p&gt;Whether you&amp;rsquo;re solving differential equations, training neural networks, inferring parameters in a statistical model or solving optimization problems, you&amp;rsquo;ll find linear algebra at the core of almost every routine.
Still, mistakes in the application of linear algebra are all too common.
Even though it&amp;rsquo;s a novice numerical linear algebra error, I&amp;rsquo;ve seen professors&amp;nbsp;solve &lt;span class="math"&gt;\(A \boldsymbol{x} = b\)&lt;/span&gt; as &lt;code&gt;b = np.linalg.inv(A) @ x&lt;/code&gt; and call it a&amp;nbsp;day.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a list of beginner linear algebra mistakes, followed by some tips for writing better and faster numerical code.
Remember to start with a correct implementation before working on&amp;nbsp;speed.&lt;/p&gt;
&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#dont-compare-floats"&gt;Don&amp;rsquo;t compare&amp;nbsp;floats&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#dont-multiply-diagonal-matrices"&gt;Don&amp;rsquo;t multiply diagonal&amp;nbsp;matrices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#dont-add-diagonal-matrices"&gt;Don&amp;rsquo;t add diagonal&amp;nbsp;matrices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#dont-multiply-matrices-in-arbitrary-order"&gt;Don&amp;rsquo;t multiply matrices in arbitrary&amp;nbsp;order&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#dont-compute-a-product-if-you-only-need-the-diagonal"&gt;Don&amp;rsquo;t compute a product if you only need the&amp;nbsp;diagonal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#dont-form-the-inverse-matrix"&gt;Don&amp;rsquo;t form the inverse&amp;nbsp;matrix&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#dont-use-memory-you-dont-need"&gt;Don&amp;rsquo;t use memory you don&amp;rsquo;t&amp;nbsp;need&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#dont-use-pure-python-loops"&gt;Don&amp;rsquo;t use pure Python&amp;nbsp;loops&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#do-use-the-woodbury-identity"&gt;Do use the Woodbury&amp;nbsp;identity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#do-think-about-what-the-meaning-is"&gt;Do think about what the meaning&amp;nbsp;is&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#do-time-everything"&gt;Do time&amp;nbsp;everything&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#do-read-the-documentation"&gt;Do read the&amp;nbsp;documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#do-consider-approximations"&gt;Do consider&amp;nbsp;approximations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#resources-and-further-reading"&gt;Resources and further&amp;nbsp;reading&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h3 id="dont-compare-floats"&gt;Don&amp;rsquo;t compare&amp;nbsp;floats&lt;/h3&gt;
&lt;p&gt;Floats are an approximation of real numbers, and comparing them bit-by-bit is too&amp;nbsp;stringent:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;power&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mf"&gt;13.0&lt;/span&gt;  &lt;span class="c1"&gt;# False&lt;/span&gt;
&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;power&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mf"&gt;13.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# True&lt;/span&gt;

&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;R&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;qr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Q&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;R&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# False&lt;/span&gt;
&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Q&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Use &lt;a href="https://numpy.org/doc/stable/reference/generated/numpy.isclose.html"&gt;&lt;code&gt;numpy.isclose&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://numpy.org/doc/stable/reference/generated/numpy.allclose.html"&gt;&lt;code&gt;numpy.allclose&lt;/code&gt;&lt;/a&gt; in general, and consider &lt;a href="https://numpy.org/doc/stable/reference/generated/numpy.testing.assert_allclose.html"&gt;&lt;code&gt;numpy.testing.assert_allclose&lt;/code&gt;&lt;/a&gt; for&amp;nbsp;testing.&lt;/p&gt;
&lt;h3 id="dont-multiply-diagonal-matrices"&gt;Don&amp;rsquo;t multiply diagonal&amp;nbsp;matrices&lt;/h3&gt;
&lt;p&gt;The&amp;nbsp;multiplication &lt;span class="math"&gt;\(A \operatorname{diag}(\boldsymbol{v})\)&lt;/span&gt; is just a column-wise scaling&amp;nbsp;of &lt;span class="math"&gt;\(A\)&lt;/span&gt; by &lt;span class="math"&gt;\(\boldsymbol{v}\)&lt;/span&gt;.&amp;nbsp;Similarly, &lt;span class="math"&gt;\(\operatorname{diag}(\boldsymbol{v}) A\)&lt;/span&gt; is a row-wise scaling.
Implement the scaling directly instead of multiplying with the diagonal matrix, since using a diagonal matrix mostly does unnecessary work by multiplying the entries&amp;nbsp;of &lt;span class="math"&gt;\(A\)&lt;/span&gt; with&amp;nbsp;zeros.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;numpy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;scipy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sp&lt;/span&gt;

&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 6.3 ms ± 1.01 ms per loop&lt;/span&gt;
&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;  &lt;span class="c1"&gt;# 369 µs ± 35.9 µs per loop&lt;/span&gt;

&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="dont-add-diagonal-matrices"&gt;Don&amp;rsquo;t add diagonal&amp;nbsp;matrices&lt;/h3&gt;
&lt;p&gt;Don&amp;rsquo;t explicitly&amp;nbsp;form &lt;span class="math"&gt;\(I\)&lt;/span&gt; when&amp;nbsp;computing &lt;span class="math"&gt;\(A + I\)&lt;/span&gt;.&amp;nbsp;If &lt;span class="math"&gt;\(A\)&lt;/span&gt; has&amp;nbsp;size &lt;span class="math"&gt;\(n \times n\)&lt;/span&gt;, then you&amp;rsquo;re&amp;nbsp;adding &lt;span class="math"&gt;\(n\)&lt;/span&gt; ones&amp;nbsp;and &lt;span class="math"&gt;\(n^2 - n\)&lt;/span&gt; unnecessary zeroes.
Instead use &lt;a href="https://numpy.org/doc/stable/reference/generated/numpy.fill_diagonal.html"&gt;&lt;code&gt;numpy.fill_diagonal&lt;/code&gt;&lt;/a&gt; and do it like&amp;nbsp;this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;I&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eye&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;I&lt;/span&gt;  &lt;span class="c1"&gt;# 1.07 ms ± 68.2 µs per loop&lt;/span&gt;
&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fill_diagonal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diagonal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 8.66 μs ± 141 ns per loop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This trick can be used to speed up adding arbitrary diagonal matrixes&amp;nbsp;too.&lt;/p&gt;
&lt;h3 id="dont-multiply-matrices-in-arbitrary-order"&gt;Don&amp;rsquo;t multiply matrices in arbitrary&amp;nbsp;order&lt;/h3&gt;
&lt;p&gt;Matrix multiplication is associative, so the&amp;nbsp;expressions &lt;span class="math"&gt;\((AB)\boldsymbol{x}\)&lt;/span&gt; and &lt;span class="math"&gt;\(A(B\boldsymbol{x})\)&lt;/span&gt; evaluate to the same value&amp;mdash;but the computational cost is not the same.
Your can either work out the &lt;a href="https://en.wikipedia.org/wiki/Matrix_chain_multiplication"&gt;optimal ordering&lt;/a&gt; by hand, or let &lt;a href="https://numpy.org/doc/stable/reference/generated/numpy.linalg.multi_dot.html"&gt;&lt;code&gt;numpy.linalg.multi_dot&lt;/code&gt;&lt;/a&gt; do the job for&amp;nbsp;you.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;  &lt;span class="c1"&gt;# 18.8 ms ± 1.11 ms per loop&lt;/span&gt;
&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 378 µs ± 114 µs per loop&lt;/span&gt;
&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;  &lt;span class="c1"&gt;# 19 ms ± 766 µs per loop&lt;/span&gt;
&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;multi_dot&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;  &lt;span class="c1"&gt;# 378 µs ± 110 µs per loop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Consider another example:&amp;nbsp;computing &lt;span class="math"&gt;\(A \boldsymbol{v} \boldsymbol{v}^T A\)&lt;/span&gt;.
Here &lt;a href="https://numpy.org/doc/stable/reference/generated/numpy.linalg.multi_dot.html"&gt;&lt;code&gt;numpy.linalg.multi_dot&lt;/code&gt;&lt;/a&gt; won&amp;rsquo;t be of much help,&amp;nbsp;since &lt;span class="math"&gt;\(\boldsymbol{v}\)&lt;/span&gt; and &lt;span class="math"&gt;\(\boldsymbol{v}^T\)&lt;/span&gt; are the same 1 dimensional array in NumPy.
Should we implement the computation&amp;nbsp;as &lt;span class="math"&gt;\(A (\boldsymbol{v} \boldsymbol{v}^T) A\)&lt;/span&gt; or &lt;span class="math"&gt;\((A \boldsymbol{v}) (\boldsymbol{v}^T A)\)&lt;/span&gt;?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;  &lt;span class="c1"&gt;# 910 ms ± 61.6 ms per loop&lt;/span&gt;
&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 41.5 ms ± 3.95 ms per loop&lt;/span&gt;

&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;In the first approach we perform one outer product and two matrix&amp;nbsp;multiplications.&lt;/li&gt;
&lt;li&gt;In the second approach we perform one outer product and two matrix-vector&amp;nbsp;multiplications.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If &lt;span class="math"&gt;\(A \in \mathbb{R}^{m \times n}\)&lt;/span&gt; and &lt;span class="math"&gt;\(\boldsymbol{v} \in \mathbb{R}^{n}\)&lt;/span&gt;, then computing the matrix-vector&amp;nbsp;multiplication &lt;span class="math"&gt;\(A \boldsymbol{v}\)&lt;/span&gt; uses &lt;span class="math"&gt;\(\mathcal{O}(mn)\)&lt;/span&gt; operations.&amp;nbsp;If &lt;span class="math"&gt;\(A \in \mathbb{R}^{m \times n}\)&lt;/span&gt; and &lt;span class="math"&gt;\(B \in \mathbb{R}^{n \times k}\)&lt;/span&gt;, then computing the matrix-matrix&amp;nbsp;multiplication &lt;span class="math"&gt;\(A B\)&lt;/span&gt; uses &lt;span class="math"&gt;\(\mathcal{O}(mnk)\)&lt;/span&gt; operations.
So a matrix-vector multiplication&amp;nbsp;is &lt;span class="math"&gt;\(\mathcal{O}(k)\)&lt;/span&gt; faster than a matrix-matrix&amp;nbsp;multiplication.&lt;/p&gt;
&lt;h3 id="dont-compute-a-product-if-you-only-need-the-diagonal"&gt;Don&amp;rsquo;t compute a product if you only need the&amp;nbsp;diagonal&lt;/h3&gt;
&lt;p&gt;Consider&amp;nbsp;computing &lt;span class="math"&gt;\(\operatorname{diag}(A B)\)&lt;/span&gt;.
Forming the full&amp;nbsp;product &lt;span class="math"&gt;\(A B\)&lt;/span&gt; only to extract the diagonal is wasteful.
Instead, compute the diagonal of the product&amp;nbsp;as &lt;code&gt;(B.T * A).sum(axis=1)&lt;/code&gt; in&amp;nbsp;NumPy:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 3.55 s ± 151 ms per loop&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 188 ms ± 5.44 ms per loop&lt;/span&gt;

&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="dont-form-the-inverse-matrix"&gt;Don&amp;rsquo;t form the inverse&amp;nbsp;matrix&lt;/h3&gt;
&lt;p&gt;There are several reasons why you &lt;a href="https://www.johndcook.com/blog/2010/01/19/dont-invert-that-matrix/"&gt;don&amp;rsquo;t invert that matrix&lt;/a&gt;: it&amp;rsquo;s slow and it&amp;rsquo;s not numerically stable.
Instead of explicitly&amp;nbsp;solving &lt;span class="math"&gt;\(A\boldsymbol{x} = b\)&lt;/span&gt; by forming and multiplying&amp;nbsp;by &lt;span class="math"&gt;\(A^{-1}\)&lt;/span&gt;, use a&amp;nbsp;solver.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;

&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;  &lt;span class="c1"&gt;# 355 ms ± 52.5 ms per loop&lt;/span&gt;
&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;solve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 86.7 ms ± 8.33 ms per loop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now let&amp;rsquo;s look at a slightly more advanced computation, which combines the idea of not forming inverses with the idea of not computing products in arbitrary order.
We want to compute the&amp;nbsp;product &lt;span class="math"&gt;\(A B^{-1} C\)&lt;/span&gt;,&amp;nbsp;where &lt;span class="math"&gt;\(A \in \mathbb{R}^{m \times n}\)&lt;/span&gt;, &lt;span class="math"&gt;\(B \in \mathbb{R}^{n \times n}\)&lt;/span&gt; and &lt;span class="math"&gt;\(C \in \mathbb{R}^{n \times k}\)&lt;/span&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If &lt;span class="math"&gt;\(m &amp;gt; k\)&lt;/span&gt;, we should&amp;nbsp;compute &lt;span class="math"&gt;\(A (B^{-1} C)\)&lt;/span&gt;, which&amp;nbsp;uses &lt;span class="math"&gt;\(\mathcal{O}( kn(m+n) )\)&lt;/span&gt; floating point&amp;nbsp;operations.&lt;/li&gt;
&lt;li&gt;If &lt;span class="math"&gt;\(k &amp;gt; m\)&lt;/span&gt;, we should&amp;nbsp;compute &lt;span class="math"&gt;\((A B^{-1}) C\)&lt;/span&gt;, which&amp;nbsp;uses &lt;span class="math"&gt;\(\mathcal{O}( mn(n +k) )\)&lt;/span&gt; floating point&amp;nbsp;operations.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Below we solve the case&amp;nbsp;when &lt;span class="math"&gt;\(k \gg m\)&lt;/span&gt; without forming the&amp;nbsp;inverse.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100_000&lt;/span&gt;

&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;C&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;  &lt;span class="c1"&gt;# 233 ms ± 56.4 ms per loop&lt;/span&gt;
&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 2.32 s ± 145 ms per loop&lt;/span&gt;

&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;solve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;  &lt;span class="c1"&gt;# 157 ms ± 7.29 ms per loop&lt;/span&gt;
&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;solve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 2.71 s ± 174 ms per loop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="dont-use-memory-you-dont-need"&gt;Don&amp;rsquo;t use memory you don&amp;rsquo;t&amp;nbsp;need&lt;/h3&gt;
&lt;p&gt;If you can, use inplace operations to avoid filling up the memory with unnecessary&amp;nbsp;arrays:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;  &lt;span class="c1"&gt;# Mutate v without creating any new arrays&lt;/span&gt;
&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;  &lt;span class="c1"&gt;# Create a new array (v - 1) in memory, assign it to v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Inplace operations are also typically a bit faster.&amp;nbsp;Win-win.&lt;/p&gt;
&lt;h3 id="dont-use-pure-python-loops"&gt;Don&amp;rsquo;t use pure Python&amp;nbsp;loops&lt;/h3&gt;
&lt;p&gt;Avoid pure Python loops in numerical code.
If the obvious approach is to loop, first try to vectorize the computation.
If that fails, look into &lt;a href="https://numba.pydata.org/"&gt;Numba&lt;/a&gt; or &lt;a href="https://cython.org/"&gt;Cython&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s an example.
The code below computes the empirical cross-covariance between two data sets.
Each variable is a column, each row is a set of&amp;nbsp;observations.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;covariance_loop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;cov&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zeros&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
        &lt;span class="n"&gt;cov&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;:],&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;:])&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cov&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;covariance_loop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 3.62 s ± 241 ms per loop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The code above computes an outer product plus a matrix-matrix addition.
This is done for each&amp;nbsp;row.&lt;/p&gt;
&lt;p&gt;Although the asymptotic complexity is the same, the code below is much faster because it eliminates the Python loop and expresses the computation as a single matrix-matrix product&amp;nbsp;instead.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;covariance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;covariance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 48.3 ms ± 8.75 ms per loop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="do-use-the-woodbury-identity"&gt;Do use the Woodbury&amp;nbsp;identity&lt;/h3&gt;
&lt;p&gt;Learn to use the &lt;a href="https://en.wikipedia.org/wiki/Woodbury_matrix_identity"&gt;Woodbury matrix identity&lt;/a&gt; and related formulas.
The Woodbury equation&amp;nbsp;is:&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation}
\left(A + UCV \right)^{-1} = A^{-1} - A^{-1}U \left(C^{-1} + VA^{-1}U \right)^{-1} VA^{-1}
\end{equation}&lt;/div&gt;
&lt;p&gt;If &lt;span class="math"&gt;\(P\)&lt;/span&gt; and &lt;span class="math"&gt;\(R\)&lt;/span&gt; are positive definite, then the following&amp;nbsp;holds:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation}
\left(P^{-1} + B^T R^{-1} B \right)^{-1} B^T R^{-1} = P B^T (B P^T B + R)^{-1}
\end{equation}&lt;/div&gt;
&lt;p&gt;A typical use case is when we have&amp;nbsp;inverted &lt;span class="math"&gt;\(A\)&lt;/span&gt;, and want to compute the inverse of a rank 1 update&amp;nbsp;of &lt;span class="math"&gt;\(A\)&lt;/span&gt;.
In other words we wish to&amp;nbsp;compute &lt;span class="math"&gt;\(\left(A + \boldsymbol{v}\boldsymbol{v}^T \right)^{-1}\)&lt;/span&gt;, and we already&amp;nbsp;have &lt;span class="math"&gt;\(A^{-1}\)&lt;/span&gt;.
This is a special case of the Woodbury matrix identity, called the &lt;a href="https://en.wikipedia.org/wiki/Sherman%E2%80%93Morrison_formula"&gt;Sherman–Morrison formula&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;A_inv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# 50.2 ms ± 6.32 ms per loop&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;rank1update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A_inv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;A_inv&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A_inv&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;A_inv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;A_inv&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;rank1update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A_inv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 8.61 ms ± 2.9 ms per loop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="do-think-about-what-the-meaning-is"&gt;Do think about what the meaning&amp;nbsp;is&lt;/h3&gt;
&lt;p&gt;Just&amp;nbsp;like &lt;span class="math"&gt;\(A \operatorname{diag}(\boldsymbol{v})\)&lt;/span&gt; simply scales the columns&amp;nbsp;of &lt;span class="math"&gt;\(A\)&lt;/span&gt; with the entries&amp;nbsp;of &lt;span class="math"&gt;\(\boldsymbol{v}\)&lt;/span&gt;, more tangled expressions occasionally have simple interpretations.
Consider the&amp;nbsp;expression &lt;span class="math"&gt;\(A(I - \frac{1}{N}\boldsymbol{1}\boldsymbol{1}^T)\)&lt;/span&gt;.
Looks fancy, right?
It simply subtracts the mean from each row.
Instead of&amp;nbsp;forming &lt;span class="math"&gt;\(I\)&lt;/span&gt;, then&amp;nbsp;forming &lt;span class="math"&gt;\(\boldsymbol{1}\boldsymbol{1}^T\)&lt;/span&gt;, performing the subtraction, multiplying, etc&amp;mdash;simply implement the row scaling&amp;nbsp;directly.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re working in a thousand dimensions, its easy to get lost.
Work out simple examples in three or four dimensions before&amp;nbsp;generalizing.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;factor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eye&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ones&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ones&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;

&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;factor&lt;/span&gt;  &lt;span class="c1"&gt;# 25 ms ± 9.58 ms per loop&lt;/span&gt;
&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;keepdims&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 1.61 ms ± 60.4 µs per loop&lt;/span&gt;

&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;factor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;keepdims&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here&amp;rsquo;s another example from the wild.
I once saw someone&amp;nbsp;implement&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5_000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cholesky&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# 724 ms ± 99.9 ms per loop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;because that&amp;rsquo;s what a paper said.
But the Cholesky decomposition of a diagonal matrix is just the square of the diagonal, so the entire factorization is a waste of time&amp;mdash;it does nothing at&amp;nbsp;all.&lt;/p&gt;
&lt;h3 id="do-time-everything"&gt;Do time&amp;nbsp;everything&lt;/h3&gt;
&lt;p&gt;We&amp;rsquo;ve been using the ubiquitous &lt;a href="https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-timeit"&gt;&lt;code&gt;%timeit&lt;/code&gt;&lt;/a&gt; Ipython magic command in almost every example so far.
If you&amp;rsquo;re in doubt about whether one approach or the other is better, then implement both and time&amp;nbsp;them.&lt;/p&gt;
&lt;p&gt;Consider solving the &lt;a href="https://en.wikipedia.org/wiki/Ridge_regression"&gt;Ridge regression&lt;/a&gt; optimization&amp;nbsp;problem 
&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation}
\underset{\boldsymbol{\beta}}{\operatorname{minimize}} \quad \lVert X \boldsymbol{\beta} - \boldsymbol{y} \rVert^2_W + \lVert \boldsymbol{\beta}  \rVert^2,
\end{equation}&lt;/div&gt;
&lt;p&gt;
where &lt;span class="math"&gt;\(\lVert \boldsymbol{x}  \rVert^2_W = \boldsymbol{x}^T W \boldsymbol{x}\)&lt;/span&gt; denotes the weighted squared norm.
We assume&amp;nbsp;that &lt;span class="math"&gt;\(W\)&lt;/span&gt; is a diagonal matrix with entries given by a&amp;nbsp;vector &lt;span class="math"&gt;\(\boldsymbol{w}\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;We will examine three approaches to solving this&amp;nbsp;problem.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The first approach&lt;/strong&gt; is to differentiate the objective function and set it equal to zero.
We obtain the &lt;em&gt;&lt;a href="https://en.wikipedia.org/wiki/Ordinary_least_squares#Normal_equations"&gt;normal&amp;nbsp;equations&lt;/a&gt;&lt;/em&gt; &lt;span class="math"&gt;\((X^T W X + I) \boldsymbol{\beta} = X^T W \boldsymbol{y}\)&lt;/span&gt;.
Forming the normal equations and solving could look like&amp;nbsp;this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10_000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;solve_normal_eqns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Solve |X @ beta - y|_w^2 + |beta|^2 for beta.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;XT_W&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;  &lt;span class="c1"&gt;# Form X.T @ diag(w)&lt;/span&gt;
    &lt;span class="n"&gt;lhs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;XT_W&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;
    &lt;span class="n"&gt;lhs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flat&lt;/span&gt;&lt;span class="p"&gt;[::&lt;/span&gt; &lt;span class="n"&gt;lhs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;  &lt;span class="c1"&gt;# Add identity&lt;/span&gt;
    &lt;span class="n"&gt;rhs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;XT_W&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;solve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;lhs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;rhs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;assume_a&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pos&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;overwrite_a&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;overwrite_b&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;solve_normal_eqns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 321 ms ± 97.2 ms per loop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The second approach&lt;/strong&gt; is to realize&amp;nbsp;that
&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation}
\lVert X \boldsymbol{\beta} - \boldsymbol{y} \rVert^2_W + \lVert \boldsymbol{\beta}  \rVert^2
= \lVert W^{1/2} X \boldsymbol{\beta} - W^{1/2} \boldsymbol{y} \rVert^2 + \lVert \boldsymbol{\beta}  \rVert^2
= \left\lVert  \begin{bmatrix}
W^{1/2} X \boldsymbol{\beta} - W^{1/2} \boldsymbol{y} \\
\boldsymbol{\beta} 
\end{bmatrix}  \right\rVert^2
= \left\lVert  \begin{bmatrix}
W^{1/2} X \\
I
\end{bmatrix} \boldsymbol{\beta}
-
\begin{bmatrix}
\boldsymbol{y} \\
\boldsymbol{0}
\end{bmatrix}
\right\rVert^2
\end{equation}&lt;/div&gt;
&lt;p&gt;
and make a call to a least squares solver with these matrices&amp;nbsp;directly.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;solve_lstsq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Solve |X @ beta - y|_w^2 + |beta|^2 for beta.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;squared_w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;
    &lt;span class="n"&gt;lhs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vstack&lt;/span&gt;&lt;span class="p"&gt;(((&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;squared_w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eye&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])))&lt;/span&gt;
    &lt;span class="n"&gt;rhs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hstack&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;squared_w&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zeros&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])))&lt;/span&gt;
    &lt;span class="n"&gt;beta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lstsq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;lhs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;rhs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;overwrite_a&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;overwrite_b&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;beta&lt;/span&gt;

&lt;span class="n"&gt;solve_lstlsq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 1.34 s ± 429 ms per loop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The third approach&lt;/strong&gt; uses the singular value decomposition (&lt;span class="caps"&gt;SVD&lt;/span&gt;).&amp;nbsp;Let &lt;span class="math"&gt;\(W^{1/2}X = U D V^T\)&lt;/span&gt;, then the&amp;nbsp;factor &lt;span class="math"&gt;\((X^T W X + I)\)&lt;/span&gt; in the normal equations may be written&amp;nbsp;as
&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation}
(X^T W X + I) = (W^{1/2}X)^T (W^{1/2}X) + I = (U D V^T)^T (U D V^T) + I = VD^2V^T + VV^T = V (D^2 + I) V^T
\end{equation}&lt;/div&gt;
&lt;p&gt;
and &lt;span class="math"&gt;\((X^T W X + I)^{-1} = V^T (D^2 + I)^{-1} V\)&lt;/span&gt;.
Plugging this back into the normal equations, we obtain the solution&amp;nbsp;as
&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation}
\boldsymbol{\beta} = (X^T W X + I)^{-1} X^T W \boldsymbol{y} = V (D^2 + I)^{-1} D U^T W^{1/2} \boldsymbol{y}.
\end{equation}&lt;/div&gt;
&lt;p&gt;
In the implementation, we exploit the fact&amp;nbsp;that &lt;span class="math"&gt;\((D^2 + I)^{-1}\)&lt;/span&gt;, &lt;span class="math"&gt;\(D\)&lt;/span&gt; and &lt;span class="math"&gt;\(W^{1/2}\)&lt;/span&gt; are diagonal&amp;nbsp;matrices:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;solve_svd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Solve |X @ beta - y|_w^2 + |beta|^2 for beta.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;w_sq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;
    &lt;span class="n"&gt;W_sq_X&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;w_sq&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;  &lt;span class="c1"&gt;# Form W^1/2 @ X&lt;/span&gt;
    &lt;span class="n"&gt;U&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;VT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;svd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;W_sq_X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;full_matrices&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;overwrite_a&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;inv_diag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Form (D^2 + I)^-1 @ D&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;multi_dot&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;VT&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;inv_diag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;U&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w_sq&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;

&lt;span class="n"&gt;solve_svd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 2.47 s ± 329 ms per loop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The approaches return the same&amp;nbsp;answer:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;solve_normal_eqns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;solve_lstsq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;solve_normal_eqns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;solve_svd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The third approach using the &lt;span class="caps"&gt;SVD&lt;/span&gt; is what you might find in a paper.
However, &lt;a href="https://github.com/scikit-learn/scikit-learn/blob/7f9bad99d6e0a3e8ddf92a7e5561245224dab102/sklearn/linear_model/_ridge.py#L211C17-L211C17"&gt;sklearn implements approach one&lt;/a&gt;, which is significantly faster.
Under the hood, &lt;a href="https://docs.scipy.org/doc/scipy/reference/generated/scipy.linalg.solve.html"&gt;&lt;code&gt;scipy.linalg.solve&lt;/code&gt;&lt;/a&gt; calls the &lt;span class="caps"&gt;LAPACK&lt;/span&gt; routine &lt;a href="https://netlib.org/lapack/explore-html/de/d80/group__real_p_osolve_gad46400c796afc8031a8c3ec5481af8c3.html"&gt;&lt;code&gt;sposv&lt;/code&gt;&lt;/a&gt;, which uses a Cholesky decomposition.
It&amp;rsquo;s typically instructive to implement a few different approaches, then test them and time them.
We have not discussed the numerical stability of the least-squares problem here, but have a look at the references at the end of this article for more information about that&amp;nbsp;issue.&lt;/p&gt;
&lt;h3 id="do-read-the-documentation"&gt;Do read the&amp;nbsp;documentation&lt;/h3&gt;
&lt;p&gt;Consider again the&amp;nbsp;function &lt;code&gt;solve_normal_eqns(X, w, y)&lt;/code&gt; that we used to solve the normal&amp;nbsp;equations &lt;span class="math"&gt;\((X^T W X + I) \boldsymbol{\beta} = X^T W \boldsymbol{y}\)&lt;/span&gt; with.
It turns out that &lt;a href="https://docs.scipy.org/doc/scipy/reference/generated/scipy.linalg.solve.html"&gt;&lt;code&gt;scipy.linalg.solve(a, b)&lt;/code&gt;&lt;/a&gt; has a&amp;nbsp;parameter &lt;code&gt;lower=False&lt;/code&gt;, and the documentation&amp;nbsp;reads:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If &lt;code&gt;False&lt;/code&gt; (default), the calculation uses only the data in the upper triangle&amp;nbsp;of &lt;code&gt;a&lt;/code&gt;; entries below the diagonal are&amp;nbsp;ignored.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It also turns out that there&amp;rsquo;s a &lt;span class="caps"&gt;BLAS&lt;/span&gt; routine with the cryptic name &lt;a href="https://netlib.org/lapack/explore-html/d1/d54/group__double__blas__level3_gae0ba56279ae3fa27c75fefbc4cc73ddf.html#gae0ba56279ae3fa27c75fefbc4cc73ddf"&gt;&lt;code&gt;dsyrk&lt;/code&gt;&lt;/a&gt; that only computes the upper-triangular part&amp;nbsp;of &lt;span class="math"&gt;\(\alpha A A^T\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;We can improve the speed of our solver by only computing the upper-triangular part&amp;nbsp;of &lt;span class="math"&gt;\(X^T W X + I\)&lt;/span&gt; as&amp;nbsp;follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;solve_normal_eqns2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Solve |X @ beta - y|_w^2 + |beta|^2 for beta.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

    &lt;span class="c1"&gt;# Compute upper triangular entries in X.T @ diag(w) @ X&lt;/span&gt;
    &lt;span class="n"&gt;XT_W_X&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blas&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dsyrk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alpha&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Add identity matrix&lt;/span&gt;
    &lt;span class="n"&gt;XT_W_X&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flat&lt;/span&gt;&lt;span class="p"&gt;[::&lt;/span&gt; &lt;span class="n"&gt;XT_W_X&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;solve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;XT_W_X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;assume_a&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pos&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# Solver only uses upper triangular elements&lt;/span&gt;
        &lt;span class="n"&gt;overwrite_a&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;overwrite_b&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10_000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;solve_normal_eqns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 4.78 s ± 393 ms per loop&lt;/span&gt;
&lt;span class="n"&gt;solve_normal_eqns2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 3.33 s ± 436 ms per loop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you&amp;rsquo;re working with linear algebra, familiarize yourself with all the functions in &lt;a href="https://docs.scipy.org/doc/scipy/reference/linalg.html"&gt;scipy.linalg&lt;/a&gt;.
When using a function, read carefully through the arguments.
If speed and accuracy truly matters, look into how functions are implemented and research the specific problem you&amp;rsquo;re working on&amp;nbsp;carefully.&lt;/p&gt;
&lt;h3 id="do-consider-approximations"&gt;Do consider&amp;nbsp;approximations&lt;/h3&gt;
&lt;p&gt;Sometimes we don&amp;rsquo;t need exact answers.
For instance, we can approximately solve the Ridge regression problem by running five Conjugate Gradient iterations on the normal&amp;nbsp;equations.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10_000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;XT_W&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;matvec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Implements (X.T @ diag(v) @ X + I) @ v&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;multi_dot&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;XT_W&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;


&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sparse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LinearOperator&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="n"&gt;matvec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;matvec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 56.9 ms ± 13 ms per loop&lt;/span&gt;
&lt;span class="n"&gt;approx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sparse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;XT_W&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;maxiter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 295 ms ± 55.1 ms per loop&lt;/span&gt;
&lt;span class="n"&gt;exact&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;solve_normal_eqns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The Pearson correlation between the exact and approximate answer is&amp;nbsp;approximately &lt;span class="math"&gt;\(0.998\)&lt;/span&gt;.
What we gain in speed might make up for the loss of accuracy, but this is highly problem dependent.
An approximate singular value decomposition can be obtained using &lt;a href="https://scikit-learn.org/stable/modules/generated/sklearn.utils.extmath.randomized_svd.html"&gt;&lt;code&gt;sklearn.utils.extmath.randomized_svd&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="resources-and-further-reading"&gt;Resources and further&amp;nbsp;reading&lt;/h2&gt;
&lt;p&gt;Three excellent&amp;nbsp;books:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com/Numerical-Linear-Algebra-Lloyd-Trefethen/dp/0898713617"&gt;Numerical Linear Algebra&lt;/a&gt; by Trefethen et al&amp;nbsp;(1997).&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com/Computations-Hopkins-Studies-Mathematical-Sciences/dp/1421407949"&gt;Matrix Computations&lt;/a&gt; by Golub et al&amp;nbsp;(2013).&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com/Accuracy-Stability-Numerical-Algorithms-Nicholas/dp/0898715210/"&gt;Accuracy and Stability of Numerical Algorithms&lt;/a&gt; by Nicholas J. Higham&amp;nbsp;(2002).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Two reference&amp;nbsp;manuals:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.ee.ic.ac.uk/hp/staff/dmb/matrix/intro.html"&gt;The Matrix Reference&amp;nbsp;Manual&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.math.uwaterloo.ca/~hwolkowi/matrixcookbook.pdf"&gt;The Matrix&amp;nbsp;Cookbook&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;An open-source book on&amp;nbsp;vectorization:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.labri.fr/perso/nrougier/from-python-to-numpy/"&gt;From Python to&amp;nbsp;Numpy&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Fri, 30 Jan 2026 00:00:00 +0100</pubDate><guid>tag:tommyodland.com,2026-01-30:/articles/2026/practical-linear-algebra-with-numpy</guid><category>articles</category><category>mathematics</category><category>algorithms</category></item><item><title>Books</title><link>https://tommyodland.com/articles/2025/books</link><description>&lt;p&gt;Here&amp;rsquo;s an inspirational, and perhaps true, quote by &lt;a href="https://youtu.be/U41e7hKAAPQ?t=5710"&gt;Stephen Boyd&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When you end up being trained in just a handful of areas, you can be unbelievably effective across fifty fields.
If you know linear algebra, optimization, probability and statistics, and computer science &amp;ndash; those are the topics.
You don&amp;rsquo;t have to know anything&amp;nbsp;else.&lt;/p&gt;
&lt;p&gt;If you know those things, and really know them, and if you&amp;rsquo;ve seen a bunch of applications across different fields, you are so valuable it&amp;rsquo;s crazy.
You can be hired instantly into a hundred fields.
But here&amp;rsquo;s the even cooler thing, and this is the Silicon Valley part of it: you can create new fields that don&amp;rsquo;t even exist.
Please train yourself broadly.
Learn these&amp;nbsp;things.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;Below is a reading list for aspiring data scientists and others interested in mathematical modeling, statistics, optimization, scientific programming,&amp;nbsp;etc.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Only my favorite books are included. Books I would&amp;nbsp;recommend.&lt;/li&gt;
&lt;li&gt;Only general topics useful to many practitioners are included. Category theory, game theory and geostatistics is cool &amp;ndash; but not &lt;em&gt;that&lt;/em&gt; useful to the average&amp;nbsp;practitioner.&lt;/li&gt;
&lt;li&gt;I have read the majority of the material, but not&amp;nbsp;everything.&lt;/li&gt;
&lt;li&gt;The resources are sorted by difficulty within reach category, but only&amp;nbsp;roughly.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The nomenclature&amp;nbsp;is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;📗&amp;nbsp;Book.&lt;/li&gt;
&lt;li&gt;📄&amp;nbsp;Paper.&lt;/li&gt;
&lt;li&gt;📺 Video&amp;nbsp;lecture(s).&lt;/li&gt;
&lt;li&gt;🤖 Book contains many code&amp;nbsp;examples.&lt;/li&gt;
&lt;li&gt;⭐ Life&amp;nbsp;changing.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="mathematics"&gt;Mathematics&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Introduction-Applied-Linear-Algebra-Matrices/dp/1316518965"&gt;Introduction to Applied Linear Algebra: Vectors, Matrices, and Least Squares&lt;/a&gt; by Boyd et al&amp;nbsp;(2018).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Advanced-Engineering-Mathematics-Michael-Greenberg/dp/0133214311"&gt;Advanced Engineering Mathematics&lt;/a&gt; by Michael Greenberg&amp;nbsp;(1998).&lt;/li&gt;
&lt;li&gt;📗 ⭐ &lt;a href="https://www.amazon.com/Introduction-Applied-Mathematics-Gilbert-Strang/dp/0961408804"&gt;Introduction to Applied Mathematics&lt;/a&gt; by Gilbert Strang&amp;nbsp;(1986). &lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Princeton-Companion-Mathematics-Timothy-Gowers/dp/0691118809"&gt;The Princeton Companion to Mathematics&lt;/a&gt; by Gowers et al&amp;nbsp;(2008).&lt;/li&gt;
&lt;li&gt;📄 &lt;a href="https://www.cs.cmu.edu/~quake-papers/painless-conjugate-gradient.pdf"&gt;An Introduction to the Conjugate Gradient Method Without the Agonizing Pain&lt;/a&gt; by Jonathan Richard Shewchuk&amp;nbsp;(1994).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="scientific-computing"&gt;Scientific&amp;nbsp;computing&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Numerical-Methods-Scientists-Engineers-Mathematics/dp/0486652416/"&gt;Numerical Methods for Scientists and Engineers&lt;/a&gt; by &lt;span class="caps"&gt;R. W.&lt;/span&gt; Hamming&amp;nbsp;(1987).&lt;/li&gt;
&lt;li&gt;📗 🤖 &lt;a href="https://www.labri.fr/perso/nrougier/from-python-to-numpy/"&gt;From Python to Numpy&lt;/a&gt; by Nicolas P. Rougier&amp;nbsp;(2017).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Numerical-Linear-Algebra-Lloyd-Trefethen/dp/0898713617/"&gt;Numerical Linear Algebra&lt;/a&gt; by Trefethen et al&amp;nbsp;(1997).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Computations-Hopkins-Studies-Mathematical-Sciences/dp/1421407949/"&gt;Matrix Computations&lt;/a&gt; by Golub et al&amp;nbsp;(2013).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="python-and-programming"&gt;Python and&amp;nbsp;programming&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Pragmatic-Programmer-Journeyman-Master/dp/020161622X"&gt;The Pragmatic Programmer&lt;/a&gt; by Hunt et al&amp;nbsp;(2019).&lt;/li&gt;
&lt;li&gt;📗 🤖 &lt;a href="https://www.amazon.com/Fluent-Python-Concise-Effective-Programming/dp/1491946008/"&gt;Fluent Python: Clear, Concise, and Effective Programming&lt;/a&gt; by Luciano Ramalho&amp;nbsp;(2015).&lt;/li&gt;
&lt;li&gt;📗 🤖 &lt;a href="https://www.amazon.com/Effective-Python-Specific-Software-Development/dp/0134853989/"&gt;Effective Python: 90 Specific Ways to Write Better Python&lt;/a&gt; by Brett Slatkin&amp;nbsp;(2019).&lt;/li&gt;
&lt;li&gt;📺 &lt;a href="https://www.youtube.com/watch?v=cKPlPJyQrt4"&gt;So you want to be a Python expert?&lt;/a&gt; by James Powell&amp;nbsp;(2017).&lt;/li&gt;
&lt;li&gt;📺 &lt;a href="https://www.youtube.com/watch?v=OSGv2VnC0go"&gt;Transforming Code into Beautiful, Idiomatic Python&lt;/a&gt; by Raymond Hettinger&amp;nbsp;(2013).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="machine-learning"&gt;Machine&amp;nbsp;learning&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Artificial-Intelligence-Modern-Approach-3rd/dp/0136042597/"&gt;Artificial Intelligence: A Modern Approach&lt;/a&gt; by Norvig et al&amp;nbsp;(2009).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Pattern-Classification-Pt-1-Richard-Duda/dp/0471056693/"&gt;Pattern Classification&lt;/a&gt; by Duda et al&amp;nbsp;(2000).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Pattern-Recognition-Learning-Information-Statistics/dp/0387310738/"&gt;Pattern Recognition and Machine Learning&lt;/a&gt; by Christopher M. Bishop&amp;nbsp;(2006).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Elements-Statistical-Learning-Prediction-Statistics/dp/0387848576/"&gt;The Elements of Statistical Learning&lt;/a&gt; by Hastie et al&amp;nbsp;(2016).&lt;/li&gt;
&lt;li&gt;📄 &lt;a href="https://sites.astro.caltech.edu/~george/ay122/cacm12.pdf"&gt;A Few Useful Things to Know about Machine Learning&lt;/a&gt; by Pedro Domingos&amp;nbsp;(2012).&lt;/li&gt;
&lt;li&gt;📄 &lt;a href="https://arxiv.org/abs/1811.10154"&gt;Stop Explaining Black Box Machine Learning Models for High Stakes Decisions and Use Interpretable Models Instead&lt;/a&gt; by Cynthia Rudin&amp;nbsp;(2018).&lt;/li&gt;
&lt;li&gt;📄 &lt;a href="https://research.google/pubs/pub43146/"&gt;Machine Learning: The High Interest Credit Card of Technical Debt&lt;/a&gt; by Sculley et al&amp;nbsp;(2014).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="deep-learning-etc"&gt;Deep learning&amp;nbsp;etc.&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;📗 🤖 &lt;a href="https://www.amazon.com/Hands-Machine-Learning-Scikit-Learn-TensorFlow/dp/1098125975/"&gt;Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow&lt;/a&gt; by Aurélien Géron&amp;nbsp;(2022).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Reinforcement-Learning-Introduction-Adaptive-Computation/dp/0262039249/"&gt;Reinforcement Learning&lt;/a&gt; by Sutton&amp;nbsp;(2018).&lt;/li&gt;
&lt;li&gt;📺 &lt;a href="https://www.youtube.com/watch?v=2pWv7GOvuf0&amp;amp;list=PLqYmG7hTraZDM-OYHWgPebj2MfCFzFObQ"&gt;DeepMind x &lt;span class="caps"&gt;UCL&lt;/span&gt; | Reinforcement Learning Course&lt;/a&gt; by David Silver&amp;nbsp;(2015).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="statistics"&gt;Statistics&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Statistical-Inference-George-Casella/dp/8131503941/"&gt;Statistical Inference&lt;/a&gt; by Casella et al&amp;nbsp;(2001).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Regression-Stories-Analytical-Methods-Research/dp/1107676517/"&gt;Regression and Other Stories&lt;/a&gt; by Gelman et al&amp;nbsp;(2020).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Analysis-Regression-Multilevel-Hierarchical-Analytical-ebook/dp/B01LYX8AKU/"&gt;Data Analysis Using Regression and Hierarchical Models&lt;/a&gt; by Gelman et al&amp;nbsp;(2006).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/All-Statistics-Statistical-Inference-Springer/dp/1441923225/"&gt;All of Statistics&lt;/a&gt; by Larry Wasserman&amp;nbsp;(2010).&lt;/li&gt;
&lt;li&gt;📗 🤖 ⭐ &lt;a href="https://www.amazon.com/Statistical-Rethinking-Bayesian-Examples-Chapman/dp/036713991X/"&gt;Statistical Rethinking&lt;/a&gt; by Richard McElreath&amp;nbsp;(2020).&lt;/li&gt;
&lt;li&gt;📺 &lt;a href="https://www.youtube.com/watch?v=BYUykHScxj8&amp;amp;list=PLDcUM9US4XdMROZ57-OIRtIK0aOynbgZN"&gt;Statistical Rethinking&lt;/a&gt; by Richard McElreath&amp;nbsp;(2022).&lt;/li&gt;
&lt;li&gt;📄 &lt;a href="https://www.semanticscholar.org/paper/The-garden-of-forking-paths-%3A-Why-multiple-can-be-a-Gelman-Loken/b63e25900013605c16f4ad74c636cfbd8e9a3e8e"&gt;The garden of forking paths&lt;/a&gt; by Gelman et al&amp;nbsp;(2019).&lt;/li&gt;
&lt;li&gt;📄 &lt;a href="https://projecteuclid.org/journals/statistical-science/volume-16/issue-3/Statistical-Modeling--The-Two-Cultures-with-comments-and-a/10.1214/ss/1009213726.full"&gt;Statistical Modeling: The Two Cultures&lt;/a&gt; by Leo Breiman&amp;nbsp;(2001).&lt;/li&gt;
&lt;li&gt;📄 &lt;a href="https://peerj.com/articles/6876/"&gt;Hierarchical generalized additive models in ecology&lt;/a&gt; by Pedersen et al&amp;nbsp;(2019).&lt;/li&gt;
&lt;li&gt;📄 &lt;a href="https://arxiv.org/abs/2012.00174"&gt;What are the most important statistical ideas of the past 50 years?&lt;/a&gt; by Gelman et al&amp;nbsp;(2021).&lt;/li&gt;
&lt;li&gt;📄 &lt;a href="https://arxiv.org/abs/1701.02434"&gt;A Conceptual Introduction to Hamiltonian Monte Carlo&lt;/a&gt; by Michael Betancourt&amp;nbsp;(2018).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="optimization"&gt;Optimization&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Basics-Practical-Optimization-Second/dp/1611977363/"&gt;The Basics of Practical Optimization&lt;/a&gt; by Adam B. Levy&amp;nbsp;(2022).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Optimization-Modelling-Ruhul-Amin-Sarker-ebook/dp/B008KZ6MK4/"&gt;Optimization Modelling: A Practical Approach&lt;/a&gt; by Sarker et al&amp;nbsp;(2007).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Essentials-Metaheuristics-Second-Sean-Luke/dp/1300549629/"&gt;Essentials of Metaheuristics&lt;/a&gt; by Sean Luke&amp;nbsp;(2012).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Linear-Programming-Foundations-Extensions-International/dp/3030394174/"&gt;Linear Programming: Foundations and Extensions&lt;/a&gt; by Robert J. Vanderbei&amp;nbsp;(2021).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Convex-Optimization-Corrections-2008-Stephen/dp/0521833787/"&gt;Convex Optimization&lt;/a&gt; by Boyd et al&amp;nbsp;(2004).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Numerical-Optimization-Operations-Financial-Engineering/dp/0387303030/"&gt;Numerical Optimization&lt;/a&gt; by Nocedal et al&amp;nbsp;(2006).&lt;/li&gt;
&lt;li&gt;📄 &lt;a href="https://pubsonline.informs.org/doi/10.1287/ited.7.2.153"&gt;Formulating Integer Linear Programs: A Rogues&amp;rsquo; Gallery&lt;/a&gt; by Brown et al&amp;nbsp;(2007).&lt;/li&gt;
&lt;li&gt;📄 &lt;a href="https://pubsonline.informs.org/doi/abs/10.1287/inte.2019.1015?journalCode=ijaa"&gt;Bus Routing Optimization Helps Boston Public Schools Design Better Policies&lt;/a&gt; by Bertsimas et al&amp;nbsp;(2020).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="algorithms"&gt;Algorithms&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Algorithm-Design-Manual-Computer-Science/dp/3030542580/"&gt;The Algorithm Design Manual&lt;/a&gt; by Steven S. Skiena&amp;nbsp;(2021).&lt;/li&gt;
&lt;li&gt;📗 ⭐ &lt;a href="https://www.amazon.com/Algorithms-Sanjoy-Dasgupta/dp/0073523402/"&gt;Algorithms&lt;/a&gt; by Dasgupta et al&amp;nbsp;(2006).&lt;/li&gt;
&lt;li&gt;📗 🤖 &lt;a href="https://cpbook.net/"&gt;Competitive Programming&lt;/a&gt; by Halim et&amp;nbsp;al.&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Introduction-Algorithms-fourth-Thomas-Cormen/dp/026204630X/"&gt;Introduction to Algorithms&lt;/a&gt; by Cormen et al&amp;nbsp;(2022).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Network-Flows-Theory-Algorithms-Applications/dp/013617549X/"&gt;Network Flows: Theory, Algorithms, and Applications&lt;/a&gt; by Ahuja et al&amp;nbsp;(1993).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="popular-science"&gt;Popular&amp;nbsp;science&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Flaws-Fallacies-Statistical-Thinking-Mathematics-ebook/dp/B00A62Y1X4/"&gt;Flaws and Fallacies in Statistical Thinking&lt;/a&gt; by Stephen K. Campbell&amp;nbsp;(2012).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Innumeracy-Mathematical-Illiteracy-Its-Consequences/dp/0809058405/"&gt;Innumeracy: Mathematical Illiteracy and Its Consequences&lt;/a&gt; by  John Allen Paulos&amp;nbsp;(2001).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Science-Fictions-Negligence-Undermine-Search/dp/1250222699"&gt;Science Fictions&lt;/a&gt; by Stuart Ritchie&amp;nbsp;(2020). &lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Concepts-Modern-Mathematics-Dover-Books/dp/0486284247/"&gt;Concepts of Modern Mathematics&lt;/a&gt; by Ian Stewart&amp;nbsp;(1995).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Euler-Master-Dolciani-Mathematical-Expositions/dp/0883853280"&gt;Euler: The Master of Us All&lt;/a&gt; by William Dunham&amp;nbsp;(1999).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Book-Why-Science-Cause-Effect-ebook/dp/B075CR9QBJ/"&gt;The Book of Why&lt;/a&gt; by Judea Pearl et al&amp;nbsp;(2018).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="miscellaneous"&gt;Miscellaneous&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Visual-Display-Quantitative-Information/dp/0961392142/"&gt;The Visual Display of Quantitative Information&lt;/a&gt; by Edward R. Tufte&amp;nbsp;(2001).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Writing-Well-Classic-Guide-Nonfiction/dp/0060891548/"&gt;On Writing Well: The Classic Guide to Writing Nonfiction&lt;/a&gt; by William Zinsser&amp;nbsp;(2016).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Handbook-Writing-Mathematical-Sciences-Nicholas/dp/161197609X/"&gt;Handbook of Writing for the Mathematical Sciences&lt;/a&gt; by Nicholas J. Higham&amp;nbsp;(2019). &lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/How-Solve-Mathematical-Princeton-Science/dp/069116407X/"&gt;How to Solve It&lt;/a&gt; by Polya et al&amp;nbsp;(2014).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.math.uwaterloo.ca/~hwolkowi/matrixcookbook.pdf"&gt;The Matrix Cookbook&lt;/a&gt; by Pedersen et al&amp;nbsp;(2012).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Opt-Art-Mathematical-Optimization-Visual/dp/0691164061"&gt;Opt Art: From Mathematical Optimization to Visual Design&lt;/a&gt; by Robert Bosch&amp;nbsp;(2019).&lt;/li&gt;
&lt;li&gt;📄 &lt;a href="https://www.jstor.org/stable/3132114"&gt;School Choice: A Mechanism Design Approach&lt;/a&gt; by Abdulkadiroğlu et al&amp;nbsp;(2003).&lt;/li&gt;
&lt;/ul&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Mon, 15 Dec 2025 00:00:00 +0100</pubDate><guid>tag:tommyodland.com,2025-12-15:/articles/2025/books</guid><category>articles</category><category>datascience</category></item><item><title>Fastlegebytter</title><link>https://tommyodland.com/articles/2025/fastlegebytter</link><description>&lt;p&gt;&lt;strong&gt;Oppdatering&lt;/strong&gt;: To dager etter at jeg skrev denne artikkelen ble jeg informert om innlegget &amp;ldquo;&lt;a href="https://www.dn.no/innlegg/helse/leger/fastleger/enkelt-a-forbedre-fastlegeordningen-med-en-algoritme/2-1-1692543"&gt;Enkelt å forbedre fastlegeordningen - med en algoritme&lt;/a&gt;&amp;rdquo; i Dagens Næringsliv.
Forfatterne argumenterer for mye av det samme som det jeg gjør nedenfor.
Innlegget deres er veldig godt skrevet, og de har også skrevet en 80-siders forskningsartikkel &amp;ldquo;&lt;a href="https://www.nber.org/papers/w32458"&gt;Designing Dynamic Reassignment Mechanisms: Evidence from &lt;span class="caps"&gt;GP&lt;/span&gt; Allocation&lt;/a&gt;&amp;rdquo;.
De beregner at 15 % (ca. 45 000 mennesker) kunne sluppet venteliste ved å endre på&amp;nbsp;algoritmen!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Det er ikke uvanlig å vente i årevis på &lt;a href="https://www.helsenorge.no/nn/bytte-fastlege/om/"&gt;fastlegebytte&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I 2024 kunne vi lese at &lt;a href="https://www.regjeringen.no/no/aktuelt/flere-fastleger/id3024659/"&gt;over 300 000 står på venteliste&lt;/a&gt;.
På &lt;a href="https://www.helsenorge.no/"&gt;helsenorge.no&lt;/a&gt; er det 608 fastleger registrert i Oslo fylke, og 60 % av disse har fulle lister.
En lege har typisk plass til 1 300 pasienter, og det er 816 000 plasser i Oslo fylke totalt.
Kun 4,5 % av disse plassene er ledige.
Situasjonen er enda verre andre steder i landet.
I 2022 var det &lt;a href="https://www.legelisten.no/blogg/100-lange-ventetider-for-a-bytte-fastlege"&gt;ille i Rogaland&lt;/a&gt;, der kun 3 % av fastlegene hadde ledige plasser.
I Bergen og Trondheim var det ikke en eneste ledig&amp;nbsp;plass.&lt;/p&gt;
&lt;p&gt;Et stort problem med fulle lister er at &lt;strong&gt;dagens ordning ikke tillater bytter mellom pasienter på fulle lister. Dette er ineffektivt fordi det låser markedet.&lt;/strong&gt;
I denne artikkelen skal jeg forklare dette utsagnet og foreslå en løsning på&amp;nbsp;problemet.&lt;/p&gt;
&lt;h2 id="bytter-mellom-fulle-lister"&gt;Bytter mellom fulle&amp;nbsp;lister&lt;/h2&gt;
&lt;p&gt;Her er et eksempel på en situasjon der vi kunne kuttet ventetiden drastisk.
Ola er på lista til lege A.
Kari er på lista til lege B.
Ola ønsker å bytte fra lege A til lege B.
Kari ønsker å bytte motsatt vei; fra lege B til lege&amp;nbsp;A.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 95%; max-width: 700px;"
src="https://tommyodland.com/images/articles/fastlegebytter/fastlegebytte_fulle_lister.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;I dag må noen forlate listen til én av legene før Ola, Kari eller Per får innvilget søknad om fastlegebytte.
Men Ola og Kari hadde ikke trengt å vente (potensielt i årevis) - &lt;strong&gt;de kunne ha byttet plass i dag&lt;/strong&gt;!
Vi trenger bare en algoritme som leter etter sykluser i bytte-grafen og gjennomfører&amp;nbsp;byttene.&lt;/p&gt;
&lt;p&gt;Er du bekymret for Per i figuren ovenfor?
Den nye algoritmen er bedre for både Ola og Kari, men Per har potensielt stått i kø lengre enn Ola.
Kan Per havne i en situasjon der han må vente lengre enn med dagens algoritme?
Svaret er ja - det er mulig å konstruere et slikt&amp;nbsp;eksempel:&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 95%; max-width: 700px;"
src="https://tommyodland.com/images/articles/fastlegebytter/fastlegebytte_fulle_lister_rekkefølge.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Anta at pasientene melder ønske om bytte i rekkefølgen Kari, Per og Ola.
Vi står i en fastlåst situasjon som vist i figuren ovenfor, der ingen får bytte fordi det ikke er ledige plasser.
Om noen dager vil lege A få en ledig plass, men det vet vi ikke&amp;nbsp;enda.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Dagens algoritme.&lt;/strong&gt; Dersom vi ikke kjører syklus-algoritmen er situasjonen fastlåst frem til lege A får en ledig plass. Da får Kari sitt ønske innvilget ettefulgt av Per, mens Ola står fremdeles i&amp;nbsp;kø.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Foreslått algoritme.&lt;/strong&gt; Dersom vi kjører syklus-algoritmen bytter Ola og Kari umiddelbart plass. Når lege A får en ledig plass påvirker det ikke Per. Han står fremdeles i&amp;nbsp;kø.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Per må vente til lege B får en ledig plass som ikke umiddelbart fylles fordi den er del av en bytte-syklus.
Oppsummert kan det å gjennomføre sykluser-bytter mellom fulle lister fjerne (potensielt lange) stier i grafen som ville innfridd flere&amp;nbsp;ønsker.&lt;/p&gt;
&lt;p&gt;Er dette eksempelet et godt argument mot den foreslåtten algoritmen?
Jeg mener at det ikke er det.
Per er kanskje misfornøyd dersom virkeligheten utspiller seg som ovenfor, men langt flere er misfornøyde med dagens regime.
Realiteten er at ingen på venteliste vet om de er en del av en syklus (slik som Ola og Kari) eller ikke (slik som Per), og mange bytter ødelegger ikke for personer som Per.
Markedet er i dag helt låst i byer som Bergen, der kun 3 av 274 fastleger har ledige plasser.
I slike situasjoner ville syklus-bytter betydd mye for&amp;nbsp;pasientene.&lt;/p&gt;
&lt;h2 id="lengre-sykluser"&gt;Lengre&amp;nbsp;sykluser&lt;/h2&gt;
&lt;p&gt;Ovenfor byttet vi plass på to personer: Ola og Kari.
Vi kan bytte plass på et vilkårlig antall personer så lenge de lager en syklus i&amp;nbsp;bytte-grafen.&lt;/p&gt;
&lt;p&gt;Av og til må vi ta et valg, fordi én persons bytteønske kan brukes i flere sykluser, slik som i figuren&amp;nbsp;nedenfor.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 95%; max-width: 350px;"
src="https://tommyodland.com/images/articles/fastlegebytter/fastlegebytte_cycle.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;I slike situasjoner kan vi enten velge den lengste syklusen (grønn), eller vi kan velge den syklusen der pasientene har ventet lengst på å få bytte.
Det er et verdispørsmål, ikke et matematisk spørsmål - og uansett hva vi velger blir utfallet bedre enn i dag for de aller&amp;nbsp;fleste.&lt;/p&gt;
&lt;p&gt;Et annet valg vi må ta er om vi utfører syklus-bytter ofte (f.eks. hver dag) eller sjeldent (f.eks. hver måned).
Å bytte ofte gir umiddelbar bevegelse i markedet.
Å bytte litt mer sjeldent kan føre til at det bygger seg opp lengre syklusen i grafen som vi kan utnytte.
En siste idé er å tillate mer enn ett ønske: hvorfor ikke la pasientene rangere flere leger som de kunne tenke seg å bytte&amp;nbsp;til?&lt;/p&gt;
&lt;h2 id="oppsummering-og-referanser"&gt;Oppsummering og&amp;nbsp;referanser&lt;/h2&gt;
&lt;p&gt;På tross av målsetninger om å digitalisere og effektivisere helsesektoren, er det fremdeles noen frukter som henger så lavt at de nesten tar i bakken.
Syklus-bytter hjelper ikke de som er villige til å stå i kø i fem år for å være pastient hos den beste legen i byen.
Men det hjelper når Ola flytter fra en bydel til en annen og vil bytte lege, mens Kari flytter motsatt vei.
Algoritmer som baserer seg på å fjerne sykluser har vært kjent siden 70-tallet, da &lt;a href="https://en.wikipedia.org/wiki/Top_trading_cycle"&gt;Top Trading Cycle&lt;/a&gt; (&lt;span class="caps"&gt;TTC&lt;/span&gt;) først ble publisert.
Selv om &lt;span class="caps"&gt;TTC&lt;/span&gt; er Pareto-effektiv, gir den ikke nødvendigvis de lengste syklusene.
&lt;a href="https://en.wikipedia.org/wiki/Optimal_kidney_exchange"&gt;Optimal kidney exchange&lt;/a&gt; handler om å finne de lengste&amp;nbsp;syklusene.&lt;/p&gt;
&lt;p&gt;Boka &lt;a href="https://www.amazon.com/Who-Gets-What-Why-Matchmaking/dp/0544705289"&gt;Who Gets What - and Why: The New Economics of Matchmaking and Market Design&lt;/a&gt; av Alvin Roth er en folkelig introduksjon til markeder uten penger og uten delelige varer.
Fagfeltet heter &lt;em&gt;mekanismedesign&lt;/em&gt;, og er godt kjent gjennom Wikipedia-artikler, bøker og en Nobelpris til Roth i 2012.
På tross av dette er denne kompetansen tilsynelatende lav, både i offentlig og privat sektor.
Dette skrev jeg også om i artikkelen &amp;ldquo;&lt;a href="https://tommyodland.com/files/storage/bedre_skole_nr3_2022_odland_murray.pdf"&gt;Mangelfull evaluering av inntaksmodeller&lt;/a&gt;&amp;rdquo;, der jeg argumenterte mot å bruke politikernes hjemmesnekra modeller til å avgjøre skjebnen til tusenvis at &lt;span class="caps"&gt;VGS&lt;/span&gt;-elever.&lt;/p&gt;
&lt;p&gt;De fleste overvurderer betydningen av sitt eget fagfelt.
Jeg er neppe noe unntak, fordi jeg tror matematikk kan spille en rolle i en effektiv helsesektor.
&lt;span class="caps"&gt;PS&lt;/span&gt;: Les gjerne artikkelen min om &lt;a href="https://tommyodland.com/articles/2024/turnusplaner-for-sykepleiere"&gt;turnusplaner for sykepleiere&lt;/a&gt;!&lt;/p&gt;
&lt;h2 id="korrespondanse-med-norsk-helsenett-sf"&gt;Korrespondanse med Norsk helsenett &lt;span class="caps"&gt;SF&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Jeg kontaktet Norsk helsenett &lt;span class="caps"&gt;SF&lt;/span&gt; (Helsenorge) om problemstillingen.
Ut fra svaret tolker jeg at denne artikkelen fremstiller dagens system&amp;nbsp;riktig.&lt;/p&gt;
&lt;p&gt;For meg virker det som en åpenbar forbedring å tillate bytter, og jeg vet ikke hvorfor Helsenorge ikke gjør det.
Det kan hende de har gode grunner til å beholde dagens system, eller at de har andre prioriteringer, eller at de har &lt;span class="caps"&gt;IT&lt;/span&gt;-systemer som er vanskelige å endre, eller mangler kompetanse til å implementere en slik&amp;nbsp;algoritme.&lt;/p&gt;
&lt;p&gt;Her er&amp;nbsp;korrespondansen:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Hei,

Dersom to (eller flere) personer innbyrdes ønsker å bytte fastlege, 
skjer byttet umiddelbart?

Eksempel: Ola har A som fastlege. Kari har B som fastlege. 
Både A og B har helt fulle lister. Ola ønsker å bytte fra A til B. 
Kari ønsker å bytte fra B til A. Ingen andre som står til kø til verken 
A eller B må vente lengre dersom Ola og Kari får bytte umiddelbart. 
Er algoritmen smart nok til å gjøre slike bytter, eller må man vente
til en på en ledig plass fordi noen først forlater listene?

// Tommy

--------------------------------

Hei,

Det er dessverre ikke mulig å bytte plass med noen. 
Dersom ønsket lege ikke har ledig plass, må man stå på venteliste.

Vennlig hilsen
Veiledning Helsenorge
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Wed, 05 Nov 2025 00:00:00 +0100</pubDate><guid>tag:tommyodland.com,2025-11-05:/articles/2025/fastlegebytter</guid><category>articles</category><category>mathematics</category></item><item><title>Monte Carlo modeling in Python with probabilit</title><link>https://tommyodland.com/articles/2025/monte-carlo-modeling-in-python-with-probabilit</link><description>
&lt;main&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=5ed59cbb-644c-4b4b-b97a-f7d39f762163"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/equinor/probabilit"&gt;probabilit&lt;/a&gt; is a Python package for Monte Carlo modeling&lt;/strong&gt; that I helped create.
It&amp;#8217;s great for prototyping uncertainty calculations in some types of problems, such as those encountered in undergraduate engineering courses.
In addition to a high-level modeling language, it contains algorithms for inducing correlations between variables, as well as computing the nearest correlation&amp;nbsp;matrix.&lt;/p&gt;
&lt;p&gt;Here is what probabilit can&amp;nbsp;do:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It has a high-level modeling language that lets us model equations and expressions using probability&amp;nbsp;distributions&lt;/li&gt;
&lt;li&gt;It supports quasi Monte Carlo sampling: Latin Hypercube, Sobol, and Halton&amp;nbsp;sequences&lt;/li&gt;
&lt;li&gt;It can correlate variables, e.g. induce a correlation between a uniform and a normal&amp;nbsp;distribution&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here is what probabilit does not&amp;nbsp;do:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It&amp;#8217;s not suited for modeling complex environments, like multi-agent systems, queues, differential equations,&amp;nbsp;etc.&lt;/li&gt;
&lt;li&gt;It&amp;#8217;s not as memory efficient nor as fast as writing pure numpy&amp;nbsp;simulations&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This article is a tutorial demonstrating the most important features of &lt;a href="https://github.com/equinor/probabilit"&gt;probabilit&lt;/a&gt;.
We&amp;#8217;ll be using version 0.1.0.
Beware that in future versions some things might change slightly.
However, the overall ideas will remain the same, and all the Python code in this tutorial is executable (it was created from a jupyter&amp;nbsp;notebook).&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs" id="cell-id=771a9dcb-5706-4cb1-9258-2a6ce94e394a"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [1]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;matplotlib.pyplot&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;plt&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=44f030f5-4157-435f-bee8-8afa16d04f01"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;h1 id="Monte-Carlo-modeling-examples"&gt;Monte Carlo modeling examples&lt;a class="anchor-link" href="#Monte-Carlo-modeling-examples"&gt;¶&lt;/a&gt;&lt;/h1&gt;&lt;p&gt;Let us jump into probabilit with some modeling&amp;nbsp;examples.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=65a7a787-aa01-49a1-83ca-23150fc1a18e"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;h2 id="Braking-distance-of-a-car"&gt;Braking distance of a car&lt;a class="anchor-link" href="#Braking-distance-of-a-car"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=e110d710-5b15-4fdb-a231-7cf9887f5c22"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;.
Suppose we&amp;#8217;re driving down the highway at approximately 100 km/h when suddenly a family of ducks appear and we have to slam the brakes.
What is the braking&amp;nbsp;distance?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Deterministic answer&lt;/strong&gt;.
It turns out that the relevant equation, obtained by realizing that the braking energy must cancel out the kinetic energy,&amp;nbsp;is &lt;code&gt;d = v^2 / (2 * mu * g)&lt;/code&gt;,&amp;nbsp;where&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;d&lt;/code&gt; = braking distance&amp;nbsp;(m)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;v&lt;/code&gt; = velocity&amp;nbsp;(m/s)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mu&lt;/code&gt; = &lt;a href="https://en.wikibooks.org/wiki/Physics_Study_Guide/Frictional_coefficients"&gt;coefficient of friction&lt;/a&gt;&amp;nbsp;(dimensionless)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;g&lt;/code&gt; = gravitational acceleration&amp;nbsp;(m/s²)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A deterministic answer is straightforward to compute in&amp;nbsp;Python:&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=5a863c5f-e926-4e99-a222-673da1c94d8a"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [2]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;braking_distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;velocity_kmh&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;9.81&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;velocity_ms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;velocity_kmh&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;3.6&lt;/span&gt;  &lt;span class="c1"&gt;# Convert to m/s&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;velocity_ms&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;mu&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;velocity_kmh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
&lt;span class="n"&gt;mu_dry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;  &lt;span class="c1"&gt;# Coefficient of friction on dry road&lt;/span&gt;
&lt;span class="n"&gt;distance_dry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;braking_distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;velocity_kmh&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mu_dry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;distance_dry&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.1f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; meters"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output" data-mime-type="text/plain" tabindex="0"&gt;
&lt;pre&gt;56.2 meters
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=b6fc3a98-5817-4a85-a376-713521bbe21d"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;&lt;strong&gt;Probabilistic answer&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;With probabilit we can compute braking distance as &lt;em&gt;a distribution&lt;/em&gt;.
More specifically we&amp;#8217;ll be able to draw samples from the braking distance distribution.
All we have to do is allow some of the input parameters to be&amp;nbsp;distributions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We do not trust the speedometer to be exact, so we set velocity to be a triangular&amp;nbsp;distribution.&lt;/li&gt;
&lt;li&gt;We do not trust that the coefficient of kinetic friction is exactly 0.7, so we use a normal&amp;nbsp;distribution.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These distributions are somewhat arbitrary.
Any distribution in scipy can be used from within probabilit, with the syntax&lt;br /&gt;
&lt;code&gt;Distribution("scipy_distr_name", scipy_distr_arg1, scipy_distr_arg2, ...)&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=36af84a6-23bf-4ef5-bdf5-21230dbe3fe6"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [3]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;probabilit&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Distribution&lt;/span&gt;

&lt;span class="c1"&gt;# Replace velocity and coefficient of friction with distributions&lt;/span&gt;
&lt;span class="n"&gt;velocity_kmh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Distribution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"triang"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;loc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;mu_dry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Distribution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"norm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;loc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.03&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# We can use the same function as earlier&lt;/span&gt;
&lt;span class="n"&gt;distance_dry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;braking_distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;velocity_kmh&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mu_dry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Sample from the answer. This samples the distributions above&lt;/span&gt;
&lt;span class="c1"&gt;# and propagates the samples through the expression defined by the&lt;/span&gt;
&lt;span class="c1"&gt;# 'braking_distance' function. "lhs" = latin hypercube sampling&lt;/span&gt;
&lt;span class="n"&gt;samples&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;distance_dry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"lhs"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;figure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;figsize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;samples&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bins&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"auto"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;density&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;edgecolor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"black"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xlabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Braking distance"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tight_layout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedImage jp-OutputArea-output" tabindex="0"&gt;
&lt;img alt="No description has been provided for this image" class="" src="data:image/png;base64,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" /&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=204471e2-0cce-4b3a-a755-e10be4244bc8"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [4]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;pd&lt;/span&gt;
&lt;span class="c1"&gt;# We can also get summary statistics&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Series&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;samples&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"braking_distance"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;percentiles&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.95&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.99&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_frame&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child jp-OutputArea-executeResult"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;Out[4]:&lt;/div&gt;
&lt;div class="jp-RenderedHTMLCommon jp-RenderedHTML jp-OutputArea-output jp-OutputArea-executeResult" data-mime-type="text/html" tabindex="0"&gt;
&lt;div&gt;
&lt;style scoped=""&gt;
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
&lt;/style&gt;
&lt;table border="1" class="dataframe"&gt;
&lt;thead&gt;
&lt;tr style="text-align: right;"&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;count&lt;/th&gt;
&lt;th&gt;mean&lt;/th&gt;
&lt;th&gt;std&lt;/th&gt;
&lt;th&gt;min&lt;/th&gt;
&lt;th&gt;1%&lt;/th&gt;
&lt;th&gt;5%&lt;/th&gt;
&lt;th&gt;50%&lt;/th&gt;
&lt;th&gt;95%&lt;/th&gt;
&lt;th&gt;99%&lt;/th&gt;
&lt;th&gt;max&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;braking_distance&lt;/th&gt;
&lt;td&gt;2500.0&lt;/td&gt;
&lt;td&gt;56.3&lt;/td&gt;
&lt;td&gt;3.3&lt;/td&gt;
&lt;td&gt;47.1&lt;/td&gt;
&lt;td&gt;49.3&lt;/td&gt;
&lt;td&gt;50.9&lt;/td&gt;
&lt;td&gt;56.2&lt;/td&gt;
&lt;td&gt;62.0&lt;/td&gt;
&lt;td&gt;64.3&lt;/td&gt;
&lt;td&gt;72.3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=f784ad1a-bf8c-44f2-b49b-cad8b37c5a81"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;With uncertainty added to some of the input parameters, the answer is not nearly as certain as it seemed in the deterministic analysis.
We have learned that braking distance could reasonably be anywhere between 51 meters and 62 meters or so.
This is the power of (simple) Monte Carlo&amp;nbsp;analysis.&lt;/p&gt;
&lt;p&gt;When &lt;code&gt;distance_dry&lt;/code&gt; is sampled, the &lt;em&gt;ancestor&amp;nbsp;nodes&lt;/em&gt; &lt;code&gt;velocity_kmh&lt;/code&gt; and &lt;code&gt;mu_dry&lt;/code&gt; are also sampled.
In the process, the&amp;nbsp;attribute &lt;code&gt;samples_&lt;/code&gt; is set on &lt;em&gt;all&lt;/em&gt; nodes.
This is useful if we want to inspect the samples drawn on ancestor variables.
For instance, here is a simple sensitivity analysis where we study the effect of the input parameters on the&amp;nbsp;output:&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=9211ac7e-50d7-43a5-a59e-7419931a2710"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [5]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;fig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ax1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ax2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subplots&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;figsize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;sharey&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ax1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;velocity_kmh&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;samples_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;distance_dry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;samples_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;zorder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ax2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mu_dry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;samples_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;distance_dry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;samples_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;zorder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ax1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_ylabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Distance"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ax1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_xlabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Velocity"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ax2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_xlabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Coefficient of friction"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ax1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ls&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"--"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;zorder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alpha&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ax2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ls&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"--"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;zorder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alpha&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;fig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tight_layout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedImage jp-OutputArea-output" tabindex="0"&gt;
&lt;img alt="No description has been provided for this image" class="" src="data:image/png;base64,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vLw8lJSUYPbs2SYfIyAgAL1799Z7XuxJdZ4NlUFISAguXryo8X5zczMuX76st5xCQkLQ2NiIuro6venai5gYVX799VfcfffdSEpKwnvvvWfysRISEgDA7mVrSozqEhIS0NzcjLNnz+pN11HKUSxz6hW5XI7evXtrfKvv168fampq0NjYaPe6yhYx6GLrusiS83b9+nVs2rSpVUfO3M+6JWwRhy7G2kxLmRPHyy+/jMceewyzZ89GTEwMHnjgASxfvhwrVqyAUqm06rXhVB0mFW9vb8jlcly5cgV79+5FWlqazv2qqqpQW1sLuVyu832pVIr4+Hjs27dP2KZUKrFv3z4kJibaJO9iGYtx3bp1iI+Px8CBA01Ou6GhAeXl5XrPiz1FRkYiJCREowyuXbuGo0ePCmWQmJiIuro6jbts+/fvh1KpFDoI2uLj4+Hh4aGRbklJCSorK+1etmJiBO7cWRozZgzi4+ORnZ0NNzfTL9+ioiIAsHvZio1RW1FREdzc3Fr95KriSOUoljn1yogRI1BWVgalUilsO336NORyOaRSqd3rKlvEoIut6yJLztuWLVtw+/ZtPProoxrbzf2sW8IWcehirM20lDlx3Lhxo1VdqOqUE5F1rw2Thog7uC+++IL27NlDZ86codzcXBo4cCAlJCRQY2Mj1dfX03PPPUf5+flUUVFBX331FQ0ePJh69epFt27dEtK455576J133hFeb9q0iWQyGeXk5NCpU6do7ty5FBAQQDU1NW0RosEYVa5evUodO3akNWvW6ExDO8Znn32WDhw4QBUVFXTo0CFKSUmhLl260MWLF20eDxFRfX09ff/99/T9998TAFq9ejV9//339MsvvxDRnUd0AwICaOfOnfTjjz9SWlqazmkF4uLi6OjRo/Ttt99Sr169NKYVqKqqoj59+tDRo0eFbfPmzaMePXrQ/v376fjx45SYmEiJiYkOGWNVVRX17NmTxo4dS1VVVRqP+eqLsaysjF555RU6fvw4VVRU0M6dOykqKopGjx7tkDEePnyYMjMzqaioiMrLy+njjz+mrl270uOPP643RiL7lqO1GKtXHnvsMfrb3/4m7F9ZWUm+vr60YMECKikpoV27dlFQUBC9+uqrotNsDzG0RV1kahwqI0eOpClTpuhMU0yd5ehxiG0z2zoOhUJBvr6+tHHjRqFdjI6OpsmT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" /&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=0413cc46-bf81-440e-92da-d827280fed9a"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;h2 id="Mutual-fund"&gt;Mutual fund&lt;a class="anchor-link" href="#Mutual-fund"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=9a0f397f-7738-4a2d-aeb5-079ebcaf1dd8"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;.
Suppose we save 100 units of money per month for 10 years and put the money in a mutual fund.
The yearly interest rate is around 7 % after accounting for inflation.
How much money will we have after 10&amp;nbsp;years?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Probabilistic solution&lt;/strong&gt;.
The interest rate will not be exactly 7 % every year, so we can add some uncertainty.
Notice how probabilit expressions can be used in for-loops without any&amp;nbsp;issues.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=1f985daf-61cd-4d9d-a7f0-00f9cc4a797d"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [6]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;probabilit&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Lognormal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plot&lt;/span&gt;

&lt;span class="n"&gt;YEARS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="n"&gt;SAVED_PER_MONTH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;

&lt;span class="n"&gt;money_saved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;year&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;YEARS&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;interest_rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Lognormal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.07&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;money_saved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;money_saved&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;interest_rate&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;SAVED_PER_MONTH&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;

&lt;span class="c1"&gt;# Sample and plot the result&lt;/span&gt;
&lt;span class="n"&gt;samples&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;money_saved&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;999&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"lhs"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pairgrid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;money_saved&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# returns a seaborn pairgrid&lt;/span&gt;
&lt;span class="n"&gt;pairgrid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;axes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_xlabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Money saved"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pairgrid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;axes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_ylabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedImage jp-OutputArea-output" tabindex="0"&gt;
&lt;img alt="No description has been provided for this image" class="" src="data:image/png;base64,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" /&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=6f2ee889-ba8a-46fb-befd-342413cb215a"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;&lt;strong&gt;Exercise&lt;/strong&gt;.
Change the example above so that money saved per month is a distribution. Choose values that are realistic for your own financial situation and study how much money you can save over the next&amp;nbsp;decade.&lt;/p&gt;
&lt;h2 id="Bird-survival"&gt;Bird survival&lt;a class="anchor-link" href="#Bird-survival"&gt;¶&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;This final example illustrates &lt;a href="https://en.wikipedia.org/wiki/Compound_probability_distribution"&gt;compound distributions&lt;/a&gt;, where one distribution is an argument to&amp;nbsp;another.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;.
Suppose we have a distribution of eggs per nest for a certain species of bird.
We also have a survival percentage for each egg.
What is the distribution of the total number of birds per nest that live till&amp;nbsp;adulthood?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Probabilistic solution&lt;/strong&gt;. We set up separate distributions and pass them as arguments to each&amp;nbsp;other:&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=531fc369-1966-4e8d-9794-4a256cf2871f"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [7]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Number of eggs per nest&lt;/span&gt;
&lt;span class="n"&gt;eggs_per_nest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Distribution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"poisson"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Assume we are uncertain of the survival probability too&lt;/span&gt;
&lt;span class="c1"&gt;# According to one source online, around 10-30% of eggs survive&lt;/span&gt;
&lt;span class="n"&gt;prob_survive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Distribution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"beta"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;loc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Here is the compound distribution, which depends on two distributions&lt;/span&gt;
&lt;span class="n"&gt;adult_birds_per_nest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Distribution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"binom"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;eggs_per_nest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;prob_survive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Calling plot() without sampling will sample a copy of the nodes&lt;/span&gt;
&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;eggs_per_nest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;prob_survive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;adult_birds_per_nest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sample_kwargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"random_state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"lhs"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;aspect&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedImage jp-OutputArea-output" tabindex="0"&gt;
&lt;img alt="No description has been provided for this image" class="" src="data:image/png;base64,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" /&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=f465fad3-3224-426c-b9a8-e9c12283300a"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;The discrete nature of the distributions make them hard to visualize without jitter in the&amp;nbsp;default &lt;code&gt;plot&lt;/code&gt; call, but hopefully you get the&amp;nbsp;idea.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Exercise&lt;/strong&gt;.
Go to Wikipedia&amp;#8217;s &lt;a href="https://en.wikipedia.org/wiki/Compound_probability_distribution#Examples"&gt;examples of compound distributions&lt;/a&gt;.
Verify computationally that this statement is true: &amp;#8220;Compounding a Gaussian distribution with variance distributed according to an exponential distribution yields a Laplace&amp;nbsp;distribution.&amp;#8221;&lt;/p&gt;
&lt;h1 id="How-does-probabilit-work?"&gt;How does probabilit work?&lt;a class="anchor-link" href="#How-does-probabilit-work?"&gt;¶&lt;/a&gt;&lt;/h1&gt;&lt;p&gt;Given an expression, probabilit creates a computational graph that is evaluated lazily.
For instance, the&amp;nbsp;expression &lt;code&gt;Constant(5) ** 2&lt;/code&gt; is converted&amp;nbsp;to &lt;code&gt;Power(Constant(5), Constant(2))&lt;/code&gt;,&amp;nbsp;where &lt;code&gt;Power&lt;/code&gt; is a &lt;em&gt;Transform&lt;/em&gt; node.
Other transform nodes&amp;nbsp;are &lt;code&gt;Exp&lt;/code&gt;, &lt;code&gt;Log&lt;/code&gt;, &lt;code&gt;Sin&lt;/code&gt;, &lt;code&gt;Abs&lt;/code&gt;, &lt;code&gt;Add&lt;/code&gt;, &lt;code&gt;Divide&lt;/code&gt;, etc.
There are also &lt;em&gt;Constant&lt;/em&gt; nodes and &lt;em&gt;Distribution&lt;/em&gt;&amp;nbsp;nodes.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=3e837024-e78f-4db5-bb3b-abfa7dd5e24e"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [8]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;probabilit&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Constant&lt;/span&gt;

&lt;span class="n"&gt;expression&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Constant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;Distribution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"uniform"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;Constant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output" data-mime-type="text/plain" tabindex="0"&gt;
&lt;pre&gt;Add(Power(Constant(5), Constant(2)), Divide(Distribution("uniform"), Constant(3)))
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=69051b69-f641-4c25-ad7d-f47367db7f3c"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;The computational graph can be visualized as a&amp;nbsp;tree:&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=fe6cdb24-432d-4ff1-876c-3589bbc36b58"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [9]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;probabilit&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;treeprint&lt;/span&gt;
&lt;span class="n"&gt;treeprint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output" data-mime-type="text/plain" tabindex="0"&gt;
&lt;pre&gt;Add
   ├──Power
   │  ├──Constant(5)
   │  └──Constant(2)
   └──Divide
      ├──Distribution("uniform")
      └──Constant(3)
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=94a3dc73-5ac3-4756-94e8-c27e5b3c18b1"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;Not every probabilit expression is a tree.
For instance,&amp;nbsp;if &lt;code&gt;a&lt;/code&gt; is a node&amp;nbsp;then &lt;code&gt;b = abs(a) + a&lt;/code&gt; creates a graph that is not strictly speaking a tree.
All probabilit graphs are DAGs (directed acyclic graphs), since they cannot contain&amp;nbsp;cycles.&lt;/p&gt;
&lt;p&gt;A probabilit expression can be converted to a networkx &lt;a href="https://networkx.org/documentation/stable/reference/classes/multidigraph.html"&gt;MultiDiGraph&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=979f7eaf-41fd-4a44-bdb7-9be99885ef12"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [10]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;networkx&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;nx&lt;/span&gt;

&lt;span class="c1"&gt;# Create a MultiDiGraph&lt;/span&gt;
&lt;span class="n"&gt;G&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;expression&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_graph&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Draw information&lt;/span&gt;
&lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;spring_layout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;

&lt;span class="c1"&gt;# Draw the graph&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;figure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;figsize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;with_labels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;node_color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"lightblue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;node_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;font_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;font_weight&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"bold"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;arrows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;arrowsize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;edge_color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"gray"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedImage jp-OutputArea-output" tabindex="0"&gt;
&lt;img alt="No description has been provided for this image" class="" src="data:image/png;base64,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" /&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=08b6a93e-aca8-47ab-8f48-a1ad45a0e50a"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;It&amp;#8217;s important to understand that no computation is performed&amp;nbsp;until &lt;code&gt;.sample()&lt;/code&gt; is called on a node.
The expression above is just a graph with instructions about &lt;em&gt;how to compute a result&lt;/em&gt;.&amp;nbsp;Once &lt;code&gt;.sample()&lt;/code&gt; is called, the actual work is&amp;nbsp;done:&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=79dfece3-5127-4059-b20c-1107c56960f5"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [11]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;expression&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child jp-OutputArea-executeResult"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;Out[11]:&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output jp-OutputArea-executeResult" data-mime-type="text/plain" tabindex="0"&gt;
&lt;pre&gt;array([25.12484671, 25.31690477, 25.24399798, 25.19955283, 25.05200621,
       25.05199817, 25.0193612 , 25.28872538, 25.20037167, 25.23602419])&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=c5b03cd0-d2b9-4ad9-9c05-349a56157fe8"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;When the call asks for the sink&amp;nbsp;node &lt;code&gt;expression&lt;/code&gt; to be sampled, all ancestors are sampled in turn and results are propagated through the graph.
This has both memory and performance overhead, which probabilit sacrifices in favor of a clean &lt;span class="caps"&gt;API&lt;/span&gt; and a pleasant modeling&amp;nbsp;experience.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=51879981-dc06-4680-b36a-5aa621168763"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;h1 id="Latin-HyperCube-sampling-(LHS)"&gt;Latin HyperCube sampling (&lt;span class="caps"&gt;LHS&lt;/span&gt;)&lt;a class="anchor-link" href="#Latin-HyperCube-sampling-(LHS)"&gt;¶&lt;/a&gt;&lt;/h1&gt;&lt;p&gt;Probabilit supports all major sampling methods in the scipy &lt;a href="https://docs.scipy.org/doc/scipy/reference/stats.qmc.html"&gt;quasi-monte carlo module&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Below we show the difference between pseudo-random sampling&amp;nbsp;(&lt;code&gt;method=None&lt;/code&gt;) and Latin HyperCube&amp;nbsp;(&lt;code&gt;method="lhs"&lt;/code&gt;).&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=b6850f9b-2902-48f1-bd3d-381ec12101e5"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [12]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Distribution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"uniform"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;exponential&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Distribution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"expon"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Sample a sum to set `.samples_` on distributions for plotting&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;exponential&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exponential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;aspect&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plot_kws&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"s"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedImage jp-OutputArea-output" tabindex="0"&gt;
&lt;img alt="No description has been provided for this image" class="" src="data:image/png;base64,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" /&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=3c1aa45e-f8f3-4b4d-87f9-b040a0bd1e4f"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [13]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;exponential&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"lhs"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exponential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;aspect&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plot_kws&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"s"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedImage jp-OutputArea-output" tabindex="0"&gt;
&lt;img alt="No description has been provided for this image" class="" src="data:image/png;base64,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" /&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=93aa3147-ba05-4381-9eba-57c2f49e61f6"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;Notice how the &lt;a href="https://en.wikipedia.org/wiki/Low-discrepancy_sequence"&gt;low-discrepancy sequence&lt;/a&gt; produced by &lt;span class="caps"&gt;LHS&lt;/span&gt; leads to distributions of samples that looks very much like the theoretical distributions.
In many Monte Carlo simulations, using &lt;span class="caps"&gt;LHS&lt;/span&gt; is an excellent choice because we obtain empirical distributions that match the theoretical ones with fewer samples drawn than what we would achieve had we used pseudo-random&amp;nbsp;sequences.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=913c72b3-0c86-42ed-9219-ccc11d64745e"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;h1 id="Inducing-correlations"&gt;Inducing correlations&lt;a class="anchor-link" href="#Inducing-correlations"&gt;¶&lt;/a&gt;&lt;/h1&gt;&lt;p&gt;Probabilit comes&amp;nbsp;with &lt;code&gt;Correlator&lt;/code&gt; objects that can be used to correlate variables.
They work within the framework of the modeling &lt;span class="caps"&gt;API&lt;/span&gt;, or outside of it (where numpy arrays can be used).
This means that these highly performant algorithms can be used outside of the modeling language&amp;nbsp;too.&lt;/p&gt;
&lt;p&gt;When all variables are Normal the Cholesky correlator can be a good choice.
While it obtains the desired correlation, it does not preserve the marginal distributions.
This is a major problem many real-world applications, so &lt;strong&gt;be careful when using the Cholesky correlator!&lt;/strong&gt;
Notice in the example below that the exponential distribution contains negative values after inducing&amp;nbsp;correlations.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=128fa4bd-a879-46e5-b354-34e025d4b947"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [14]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;probabilit.correlation&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Cholesky&lt;/span&gt;

&lt;span class="c1"&gt;# Correlation matrix with desired Pearson correlation of 0.8&lt;/span&gt;
&lt;span class="n"&gt;corr_mat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([[&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;

&lt;span class="n"&gt;expression&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;exponential&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;correlate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exponential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;corr_mat&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;corr_mat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;expression&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"lhs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;correlator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Cholesky&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exponential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;aspect&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plot_kws&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"s"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;# Observed Pearson correlation&lt;/span&gt;
&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;corrcoef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;samples_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exponential&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;samples_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child jp-OutputArea-executeResult"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;Out[14]:&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output jp-OutputArea-executeResult" data-mime-type="text/plain" tabindex="0"&gt;
&lt;pre&gt;array([[1. , 0.8],
       [0.8, 1. ]])&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedImage jp-OutputArea-output" tabindex="0"&gt;
&lt;img alt="No description has been provided for this image" class="" src="data:image/png;base64,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" /&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=b620f9cb-eec8-47e9-a37c-f2705e464ff6"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;Above, the marginal of the uniform distribution was preserved.
This is a happy accident since we defined that distribution &lt;em&gt;before&lt;/em&gt; we defined the exponential distribution in the code.
If we switch them, then we will preserve the marginals of the exponential but destroy the marginal of the uniform.
In general the Cholesky comes with no guarantees of preserving&amp;nbsp;marginals.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Exercise&lt;/strong&gt;.
Switch the order and define the exponential distribution first, then the uniform distribution.
Use the Cholesky correlator and notice how the marginal of the uniform is&amp;nbsp;destroyed.&lt;/p&gt;
&lt;h2 id="Iman-Conover"&gt;Iman-Conover&lt;a class="anchor-link" href="#Iman-Conover"&gt;¶&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;The Iman-Conover transformation is almost always a better choice than Cholesky.
It leaves the marginal distributions unchanged, while attempting to induce the desired correlation.
Iman-Conover is essentially a heuristic where data is transformed to rank-space, then Cholesky is used, then the data is transformed back.
The end result is a permutation of the samples in each distribution.
The idea is that by choosing a permutation that induces correlation in rank space, the permutation will also get close to the desired correlation in the original space.
This typically works pretty well in most cases, but the algorithm comes without any optimality&amp;nbsp;guarantees.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=3d91c9bf-b73e-4bb6-994a-385464cb9b26"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [15]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;probabilit.correlation&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ImanConover&lt;/span&gt;

&lt;span class="n"&gt;expression&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;exponential&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;expression&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;correlate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exponential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;corr_mat&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;corr_mat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;expression&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"lhs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;correlator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ImanConover&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exponential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;aspect&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plot_kws&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"s"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;corrcoef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;samples_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exponential&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;samples_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child jp-OutputArea-executeResult"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;Out[15]:&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output jp-OutputArea-executeResult" data-mime-type="text/plain" tabindex="0"&gt;
&lt;pre&gt;array([[1.        , 0.70443783],
       [0.70443783, 1.        ]])&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedImage jp-OutputArea-output" tabindex="0"&gt;
&lt;img alt="No description has been provided for this image" class="" src="data:image/png;base64,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" /&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=bfb11846-ec78-448f-859c-54fe55a5c335"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;Above we do not achieve the exact correlation that we wanted, but we get pretty close.
The marginals are unaffected by the&amp;nbsp;transformation.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=5021a904-4c86-4347-a4f9-4337bccb2818"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;h2 id="Inducing-correlations-by-permutation"&gt;Inducing correlations by permutation&lt;a class="anchor-link" href="#Inducing-correlations-by-permutation"&gt;¶&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Since Iman-Conover is just a clever and fast heuristic to pick a good permutation of the samples (rows) within a variable (column) in a matrix, perhaps we can do even better if we search the space of all permutations?
This space is huge, so we can only afford to search a small part of it.&amp;nbsp;The &lt;code&gt;Permutation&lt;/code&gt; correlation does this, and by spending some time on computation we often end up with even better correlations than what Iman-Conover achieves.
Notice below that we get extremely close to the desired&amp;nbsp;correlation.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=a3070d93-647e-4270-b52e-65240fb8276e"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [16]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;probabilit.correlation&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Permutation&lt;/span&gt;

&lt;span class="n"&gt;expression&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;exponential&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;correlate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exponential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;corr_mat&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;corr_mat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;correlator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Permutation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;expression&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"lhs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;correlator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;correlator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exponential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;aspect&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plot_kws&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"s"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;corrcoef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;samples_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exponential&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;samples_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child jp-OutputArea-executeResult"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;Out[16]:&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output jp-OutputArea-executeResult" data-mime-type="text/plain" tabindex="0"&gt;
&lt;pre&gt;array([[1.        , 0.78002787],
       [0.78002787, 1.        ]])&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedImage jp-OutputArea-output" tabindex="0"&gt;
&lt;img alt="No description has been provided for this image" class="" src="data:image/png;base64,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" /&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=6d401403-4303-4766-80b2-03fc355e9d3e"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;Finally, let us&amp;nbsp;compare &lt;code&gt;ImanConover&lt;/code&gt; with&amp;nbsp;the &lt;code&gt;Composite&lt;/code&gt; algorithm.
The composite approach is what probabilit does by default.
It first uses Iman-Conover to find a good starting point, then proceeds by running Permutation on that starting point for a number of&amp;nbsp;iterations.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=dcd5af9e-542f-4547-97f3-4fc257b9ae61"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [17]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;variables&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Distribution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"lognorm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

&lt;span class="c1"&gt;# Set up correlation matrix&lt;/span&gt;
&lt;span class="n"&gt;corr_mat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ones&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;
&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fill_diagonal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;corr_mat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;expression&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;correlate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;corr_mat&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;corr_mat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Iman-Conover&lt;/span&gt;
&lt;span class="n"&gt;correlator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImanConover&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;expression&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;999&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"lhs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;correlator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;correlator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;observed_corr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;corrcoef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;samples_&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;span class="n"&gt;RMSE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;corr_mat&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;observed_corr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"RMSE (observed-desired) for Iman-Conover: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;RMSE&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.4f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output" data-mime-type="text/plain" tabindex="0"&gt;
&lt;pre&gt;RMSE (observed-desired) for Iman-Conover: 0.1011
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=49fc29c8-43d7-4b10-81eb-eacc14a0df34"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [18]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;probabilit.correlation&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Composite&lt;/span&gt;

&lt;span class="c1"&gt;# Composite() =&amp;gt; ImanConover(), then Permutation()&lt;/span&gt;
&lt;span class="n"&gt;correlator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Composite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;expression&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;999&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"lhs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;correlator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;correlator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;observed_corr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;corrcoef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;samples_&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;span class="n"&gt;RMSE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;corr_mat&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;observed_corr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"RMSE norm (observed-desired) for Composite: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;RMSE&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.4f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output" data-mime-type="text/plain" tabindex="0"&gt;
&lt;pre&gt;RMSE norm (observed-desired) for Composite: 0.0367
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=9226feb3-f3e4-4a91-b168-274a34f17027"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;If we are willing to spend some additional time on computation (here 2.5 seconds instead of 15 milliseconds), then we can obtain correlations that are much closer to what we desired.
Often this is worth it, as speed is rarely a bottleneck in these types of&amp;nbsp;analyses.&lt;/p&gt;
&lt;h1 id="Nearest-correlation-matrix"&gt;Nearest correlation matrix&lt;a class="anchor-link" href="#Nearest-correlation-matrix"&gt;¶&lt;/a&gt;&lt;/h1&gt;&lt;p&gt;Just like&amp;nbsp;the &lt;code&gt;ImanConover&lt;/code&gt; and &lt;code&gt;Permutation&lt;/code&gt; correlator classes can be used without using the modeling &lt;span class="caps"&gt;API&lt;/span&gt;,&amp;nbsp;the &lt;code&gt;nearest_correlation_matrix&lt;/code&gt; can also be used without using the modeling &lt;span class="caps"&gt;API&lt;/span&gt;.
This algorithm is useful when users (e.g. a domain expert) proposes correlations, but they end up proposing a correlation matrix that is not valid.
This happens quite frequently, since specifying a high-dimensional, non-trivial correlation matrix that is positive definite is&amp;nbsp;hard.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=75820de5-3d70-4117-861b-ebd3f5ceb192"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [19]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;probabilit.correlation&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;nearest_correlation_matrix&lt;/span&gt;

&lt;span class="n"&gt;rng&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default_rng&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="n"&gt;proposed_corr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;high&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;# Make it symmetric&lt;/span&gt;
&lt;span class="n"&gt;proposed_corr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;triu&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proposed_corr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;triu&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proposed_corr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;
&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fill_diagonal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proposed_corr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proposed_corr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child jp-OutputArea-executeResult"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;Out[19]:&lt;/div&gt;
&lt;div class="jp-RenderedHTMLCommon jp-RenderedHTML jp-OutputArea-output jp-OutputArea-executeResult" data-mime-type="text/html" tabindex="0"&gt;
&lt;div&gt;
&lt;style scoped=""&gt;
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
&lt;/style&gt;
&lt;table border="1" class="dataframe"&gt;
&lt;thead&gt;
&lt;tr style="text-align: right;"&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;0&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;0&lt;/th&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;-46.0&lt;/td&gt;
&lt;td&gt;-91.8&lt;/td&gt;
&lt;td&gt;-96.7&lt;/td&gt;
&lt;td&gt;62.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;td&gt;-46.0&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;45.9&lt;/td&gt;
&lt;td&gt;8.7&lt;/td&gt;
&lt;td&gt;87.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;td&gt;-91.8&lt;/td&gt;
&lt;td&gt;45.9&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;-93.3&lt;/td&gt;
&lt;td&gt;45.9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;td&gt;-96.7&lt;/td&gt;
&lt;td&gt;8.7&lt;/td&gt;
&lt;td&gt;-93.3&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;-15.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;td&gt;62.7&lt;/td&gt;
&lt;td&gt;87.0&lt;/td&gt;
&lt;td&gt;45.9&lt;/td&gt;
&lt;td&gt;-15.5&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=5b238a28-18ad-4f8a-8070-b342d2a5c333"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [20]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eigvals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proposed_corr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Negative eigenvalues =&amp;gt; not positive definite&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child jp-OutputArea-executeResult"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;Out[20]:&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output jp-OutputArea-executeResult" data-mime-type="text/plain" tabindex="0"&gt;
&lt;pre&gt;array([-1.05418258, -0.00675076,  1.41936444,  2.20321725,  2.43835165])&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=397a687a-8991-4c27-b958-8642a4685be5"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [21]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;nearest_corr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nearest_correlation_matrix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proposed_corr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nearest_corr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child jp-OutputArea-executeResult"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;Out[21]:&lt;/div&gt;
&lt;div class="jp-RenderedHTMLCommon jp-RenderedHTML jp-OutputArea-output jp-OutputArea-executeResult" data-mime-type="text/html" tabindex="0"&gt;
&lt;div&gt;
&lt;style scoped=""&gt;
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
&lt;/style&gt;
&lt;table border="1" class="dataframe"&gt;
&lt;thead&gt;
&lt;tr style="text-align: right;"&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;0&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;0&lt;/th&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;-35.0&lt;/td&gt;
&lt;td&gt;-42.5&lt;/td&gt;
&lt;td&gt;-51.7&lt;/td&gt;
&lt;td&gt;35.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;td&gt;-35.0&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;49.9&lt;/td&gt;
&lt;td&gt;11.4&lt;/td&gt;
&lt;td&gt;75.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;td&gt;-42.5&lt;/td&gt;
&lt;td&gt;49.9&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;-51.0&lt;/td&gt;
&lt;td&gt;26.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;td&gt;-51.7&lt;/td&gt;
&lt;td&gt;11.4&lt;/td&gt;
&lt;td&gt;-51.0&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;-32.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;td&gt;35.2&lt;/td&gt;
&lt;td&gt;75.0&lt;/td&gt;
&lt;td&gt;26.8&lt;/td&gt;
&lt;td&gt;-32.1&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=8d308d4b-df73-492d-9e66-5201497c449d"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;h1 id="Summary-and-references"&gt;Summary and references&lt;a class="anchor-link" href="#Summary-and-references"&gt;¶&lt;/a&gt;&lt;/h1&gt;&lt;p&gt;&lt;a href="https://github.com/equinor/probabilit"&gt;probabilit&lt;/a&gt; has two&amp;nbsp;layers:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A high-level modeling language, built on top of&amp;nbsp;Python&lt;/li&gt;
&lt;li&gt;Low-level functionality for correlating variables, finding nearest correlation matrices,&amp;nbsp;etc&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Depending on what kind of Monte Carlo analysis you want to do, one or both (or none!) of these might be&amp;nbsp;useful.&lt;/p&gt;
&lt;p&gt;Probabilit is a bit like Tensorflow in that it uses a lazily evaluated computational graph.
The idea of building a Monte Carlo analysis tool in this fashion is due to &lt;a href="https://github.com/dafeda/"&gt;Feda&lt;/a&gt;.
Integrating &lt;span class="caps"&gt;LHS&lt;/span&gt; and ImanConover into the tool was also his idea.
&lt;a href="https://github.com/knutuh/"&gt;Knut&lt;/a&gt; provided feedback on practical methods and distributions used in some types of Monte Carlo analysis, e.g. the importance of Triangular and &lt;span class="caps"&gt;PERT&lt;/span&gt; distributions.
The idea behind the Permutation correlation and how to speed it up by not re-calculating the correlation matrix in every iteration (an idea we did not have time to go into in this tutorial) is my own, but these are not novel&amp;nbsp;ideas.&lt;/p&gt;
&lt;p&gt;To read more on these subjects, I&amp;nbsp;recommend:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;a href="https://github.com/equinor/probabilit"&gt;probabilit&lt;/a&gt; repository. Have a look at the implementations. Create an issue or &lt;span class="caps"&gt;PR&lt;/span&gt; if you find a bug&amp;nbsp;:)&lt;/li&gt;
&lt;li&gt;To find the nearest correlation matrix, we use Equation (3) in the paper &lt;a href="https://ieeexplore.ieee.org/document/8160870"&gt;An augmented Lagrangian dual approach for the H-weighted nearest correlation matrix&amp;nbsp;problem&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;An excellent explanation of the Iman Conover algorithm is found in &lt;a href="https://blogs.sas.com/content/iml/2021/06/16/geometry-iman-conover-transformation.html"&gt;The geometry of the Iman-Conover&amp;nbsp;transformation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The book &lt;a href="https://onlinelibrary.wiley.com/doi/book/10.1002/9780470725184"&gt;Global Sensitivity Analysis&lt;/a&gt; by Saltelli et al. is not directly related to Monte Carlo modeling, but Monte Carlo is often used for sensitivity analysis of models, and therefore this book might be of&amp;nbsp;interest&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/main&gt;
</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Fri, 17 Oct 2025 00:00:00 +0200</pubDate><guid>tag:tommyodland.com,2025-10-17:/articles/2025/monte-carlo-modeling-in-python-with-probabilit</guid><category>articles</category><category>statistics</category></item><item><title>A Recipe for Curve Fitting</title><link>https://tommyodland.com/articles/2025/a-recipe-for-curve-fitting</link><description>&lt;p&gt;Inspired by &lt;a href="http://karpathy.github.io/2019/04/25/recipe/"&gt;A Recipe for Training Neural Networks&lt;/a&gt;, here are some tips and tricks for curve fitting.
Assume we are fitting a non-linear, parametric&amp;nbsp;function &lt;span class="math"&gt;\(f(x; \boldsymbol{\theta})\)&lt;/span&gt; to&amp;nbsp;data &lt;span class="math"&gt;\(y\)&lt;/span&gt;.
The problem could be anything.
Many highly technical problems are illuminated by curve fitting, but it helps to think of everyday examples such as: personal savings vs. time, apartment price vs. square meters, salary vs. experience, strength vs. bodyweight, world population vs. year,&amp;nbsp;etc.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-height: 260px;"
src="https://tommyodland.com/images/articles/curve_fitting/curve_fitting_animation.gif"
class="img-responsive"&gt;&lt;/p&gt;
&lt;h2 id="modeling"&gt;Modeling&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Determine the purpose.&lt;/strong&gt; Do you want to extrapolate, or do you want to interpolate? Is &lt;em&gt;prediction&lt;/em&gt; the overall goal, or is it &lt;em&gt;inference&lt;/em&gt; of latent variables? In other words: raw predictive power or understanding underlying phenomena? Is uncertainty needed? If yes, then what kind? A posterior conditional&amp;nbsp;distribution &lt;span class="math"&gt;\(p(x | \hat{\boldsymbol{\theta}})\)&lt;/span&gt; or a posterior predictive&amp;nbsp;distribution &lt;span class="math"&gt;\(p(x | \boldsymbol{\theta}) p(\boldsymbol{\theta} | X)\)&lt;/span&gt; that accounts for parameter uncertainty? Is curve fitting a one-time job, or will it every night or on demand? Will it run on a single data set, or perhaps on thousands of&amp;nbsp;datasets?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Read the literature.&lt;/strong&gt; Before deciding on the&amp;nbsp;function &lt;span class="math"&gt;\(f\)&lt;/span&gt;, search the web and review papers. What functions are used on similar problems? Avoid ad-hoc models like unregularized high-degree polynomials or very intricate functions. Apply &lt;a href="https://en.wikipedia.org/wiki/Occam%27s_razor"&gt;Occam&amp;rsquo;s razor&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Know basic functions and how to combine them.&lt;/strong&gt; Know the behavior of &lt;a href="https://en.wikipedia.org/wiki/List_of_mathematical_functions"&gt;basic building blocks&lt;/a&gt; like linear functions, powers, exponentials, logarithms, sigmoids, hyperbolic functions, etc. Sometimes you can transition between them in more natural ways than the convex&amp;nbsp;combination &lt;span class="math"&gt;\(\alpha f(x) + (1- \alpha) g(x)\)&lt;/span&gt;. For instance, the &lt;a href="https://en.wikipedia.org/wiki/Power_transform"&gt;power&amp;nbsp;transform&lt;/a&gt; &lt;span class="math"&gt;\((x^\alpha - 1) / \alpha\)&lt;/span&gt; lies between the affine&amp;nbsp;function &lt;span class="math"&gt;\(x-1\)&lt;/span&gt; when &lt;span class="math"&gt;\(\alpha=1\)&lt;/span&gt; and the logarithmic&amp;nbsp;function &lt;span class="math"&gt;\(\ln x\)&lt;/span&gt; when &lt;span class="math"&gt;\(\alpha \to 0\)&lt;/span&gt;. Similarly, the &lt;a href="https://en.wikipedia.org/wiki/Generalized_Pareto_distribution"&gt;generalized pareto&lt;/a&gt; distribution lies &amp;ldquo;between&amp;rdquo; the&amp;nbsp;exponential &lt;span class="math"&gt;\(e^{-x}\)&lt;/span&gt; and the&amp;nbsp;hyperbola &lt;span class="math"&gt;\((1 + x)^{-1}\)&lt;/span&gt;. Probability density functions and their antiderivatives are a rich source of modeling functions. Know how to combine functions to create new&amp;nbsp;models.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use an identifiable model.&lt;/strong&gt; Avoid using non-identifiable models&amp;nbsp;like &lt;span class="math"&gt;\(f(x; \boldsymbol{\theta}) = \theta_1 \exp(x - \theta_2)\)&lt;/span&gt;, since they do not have a unique solution. This seems obvious, but I&amp;rsquo;ve seen papers where non-identifiable models are used without&amp;nbsp;priors.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Choose an appropriate loss.&lt;/strong&gt; The least squares&amp;nbsp;loss &lt;span class="math"&gt;\(\mathcal{L}(\boldsymbol{\theta}) = \sum_i (y_i - f(x_i; \boldsymbol{\theta}))^2\)&lt;/span&gt; has nice properties, but might &lt;a href="https://tommyodland.com/articles/2024/the-average-value"&gt;not be what you want&lt;/a&gt;. Know when to&amp;nbsp;use &lt;span class="math"&gt;\(p\)&lt;/span&gt;-norms, huber loss, &lt;a href="https://tommyodland.com/articles/2024/the-reverse-pseudo-huber-loss-function"&gt;pseudohuber&lt;/a&gt;, geometric loss, harmonic loss and statistical losses. Remember that fitting log-data to log-model with a loss&amp;nbsp;like &lt;span class="math"&gt;\((\log y_i - \log f(x_i; \boldsymbol{\theta}))^2\)&lt;/span&gt; is not equivalent to fitting data to a model without logs. Determining the appropriate loss function is a big part of the job. Always align the primary metric with the loss. If you care about &lt;span class="caps"&gt;MAE&lt;/span&gt; as the metric, it makes little sense to use &lt;span class="caps"&gt;RMSE&lt;/span&gt; as the&amp;nbsp;loss.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use a prior&lt;/strong&gt;, or if the model is not statistical, a regularization term. A prior encodes prior knowledge and helps the solver numerically. It also lets you get a solution when you have little data or no data at all. The&amp;nbsp;2-norm &lt;span class="math"&gt;\(\lVert \boldsymbol{\theta} - \boldsymbol{\mu}_{\boldsymbol{\theta}} \rVert_2\)&lt;/span&gt; is a good start&amp;nbsp;and &lt;span class="math"&gt;\((\boldsymbol{\theta} - \boldsymbol{\mu}_{\boldsymbol{\theta}})^T A (\boldsymbol{\theta} - \boldsymbol{\mu}_{\boldsymbol{\theta}})\)&lt;/span&gt; can be even better.&amp;nbsp;Often &lt;span class="math"&gt;\(A\)&lt;/span&gt; is&amp;nbsp;diagonal.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deal with parameter constraints.&lt;/strong&gt; There are several options: (1) use an optimizer that handles constraints natively, (2) re-parametrize the function&amp;nbsp;as &lt;span class="math"&gt;\(f(x; t(\boldsymbol{\theta}))\)&lt;/span&gt; with &lt;span class="math"&gt;\(t\)&lt;/span&gt; mapping from unbounded real numbers to the desired bounds or (3) add a &lt;a href="https://en.wikipedia.org/wiki/Barrier_function"&gt;barrier function&lt;/a&gt; to the objective. For instance,&amp;nbsp;if &lt;span class="math"&gt;\(\theta_1 \in [0, 1]\)&lt;/span&gt;, then you can (1) use an algorithm like &lt;span class="caps"&gt;BFGS&lt;/span&gt;-B (the B stands for &amp;ldquo;box constraints&amp;rdquo;), (2) transform with e.g. a sigmoid&amp;nbsp;like &lt;span class="math"&gt;\(\theta_1 = t(z) = (1 + e^{-z})^{-1}\)&lt;/span&gt; or (3) add barriers such&amp;nbsp;as &lt;span class="math"&gt;\(-\log(\theta_1) - \log (1 - \theta_1 )\)&lt;/span&gt; to the objective. The log-barrier is equivalent to a&amp;nbsp;beta-prior.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Weight the data.&lt;/strong&gt; Consider a weighted loss&amp;nbsp;like &lt;span class="math"&gt;\(\sum_i w_i (y_i - f(x_i; \boldsymbol{\theta}))^2\)&lt;/span&gt; if (1) some data points are more important than others or (2) you are working on a temporal forecasting problem. Beware of the difference between (a) &lt;a href="https://en.wikipedia.org/wiki/Inverse-variance_weighting"&gt;inverse variance weights&lt;/a&gt; and (b) weighting the log-likelihood. They are the same when estimating the mean of a normal, but &lt;a href="https://notstatschat.rbind.io/2020/08/04/weights-in-statistics/"&gt;not in general&lt;/a&gt; (see also &lt;a href="https://blogs.sas.com/content/iml/2017/10/02/weight-variables-in-statistics-sas.html"&gt;this post&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consider partial pooling.&lt;/strong&gt; Use &lt;a href="https://en.wikipedia.org/wiki/Bayesian_hierarchical_modeling"&gt;hierarchical models&lt;/a&gt; to automatically regularize with hyperpriors. This is relevant if you have many curve fitting instances that you can conceptualize as being drawn from some population, and you want to learn the distribution of that population. It&amp;rsquo;s also extremely useful if some instances have little&amp;nbsp;data.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="validation"&gt;Validation&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cross validate.&lt;/strong&gt; Split randomly if you&amp;rsquo;re interpolating. Use a time based split if you&amp;rsquo;re forecasting. Always &lt;a href="https://tommyodland.com/articles/2024/an-opinionated-guide-to-scikit-learn"&gt;match cross validation&lt;/a&gt; to how the model will be used. Compute metrics and study&amp;nbsp;them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use your eyes.&lt;/strong&gt; Curve fitting can be visualized! Sample prior curves and plot them, plot the initial guess, curve fit &amp;ldquo;by hand&amp;rdquo; a few times, fit on every instance you have in your database. Visually look for errors, edge cases,&amp;nbsp;etc.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Implement the forward model.&lt;/strong&gt; Implement data generation based on your model if it&amp;rsquo;s statistical. This is called a prior predictive check. Can you tell a simulation apart from real-world observed data? How? If you can, does it give you ideas for how to improve the&amp;nbsp;model?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Test parameter estimation.&lt;/strong&gt; Some property-based tests: (1) generate a&amp;nbsp;random &lt;span class="math"&gt;\(\boldsymbol{\theta}\)&lt;/span&gt;, use it to generate a dataset, fit on the dataset.&amp;nbsp;Is &lt;span class="math"&gt;\(\lVert \boldsymbol{\theta} - \hat{\boldsymbol{\theta}} \rVert\)&lt;/span&gt; small? It should be, especially in the limit of much data. (2) Test&amp;nbsp;that &lt;span class="math"&gt;\(\mathcal{L}(\hat{\boldsymbol{\theta}}) \leq \mathcal{L}(\boldsymbol{\theta})\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Plot parameter-space.&lt;/strong&gt; You can plot the contours of the loss as a function of two parameters&amp;nbsp;in &lt;span class="math"&gt;\(\boldsymbol{\theta}\)&lt;/span&gt; at a time, while fixing the remaining parameters&amp;nbsp;at &lt;span class="math"&gt;\(\hat{\boldsymbol{\theta}}\)&lt;/span&gt;. Sometimes studying the Hessian is&amp;nbsp;worthwhile.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Plot residuals.&lt;/strong&gt; When are residuals large? Are residuals &lt;a href="https://en.wikipedia.org/wiki/Homoscedasticity_and_heteroscedasticity"&gt;homoscedastic&lt;/a&gt;? Should you model&amp;nbsp;heteroscedasticity?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="optimization"&gt;Optimization&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Understand the optimization algorithm.&lt;/strong&gt; Have at least a rudimentary understanding of the algorithms you use. Read the documentation and perhaps a summary paper. Play with the optimizer on toy problems. Get a feeling for how it works, and more importantly how and when it fails. Study the full verbose output of the&amp;nbsp;algorithm.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Put variables on the same scale.&lt;/strong&gt; First order methods (gradient methods) depend critically on the condition number. Pure second order methods (Newton&amp;rsquo;s method) on the other hand are affine invariant and in principle unaffected by the condition number (numerics and quasi-Newton methods make this untrue in reality). Standardize or take logs/exp/sigmoid/etc as&amp;nbsp;needed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;De-correlate variables.&lt;/strong&gt; Sometimes you can de-correlate variables through re-parametrization of the curve&amp;nbsp;function.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use a prior&lt;/strong&gt;. Even if you lack prior knowledge in the modeling sense, weak regularization tends to help the&amp;nbsp;optimizer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pick a good starting point.&lt;/strong&gt; Graphical trial and error often works. You can also solve a simpler problem, e.g. solve a least squares problem first and use that solution as the starting point for&amp;nbsp;your &lt;span class="math"&gt;\(p=1\)&lt;/span&gt; norm problem. The curve&amp;nbsp;function &lt;span class="math"&gt;\(f\)&lt;/span&gt; can sometimes be relaxed by removing non-linearities. You could&amp;nbsp;use &lt;code&gt;(y[-1] - y[0])/(x[-1] - x[0])&lt;/code&gt; as initial guess for&amp;nbsp;slope, &lt;code&gt;mean(y)&lt;/code&gt; as initial guess for bias, etc. Stress test the solver on bad initial solutions&amp;nbsp;too.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Implement gradients and/or Hessian.&lt;/strong&gt; Take the time to work out gradients. Use a &lt;span class="caps"&gt;CAS&lt;/span&gt; like &lt;a href="https://www.sympy.org/en/index.html"&gt;SymPy&lt;/a&gt; or &lt;a href="https://www.wolframalpha.com/"&gt;WolframAlpha&lt;/a&gt; along with pen and paper. Verify gradients numerically with &lt;a href="https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.check_grad.html"&gt;scipy.optimize.check_grad&lt;/a&gt; or similar functions. If the gradient is too unwieldy, consider autograd with one of the many available packages: &lt;a href="https://github.com/jax-ml/jax"&gt;jax&lt;/a&gt;, &lt;a href="https://github.com/hips/autograd"&gt;autograd&lt;/a&gt;, &lt;a href="https://pytorch.org/"&gt;PyTorch&lt;/a&gt;, &lt;a href="https://github.com/tensorflow/tensorflow"&gt;TensorFlow&lt;/a&gt; or &lt;a href="https://mc-stan.org/"&gt;Stan&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use numerically stable implementations.&lt;/strong&gt; Watch out for expressions&amp;nbsp;like &lt;span class="math"&gt;\(\ln(1 + e^{\theta})\)&lt;/span&gt; that overflow, &lt;a href="https://en.wikipedia.org/wiki/Catastrophic_cancellation"&gt;catastrophic cancellation&lt;/a&gt; and other phenomena. See &lt;a href="https://www.plunk.org/~hatch/rightway.html"&gt;The Right Way to Calculate Stuff&lt;/a&gt; for more&amp;nbsp;examples.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Curve fitting seems easy, but this perception can be deceptive.
It&amp;rsquo;s not uncommon that people fail to get the desired results, often because they call standard routines without knowing how they work.
I hope the tips and tricks above can help.
Experience helps too: work on diverse practical problems and try to solve them.
Make notes on what works in&amp;nbsp;practice.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Sat, 04 Oct 2025 00:00:00 +0200</pubDate><guid>tag:tommyodland.com,2025-10-04:/articles/2025/a-recipe-for-curve-fitting</guid><category>articles</category><category>mathematics</category></item><item><title>Lønna til norske utviklere i 2025</title><link>https://tommyodland.com/articles/2025/lonna-til-norske-utviklere-i-2025</link><description>&lt;p&gt;I denne artikkelen skal vi gå gjennom betraktninger og metoder for å lage en lønnskalkulator for &lt;span class="caps"&gt;IT&lt;/span&gt;-utviklere.
Utålmodige lesere kan gå rett til &lt;strong&gt;&lt;a href="https://tommyodland.com/tools/salary.html"&gt;Lønnskalkulatoren&lt;/a&gt;&lt;/strong&gt; og se hovedfunnene i figuren&amp;nbsp;nedenfor.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://tommyodland.com/images/articles/kode24_lonn_2025/log_linear_effects.png"&gt;
&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 780px;"
src="https://tommyodland.com/images/articles/kode24_lonn_2025/log_linear_effects.png"
class="img-responsive"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Nettsiden &lt;a href="https://www.kode24.no/"&gt;kode24&lt;/a&gt; har lenge hatt årlige spørreundersøkelser om lønn og publisert data i etterkant.
Vi kan finne datasett for &lt;a href="https://www.kode24.no/artikkel/vi-deler-ut-lonna-til-1542-utviklere-lag-noe-goy/136731"&gt;2021&lt;/a&gt;, &lt;a href="https://www.kode24.no/artikkel/vaer-sa-god-her-er-lonningene-til-over-1200-norske-utviklere/153993"&gt;2022&lt;/a&gt;, &lt;a href="https://www.kode24.no/artikkel/dykk-ned-i-kode24s-lonnstall-vis-oss-hva-du-lager/174298"&gt;2023&lt;/a&gt;, &lt;a href="https://www.kode24.no/artikkel/her-er-lonnstallene-for-norske-utviklere-2024/203888"&gt;2024&lt;/a&gt; og &lt;a href="https://www.kode24.no/artikkel/last-ned-kode24s-lonnstall-for-2025-og-lag-noe-sjaelv/244319"&gt;2025&lt;/a&gt; på&amp;nbsp;nettsidene.&lt;/p&gt;
&lt;p&gt;Jeg har tidligere gjort flere analyser av&amp;nbsp;spørreundersøkelsene:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I 2021 gjorde jeg en analyse i det &lt;a href="https://tommyodland.com/articles/2021/lonn-og-kjonn-blant-utviklere"&gt;probabilistiske programmeringsspråket&amp;nbsp;Stan&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;I 2023 brukte jeg &lt;a href="https://tommyodland.com/articles/2023/lonna-til-norske-utviklere-i-2023"&gt;generaliserte additive&amp;nbsp;modeller&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;I 2024 &lt;a href="https://tommyodland.com/articles/2024/lonna-til-norske-utviklere-i-2024"&gt;gjentok jeg analysen fra 2023 med det nye&amp;nbsp;datasettet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Basert på 2024-tallene hjalp jeg også til med å lage kodejobb.no&amp;rsquo;s &lt;a href="https://kodejobb.no/lonn"&gt;Lønnskalkulator&amp;nbsp;2024&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Det meste som kan sies har blitt sagt: konsulenter tjener noe mer enn fast ansatte, bosatte i Oslo får en lønnspremie, ledere og arkitekter tjener mest, kjønn er (heldigvis) ubetydelig, og antall år med erfaring dominerer alle andre variabler.
Størsteparten av variasjonen i lønn skyldes variabler som spørreundersøkelsene ikke plukker opp.
Til sammen forklarer variablene i lønnsundersøkelsene mellom 30% og 50% av variasjonen i lønna; hvor mye avhenger av datasettet og modellen.
Les mine tidligere analyser for en nøye&amp;nbsp;gjennomgang.&lt;/p&gt;
&lt;p&gt;Årets pro-bono bidrag til &lt;span class="caps"&gt;IT&lt;/span&gt;-miljøet blir å slå sammen alle datasettene og lage en robust &lt;a href="https://tommyodland.com/tools/salary.html"&gt;lønnskalulator&lt;/a&gt;.
Den er robust fordi den (1) baserer seg på nesten ti tusen observasjoner og (2) tar automatisk høyde for lønnsvekst i&amp;nbsp;fremtiden.&lt;/p&gt;
&lt;h1 id="datasett"&gt;Datasett&lt;/h1&gt;
&lt;p&gt;Det er fem datasett for fem ulike år.
&amp;ldquo;Data engineering&amp;rdquo; er alltid mer jobb enn man håper, og innsatsen for å slå sammen disse datasettene var ikke noe unntak.
Jeg&amp;nbsp;måtte:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sammenstille lønnstall på format&amp;nbsp;som &lt;code&gt;600 000&lt;/code&gt; med &lt;code&gt;600.000,00 kr&lt;/code&gt; og&amp;nbsp;lignende.&lt;/li&gt;
&lt;li&gt;Sammenstille små variasjoner i spørsmålene fra år til år,&amp;nbsp;f.eks. &lt;code&gt;I hvilket fylke ligger jobben din?&lt;/code&gt; og &lt;code&gt;I hvilket fylke jobber du?&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Ta hensyn til at det noen år var spurt om lønn med bonus og andre år lønn uten bonus. Dette fikser jeg med en latent variabel i modelleringen, men vi går ikke inn på det i denne&amp;nbsp;artikkelen.&lt;/li&gt;
&lt;li&gt;Fjerne tvilsomme observasjoner (omtrent 1.5% av dataene, der lønn var under 400 &lt;span class="caps"&gt;TNOK&lt;/span&gt; eller over 2000 &lt;span class="caps"&gt;TNOK&lt;/span&gt;).&lt;/li&gt;
&lt;li&gt;Noen har sannsynligvis tolket spørsmålet &amp;ldquo;Hvor mange års relevant, formell utdannelse har du?&amp;rdquo; til å inkludere barneskolen, ungdomsskolen og &lt;span class="caps"&gt;VGS&lt;/span&gt;. Dette må fikses etter beste evne (jeg håper og tror ingen har vært student på et universitet i mer enn femten&amp;nbsp;år).&lt;/li&gt;
&lt;li&gt;Ta hensyn til &lt;a href="https://no.wikipedia.org/wiki/Regionreformen_i_Norge"&gt;regionreformen&lt;/a&gt; og dens delvise&amp;nbsp;omgjørelse.&lt;/li&gt;
&lt;li&gt;Slå sammen fagdisipliner, f.eks.&amp;nbsp;var &lt;code&gt;arkitektur&lt;/code&gt; et alternativ noen år, mens det&amp;nbsp;het &lt;code&gt;arkitekt&lt;/code&gt; andre&amp;nbsp;år.&lt;/li&gt;
&lt;li&gt;Slå sammen spørsmål: tidligere var jobb (in-house, konsulent, osv.) og sektor (privat, offentlig) to spørsmål&amp;mdash;nå er det slått sammen til ett&amp;nbsp;spørsmål.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Etter en grundig datavask står vi igjen med 9610 observasjoner.
En del av disse er nok de samme personene som svarer på nytt, men det tenker jeg at er greit i denne sammenhengen.
Som alltid er det masse forbehold: dataene er selvrapporterte, kode24&amp;rsquo;s lesere er ikke representative for utviklere generelt, osv.
Nedenfor er lønn og erfaring visualisert for alle&amp;nbsp;årene.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 600px;"
src="https://tommyodland.com/images/articles/kode24_lonn_2025/erfaring_vs_lønn.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Medianlønna har økt de siste fem årene, men det har også gjennomsnittlig erfaring blant kode24&amp;rsquo;s&amp;nbsp;lesere.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: right;"&gt;  år&lt;/th&gt;
&lt;th style="text-align: right;"&gt;  erfaring&lt;/th&gt;
&lt;th style="text-align: right;"&gt;  lønn&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;2021&lt;/td&gt;
&lt;td style="text-align: right;"&gt;       8.2&lt;/td&gt;
&lt;td style="text-align: right;"&gt;   690&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;2022&lt;/td&gt;
&lt;td style="text-align: right;"&gt;       8.3&lt;/td&gt;
&lt;td style="text-align: right;"&gt;   725&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;2023&lt;/td&gt;
&lt;td style="text-align: right;"&gt;       8.8&lt;/td&gt;
&lt;td style="text-align: right;"&gt;   790&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;2024&lt;/td&gt;
&lt;td style="text-align: right;"&gt;       8.5&lt;/td&gt;
&lt;td style="text-align: right;"&gt;   850&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;2025&lt;/td&gt;
&lt;td style="text-align: right;"&gt;       9.4&lt;/td&gt;
&lt;td style="text-align: right;"&gt;   880&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;En regresjon på log-lønna avslører at den gjennomsnittlige lønnsveksten har vært omtrent 6.4% de siste årene, men dette tar ikke hensyn til at erfaringen også har økt.
Om vi korrigerer for dette blir &lt;strong&gt;lønnsøkningen omtrent 5.1% per år&lt;/strong&gt;.&lt;/p&gt;
&lt;h1 id="lnnskalkulatorer"&gt;Lønnskalkulatorer&lt;/h1&gt;
&lt;p&gt;En kalkulator implementerer vanligvis en kjent formel.
For eksempel implementerer en skattekalkulator formelen for skatt (noe som kan være &lt;a href="https://tommyodland.com/articles/2024/smooth-taxes-without-brackets"&gt;utfordrende&lt;/a&gt; å finne ut av).
På samme måte kan en lønnskalkulator implementere &amp;ldquo;formelen for lønn&amp;rdquo;, men den vet vi ikke hva er.
Derfor må vi først bruke en modell til å lære en formel fra datasettet, og deretter implementere denne.
Et alternativ er å ikke implementere noen formel i det hele tatt, men heller bruke en såkalt &lt;em&gt;ikke-parametrisk modell&lt;/em&gt; og vise brukeren sammenlignbare personer.
Vi skal se på disse to alternativene i denne artikkelen, men det finnes andre muligheter&amp;nbsp;også.&lt;/p&gt;
&lt;p&gt;Uansett fremgangsmåte bør en god&amp;nbsp;kalkulator:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Predikere godt.&lt;/strong&gt; Estimert lønn bør matche faktisk lønn for så mange som mulig. Dette kan&amp;nbsp;kryssvalideres.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kommunisere usikkerheten.&lt;/strong&gt; I dette datasettet har hver prediksjon stor usikkerhet, og det må brukeren&amp;nbsp;forstå.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="ikke-parametriske-modeller"&gt;Ikke-parametriske&amp;nbsp;modeller&lt;/h2&gt;
&lt;p&gt;Anta at en typisk utvikler ønsker å vite hva han eller hun kan forvente å&amp;nbsp;tjene:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;år&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;fylke&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Oslo&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;jobb&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;in-house, privat sektor&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;&amp;#39;erfaring&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;utdannelse&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;fag&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Fullstack&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="k-nrmeste-naboer"&gt;K-nærmeste&amp;nbsp;naboer&lt;/h3&gt;
&lt;p&gt;Vi kan bruke &lt;a href="https://en.wikipedia.org/wiki/K-nearest_neighbors_algorithm"&gt;K-nærmeste naboer&lt;/a&gt;&amp;nbsp;med &lt;span class="math"&gt;\(k=10\)&lt;/span&gt; på 2025-data for å vise ti personer som ligner.
Disse&amp;nbsp;er&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: right;"&gt;  erfaring&lt;/th&gt;
&lt;th style="text-align: right;"&gt;  utdannelse&lt;/th&gt;
&lt;th style="text-align: left;"&gt;fylke  &lt;/th&gt;
&lt;th style="text-align: left;"&gt;jobb                    &lt;/th&gt;
&lt;th style="text-align: left;"&gt;fag      &lt;/th&gt;
&lt;th style="text-align: right;"&gt;    lønn&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         5&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;in-house, privat sektor&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Fullstack&lt;/td&gt;
&lt;td style="text-align: right;"&gt; 775    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         7&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;in-house, privat sektor&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Fullstack&lt;/td&gt;
&lt;td style="text-align: right;"&gt; 803    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;in-house, privat sektor&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Fullstack&lt;/td&gt;
&lt;td style="text-align: right;"&gt; 819    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         5&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;in-house, privat sektor&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Fullstack&lt;/td&gt;
&lt;td style="text-align: right;"&gt; 877    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;in-house, privat sektor&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Fullstack&lt;/td&gt;
&lt;td style="text-align: right;"&gt; 895    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;in-house, privat sektor&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Fullstack&lt;/td&gt;
&lt;td style="text-align: right;"&gt; 900    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;in-house, privat sektor&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Fullstack&lt;/td&gt;
&lt;td style="text-align: right;"&gt; 914.375&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         5&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;in-house, privat sektor&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Fullstack&lt;/td&gt;
&lt;td style="text-align: right;"&gt; 950    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;in-house, privat sektor&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Fullstack&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1000    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;in-house, privat sektor&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Fullstack&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1400    &lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Her var vi heldige, fordi det er mange personer i Oslo med lignende egenskaper.
Medianlønna blant disse ti personene er 897 &lt;span class="caps"&gt;TNOK&lt;/span&gt;, og 80% ligger i intervallet mellom 800 og 1040 &lt;span class="caps"&gt;TNOK&lt;/span&gt;.&lt;/p&gt;
&lt;h3 id="beslutningstre"&gt;Beslutningstre&lt;/h3&gt;
&lt;p&gt;Dersom vi bruker et &lt;a href="https://en.wikipedia.org/wiki/Decision_tree"&gt;beslutningstre&lt;/a&gt; i stedet, får vi følgende tabell.
Modellen er satt til å alltid returnere minst ti personer, og for denne spørringen returnerer den 15.
Sammenlignet med K-nærmeste naboer er beslutningstreet mer opptatt av erfaring, og mindre opptatt av fag og&amp;nbsp;jobb.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: right;"&gt;  erfaring&lt;/th&gt;
&lt;th style="text-align: right;"&gt;  utdannelse&lt;/th&gt;
&lt;th style="text-align: left;"&gt;fylke  &lt;/th&gt;
&lt;th style="text-align: left;"&gt;jobb                                  &lt;/th&gt;
&lt;th style="text-align: left;"&gt;fag                      &lt;/th&gt;
&lt;th style="text-align: right;"&gt;    lønn&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;frilans / selvstendig næringsdrivende&lt;/td&gt;
&lt;td style="text-align: left;"&gt;DevOps og Automatisering&lt;/td&gt;
&lt;td style="text-align: right;"&gt; 500    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;in-house, privat sektor              &lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;span class="caps"&gt;UX&lt;/span&gt; og Design            &lt;/td&gt;
&lt;td style="text-align: right;"&gt; 650    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;konsulent                            &lt;/td&gt;
&lt;td style="text-align: left;"&gt;Ledelse                  &lt;/td&gt;
&lt;td style="text-align: right;"&gt; 735    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;konsulent                            &lt;/td&gt;
&lt;td style="text-align: left;"&gt;Frontend                &lt;/td&gt;
&lt;td style="text-align: right;"&gt; 780    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;in-house, privat sektor              &lt;/td&gt;
&lt;td style="text-align: left;"&gt;Fullstack                &lt;/td&gt;
&lt;td style="text-align: right;"&gt; 819    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;konsulent                            &lt;/td&gt;
&lt;td style="text-align: left;"&gt;Testing                  &lt;/td&gt;
&lt;td style="text-align: right;"&gt; 830    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;in-house, privat sektor              &lt;/td&gt;
&lt;td style="text-align: left;"&gt;Fullstack                &lt;/td&gt;
&lt;td style="text-align: right;"&gt; 895    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;in-house, privat sektor              &lt;/td&gt;
&lt;td style="text-align: left;"&gt;Frontend                &lt;/td&gt;
&lt;td style="text-align: right;"&gt; 900    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;in-house, privat sektor              &lt;/td&gt;
&lt;td style="text-align: left;"&gt;Fullstack                &lt;/td&gt;
&lt;td style="text-align: right;"&gt; 900    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;in-house, privat sektor              &lt;/td&gt;
&lt;td style="text-align: left;"&gt;Fullstack                &lt;/td&gt;
&lt;td style="text-align: right;"&gt; 914.375&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;konsulent                            &lt;/td&gt;
&lt;td style="text-align: left;"&gt;Fullstack                &lt;/td&gt;
&lt;td style="text-align: right;"&gt; 918    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;konsulent                            &lt;/td&gt;
&lt;td style="text-align: left;"&gt;Frontend                &lt;/td&gt;
&lt;td style="text-align: right;"&gt; 940    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;in-house, privat sektor              &lt;/td&gt;
&lt;td style="text-align: left;"&gt;Fullstack                &lt;/td&gt;
&lt;td style="text-align: right;"&gt;1000    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;in-house, privat sektor              &lt;/td&gt;
&lt;td style="text-align: left;"&gt;Fullstack                &lt;/td&gt;
&lt;td style="text-align: right;"&gt;1400    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;konsulent                            &lt;/td&gt;
&lt;td style="text-align: left;"&gt;Fullstack                &lt;/td&gt;
&lt;td style="text-align: right;"&gt;1400    &lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Medianlønna blant disse 15 personene er 900 &lt;span class="caps"&gt;TNOK&lt;/span&gt;, og 80% ligger i intervallet mellom 684 og 1240 &lt;span class="caps"&gt;TNOK&lt;/span&gt;.&lt;/p&gt;
&lt;h3 id="sammenligning-k-nrmeste-naboer-vs-beslutningstre"&gt;Sammenligning: K-nærmeste naboer vs.&amp;nbsp;beslutningstre&lt;/h3&gt;
&lt;p&gt;Vi kan sammenligne Mean Absolute Error (&lt;span class="caps"&gt;MAE&lt;/span&gt;) på en 10-folds kryssvalidering for å se hvor mange personer modellen bør returnere for å gi gode&amp;nbsp;prediksjoner.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 600px;"
src="https://tommyodland.com/images/articles/kode24_lonn_2025/knn_vs_tree.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Vi observerer&amp;nbsp;at:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Beslutningstrær er klart bedre enn K-nærmeste naboer, med lavere &lt;span class="caps"&gt;MAE&lt;/span&gt; over hele&amp;nbsp;linja.&lt;/li&gt;
&lt;li&gt;Så lenge vi returnerer omtrent 20 personer så får vi gode&amp;nbsp;prediksjoner.&lt;/li&gt;
&lt;li&gt;Jo flere personer vi returnerer, desto bedre kan vi estimere et intervall og si noe om&amp;nbsp;lønnspennet.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Grunnen til at beslutningstrær er bedre enn K-nærmeste naboer er fordi førstnevnte automatisk velger hvilke variabler som er viktige (erfaring er helt klart viktigst), mens sistnevnte må gå like langt ut i alle dimensjoner for å finne&amp;nbsp;naboer.&lt;/p&gt;
&lt;p&gt;Både K-nærmeste naboer og beslutningstrær har noen felles svakheter i denne&amp;nbsp;problemstillingen:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Nær endepunktene i hver variabel er de nødt til å se innover for å gjøre prediksjoner.
  For eksempel, dersom man har 45 års erfaring vil modellen ikke finne noen personer som har &lt;em&gt;mer&lt;/em&gt; erfaring, kun personer som har &lt;em&gt;mindre&lt;/em&gt; erfaring.
  Det samme gjelder de som har null år med erfaring eller null år med utdannelse.
  Dette er spesielt kritisk i høye dimensjoner, fordi da er sannsynligvis &lt;a href="https://www.johndcook.com/blog/2011/09/01/multivariate-normal-shell/"&gt;alle datapunktene nær endepunktene&lt;/a&gt;&amp;mdash;jo flere ting vi måler, desto mer er alle mennesker ulike på hver sin&amp;nbsp;måte!&lt;/li&gt;
&lt;li&gt;Vi brukte 2025-data til nå, men vi ønsker å bruke hele datasettet. 
  Vi vet at tidligere år har lavere lønn på grunn av generell lønnsøkning i samfunnet, men 2025-tallene er nær endepunktet på variabelen &amp;ldquo;år&amp;rdquo;.
  Derfor vil modellene ofte kun hente informasjon fra 2025 og ikke bruke hele datasettet.
  Begge modellen er lokale heller enn globale, og evner ikke å bruke informasjon fra tidligere år på en god&amp;nbsp;måte.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="parametriske-modeller"&gt;Parametriske&amp;nbsp;modeller&lt;/h2&gt;
&lt;p&gt;En parametrisk modell lærer parametrene i en formel som beskriver et datasett.
Fordelen er at hele datasettet kan komprimeres ned til én kort formel som beskriver sammenhengene.
Ulempen er at man må selv spesifisere en fornuftig formel.
Parametriske modeller er globale og ekstrapolerer vanligvis godt, og dette er en fordel i vår sammenheng&amp;mdash;spesielt med tanke på årstall inn i fremtiden og&amp;nbsp;lønnsinflasjon.&lt;/p&gt;
&lt;p&gt;Første idé er å bruke en &lt;a href="https://en.wikipedia.org/wiki/Ridge_regression"&gt;Ridge-modell&lt;/a&gt; og&amp;nbsp;minimere&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\text{minimize} \, &amp;amp;\lVert \text{lønn} - \boldsymbol{\mu} \rVert_2^2 + \alpha \lVert \boldsymbol{\beta} \rVert_2^2 \\
\mu &amp;amp;= \beta_0 + f(\text{erfaring}) + \beta_{\text{utdannelse}} \text{utdannelse} + \beta_{\text{fylke}} + \beta_{\text{jobb}} + \beta_{\text{fag}} \\
f(z) &amp;amp;= \beta_1 z + \beta_2 z^2 + \beta_3 z^3 + \cdots
\end{align*}&lt;/div&gt;
&lt;p&gt;og dette er et godt utgangspunkt som gir helt kurante resultater.
Høyere-ordens polynomer er vanligvis ingen god idé, fordi de kan overtilpasse og svinge voldsomt.
Det er derfor viktig å plotte polynomet (noe som ble gjort men ikke er med i&amp;nbsp;artikkelen).&lt;/p&gt;
&lt;p&gt;Hvordan kan vi forbedre denne modellen?
For det første vil vi finne medianlønna heller enn gjennomsnittslønna, fordi medianen er mindre påvirket av ekstreme observasjoner og er ofte et bedre valg i slike sammenhenger.
Det var derfor vi brukte Mean Absolute Error (&lt;span class="caps"&gt;MAE&lt;/span&gt;) heller enn Mean Squared Error (&lt;span class="caps"&gt;MSE&lt;/span&gt;) tidligere; &lt;a href="https://tommyodland.com/articles/2024/the-average-value"&gt;&lt;span class="caps"&gt;MAE&lt;/span&gt; er minimert av medianen&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For det andre er en tradisjonell regresjonsmodell påvirket av feil i absolutte tall, mens multiplikative feil gir mer mening i lønnssammenheng.
Med andre ord, å bomme med 100 &lt;span class="caps"&gt;TNOK&lt;/span&gt; når lønna er 500 &lt;span class="caps"&gt;TNOK&lt;/span&gt; er en stor feil, mens å bomme med 100 &lt;span class="caps"&gt;TNOK&lt;/span&gt; når lønna er 1200 &lt;span class="caps"&gt;TNOK&lt;/span&gt; er ikke like ille, fordi det er en mindre multiplikativ&amp;nbsp;feil.&lt;/p&gt;
&lt;p&gt;Vi endrer modellen til å bruke&amp;nbsp;en &lt;span class="math"&gt;\(\ell_1\)&lt;/span&gt;-norm på likelihood-leddet og tilpasse log-lønn heller enn&amp;nbsp;lønn:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\text{minimize} \, &amp;amp;\lVert \log \left( \text{lønn} \right) - \boldsymbol{\mu} \rVert_1 + \alpha \lVert \boldsymbol{\beta} \rVert_2^2
\end{align*}&lt;/div&gt;
&lt;p&gt;Dette minner om en &lt;a href="https://en.wikipedia.org/wiki/Lasso_(statistics)"&gt;Lasso&lt;/a&gt;, men med normene byttet om: vi&amp;nbsp;har &lt;span class="math"&gt;\(\ell_1\)&lt;/span&gt;-norm på likelihood-leddet&amp;nbsp;og &lt;span class="math"&gt;\(\ell_2\)&lt;/span&gt;-norm på regulariseringsleddet.
Her er koeffisientene i modellen (klikk på figuren for å se større&amp;nbsp;versjon):&lt;/p&gt;
&lt;p&gt;&lt;a href="https://tommyodland.com/images/articles/kode24_lonn_2025/log_linear_effects.png"&gt;
&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 780px;"
src="https://tommyodland.com/images/articles/kode24_lonn_2025/log_linear_effects.png"
class="img-responsive"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Legg merke til at lønnsveksten estimeres til 5.1% per&amp;nbsp;år.&lt;/p&gt;
&lt;p&gt;Å sammenligne mellom kategorier innebærer å konvertere prosentene til relative tall, ikke å legge dem sammen additivt.
For eksempel, dersom man går fra en in-house offentlig stilling til å bli konsulent, kan man forvente en lønnsøkning på&amp;nbsp;omtrent &lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\frac{1 + 5.9\%}{1 - 5.4\%} = \frac{1.059}{0.946}= 1.119 = 11.9\% .
\end{align*}&lt;/div&gt;
&lt;p&gt;Her er residualene til&amp;nbsp;modellen:&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 740px;"
src="https://tommyodland.com/images/articles/kode24_lonn_2025/log_linear_residuals.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Dette er relativt pene residualer, fordi det ikke er stor endring i residualene over lønnsintervallet, såkalt heteroskadist&amp;hellip; hetroskedasisti&amp;#8230;. &lt;a href="https://no.wikipedia.org/wiki/Heteroskedastisitet"&gt;heteroskedastisitet&lt;/a&gt;.
Polynomet som ble brukt var et fjerdegradspolynom, som på grunn av regularisering oppfører seg pent når erfaring er mellom null og førti&amp;nbsp;år.&lt;/p&gt;
&lt;p&gt;La oss kjøre vår &amp;ldquo;typiske&amp;rdquo; utvikler gjennom alle tre&amp;nbsp;modellene:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;år&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;fylke&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Oslo&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;jobb&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;in-house, privat sektor&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;&amp;#39;erfaring&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;utdannelse&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;fag&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Fullstack&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Vi observerer noe ulike estimater av medianlønn og intervaller fra de ulike&amp;nbsp;modellene:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;span class="caps"&gt;KNN&lt;/span&gt;&lt;/strong&gt;: 897 &lt;span class="caps"&gt;TNOK&lt;/span&gt; med 80% intervall mellom 800 og 1040 &lt;span class="caps"&gt;TNOK&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tre&lt;/strong&gt;: 900 &lt;span class="caps"&gt;TNOK&lt;/span&gt; med 80% intervall mellom 684 og 1240 &lt;span class="caps"&gt;TNOK&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lineær modell&lt;/strong&gt;: 918 &lt;span class="caps"&gt;TNOK&lt;/span&gt; med 80% intervall mellom 748 og 1136 &lt;span class="caps"&gt;TNOK&lt;/span&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Intervallene fra de ikke-parametriske modellene (&lt;span class="caps"&gt;KNN&lt;/span&gt; og tre) er mindre robuste, ettersom de er basert på henholdsvis 10 og 15 observasjoner.
Intervallet fra den lineære modellen er basert på alle datapunktene, og har høyere&amp;nbsp;kvalitet.&lt;/p&gt;
&lt;p&gt;La oss nå sammenligne &lt;span class="caps"&gt;MAE&lt;/span&gt; for alle modellene, samt en svak dummy-modell som alltid predikerer medianen og en kraftig gradient boosting&amp;nbsp;modell.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt; Modell / Metrikk&lt;/th&gt;
&lt;th&gt; &lt;span class="caps"&gt;MAE&lt;/span&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Dummy            &lt;/td&gt;
&lt;td&gt;202  &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span class="caps"&gt;KNN&lt;/span&gt;              &lt;/td&gt;
&lt;td&gt;152  &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tre              &lt;/td&gt;
&lt;td&gt;128  &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lineær            &lt;/td&gt;
&lt;td&gt;119  &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gradient boosting&lt;/td&gt;
&lt;td&gt;114  &lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Gradient boosting har lavest &lt;span class="caps"&gt;MAE&lt;/span&gt;, men den lineære modellen er bare marginalt verre.
Likevel har den noen klare&amp;nbsp;fordeler:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Det estimerte intervallet (usikkerheten) er nok mer robust og&amp;nbsp;realistisk.&lt;/li&gt;
&lt;li&gt;Den er veldig enkel å implementere i Javascript. Man bruker bare&amp;nbsp;formelen.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="oppsummering"&gt;Oppsummering&lt;/h1&gt;
&lt;p&gt;Kode24 har gjort en kjempejobb med å samle inn og publisere data på utvikleres lønn.
I denne artikkelen kombinerte vi fem år med lønnsdata til ett datasett med 9610 observasjoner, etter datavask og sammenstilling av variabler på tvers av&amp;nbsp;år.&lt;/p&gt;
&lt;p&gt;Vi sammenlignet to ulike tilnærminger til&amp;nbsp;lønnskalkulatorer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Ikke-parametriske modeller&lt;/strong&gt; (&lt;span class="caps"&gt;KNN&lt;/span&gt; og beslutningstrær) som viser faktiske, sammenlignbare&amp;nbsp;personer&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Parametriske modeller&lt;/strong&gt; (log-lineær regresjon&amp;nbsp;med &lt;span class="math"&gt;\(\ell_1\)&lt;/span&gt;-loss) som bruker en global&amp;nbsp;formel&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Den enkle log-lineære modellen (&lt;span class="caps"&gt;MAE&lt;/span&gt;: 119) presterte bedre enn de to ikke-parametriske modellene, fordi &lt;span class="caps"&gt;MAE&lt;/span&gt; var lavere og intervallene var mer robuste. 
Faktisk hadde modellen nesten like lav &lt;span class="caps"&gt;MAE&lt;/span&gt; som gradient boosting (&lt;span class="caps"&gt;MAE&lt;/span&gt;: 114), men med fordelen av å være tolkbar og enkel å implementere i JavaScript.
Beslutningstrær var gode for å finne sammenlignbare personer, betydelig bedre enn K-nærmeste&amp;nbsp;naboer.&lt;/p&gt;
&lt;p&gt;Når det gjelder metodikk for lønnskalkulatorer er lærdommen klar: prioriter &lt;a href="https://eugeneyan.com/writing/simplicity/"&gt;enkelhet&lt;/a&gt; og transparens.
Brukere trenger to ting: (1) fornuftige prediksjoner og (2) ærlig kommunikasjon om usikkerheten. 
&lt;a href="https://tommyodland.com/tools/salary.html"&gt;Kalkulatoren&lt;/a&gt; implementerer den enkle lineære&amp;nbsp;modellen.&lt;/p&gt;
&lt;p&gt;Selv om jeg fulgte &lt;a href="https://tommyodland.com/articles/2024/the-ten-commandments-of-data-science"&gt;bud (5) og bud (9)&lt;/a&gt; har jeg dårlig samvittighet for å ha brukt et fjerdegradspolynom i modelleringen uten progressiv regularisering&amp;mdash;men jeg slapp unna med det på partial dependence plottet. Aldri&amp;nbsp;igjen.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Wed, 17 Sep 2025 00:00:00 +0200</pubDate><guid>tag:tommyodland.com,2025-09-17:/articles/2025/lonna-til-norske-utviklere-i-2025</guid><category>articles</category><category>datascience</category></item><item><title>People rating people</title><link>https://tommyodland.com/articles/2025/people-rating-people</link><description>&lt;p&gt;Ratings and rankings are everywhere in daily life.
We rank &lt;a href="https://tommyodland.com/articles/2020/modeling-the-outcome-of-football-matches"&gt;sports teams&lt;/a&gt;, chess players, movies and &lt;span class="caps"&gt;TV&lt;/span&gt;-shows, &lt;a href="https://tommyodland.com/articles/2023/ranking-doctors"&gt;doctors&lt;/a&gt;, local businesses, and sometimes even each&amp;nbsp;other.&lt;/p&gt;
&lt;p&gt;This article is about computing an overall rating in a setting where people have individually rated each other.
Examples include dating apps like Tinder, e-commerce websites where people act as both sellers and buyers, and other platforms where we want to quantify the trustworthiness or quality of&amp;nbsp;members.&lt;/p&gt;
&lt;p&gt;We distinguish between two&amp;nbsp;scenarios:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Symmetry&lt;/strong&gt;: if&amp;nbsp;person &lt;span class="math"&gt;\(a\)&lt;/span&gt; rates &lt;span class="math"&gt;\(b\)&lt;/span&gt;,&amp;nbsp;then &lt;span class="math"&gt;\(b\)&lt;/span&gt; must also&amp;nbsp;rate &lt;span class="math"&gt;\(a\)&lt;/span&gt;. Their rating values may&amp;nbsp;differ.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No symmetry&lt;/strong&gt;: if&amp;nbsp;person &lt;span class="math"&gt;\(a\)&lt;/span&gt; rates &lt;span class="math"&gt;\(b\)&lt;/span&gt;, then it does not necessarily follow&amp;nbsp;that &lt;span class="math"&gt;\(b\)&lt;/span&gt; rates &lt;span class="math"&gt;\(a\)&lt;/span&gt; back.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this article we&amp;rsquo;ll consider the more general &lt;strong&gt;non-symmetric&lt;/strong&gt; scenario.
Given individual ratings that are either &lt;em&gt;positive&lt;/em&gt; or &lt;em&gt;negative&lt;/em&gt;, the goal is to produce an overall rating of everyone.
The figure below shows 5 people, where blue arrows indicate positive ratings and red arrows indicate negative&amp;nbsp;ratings.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 500px; width: 95%;"
src="https://tommyodland.com/images/articles/people_rating_people/people_ratings.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;In the figure above, we&amp;nbsp;expect &lt;span class="math"&gt;\(a\)&lt;/span&gt; to be highly rated&amp;nbsp;and &lt;span class="math"&gt;\(c\)&lt;/span&gt; to be poorly&amp;nbsp;rated.&lt;/p&gt;
&lt;h1 id="simple-aggregation"&gt;Simple&amp;nbsp;aggregation&lt;/h1&gt;
&lt;p&gt;The simplest thing we can do is to count the number of ratings and create a&amp;nbsp;ratio &lt;span class="math"&gt;\(r_i\)&lt;/span&gt; between &lt;span class="math"&gt;\(0\)&lt;/span&gt; and &lt;span class="math"&gt;\(1\)&lt;/span&gt; for each&amp;nbsp;person.&lt;/p&gt;
&lt;p&gt;We define a binary&amp;nbsp;matrix &lt;span class="math"&gt;\(P\)&lt;/span&gt; consisting of positive ratings and a&amp;nbsp;matrix &lt;span class="math"&gt;\(N\)&lt;/span&gt; of negative&amp;nbsp;ratings.&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
P_{ij} &amp;amp;= \begin{cases}
1 &amp;amp; \text{if person } i \text{ is rated positively by person } j \\
0 &amp;amp; \text{otherwise}
\end{cases} \\
N_{ij} &amp;amp;= \begin{cases}
1 &amp;amp; \text{if person } i \text{ is rated negatively by person } j \\
0 &amp;amp; \text{otherwise}
\end{cases}
\end{align*}&lt;/div&gt;
&lt;p&gt;The rating&amp;nbsp;vector &lt;span class="math"&gt;\(\boldsymbol{r}\)&lt;/span&gt; is computed by summing positive ratings and dividing by total ratings.
One issue with the naive approach is that someone who has few ratings might end up with a total rating&amp;nbsp;of &lt;span class="math"&gt;\(0\)&lt;/span&gt; or &lt;span class="math"&gt;\(1\)&lt;/span&gt;, so we employ the &lt;a href="https://en.wikipedia.org/wiki/Rule_of_succession"&gt;rule of succession&lt;/a&gt; by adding two dummy ratings: one positive and one negative.
The equation&amp;nbsp;becomes&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
r_i &amp;amp;= \frac{1 + \sum_j P_{ij}}{2 + \sum_j P_{ij} + \sum_j N_{ij}}
\end{align*}&lt;/div&gt;
&lt;p&gt;Or alternatively, in matrix notation,&amp;nbsp;where &lt;span class="math"&gt;\(\boldsymbol{1}\)&lt;/span&gt; and &lt;span class="math"&gt;\(\boldsymbol{2}\)&lt;/span&gt; denote vectors of ones and twos and division is&amp;nbsp;entry-wise:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\boldsymbol{r} &amp;amp;= \frac{\boldsymbol{1} + P \boldsymbol{1}}{\boldsymbol{2} + P \boldsymbol{1} + N \boldsymbol{1}}
\end{align*}&lt;/div&gt;
&lt;p&gt;Here&amp;rsquo;s a piece of Python code that implements this computation on the graph structure shown in the initial&amp;nbsp;figure:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;numpy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;np&lt;/span&gt;

&lt;span class="n"&gt;P&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
     &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;- Positive ratings given to A&lt;/span&gt;
     &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;- Positive ratings given to B&lt;/span&gt;
     &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
     &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
     &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;- Positive ratings given to E&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
     &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;- Negative ratings given to A&lt;/span&gt;
     &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;- Negative ratings given to B&lt;/span&gt;
     &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
     &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
     &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;- Negative ratings given to E&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Simple aggregation&lt;/span&gt;
&lt;span class="n"&gt;ratings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ratings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# [0.83 0.33 0.2  0.5  0.67]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The results are superimposed on the graph in the figure&amp;nbsp;below.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 500px; width: 95%;"
src="https://tommyodland.com/images/articles/people_rating_people/people_ratings_result_simple.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;The popular&amp;nbsp;person &lt;span class="math"&gt;\(a\)&lt;/span&gt; gets the highest&amp;nbsp;rating &lt;span class="math"&gt;\(0.83\)&lt;/span&gt; as expected, and unpopular&amp;nbsp;person &lt;span class="math"&gt;\(c\)&lt;/span&gt; gets the lowest&amp;nbsp;rating &lt;span class="math"&gt;\(0.2\)&lt;/span&gt; as&amp;nbsp;expected.&lt;/p&gt;
&lt;h1 id="weighted-aggregation"&gt;Weighted&amp;nbsp;aggregation&lt;/h1&gt;
&lt;p&gt;Perhaps all users in a system should not be considered equally trustworthy.
In other words,&amp;nbsp;if &lt;span class="math"&gt;\(a\)&lt;/span&gt; rates &lt;span class="math"&gt;\(b\)&lt;/span&gt; positively, then our opinion&amp;nbsp;of &lt;span class="math"&gt;\(b\)&lt;/span&gt; &lt;em&gt;might also depend&amp;nbsp;on &lt;span class="math"&gt;\(a\)&lt;/span&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If &lt;span class="math"&gt;\(a\)&lt;/span&gt; is a long-time user, who is positively rated by others and who has given balanced ratings to others, then we should&amp;nbsp;trust &lt;span class="math"&gt;\(a\)&lt;/span&gt;&amp;rsquo;s&amp;nbsp;ratings.&lt;/li&gt;
&lt;li&gt;If &lt;span class="math"&gt;\(a\)&lt;/span&gt; is a new user, or is negatively rated by others, or rates everyone negatively, then we should perhaps &lt;em&gt;not&lt;/em&gt;&amp;nbsp;trust &lt;span class="math"&gt;\(a\)&lt;/span&gt;&amp;rsquo;s ratings as&amp;nbsp;much.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We weight each user with a&amp;nbsp;weight &lt;span class="math"&gt;\(w_j \in [0, 1]\)&lt;/span&gt; before we aggregate their individual&amp;nbsp;ratings:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
r_i &amp;amp;= \frac{1 + \sum_j P_{ij} w_j}{2 + \sum_j P_{ij} w_j + \sum_j N_{ij} w_j}
\end{align*}&lt;/div&gt;
&lt;p&gt;How do we choose the&amp;nbsp;weights &lt;span class="math"&gt;\(w_j\)&lt;/span&gt;?
There are many possibilities, but the&amp;nbsp;weights &lt;span class="math"&gt;\(w_j\)&lt;/span&gt; must somehow represent the trustworthiness of&amp;nbsp;person &lt;span class="math"&gt;\(j\)&lt;/span&gt;.&lt;/p&gt;
&lt;h2 id="weighting-by-discrimination"&gt;Weighting by&amp;nbsp;discrimination&lt;/h2&gt;
&lt;p&gt;One idea is to look at how much &lt;em&gt;discrimination&lt;/em&gt; each user applies.
Whose ratings do you trust more: someone who rates everyone positively, or someone who rates half the population negatively and the other half positively?
If you agree with me that the second person provides more useful information, then one option is to compute a scaled version of the &lt;a href="https://en.wikipedia.org/wiki/Entropy_(information_theory)"&gt;entropy&lt;/a&gt;:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
H(p, n) = \frac{1}{\ln 2} \left[ \frac{p}{p+n} \ln \left( \frac{p+n}{p} \right) + \frac{n}{p+n} \ln \left(\frac{p+n}{n}\right) \right],
\end{align*}&lt;/div&gt;
&lt;p&gt;where &lt;span class="math"&gt;\(n\)&lt;/span&gt; is the number of negative ratings the person has given&amp;nbsp;and &lt;span class="math"&gt;\(p\)&lt;/span&gt; is the number of positive&amp;nbsp;ratings.&lt;/p&gt;
&lt;p&gt;Observe&amp;nbsp;that &lt;span class="math"&gt;\(H(p=k, n=k) = 1\)&lt;/span&gt; for&amp;nbsp;all &lt;span class="math"&gt;\(k \geq 1\)&lt;/span&gt; and&amp;nbsp;that &lt;span class="math"&gt;\(H(p=k, n=0) = H(p=0, n=k) = 0\)&lt;/span&gt; for&amp;nbsp;all &lt;span class="math"&gt;\(k \geq 1\)&lt;/span&gt;.
In other words, this weighting function accomplishes what we set out to do: users with high discriminatory power are given a weight of one, while users who show no discrimination in their ratings are given a weight of&amp;nbsp;zero.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;scipy.stats&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;entropy&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;H&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;entropy&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# 1.0&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# 0.0&lt;/span&gt;

&lt;span class="n"&gt;weights&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entropy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))),&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Weighted positive and negative sums&lt;/span&gt;
&lt;span class="n"&gt;P_w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;weights&lt;/span&gt;
&lt;span class="n"&gt;N_w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;weights&lt;/span&gt;

&lt;span class="c1"&gt;# Weighted ratings&lt;/span&gt;
&lt;span class="n"&gt;ratings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;P_w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;P_w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N_w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ratings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# [0.83 0.34 0.21 0.49 0.67]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In this particular problem instance, the results do not differ much from the previous&amp;nbsp;ratings.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 500px; width: 95%;"
src="https://tommyodland.com/images/articles/people_rating_people/people_ratings_result_weighted.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Entropy is by no means the only possible quality measure&amp;mdash;it depends strongly on the specific use-case.
A number of more complicated weighting functions could be used.
For instance, we could also weight a user&amp;nbsp;by:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;how many ratings they have given in&amp;nbsp;total&lt;/li&gt;
&lt;li&gt;how long they have been users in the&amp;nbsp;system/platform&lt;/li&gt;
&lt;li&gt;how much information they have on their&amp;nbsp;profile&lt;/li&gt;
&lt;li&gt;how well they themselves are rated by&amp;nbsp;others.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;All of these signal high levels of trustworthiness.
We will now pursue option (4): we&amp;rsquo;ll weight each person&amp;rsquo;s ratings by &lt;em&gt;how highly they themselves are rated&lt;/em&gt;.&lt;/p&gt;
&lt;h1 id="weighted-aggregation-by-rating"&gt;Weighted aggregation by&amp;nbsp;rating&lt;/h1&gt;
&lt;p&gt;The final model that we&amp;rsquo;ll consider is the most advanced one.
The idea is that the rating&amp;nbsp;person &lt;span class="math"&gt;\(a\)&lt;/span&gt; gives to&amp;nbsp;person &lt;span class="math"&gt;\(b\)&lt;/span&gt; should be weighted by &lt;em&gt;the rating&amp;nbsp;person &lt;span class="math"&gt;\(a\)&lt;/span&gt; has&lt;/em&gt;.
Building on the matrix notation we established earlier, we write the equation&amp;nbsp;as:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\boldsymbol{r} &amp;amp;= \frac{\boldsymbol{1} + P \boldsymbol{r}}{\boldsymbol{2} + P \boldsymbol{r} + N \boldsymbol{r}}
\end{align*}&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;In an e-commerce platform, this means that&amp;nbsp;if &lt;span class="math"&gt;\(a\)&lt;/span&gt; is highly rated and gives a positive rating, then that is worth more than a positive rating from a user with mixed or negative&amp;nbsp;ratings.&lt;/li&gt;
&lt;li&gt;In a dating app like Tinder where ratings signify attractiveness, the interpretation is that if someone who is attractive rates us favorably, then that rating is worth more than a rating from someone who is not considered attractive by&amp;nbsp;others.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Notice the recursive definition: to weight the ratings given by&amp;nbsp;person &lt;span class="math"&gt;\(a\)&lt;/span&gt; to others, we need to know the overall rating of&amp;nbsp;person &lt;span class="math"&gt;\(a\)&lt;/span&gt;&amp;mdash;but that quantity is exactly what we&amp;rsquo;re trying to compute in the first&amp;nbsp;place!&lt;/p&gt;
&lt;h2 id="solution-by-fixed-point-iteration"&gt;Solution by fixed-point&amp;nbsp;iteration&lt;/h2&gt;
&lt;p&gt;The simplest and fastest way to&amp;nbsp;compute &lt;span class="math"&gt;\(\boldsymbol{r}\)&lt;/span&gt; is to iterate the equation until it converges.
Starting with an initial&amp;nbsp;guess &lt;span class="math"&gt;\(\boldsymbol{r}_0 = \boldsymbol{1} / 2\)&lt;/span&gt; we&amp;nbsp;compute
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\boldsymbol{r}_{k+1} &amp;amp;= \frac{\boldsymbol{1} + P \boldsymbol{r}_{k}}{\boldsymbol{2} + P \boldsymbol{r}_{k} + N \boldsymbol{r}_{k}}
\end{align*}&lt;/div&gt;
&lt;p&gt;
repeatedly until convergence, as measured&amp;nbsp;by &lt;span class="math"&gt;\(\lVert \boldsymbol{r}_{k+1} - \boldsymbol{r}_{k}  \rVert &amp;lt; \epsilon\)&lt;/span&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;epsilon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1e-12&lt;/span&gt;
&lt;span class="n"&gt;ratings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ones&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;iteration&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;new_ratings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;ratings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;ratings&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;ratings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_ratings&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;ratings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;epsilon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="n"&gt;ratings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_ratings&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ratings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# [0.74 0.44 0.29 0.42 0.63]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Convergence is linear, and after 13 iterations the procedure&amp;nbsp;terminates.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 500px; width: 95%;"
src="https://tommyodland.com/images/articles/people_rating_people/people_ratings_result.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;All three ratings induce rankings on the people, and they are compared in the figure below.
&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 400px; width: 95%;"
src="https://tommyodland.com/images/articles/people_rating_people/people_rating_people_results.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Notice&amp;nbsp;how:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Person &lt;span class="math"&gt;\(b\)&lt;/span&gt; moves up compared to the simple rating. He is given a negative rating&amp;nbsp;by &lt;span class="math"&gt;\(c\)&lt;/span&gt;,&amp;nbsp;but &lt;span class="math"&gt;\(c\)&lt;/span&gt; has a low overall rating and this lessens the weight given&amp;nbsp;to &lt;span class="math"&gt;\(c\)&lt;/span&gt;&amp;rsquo;s opinion&amp;nbsp;of &lt;span class="math"&gt;\(b\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Person &lt;span class="math"&gt;\(d\)&lt;/span&gt; moves down, because the negative rating&amp;nbsp;from &lt;span class="math"&gt;\(a\)&lt;/span&gt; matters a lot and the positive rating&amp;nbsp;from &lt;span class="math"&gt;\(c\)&lt;/span&gt; matters&amp;nbsp;little.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="summary-and-references"&gt;Summary and&amp;nbsp;references&lt;/h2&gt;
&lt;p&gt;Rating and ranking is interesting because it is part science and part art.
There&amp;rsquo;s no fixed answer: every scenario is different and must be modeled&amp;nbsp;appropriately.&lt;/p&gt;
&lt;p&gt;The problem considered in this article, where people rate people, is special in two ways: (1) there is just one group and (2) ratings are not symmetric.
The defining equation&amp;nbsp;is
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\boldsymbol{r} &amp;amp;= \frac{\boldsymbol{1} + P \boldsymbol{r}}{\boldsymbol{2} + P \boldsymbol{r} + N \boldsymbol{r}},
\end{align*}&lt;/div&gt;
&lt;p&gt;
which is non-linear in the&amp;nbsp;vector &lt;span class="math"&gt;\(\boldsymbol{r}\)&lt;/span&gt; and may contain millions of unknown variables.
Despite this, it&amp;rsquo;s solved by fixed-point iteration in a few seconds on a&amp;nbsp;laptop.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve previously written about how &lt;a href="https://tommyodland.com/articles/2023/ranking-doctors"&gt;patients can rate doctors&lt;/a&gt;.
In that problem there were two separate groups: doctors and patients.
One group rates the other group.
I&amp;rsquo;ve also written about &lt;a href="https://tommyodland.com/articles/2020/modeling-the-outcome-of-football-matches"&gt;rating football teams&lt;/a&gt;, which is a symmetric problem.
If&amp;nbsp;team &lt;span class="math"&gt;\(a\)&lt;/span&gt; plays &lt;span class="math"&gt;\(b\)&lt;/span&gt;,&amp;nbsp;then &lt;span class="math"&gt;\(b\)&lt;/span&gt; necessarily&amp;nbsp;plays &lt;span class="math"&gt;\(a\)&lt;/span&gt; too.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://en.wikipedia.org/wiki/PageRank"&gt;PageRank&lt;/a&gt; algorithm ranks websites, and it&amp;rsquo;s what made Google famous in the late 90s.
The paper &lt;a href="https://projecteuclid.org/journals/internet-mathematics/volume-2/issue-1/A-Survey-on-PageRank-Computing/im/1128530802.full"&gt;A Survey on PageRank Computing&lt;/a&gt; is an interesting read.
Similar algorithms include &lt;a href="https://en.wikipedia.org/wiki/HITS_algorithm"&gt;&lt;span class="caps"&gt;HITS&lt;/span&gt;&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/SALSA_algorithm"&gt;&lt;span class="caps"&gt;SALSA&lt;/span&gt;&lt;/a&gt; and &lt;a href="https://nlp.stanford.edu/pubs/eigentrust.pdf"&gt;EigenTrust&lt;/a&gt;.
The book &lt;a href="https://www.amazon.com/Whos-1-Science-Rating-Ranking/dp/069116231X"&gt;Who&amp;rsquo;s #1?: The Science of Rating and Ranking&lt;/a&gt; gives an overview of simple methods, with a focus on sports.
When users interact with each other, the field is sometimes called &lt;a href="https://en.wikipedia.org/wiki/Reputation_system"&gt;reputation systems&lt;/a&gt;.
The paper &lt;a href="https://www.jstor.org/stable/4134013"&gt;The Digitization of Word of Mouth: Promise and Challenges of Online Feedback Mechanisms&lt;/a&gt; explores the online reputation mechanisms of the early&amp;nbsp;internet. &lt;/p&gt;
&lt;p&gt;The ideas in the paper &lt;a href="https://snap.stanford.edu/class/cs224w-readings/guha04trust.pdf"&gt;Propagation of Trust and Distrust&lt;/a&gt; are similar to those expressed in this article, but the authors focus more on local propagation while we solve a global problem.
Reputation and ranking is closely related to content discovery, recommendation systems and &lt;a href="https://en.wikipedia.org/wiki/Collaborative_filtering"&gt;collaborative filtering&lt;/a&gt;, and the book &lt;a href="https://link.springer.com/book/10.1007/978-3-319-29659-3"&gt;Recommender Systems&lt;/a&gt; by Aggarwal is a handy reference.
This has social implications too, which the paper &lt;a href="https://arxiv.org/abs/1904.13316"&gt;On Social Machines for Algorithmic Regulation&lt;/a&gt;&amp;nbsp;explores.&lt;/p&gt;
&lt;!--
https://snap.stanford.edu/class/cs224w-readings/lauterbach09trust.pdf
https://math.stackexchange.com/questions/5088792/convergence-of-fixed-point-iteration-x-frac1-px2-px-nx
https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0050843
https://arxiv.org/abs/2309.02413v2
https://arxiv.org/abs/1304.7921
--&gt;

&lt;hr&gt;
&lt;h2 id="appendix-a-empirical-results"&gt;Appendix A: Empirical&amp;nbsp;results&lt;/h2&gt;
&lt;p&gt;I created random sparse rating matrices for a million people.
I set the density&amp;nbsp;to &lt;span class="math"&gt;\(p=10^{-5}\)&lt;/span&gt;, so each matrix has&amp;nbsp;approximately &lt;span class="math"&gt;\(\left( 10^6 \right)^2 \times p = 10^7\)&lt;/span&gt; non-negative entries.
Convergence is shown in the figure&amp;nbsp;below.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 600px; width: 95%;"
src="https://tommyodland.com/images/articles/people_rating_people/fixed_point_solver.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Below is the computation on the third largest strongly connected component of the &lt;a href="https://snap.stanford.edu/data/soc-sign-epinions.html"&gt;Epinions social network&lt;/a&gt; dataset.
The full dataset has 131,828 nodes and 841,372 and is solved quickly, but it&amp;rsquo;s too large to visualize in&amp;nbsp;full.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 450px; width: 95%;"
src="https://tommyodland.com/images/articles/people_rating_people/ratings_epinions.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;h2 id="appendix-b-solution-by-newtons-method"&gt;Appendix B: Solution by Newton&amp;rsquo;s&amp;nbsp;method&lt;/h2&gt;
&lt;p&gt;Newton&amp;rsquo;s method and its variants can be an alternative to fixed-point iteration.
While Newton&amp;rsquo;s method exhibits quadratic convergence (fixed-point iteration is linear), the time spent per iteration is so high that it ends up being an inferior choice in practice.
Also, if the starting point is not good&amp;nbsp;(&lt;span class="math"&gt;\(\boldsymbol{r} = 1/2\)&lt;/span&gt; tends to work,&amp;nbsp;while &lt;span class="math"&gt;\(\boldsymbol{r} = 0\)&lt;/span&gt; does not) the algorithm will fail.
Regardless, here&amp;rsquo;s how it can be&amp;nbsp;done.&lt;/p&gt;
&lt;p&gt;The matrix&amp;nbsp;equation
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\boldsymbol{r} &amp;amp;= \frac{1 + P \boldsymbol{r}}{2 + P \boldsymbol{r} + N \boldsymbol{r}},
\end{align*}&lt;/div&gt;
&lt;p&gt;
 is equivalent to&amp;nbsp;solving
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
F(\boldsymbol{r}) = \boldsymbol{r} \odot \left( \boldsymbol{2} + P \boldsymbol{r} + N \boldsymbol{r} \right) - \left( \boldsymbol{1} + P \boldsymbol{r} \right) = \boldsymbol{0}
\end{align*}&lt;/div&gt;
&lt;p&gt;
which we can do with &lt;a href="https://en.wikipedia.org/wiki/Newton%27s_method#Multidimensional_formulations"&gt;Newton&amp;rsquo;s method&lt;/a&gt;:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\boldsymbol{r}_{k+1} = \boldsymbol{r}_{k} - J(\boldsymbol{r}_{k})^{-1} F(\boldsymbol{r}_{k}).
\end{align*}&lt;/div&gt;
&lt;p&gt;To find the Jacobian&amp;nbsp;matrix &lt;span class="math"&gt;\(J_{ij}(\boldsymbol{r}) = \partial F_i(\boldsymbol{r}) / \partial \boldsymbol{r}_{j}\)&lt;/span&gt;, we first write out the components&amp;nbsp;of &lt;span class="math"&gt;\(F(\boldsymbol{r})\)&lt;/span&gt;.&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
F_i(\boldsymbol{r}) = r_i \left( 2 + \sum_k P_{ik} r_k + \sum_k N_{ik} r_k\right) - 1 - \sum_k P_{ik} r_k
\end{align*}&lt;/div&gt;
&lt;p&gt;Differentiating with respect&amp;nbsp;to &lt;span class="math"&gt;\(r_j\)&lt;/span&gt;, then switching back to matrix notation, we&amp;nbsp;obtain:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
J(\boldsymbol{r}) = \partial_{r_j}  F_i(\boldsymbol{r}) &amp;amp; = 
\delta_{ij} \left( 2 + \sum_k P_{ik} r_k + \sum_k N_{ik} r_k\right) + r_i \left( P_{ij} + N_{ij} \right) - 0 - P_{ij} \\
&amp;amp;= 2 I + \operatorname{diag}(P \boldsymbol{r} + N \boldsymbol{r}) + \operatorname{diag}(\boldsymbol{r}) (P + N) - P
\end{align*}&lt;/div&gt;
&lt;p&gt;This can be implemented in Python&amp;nbsp;as:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;F_and_J&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ratings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Returns a tuple (F, J), with function value F and its jacobian J.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;Pr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;ratings&lt;/span&gt;
    &lt;span class="n"&gt;Pr_Nr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Pr&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;ratings&lt;/span&gt;
    &lt;span class="n"&gt;F&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ratings&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;Pr_Nr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Pr&lt;/span&gt;
    &lt;span class="n"&gt;J&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ratings&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;P&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt;
    &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fill_diagonal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;J&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;J&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diagonal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;Pr_Nr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;J&lt;/span&gt;

&lt;span class="n"&gt;epsilon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1e-12&lt;/span&gt;
&lt;span class="n"&gt;ratings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ones&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;iteration&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;J&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;F_and_J&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ratings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;new_ratings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ratings&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;solve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;J&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_ratings&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;ratings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;epsilon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="n"&gt;ratings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_ratings&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ratings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# [0.74 0.44 0.29 0.42 0.63] &amp;lt;- Same as fixed-point&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note that the code above explicitly stores zeros (it does not use sparse matrices), and would therefore be unsuitable for large&amp;nbsp;matrices.&lt;/p&gt;
&lt;h2 id="appendix-c-properties-of-the-fixed-point-iteration"&gt;Appendix C: Properties of the fixed-point&amp;nbsp;iteration&lt;/h2&gt;
&lt;p&gt;In millions of computational experiments with random&amp;nbsp;matrices &lt;span class="math"&gt;\(P\)&lt;/span&gt; and &lt;span class="math"&gt;\(N\)&lt;/span&gt;, the fixed-point iteration seemingly always converges to a unique fixed point.
For a long time I conjectured that the fixed point iteration always converges to a unique, attracting fixed point.
However, it turns out this conjecture is&amp;nbsp;false.&lt;/p&gt;
&lt;p&gt;A counterexample is the following structure:
&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 600px; width: 95%;"
src="https://tommyodland.com/images/articles/people_rating_people/counterexample_cycle.png"
class="img-responsive"&gt;
There are three groups and at least 16 people in each group.
Within each group, everyone rates everyone else negatively.
There are no&amp;nbsp;self-ratings.&lt;/p&gt;
&lt;p&gt;With 16 people in each group, the algorithm cycles&amp;nbsp;between &lt;span class="math"&gt;\((0.509, 0.366, 0.509)\)&lt;/span&gt; and &lt;span class="math"&gt;\((0.29, 0.585, 0.29)\)&lt;/span&gt; when started at a&amp;nbsp;random &lt;span class="math"&gt;\(\boldsymbol{r}\)&lt;/span&gt;.
With 15 people in each group, the algorithm does not cycle, but will eventually&amp;nbsp;converge.&lt;/p&gt;
&lt;h2 id="appendix-d-code"&gt;Appendix D:&amp;nbsp;Code&lt;/h2&gt;
&lt;p&gt;Here is some Python 3.12 code for the&amp;nbsp;model.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;numpy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;np&lt;/span&gt;  &lt;span class="c1"&gt;# Version: 2.1.3&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;RatingProblem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Initialize a problem given positive binary ratings P&lt;/span&gt;
&lt;span class="sd"&gt;        and negative binary ratings N.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;

    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Create a random problem instance of size n.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="n"&gt;rng&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default_rng&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Create adjacency matrix with random density&lt;/span&gt;
        &lt;span class="n"&gt;density&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;adj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;density&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Zero out diagonal (can&amp;#39;t rate yourself)&lt;/span&gt;
        &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fill_diagonal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;adj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Randomly split into positive and negative ratings&lt;/span&gt;
        &lt;span class="n"&gt;split&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;adj&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;adj&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__call__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;jacobian&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Compute the Jacobian matrix at r.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;
        &lt;span class="n"&gt;Pr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Nr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;

        &lt;span class="c1"&gt;# Vectorized jacobian computation&lt;/span&gt;
        &lt;span class="n"&gt;numerator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;Nr&lt;/span&gt;&lt;span class="p"&gt;)[:,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;Pr&lt;/span&gt;&lt;span class="p"&gt;)[:,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;
        &lt;span class="n"&gt;denominator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;Pr&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;Nr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;numerator&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;denominator&lt;/span&gt;&lt;span class="p"&gt;[:,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;solve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r_0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;epsilon&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1e-12&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Solve using fixed-point iteration.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="n"&gt;r_0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ones&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r_0&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;r_0&lt;/span&gt;

        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r_0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="n"&gt;ratings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r_0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;iteration&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;new_ratings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ratings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_ratings&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;ratings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;epsilon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;
            &lt;span class="n"&gt;ratings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_ratings&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ratings&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

    &lt;span class="n"&gt;rng&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default_rng&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Test that different starting points converge to the same value&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

        &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;integers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;problem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RatingProblem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;r1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;r2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;s1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;problem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;solve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;s2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;problem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;solve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;1_000&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Mon, 04 Aug 2025 00:00:00 +0200</pubDate><guid>tag:tommyodland.com,2025-08-04:/articles/2025/people-rating-people</guid><category>articles</category><category>mathematics</category></item><item><title>Hva kan jeg bruke matte til?</title><link>https://tommyodland.com/articles/2025/hva-kan-jeg-bruke-matte-til</link><description>&lt;p&gt;Elever spør ofte &amp;ldquo;&lt;em&gt;når får jeg bruk for dette?&lt;/em&gt;&amp;rdquo; eller &amp;ldquo;&lt;em&gt;hva skal jeg bruke matematikk til?&lt;/em&gt;&amp;ldquo;&lt;/p&gt;
&lt;p&gt;Det er et godt spørsmål som ofte går&amp;nbsp;ubesvart.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 400px; width: 90%;"
src="https://tommyodland.com/images/unsplash/mechanic.jpg"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Det er flere måter å svare på slike spørsmål på.
Her en én type svar: en liste med 100 konkrete problemstillinger der matematikk er&amp;nbsp;nyttig.&lt;/p&gt;
&lt;p&gt;Noen problemstillinger er relativt enkle, som å &lt;em&gt;&lt;a href="https://math.stackexchange.com/questions/74440/optimization-volume-of-a-box"&gt;bygge en boks med størst mulig volum&lt;/a&gt;&lt;/em&gt;.
Andre er vanskelige, som å &lt;em&gt;&lt;a href="https://www.youtube.com/watch?v=WXuK6gekU1Y"&gt;spille Go&lt;/a&gt;&lt;/em&gt;.
Å &lt;em&gt;&lt;a href="https://www.maa.org/programs/faculty-and-departments/classroom-capsules-and-notes/design-of-an-oscillating-sprinkler"&gt;vanne plenen&lt;/a&gt;&lt;/em&gt; fremstår som et tøyeste problem, men illustrerer hvor dypt man kan gå om man begynner å undersøke en problemstilling.
Andre er store og mer seriøse problemer, som å &lt;em&gt;&lt;a href="https://arxiv.org/abs/2106.09125"&gt;lande en rakett&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Det er på ingen måte ment å være en uttømmende eller spesielt seriøs liste&amp;nbsp;:)&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com/Applied-Combinatorics-Problem-Solving-Bradley/dp/0201129086"&gt;Telle&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=WXuK6gekU1Y"&gt;Spille&amp;nbsp;Go&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.stat.cmu.edu/~ryantibs/darts/"&gt;Spille&amp;nbsp;dart&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Sorting_algorithm"&gt;Sortere&amp;nbsp;tall&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://buy.inconvergent.net/"&gt;Lage&amp;nbsp;kunst&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Løse&amp;nbsp;Soduko&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tommyodland.com/articles/2023/politikk-og-meningsrommet"&gt;Forstå&amp;nbsp;politikk&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.maa.org/programs/faculty-and-departments/classroom-capsules-and-notes/design-of-an-oscillating-sprinkler"&gt;Vanne&amp;nbsp;plenen&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tommyodland.com/articles/2020/optimal-skill-tree-growth"&gt;Spille&amp;nbsp;dataspill&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Kalman_filter"&gt;Styre en&amp;nbsp;missil&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hdl.handle.net/11250/2832653"&gt;Lagre&amp;nbsp;fiskemat&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://arxiv.org/abs/2106.09125"&gt;Lande en&amp;nbsp;rakett&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Maze-solving_algorithm"&gt;Løse en&amp;nbsp;labyrint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Maze_generation_algorithm"&gt;Lage en&amp;nbsp;labyrint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Bill_Benter"&gt;Vedde på&amp;nbsp;hester&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.mpi-inf.mpg.de/fileadmin/inf/d1/teaching/winter18/Ideen/Materialien/Dantzig-Diet.pdf"&gt;Lage en&amp;nbsp;matplan&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://martin.kleppmann.com/2011/03/07/accounting-for-computer-scientists.html"&gt;Føre et&amp;nbsp;regnskap&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jvns.ca/blog/2023/01/13/examples-of-floating-point-problems/"&gt;Regne med&amp;nbsp;flyttall&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Kelly_criterion"&gt;Gå inn i&amp;nbsp;veddemål&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://yetanothermathprogrammingconsultant.blogspot.com/2022/02/4-color-maps-another-model.html"&gt;Fargelegge et&amp;nbsp;kart&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lindeloev.github.io/tests-as-linear/"&gt;Teste en&amp;nbsp;hypotese&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/PageRank"&gt;Lage en&amp;nbsp;søkemotor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.ethanrosenthal.com/2022/04/15/bayesian-rock-climbing/"&gt;Rangere&amp;nbsp;klatreruter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Shapley_value"&gt;Dele en&amp;nbsp;taxiregning&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Bygge en&amp;nbsp;database&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Stable_marriage_problem"&gt;Arrangere&amp;nbsp;ekteskap&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Vickrey_auction"&gt;Designe en&amp;nbsp;auksjon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jakevdp.github.io/blog/2017/12/18/simulating-chutes-and-ladders/"&gt;Simulere&amp;nbsp;stigespillet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pubsonline.informs.org/doi/abs/10.1287/inte.2019.1015"&gt;Planlegge&amp;nbsp;bussruter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/PageRank"&gt;Bygge en&amp;nbsp;søkemotor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Regne på et&amp;nbsp;boliglån&lt;/li&gt;
&lt;li&gt;&lt;a href="https://magazine.sebastianraschka.com/p/understanding-large-language-models"&gt;Forstå&amp;nbsp;språkmodeller&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Elo_rating_system"&gt;Rangere&amp;nbsp;sjakkspillere&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Bygge en&amp;nbsp;atomreaktor&lt;/li&gt;
&lt;li&gt;Digital&amp;nbsp;bildebehandling&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Gerrymandering"&gt;Manipulere&amp;nbsp;valgkretser&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Modern_portfolio_theory"&gt;Lage en&amp;nbsp;aksjeportefølje&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/List_of_causes_of_death_by_rate"&gt;Undersøke&amp;nbsp;dødsårsaker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Bygge elektriske&amp;nbsp;kretser&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Recommender_system"&gt;Anbefale filmer og&amp;nbsp;serier&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tommyodland.com/articles/2020/optimizing-sets-and-reps-for-strength-training"&gt;Lage et&amp;nbsp;treningsprogram&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Forstå kunstig&amp;nbsp;intelligens&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dx.doi.org/10.2139/ssrn.3928966"&gt;Matche spillere i&amp;nbsp;dataspill&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Runge%E2%80%93Kutta_methods"&gt;Løse en&amp;nbsp;differensialligning&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Dataclysm"&gt;Analysere&amp;nbsp;dating-markedet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://doi.org/10.1287/inte.2022.1132"&gt;Lage turnus for&amp;nbsp;sykepleiere&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/playlist?list=PLUl4u3cNGP61Oq3tWYp6V_F-5jb5L2iHb"&gt;Designe effektive&amp;nbsp;algoritmer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Factfulness:_Ten_Reasons_We%27re_Wrong_About_the_World_%E2%80%93_and_Why_Things_Are_Better_Than_You_Think"&gt;Forstå verden gjennom&amp;nbsp;data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.regjeringen.no/contentassets/e9f862f850474dca89826e25e4b01314/pwc-rapport-utvikling-og-implementering-av-nytt-system-for-verdsettelse-av-fritidsboliger.pdf"&gt;Verdtsettelse av&amp;nbsp;fritidsboliger&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web.stanford.edu/~boyd/cvxbook/"&gt;Finne minimum av en&amp;nbsp;funksjon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Lage vaktplaner for et&amp;nbsp;sykehus&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Category:Electoral_systems"&gt;Designe et demokratisk&amp;nbsp;system&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hdl.handle.net/11250/2835304"&gt;Finne korteste reisevei&amp;nbsp;offshore&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Vedde penger på&amp;nbsp;fotballkamper&lt;/li&gt;
&lt;li&gt;&lt;a href="https://opendatastructures.org/"&gt;Designe effektive&amp;nbsp;datastrukturer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pubsonline.informs.org/doi/abs/10.1287/trsc.2018.0873"&gt;Effektivisere robotiserte&amp;nbsp;varehus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tommyodland.com/articles/2020/pareto-non-dominated-front-as-a-consumer-strategy"&gt;Få mest mulig bolig for&amp;nbsp;pengene&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Modellere et&amp;nbsp;petroleumsreservoir&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.equinor.com/no/energi/maskin-mot-maskin"&gt;Vedlikeholde roterende&amp;nbsp;maskiner&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://use-the-index-luke.com/"&gt;Lagre store mengder&amp;nbsp;informasjon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Lage et program som spiller&amp;nbsp;sjakk&lt;/li&gt;
&lt;li&gt;&lt;a href="https://yetanothermathprogrammingconsultant.blogspot.com/2016/12/table-assignment-for-kindergartners.html"&gt;Planlegge sitteplasser i&amp;nbsp;barnehagen&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://doi.org/10.1287/inte.1060.0252"&gt;Forsvare infrastruktur mot&amp;nbsp;terrorister&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Seam_carving"&gt;Innholdsbevisst reskalering av&amp;nbsp;bilder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Forstå hvordan et aksjefond&amp;nbsp;fungerer&lt;/li&gt;
&lt;li&gt;&lt;a href="https://store.steampowered.com/app/1444480/Turing_Complete/"&gt;Bygge en prosessor til en&amp;nbsp;datamaskin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Lage en valgomat for&amp;nbsp;kommunevalget&lt;/li&gt;
&lt;li&gt;Bygge skyskrapere som ikke&amp;nbsp;kollapser&lt;/li&gt;
&lt;li&gt;&lt;a href="https://studenttorget.no/index.php?show=4524&amp;amp;expand=3797,4524&amp;amp;artikkelid=19709"&gt;Effektivisere Lånekassens&amp;nbsp;bokontroller&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://math.stackexchange.com/questions/74440/optimization-volume-of-a-box"&gt;Bygge en boks med størst mulig&amp;nbsp;volum&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tommyodland.com/articles/2022/optimal-pupil-classroom-assignments"&gt;Sette sammen en&amp;nbsp;ungdomsskoleklasse&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.shifter.no/nyheter/na-kommer-ai-teamet-til-ruter-med-en-slags-ruter-gpt-for-nesten-all-kollektivtransport-i-norge/283598"&gt;Predikere hvor billettkontrollører bør&amp;nbsp;stå&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Analysere data fra&amp;nbsp;spørreundersøkelser&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Cost_estimation_models"&gt;Estimere kostnadene til et stort&amp;nbsp;prosjekt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web.stanford.edu/~boyd/papers/opt_ss_claim.html"&gt;Bestemme når man bør pensjonere&amp;nbsp;seg&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tommyodland.com/articles/2022/the-2022-norwegian-football-elite-series"&gt;Predikere hvem som finner&amp;nbsp;fotballkamper&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.regjeringen.no/contentassets/e9f862f850474dca89826e25e4b01314/pwc-rapport-utvikling-og-implementering-av-nytt-system-for-verdsettelse-av-fritidsboliger.pdf"&gt;Verdsette fritidsboliger med&amp;nbsp;maskinlæring&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tommyodland.com/articles/2021/the-best-pokemon-party"&gt;Sette sammen det beste&amp;nbsp;Pokémon-partiet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://arxiv.org/abs/2308.01074"&gt;Logge tastetrykk ved hjelp av en&amp;nbsp;mikrofon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Sette best mulig pris på en&amp;nbsp;Airbnb-leilighet&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mc-stan.org/"&gt;Lage en Bayesiansk&amp;nbsp;sannsynlighetsmodell&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.semanticscholar.org/paper/Effect-of-Optimized-Versus-Guidelines%E2%80%90Based-on-An-Sun-Karlsson/7f19d1c6ab3ece5ea639270b574c5efd181734ef"&gt;Plassere hjertestartere på best mulig&amp;nbsp;steder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Forstå &lt;span class="caps"&gt;DNA&lt;/span&gt;-sekvenser til mennesker og&amp;nbsp;dyr&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tommyodland.com/articles/2023/en-pose-twist"&gt;Finne ut om du blir snytt når du kjøper&amp;nbsp;Twist&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.tu.no/artikler/lars-magnar-rettet-ut-kommunens-svingete-tunnelforslag-i-siste-oyeblikk-sparte-125-millioner-kroner/365455"&gt;Spare 125 millioner på å borre en rett&amp;nbsp;tunnel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Programmere og studere logikken til en&amp;nbsp;heis&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Monte_Carlo_method"&gt;Regne ut om det er best å eie eller leie&amp;nbsp;bolig&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hdl.handle.net/1956/20441"&gt;Sette sammen forsikringer for marin&amp;nbsp;shipping&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web.archive.org/web/20170216052444/https://e-reports-ext.llnl.gov/pdf/240921.pdf"&gt;Projisere høye dimensjoner til få&amp;nbsp;dimensjoner&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tommyodland.com/articles/2020/fritt-skolevalg-sammendrag"&gt;Lage en inntaksmodell for videregående&amp;nbsp;skole&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Bestemme hvor mye et bakeri bør bake hver&amp;nbsp;dag&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Cutting_stock_problem"&gt;Kutte tekstilruller slik at man får minst mulig&amp;nbsp;svinn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Designe en kaffekopp som holder godt på&amp;nbsp;varmen&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mc-stan.org/users/documentation/case-studies/golf.html"&gt;Regne ut sannsynligheten for suksessfull putt i&amp;nbsp;golf&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://doi.org/10.1287/mnsc.2020.3748"&gt;Designe loot-boxes i dataspill for å maksimere&amp;nbsp;profitt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tommyodland.com/articles/2021/lonn-og-kjonn-blant-utviklere"&gt;Finne ut hvilke faktorer som er assosiert med høy&amp;nbsp;lønn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sortere.no/kart"&gt;Plassere returpunkter for glass- og metallembalasje i&amp;nbsp;byen&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/FET_(timetabling_software)"&gt;Lage timeplan for en skole for best mulig&amp;nbsp;ressursutnyttelse&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Teste om man klarer å smake forskjell på brus med og uten&amp;nbsp;sukker&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Gibbard%E2%80%93Satterthwaite_theorem"&gt;Forstå hvorfor man ikke alltid bør stemme på politikerne man&amp;nbsp;ønsker&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Thu, 17 Jul 2025 00:00:00 +0200</pubDate><guid>tag:tommyodland.com,2025-07-17:/articles/2025/hva-kan-jeg-bruke-matte-til</guid><category>articles</category><category>mathematics</category></item><item><title>Who’s the best bettor?</title><link>https://tommyodland.com/articles/2025/whos-the-best-bettor</link><description>&lt;p&gt;A friend of mine is part of a group that arranges bets at their workplace.
Each person proposes bets they believe will come true.
The bets have &lt;a href="https://en.wikipedia.org/wiki/Odds#Decimal_odds"&gt;European-style&amp;nbsp;odds&lt;/a&gt; &lt;span class="math"&gt;\(o\)&lt;/span&gt; and are from a betting exchange.
The money placed on each bet is always the same, so we arbitrarily take it to be one unit.
Each bet leads to an&amp;nbsp;outcome &lt;span class="math"&gt;\(y = 1\)&lt;/span&gt; (win)&amp;nbsp;or &lt;span class="math"&gt;\(y = 0\)&lt;/span&gt; (loss).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Given histories of bets for each person, how can we decide who the best bettor&amp;nbsp;is?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For instance, given three people Bob, Alice and Mark with&amp;nbsp;histories:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;person    odds (o)             outcomes (y)
bob       (2, 2, 2)            (1, 1, 1)    
alice     (3, 3)               (1, 1)    
mark      (2, 2, 2, 2, 2)      (1, 1, 1, 1, 0)  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Who is the best bettor? 
Both Bob and Alice have won all their bets, but Alice took higher risk.
Mark placed more bets that came through, but one of his bets was&amp;nbsp;wrong.&lt;/p&gt;
&lt;p&gt;In this article we will define the likelihood of an outcome over a sequence of bets.
Then we create a latent variable that encodes the skill of a bettor, relative to the probability given by the odds.
Given a sequence of bets and outcomes we can then solve for this latent variable, and the best bettor is the person with the greatest&amp;nbsp;value.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 550px; width: 95%;"
src="https://tommyodland.com/images/articles/best_bettor/best_bettor_players.jpg"
class="img-responsive"&gt;&lt;/p&gt;
&lt;h2 id="the-probability-of-an-outcome"&gt;The probability of an&amp;nbsp;outcome&lt;/h2&gt;
&lt;p&gt;A person performs a sequence&amp;nbsp;of &lt;span class="math"&gt;\(n\)&lt;/span&gt; bets with&amp;nbsp;odds &lt;span class="math"&gt;\(o_i\)&lt;/span&gt; and attains&amp;nbsp;outcomes &lt;span class="math"&gt;\(y_i\)&lt;/span&gt; for each&amp;nbsp;bet &lt;span class="math"&gt;\(i=1, 2, \ldots, n\)&lt;/span&gt;.
Ignoring the margin of the bookmaker, the odds can be converted to a probabilities by the&amp;nbsp;equation &lt;span class="math"&gt;\(p_i = 1 / o_i\)&lt;/span&gt;.
Each bet is independent, so the probability of the joint outcome&amp;nbsp;is
&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
P(\boldsymbol{p}, \boldsymbol{y}) = \prod_i^n p_i^{y_i} (1 - p_i)^{1 - y_i}.
\end{equation*}&lt;/div&gt;
&lt;h2 id="insight"&gt;Insight&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;When a bet will be made.&lt;/strong&gt;
Suppose a person believes the true probability of winning a bet&amp;nbsp;is &lt;span class="math"&gt;\(\tilde{p}\)&lt;/span&gt;.
With European-style odds the net gain&amp;nbsp;is &lt;span class="math"&gt;\((o-1)\)&lt;/span&gt; if a bet is won&amp;nbsp;and &lt;span class="math"&gt;\(-1\)&lt;/span&gt; if it is lost.
A person will only bet if the expected payout is larger than zero, that is,&amp;nbsp;if
&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
P(\text{win}) (\text{amount won}) + P(\text{loss}) (\text{amount lost}) = \tilde{p} (o - 1) + (1 - \tilde{p}) (-1) \geq 0.
\end{equation*}&lt;/div&gt;
&lt;p&gt;
Solving&amp;nbsp;for &lt;span class="math"&gt;\(\tilde{p}\)&lt;/span&gt; we learn that a person will only bet&amp;nbsp;if &lt;span class="math"&gt;\(\tilde{p} \geq 1/o = p\)&lt;/span&gt;.
In other words, they will bet if their own personal probability is greater than the one deduced from the&amp;nbsp;odds.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Introducing the latent&amp;nbsp;insight &lt;span class="math"&gt;\(\theta\)&lt;/span&gt;.&lt;/strong&gt;
We will assume that each person has some &lt;em&gt;insight&lt;/em&gt; &lt;span class="math"&gt;\(\theta\)&lt;/span&gt;, which is a latent (unobserved) variable representing their additional information.
We would like to infer how much insight each person has, and rank their betting ability by their insight.
One idea is to&amp;nbsp;set &lt;span class="math"&gt;\(\tilde{p} = p + \theta\)&lt;/span&gt;, but we&amp;nbsp;prefer &lt;span class="math"&gt;\(\theta\)&lt;/span&gt; to be an unbounded variable and the&amp;nbsp;probabilities &lt;span class="math"&gt;\(\tilde{p}\)&lt;/span&gt; to be restricted to the&amp;nbsp;domain &lt;span class="math"&gt;\([0, 1]\)&lt;/span&gt;.
To accomplish this we use the logistic function (sigmoid or &lt;em&gt;expit&lt;/em&gt;) and its inverse the &lt;em&gt;logit&lt;/em&gt;.
The functions&amp;nbsp;are:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
\operatorname{expit}(z) = \frac{1}{1 + \exp(-z)} 
\quad \text{and} \quad
\operatorname{logit}(z) = \log\left( \frac{z}{1 - z} \right).
\quad
\end{equation*}&lt;/div&gt;
&lt;p&gt;
To ensure&amp;nbsp;that &lt;span class="math"&gt;\(\tilde{p}\)&lt;/span&gt; ends up in the&amp;nbsp;domain &lt;span class="math"&gt;\([0, 1]\)&lt;/span&gt; we transform the probabilities to logits&amp;nbsp;on &lt;span class="math"&gt;\(\mathbb{R}\)&lt;/span&gt;, add the&amp;nbsp;insight &lt;span class="math"&gt;\(\theta\)&lt;/span&gt;, and transform the result back&amp;nbsp;to &lt;span class="math"&gt;\([0, 1]\)&lt;/span&gt;:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
\tilde{p}(\theta, p) = \operatorname{expit}\left( \operatorname{logit}(p) + \theta \right) = \frac{p e^{\theta}}{p e^{\theta} - p + 1}
\end{equation*}&lt;/div&gt;
&lt;p&gt;Substituting &lt;span class="math"&gt;\(\tilde{p}(\theta, p)\)&lt;/span&gt; into the probability of the joint outcome, we obtain the negative log-likelihood&amp;nbsp;function
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
- \log P( \boldsymbol{p}, \boldsymbol{y} \mid \theta) &amp;amp;= -
\sum_i^n  y_i \log \left( \tilde{p}_i(\theta, p_i) \right) +
(1 - y_i) \log \left( 1 -  \tilde{p}_i(\theta, p_i) \right) \\
&amp;amp;= \sum_i^n
- y_i \log{\left(\frac{p_i e^{\theta}}{p_i e^{\theta} - p_i + 1} \right)}
+ \left(y_i - 1\right) \log{\left(\frac{1 - p_i}{p_i e^{\theta} - p_i + 1} \right)}.
\end{align*}&lt;/div&gt;
&lt;p&gt;
The likelihood is the probability of&amp;nbsp;observing &lt;span class="math"&gt;\((\boldsymbol{p}, \boldsymbol{y})\)&lt;/span&gt; given a value&amp;nbsp;of &lt;span class="math"&gt;\(\theta\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why a prior is needed.&lt;/strong&gt;
Suppose someone has made three bets with&amp;nbsp;odds &lt;span class="math"&gt;\(\boldsymbol{o} = (2, 2, 2)\)&lt;/span&gt; and&amp;nbsp;outcomes &lt;span class="math"&gt;\(\boldsymbol{y} = (1, 1, 1)\)&lt;/span&gt;.
Plotting the negative log-likelihood as a function&amp;nbsp;of &lt;span class="math"&gt;\(\theta\)&lt;/span&gt; we get the following figure.
&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 550px; width: 95%;"
src="https://tommyodland.com/images/articles/best_bettor/best_bettor_negative_log_likelihood.png"
class="img-responsive"&gt;
Minimizing the negative log-likelihood leads&amp;nbsp;to &lt;span class="math"&gt;\(\theta \to \infty\)&lt;/span&gt;.
In a sense, the person is able to claim &amp;ldquo;I&amp;rsquo;m great at betting! I knew all along that the true probability was one for all these&amp;nbsp;bets!&amp;rdquo;&lt;/p&gt;
&lt;p&gt;This is analogous to estimating the probability of heads for a sequence of coin tosses as the number of heads divided by the total number of coin tosses.
If we get three heads, then the estimated probability is one, and that is&amp;nbsp;unreasonable.&lt;/p&gt;
&lt;h2 id="putting-a-prior-on-insight"&gt;Putting a prior on&amp;nbsp;insight&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s far-fetched to believe that someone who wins a bet or two really has great insight.
To fix this issue&amp;nbsp;of &lt;span class="math"&gt;\(\theta\)&lt;/span&gt; going to infinity, we put a prior distribution on it.
We center a normal distribution around zero, and since the standard deviation is somewhat arbitrary we choose unity.
In other words we&amp;nbsp;choose &lt;span class="math"&gt;\(\theta \sim \mathcal{N}(1, 0)\)&lt;/span&gt; as the prior.
A single squared term is added to the negative log-likelihood (now a log-posterior), which&amp;nbsp;becomes
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
- \log P(\theta \mid \boldsymbol{p}, \boldsymbol{y}) &amp;amp;\propto -
\sum_i^n  y_i \log \left( \tilde{p}_i(\theta, p_i) \right) +
(1 - y_i) \log \left( 1 -  \tilde{p}_i(\theta, p_i) \right) + \theta^2 \\
&amp;amp;\propto \theta^{2} - \sum_i^n \theta y_i + \log{\left(p_i e^{\theta} - p_i + 1 \right)}.
\end{align*}&lt;/div&gt;
&lt;p&gt;
As a result, the negative log-posterior now has a finite minimizer:
&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 550px; width: 95%;"
src="https://tommyodland.com/images/articles/best_bettor/best_bettor_negative_log_posterior.png"
class="img-responsive"&gt;
We now have a reasonable statistical&amp;nbsp;model.&lt;/p&gt;
&lt;p&gt;To summarize, we assumed that each person had some&amp;nbsp;insight &lt;span class="math"&gt;\(\theta\)&lt;/span&gt; that led them to believe that the true&amp;nbsp;probabilities &lt;span class="math"&gt;\(\tilde{p}_i\)&lt;/span&gt; are higher than&amp;nbsp;the &lt;span class="math"&gt;\(p_i\)&lt;/span&gt; deduced from the odds.
Then we compute the maximum a posteriori (&lt;span class="caps"&gt;MAP&lt;/span&gt;) estimate&amp;nbsp;of &lt;span class="math"&gt;\(\theta\)&lt;/span&gt;, which is the most probable value&amp;nbsp;of &lt;span class="math"&gt;\(\theta\)&lt;/span&gt; given the observed sequence of bets.
A higher value&amp;nbsp;of &lt;span class="math"&gt;\(\theta\)&lt;/span&gt; means the person has insight and beats the&amp;nbsp;bookmaker, &lt;span class="math"&gt;\(\theta\)&lt;/span&gt; around zero means no additional insight is available apart from the one deduced by the odds, and a negative value&amp;nbsp;of &lt;span class="math"&gt;\(\theta\)&lt;/span&gt; means the person consistently&amp;nbsp;underperforms.&lt;/p&gt;
&lt;h2 id="cases"&gt;Cases&lt;/h2&gt;
&lt;p&gt;We will now go through some concrete cases and check if the model is&amp;nbsp;sensible.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Placing more and more bets.&lt;/strong&gt;
Suppose the winning streak is increased by one bet at a time.
The table below shows odds, outcomes and &lt;span class="caps"&gt;MAP&lt;/span&gt; estimates of the&amp;nbsp;insight &lt;span class="math"&gt;\(\theta\)&lt;/span&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;case  odds (o)       outcomes (y)  insight (theta)
1     (,)            (,)            0.0000
2     (2,)           (1,)           0.2223
3     (2, 2)         (1, 1)         0.4011
4     (2, 2, 2)      (1, 1, 1)      0.5491
5     (2, 2, 2, 2)   (1, 1, 1, 1)   0.6748
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;No bets placed implies zero insight, as there is no reason to believe the bettor is any better than randomly guessing.
As the number of consecutive wins increases, the posterior mode moves away from the prior and we believe more and more that the person really has insight and outperforms the&amp;nbsp;bookmaker.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Equal gains.&lt;/strong&gt;
Suppose earlier wins can be used to be more money on the next bets.
Here are three bettors who all multiplied their money&amp;nbsp;by &lt;span class="math"&gt;\(16\)&lt;/span&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;case  odds (o)       outcomes (y)  insight (theta)
1     (16,)          (1,)           0.4526
2     (4, 4)         (1, 1)         0.6179
3     (2, 2, 2, 2)   (1, 1, 1, 1)   0.6748
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The model assigns the most insight to the person who placed four bets.
Suppose each person has some small&amp;nbsp;insight &lt;span class="math"&gt;\(\epsilon\)&lt;/span&gt;.
Then the&amp;nbsp;probability &lt;span class="math"&gt;\((1/16 + \epsilon) \geq (1/2 + \epsilon)^4 = (1/2)^4 + (1/2)^3 \epsilon + \mathcal{O}(\epsilon^2)\)&lt;/span&gt; when &lt;span class="math"&gt;\(\epsilon\)&lt;/span&gt; is small.
The probability that the person who places four bets ends up with four wins is lower, and it is therefore more impressive.
In other words, the model favors winning on many smaller bets rather than one big bet, even though the final monetary gain is&amp;nbsp;identical.&lt;/p&gt;
&lt;p&gt;Although the amount of money placed on each bet is the same in our setup, the result above makes even more sense if we were betting on a sequence and re-investing our wins.
A longer sequence of smaller wins is more beneficial than one large win, since we can re-invest our previous earnings along the&amp;nbsp;way.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A win and a loss.&lt;/strong&gt;
Now consider four bettors who all win one bet and lose one&amp;nbsp;bet.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;case  odds (o)       outcomes (y)  insight (theta)
1     (2, 2)         (1, 0)         0.0
2     (3, 3)         (1, 0)         0.1358
3     (4, 4)         (1, 0)         0.2088
4     (5, 5)         (1, 0)         0.2559
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The model assigns the most insight to the person who took the most risk.
This also makes&amp;nbsp;sense.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Case 1 represents winning once with odds two (doubling the money), then losing one unit of money. This bettor is back to where he&amp;nbsp;started.&lt;/li&gt;
&lt;li&gt;Case 5 represents winning once with odds five (returning five times the original amount of money), then losing one unit of money. This bettor is still up by four units of&amp;nbsp;money.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Some random cases.&lt;/strong&gt;
Below is a table with nine cases, sorted by the inferred&amp;nbsp;insight &lt;span class="math"&gt;\(\theta\)&lt;/span&gt; of each bettor.
These cases are all pretty random, and are shown so the reader can study how different odds and outcomes measure up against each&amp;nbsp;other.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;case  odds (o)       outcomes (y)  insight (theta)
1     (2, 2, 2)      (0, 0, 1)     -0.182
2     (2, 2)         (1, 0)         0.0
3     (2, 2, 2)      (0, 1, 1)      0.182
4     (2, 2, 2)      (1, 0, 1)      0.182
5     (5, 5)         (1, 0)         0.2559
6     (8,)           (1,)           0.4113
7     (16,)          (1,)           0.4526
8     (3, 3)         (1, 1)         0.5386
9     (2, 2, 2)      (1, 1, 1)      0.5491
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;To determine who the best bettor is, we modeled a bettor&amp;rsquo;s&amp;nbsp;insight &lt;span class="math"&gt;\(\theta\)&lt;/span&gt; as a latent variable.
The insight is added to the probability deduced from the odds, and the best bettor is the one with the greatest insight.
In order to&amp;nbsp;avoid &lt;span class="math"&gt;\(\theta\)&lt;/span&gt; becoming very large or very small in small sequences with only wins or losses, we put a prior on it.
The skill of a bettor is the maximum a posteriori estimate&amp;nbsp;of &lt;span class="math"&gt;\(\theta\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;There might be other interesting ways of solving this problem, but I think this approach works well.
It&amp;rsquo;s conceptually simple, all test cases look good, and it&amp;rsquo;s simple to implement and compute.
Further work that might be interesting includes: making the optimization routine faster, studying the equations in greater depth, or computing the entire posterior distribution instead of finding the value that maximizes&amp;nbsp;it.&lt;/p&gt;
&lt;h2 id="code"&gt;Code&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s a Python 3.11 code snippet if you want to run the&amp;nbsp;model.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;numpy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;np&lt;/span&gt;  &lt;span class="c1"&gt;# 1.26.4&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;scipy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sp&lt;/span&gt;  &lt;span class="c1"&gt;# 1.13.0&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;functools&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;log_posterior&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;theta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;odds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Compute the log-posterior of theta, given `odds` and `y`.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

    &lt;span class="c1"&gt;# Convert from odds to base probabilities&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;odds&lt;/span&gt;
    &lt;span class="c1"&gt;# Convert to logit scale using logit(z) = log(z / (1-z))&lt;/span&gt;
    &lt;span class="n"&gt;logits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;special&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Add `theta` (insight) and convet back using expit(z) = 1/(1+exp(-z))&lt;/span&gt;
    &lt;span class="n"&gt;p_tilde&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;special&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logits&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;theta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Compute the log-likelihood&lt;/span&gt;
    &lt;span class="n"&gt;log_likelihood&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p_tilde&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;p_tilde&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# A more numerically stable version as theta -&amp;gt; oo is:&lt;/span&gt;
    &lt;span class="c1"&gt;# log_likelihood = -theta * (1 - y) - np.logaddexp(&lt;/span&gt;
    &lt;span class="c1"&gt;#     np.log(p), -theta + np.log1p(-p))&lt;/span&gt;

    &lt;span class="c1"&gt;# Add prior and return the negative log-posterior&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_likelihood&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;theta&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;


&lt;span class="c1"&gt;# Example case&lt;/span&gt;
&lt;span class="n"&gt;odds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Optimize the log-posterior for optimal theta&lt;/span&gt;
&lt;span class="n"&gt;fun&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;functools&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;partial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_posterior&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;odds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;odds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;optimize&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;minimize_scalar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fun&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;fun&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 0.549107...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Sat, 07 Jun 2025 00:00:00 +0200</pubDate><guid>tag:tommyodland.com,2025-06-07:/articles/2025/whos-the-best-bettor</guid><category>articles</category><category>statistics</category></item><item><title>Fraværsgrensa</title><link>https://tommyodland.com/articles/2025/fravaersgrensa</link><description>&lt;p&gt;&lt;a href="https://www.ung.no/utdanning/vgs/2816_Reglene_for_frav%C3%A6r_p%C3%A5_videreg%C3%A5ende_skole.html"&gt;Fraværsgrensa&lt;/a&gt; i videregående skole er på 10%. 
Det betyr at dersom en elev har udokumentert fravær på 10% eller mer i et fag, så mister eleven karakter i faget.
I Reddit-tråden &amp;ldquo;&lt;a href="https://old.reddit.com/r/norge/comments/1jyw373/videreg%C3%A5ende_skole_er_no_bullshit/"&gt;Videregående skole er no bullshit&lt;/a&gt;&amp;rdquo; har en elev fått varsel fordi han eller hun var borte én dag, som utløste 3 timers fravær i kroppsøving og oversteg&amp;nbsp;5%.&lt;/p&gt;
&lt;p&gt;Kroppsøving inneholder &lt;a href="https://www.udir.no/laring-og-trivsel/lareplanverket/fag-og-timefordeling/Tidligere-rundskriv/Udir-1-2015/Vedlegg-1/3/34-Yrkesfaglige-utdanningsprogram/"&gt;56 undervisningstimer&lt;/a&gt; totalt, mens f.eks. matematikk har 140 timer.
Det betyr at fraværsgrensen har ulik effekt avhengig av hvor mange skoledager som inneholder&amp;nbsp;faget:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Om kroppsøving er på 3 timer per dag, så vil 2 dager med fravær betyr at man mister karakteren,&amp;nbsp;fordi &lt;span class="math"&gt;\(6 / 56 \approx 0.107 \geq 0.1\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Om matematikk er på 2 timer per dag, så vil 7 dager med fravær betyr at man mister karakteren,&amp;nbsp;fordi &lt;span class="math"&gt;\(14 / 140 = 0.1 \geq 0.1\)&lt;/span&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Selv om det er mye fravær som kan &lt;a href="https://www.udir.no/regelverk-og-tilsyn/skole-og-opplaring/rundskriv-om-fravarsgrensen/3.-hva-omfattes-av-fravarsgrensen/"&gt;unntas fraværsgrensen&lt;/a&gt;, er det rimelig å anta at noe udokumentert fravær vil forekomme.
Denne artikkelen svarer på spørsmålet: &amp;ldquo;gitt at udokumentert fravær inntreffer tilfeldig, hva er sannsynligheten for at en elev mister karakteren i et&amp;nbsp;fag?&amp;rdquo;&lt;/p&gt;
&lt;h2 id="sannsynligheten-for-a-miste-karaktergrunnlaget-i-et-fag"&gt;Sannsynligheten for å miste karaktergrunnlaget i et&amp;nbsp;fag&lt;/h2&gt;
&lt;p&gt;La oss gjøre to forenklinger før vi&amp;nbsp;fortsetter:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Vi antar at hver dag med et fag inneholder like mange timer&lt;/strong&gt;. Dette er tilfellet om man har 3 timer gym hver onsdag. Det er ikke tilfellet om man for eksempel har 2 timer matte på mandager og 1 time   på torsdager. Denne antagelsen betyr at vi slipper å telle opp antall timer innad i hver dag og antall timer i faget totalt. Dette gjør utregningene litt enklere fordi vi slipper å forholde oss til faktiske timeplaner, som uansett vil variere fra skole til&amp;nbsp;skole.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vi antar at udokumentert fravær inntreffer tilfeldig&lt;/strong&gt; med en viss sannsynlighet, og at fravær er uavhengig. Med andre ord: om en elev hadde fravær i går påvirker ikke sannsynligheten for at eleven har fravær i&amp;nbsp;dag.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Figuren nedenfor viser sannsynligheten for å miste karakter et fag, som funksjon av antall dager med&amp;nbsp;faget.
&lt;span class="math"&gt;\(P(\text{fravær})\)&lt;/span&gt; er sannsynligheten for fravær på en gitt dag, og utregningen er visualisert for 1%, 2% og&amp;nbsp;3%.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 750px; width: 95%;"
src="https://tommyodland.com/images/articles/fraværsgrensa/sannsynlighet_miste_karakter.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Den overordnede trenden er at sannsynligheten for å miste karakter er høyere desto mindre antall dager man har med faget.
Det stemmer med de innledende observasjonene om kroppsøving og&amp;nbsp;matematikk.&lt;/p&gt;
&lt;p&gt;Vi skal nå forklare knekkene i grafen: 
før man har 11 dager med et fag, vil én fraværsdag føre til at man overstiger grensa på 10%.
På 11 dager har man litt rom for fravær ettersom én dag ikke lengre gjør at man overstiger grensa.
Situasjonen blir likevel verre fra 11 dager til og med 20 dager.
I dette intervallet vil to eller flere fraværsdager utløse grensa, og &lt;strong&gt;med flere dager er det større sannsynlighet for at det skjer&lt;/strong&gt;&amp;mdash;derfor stiger grafen.
På 21 dager har man igjen litt mer slingring, og slik fortsetter&amp;nbsp;mønsteret.&lt;/p&gt;
&lt;h2 id="utregning"&gt;Utregning&lt;/h2&gt;
&lt;p&gt;La sannsynligheten for fravær&amp;nbsp;være &lt;span class="math"&gt;\(p = P(\text{fravær})\)&lt;/span&gt;, la fraværsgrensa&amp;nbsp;være &lt;span class="math"&gt;\(g=0.1\)&lt;/span&gt; og la antall dager med faget&amp;nbsp;være &lt;span class="math"&gt;\(d\)&lt;/span&gt;.
Antall&amp;nbsp;fraværsdager &lt;span class="math"&gt;\(X \sim \text{Binom}(p, d)\)&lt;/span&gt; er da en stokastisk variabel med &lt;a href="https://snl.no/binomisk_fordeling"&gt;binomisk sannsynlighetsfordeling&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Sannsynligheten for at man mister karakter&amp;nbsp;er &lt;span class="math"&gt;\(P(X \geq \lceil g d \rceil)\)&lt;/span&gt;,&amp;nbsp;der &lt;span class="math"&gt;\(\lceil \cdot \rceil\)&lt;/span&gt; er matematisk notasjon for å runde opp, f.eks. så&amp;nbsp;er &lt;span class="math"&gt;\(\lceil 1.7 \rceil = 2\)&lt;/span&gt;.
Vi regner oss frem til sannsynligheten ved hjelp av den kumulative&amp;nbsp;sannsynlighetsfordelingen.&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
P(X \geq \lceil g d \rceil) = 1 - P(X &amp;lt; \lceil g d \rceil) = 1 - (P(X \leq \lceil g d \rceil) - P(X = \lceil g d \rceil)).
\end{equation*}&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Eksempel&lt;/strong&gt;  &lt;br&gt;
Om vi&amp;nbsp;setter &lt;span class="math"&gt;\(p=0.02\)&lt;/span&gt; og antall dager&amp;nbsp;lik &lt;span class="math"&gt;\(d=13\)&lt;/span&gt;, så&amp;nbsp;er
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
P(X \geq \lceil (0.1) 13 \rceil) &amp;amp;= P(X \geq 2 ) \\
&amp;amp;= 1 - P(X &amp;lt; 2) \\
&amp;amp;= 1 - \left[ P(X = 0) + P(X = 1) \right] \\
&amp;amp;= 1 - \left[ \binom{13}{0} 0.02^{0} (1 - 0.02)^{13} + \binom{12}{1} 0.02^{1} (1 - 0.02)^{12} \right] \\
&amp;amp;= 1 - \left[ 1 (0.98)^{13} + 12 0.02 (0.98)^{12} \right] \\
&amp;amp;\approx 1 - \left[ 0.769 + 0.188 \right] \approx 0.0426455.
\end{align*}&lt;/div&gt;
&lt;h2 id="hva-br-fravrsgrensa-vre"&gt;Hva bør fraværsgrensa&amp;nbsp;være?&lt;/h2&gt;
&lt;p&gt;Dette er et verdispørsmål som matematikk alene ikke kan svare på, men la oss gjøre noen antagelser og undersøke hva en rimelig fraværsgrense som funksjon av antall&amp;nbsp;dager &lt;span class="math"&gt;\(d\)&lt;/span&gt; bør&amp;nbsp;være:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Anta at vi ønsker&amp;nbsp;at &lt;span class="math"&gt;\(P(\text{ingen karakter}) \leq 0.001\)&lt;/span&gt;, gitt et normalt udokumentert&amp;nbsp;fravær.&lt;/li&gt;
&lt;li&gt;Anta at noe udokumentert fravær vil forekomme, f.eks.&amp;nbsp;at &lt;span class="math"&gt;\(p = 0.02\)&lt;/span&gt; eller lignende er&amp;nbsp;normalnivå.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Da kan vi spørre &amp;ldquo;Hva er minste (strengeste)&amp;nbsp;fraværsgrense &lt;span class="math"&gt;\(g\)&lt;/span&gt; vi kan ha som&amp;nbsp;oppfyller &lt;span class="math"&gt;\(P(\text{ingen karakter}) \leq 0.001\)&lt;/span&gt;?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Svaret er gitt grafisk i figuren nedenfor, for tre ulike verdier&amp;nbsp;av &lt;span class="math"&gt;\(p\)&lt;/span&gt; og ulike verdier av&amp;nbsp;dager &lt;span class="math"&gt;\(d\)&lt;/span&gt; med et gitt&amp;nbsp;fag.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 750px; width: 95%;"
src="https://tommyodland.com/images/articles/fraværsgrensa/minste_fraværsgrense.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Å&amp;nbsp;velge &lt;span class="math"&gt;\(P(\text{ingen karakter})\)&lt;/span&gt; og &lt;span class="math"&gt;\(p\)&lt;/span&gt; er en skjønnsmessig vurdering.
Uansett er konklusjonen at antall dager med faget spiller en viktig rolle.
Desto lavere (strengere) fraværsgrense, desto høyere sannsynlighet for at man har uflaks og mister karaktergrunnlaget, selv når sannsynligheten for&amp;nbsp;fravær &lt;span class="math"&gt;\(p\)&lt;/span&gt; i utgangspunktet er&amp;nbsp;lav.&lt;/p&gt;
&lt;p&gt;Personlig ville jeg heller straffet elever med nedsatt karakter dersom fraværet blir stort, heller enn å ta bort hele karaktergrunnlaget.
Dette gir en mer glidende overgang, i motsetning til en hard statisk grense på 10%.
Det er litt rart at politikerne ikke har valgt en annen &lt;a href="https://en.wikipedia.org/wiki/Mechanism_design"&gt;mekanisme&lt;/a&gt; enn en statisk grense på 10% for alle fagene.
Kanskje de selv hadde fravær? All matematikken vi har brukt i denne artikkelen er i hvert fall &lt;span class="caps"&gt;VGS&lt;/span&gt;-pensum.&lt;/p&gt;
&lt;h2 id="kode"&gt;Kode&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Python 3.12.9,&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;numpy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;np&lt;/span&gt;  &lt;span class="c1"&gt;# numpy: 2.1.3&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;scipy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sp&lt;/span&gt;  &lt;span class="c1"&gt;# scipy: 1.15.1&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;prob_lose_grade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num_days&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prob_absence&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit_absence&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Probability of losing your grade, given a course with `num_days` days.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="c1"&gt;# Limit for losing grades, in days. Hitting this means losing the grade.&lt;/span&gt;
    &lt;span class="n"&gt;days_limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num_days&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;limit_absence&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="c1"&gt;# Probability of absence is Binomial&lt;/span&gt;
    &lt;span class="n"&gt;distr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;binom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;num_days&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;prob_absence&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# P(X &amp;gt;= limit) = 1 - P(X &amp;lt; limit) = 1 - (CDF(limit) - P(X=limit))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;distr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days_limit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;distr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pmf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days_limit&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;find_limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num_days&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prob_absence&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_prob_lose_grade&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.001&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Find largest fraværsgrense (limit), such that the probability of&lt;/span&gt;
&lt;span class="sd"&gt;    losing grade is &amp;lt;= target_value.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;objective&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;prob_lose_grade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num_days&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prob_absence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;max_prob_lose_grade&lt;/span&gt;

    &lt;span class="c1"&gt;# The goal is to find the largest possible limit that ensures that the&lt;/span&gt;
    &lt;span class="c1"&gt;# return value of prob_lose_grade is smaller than or equal to target_value.&lt;/span&gt;
    &lt;span class="n"&gt;optimal_limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;optimize&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bisect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;objective&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;1e-8&lt;/span&gt;
    &lt;span class="c1"&gt;# print(f&amp;quot;The absence limit should be set to {optimal_limit:.4f}&amp;quot;)&lt;/span&gt;

    &lt;span class="n"&gt;prob_lose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prob_lose_grade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num_days&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prob_absence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;optimal_limit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# print(f&amp;quot;Then the prob of losing the grade is {prob_lose:.4f}&amp;quot;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;prob_lose&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;max_prob_lose_grade&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;optimal_limit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Test case - single day&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prob_lose_grade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prob_absence&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit_absence&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Test case - two days&lt;/span&gt;
    &lt;span class="n"&gt;prob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prob_lose_grade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prob_absence&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit_absence&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;prob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Test case - tree days&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt;
    &lt;span class="n"&gt;prob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prob_lose_grade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prob_absence&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit_absence&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;prob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Thu, 22 May 2025 00:00:00 +0200</pubDate><guid>tag:tommyodland.com,2025-05-22:/articles/2025/fravaersgrensa</guid><category>articles</category><category>statistics</category></item><item><title>How do Voting Advice Applications work?</title><link>https://tommyodland.com/articles/2025/how-do-voting-advice-applications-work</link><description>&lt;p&gt;&lt;strong&gt;This is a translation of my &lt;a href="https://tommyodland.com/articles/2025/hvordan-fungerer-en-valgomat"&gt;Norwegian article about VAAs&lt;/a&gt;.
The figures have not been translated, but are hopefully understandable from the&amp;nbsp;context.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;This article presents the mathematics behind voting advice applications (VAAs).
VAAs are essentially questionnaires: a user expresses agreement or disagreement with statements about politics, and the VAAs tells the user what parties they align&amp;nbsp;with.&lt;/p&gt;
&lt;p&gt;This article consists of three main&amp;nbsp;parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Part 1: VAAs and&amp;nbsp;geometry&lt;/li&gt;
&lt;li&gt;Part 2: How does a &lt;span class="caps"&gt;VAA&lt;/span&gt; explain the&amp;nbsp;results?&lt;/li&gt;
&lt;li&gt;Part 3: How should a &lt;span class="caps"&gt;VAA&lt;/span&gt; present questions to the&amp;nbsp;user?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The article begins at a basic mathematical level and becomes progressively more challenging.
The advantage of this approach is that there is something to learn for everyone regardless of level.
The disadvantage is that the article does not have a single clear target&amp;nbsp;audience.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span class="caps"&gt;PS&lt;/span&gt;:&lt;/strong&gt; In 2023, &lt;a href="https://tommyodland.com/articles/2023/politikk-og-meningsrommet"&gt;I visualized the parties in the Norwegian municipal election&lt;/a&gt;.
The focus of that article was insight into party politics.
This article focuses more on the design of&amp;nbsp;VAAs.&lt;/p&gt;
&lt;h1 id="part-1-vaas-and-geometry"&gt;Part 1: VAAs and&amp;nbsp;geometry&lt;/h1&gt;
&lt;h2 id="opinions-and-matrices"&gt;Opinions and&amp;nbsp;matrices&lt;/h2&gt;
&lt;p&gt;To create a &lt;span class="caps"&gt;VAA&lt;/span&gt;, we ask political parties to which degree they agree with various statements.
The results are quantified by translating responses like &amp;ldquo;Strongly agree&amp;rdquo; into numbers.
The answers can then be visualized in a table with one row per statement and one column per party.
Below, &amp;ldquo;Strongly agree&amp;rdquo; is translated&amp;nbsp;to &lt;span class="math"&gt;\(2\)&lt;/span&gt; and &amp;ldquo;Strongly disagree&amp;rdquo;&amp;nbsp;to &lt;span class="math"&gt;\(-2\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 550px; width: 95%;"
src="https://tommyodland.com/images/articles/valgomat_design/intro_figure.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;The table forms a&amp;nbsp;matrix &lt;span class="math"&gt;\(X\)&lt;/span&gt;.
The rows and columns have a geometric interpretation as &lt;em&gt;vectors&lt;/em&gt;.
A vector is a list of numbers,&amp;nbsp;e.g., &lt;span class="math"&gt;\((1, 2)\)&lt;/span&gt;, and can be interpreted geometrically as an arrow or as a&amp;nbsp;point.&lt;/p&gt;
&lt;h2 id="statements-and-parties"&gt;Statements and&amp;nbsp;parties&lt;/h2&gt;
&lt;h3 id="statements-as-vectors"&gt;Statements as&amp;nbsp;vectors&lt;/h3&gt;
&lt;p&gt;To visualize statements and parties as vectors, we must limit ourselves to two or three dimensions.
Higher dimensions cannot be&amp;nbsp;visualized.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s study geometrically what the parties &lt;span class="caps"&gt;SV&lt;/span&gt; and Sp think about a selection of&amp;nbsp;statements.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 750px; width: 95%;"
src="https://tommyodland.com/images/articles/valgomat_design/vectors_statements.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;In the upper right of the figure, we find statements that both parties agree with.
In the lower right, we find statements that &lt;span class="caps"&gt;SV&lt;/span&gt; agrees with, but Sp disagrees with.
We can make similar observations for the other two&amp;nbsp;quadrants.&lt;/p&gt;
&lt;h3 id="which-statements-are-good-and-which-are-bad"&gt;Which statements are good and which are&amp;nbsp;bad?&lt;/h3&gt;
&lt;p&gt;A good statement allows us to distinguish parties from each&amp;nbsp;other:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In the figure above, &amp;ldquo;Build railway northward to Tromsø&amp;rdquo; (Norwegian: &amp;ldquo;Bygg jernbane nordover til Tromsø.&amp;rdquo;) is a poor statement. Regardless of whether you agree or disagree, the statement does not clarify which of the two parties you are politically closest to. Trivial statements, such as &amp;ldquo;A just society is good,&amp;rdquo; are agreed upon by all parties. Therefore, they provide no information. The same applies to trivial statements that all parties disagree with, such as &amp;ldquo;The county municipality should be abolished&amp;rdquo; (Norwegian: &amp;ldquo;Fylkeskommunen bør avskaffes.&amp;rdquo;) in the figure&amp;nbsp;above.&lt;/li&gt;
&lt;li&gt;&lt;span class="dquo"&gt;&amp;ldquo;&lt;/span&gt;Drop the ferry-free E39 along the west coast&amp;rdquo; (Norwegian: &amp;ldquo;Dropp ferjefri E39 langs Vestlandet.&amp;rdquo;) is a good statement because it &lt;em&gt;discriminates&lt;/em&gt; between the parties. If you agree, you are politically closer to &lt;span class="caps"&gt;SV&lt;/span&gt;. If you disagree, you are closer to&amp;nbsp;Sp.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In two dimensions (two parties), statements are good if they are far from the subspace spanned by the&amp;nbsp;vector &lt;span class="math"&gt;\(\boldsymbol{e} = (1, 1)\)&lt;/span&gt;, i.e., the diagonal&amp;nbsp;line &lt;span class="math"&gt;\(y=x\)&lt;/span&gt;.
At the end of this article, we will generalize this idea to construct an algorithm that finds a good ordering of&amp;nbsp;questions.&lt;/p&gt;
&lt;h3 id="parties-as-vectors"&gt;Parties as&amp;nbsp;vectors&lt;/h3&gt;
&lt;p&gt;Above, we considered the rows (statements) in the table as vectors.
Let&amp;rsquo;s now look at the complementary vector space where the columns (parties) are vectors.
We choose two random statements: one about wolves (&amp;ldquo;Det skytes for mye ulv her i landet.&amp;rdquo; / &amp;ldquo;Too many wolves are shot in this country.&amp;rdquo;) and one about state ownership in companies (&amp;ldquo;Staten bør eie flere bedrifter.&amp;rdquo; / &amp;ldquo;The state should own more&amp;nbsp;companies.&amp;rdquo;).&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 350px; width: 95%;"
src="https://tommyodland.com/images/articles/valgomat_design/vectors_parties.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Two parties are similar if they are close to each other in the space of opinions in the figure above.
For example, we can measure distance as the distance on the horizontal axis plus the distance on the vertical axis (a mathematician might call this &amp;ldquo;Manhattan distance&amp;rdquo;&amp;nbsp;or &lt;span class="math"&gt;\(p=1\)&lt;/span&gt; norm).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Parties R and &lt;span class="caps"&gt;AP&lt;/span&gt; are close to each other. The distance&amp;nbsp;is &lt;span class="math"&gt;\(0 + 1 = 1\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Parties R and V are far from each other. The distance&amp;nbsp;is &lt;span class="math"&gt;\(3 + 4 = 7\)&lt;/span&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The distance in this space is the key to how a &lt;span class="caps"&gt;VAA&lt;/span&gt; determines which parties you most agree with when you take the test.
This is what we will cover in part&amp;nbsp;2.&lt;/p&gt;
&lt;h1 id="part-2-how-does-a-vaa-explain-the-results"&gt;Part 2: How does a &lt;span class="caps"&gt;VAA&lt;/span&gt; explain the&amp;nbsp;results?&lt;/h1&gt;
&lt;h2 id="explanation-ranking-parties-by-distance"&gt;Explanation: ranking parties by&amp;nbsp;distance&lt;/h2&gt;
&lt;p&gt;Above, we saw that party similarity can be quantified by looking at the distance between parties.
We visualized this in two dimensions, but the calculation is the same in any number of&amp;nbsp;dimensions.&lt;/p&gt;
&lt;p&gt;Here is a matrix with the numbers from the first figure in this&amp;nbsp;article:&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
X =
\begin{bmatrix}
  \text{AP} &amp;amp;  \text{Sp} &amp;amp; \text{Frp} &amp;amp; \text{H}\\
  -1 &amp;amp;  1 &amp;amp; -2 &amp;amp; -1\\
  2  &amp;amp;  1 &amp;amp; -2 &amp;amp; -2\\
  -2 &amp;amp; -2 &amp;amp;  2 &amp;amp;  2\\
  1  &amp;amp; -1 &amp;amp; -2 &amp;amp; -2\\
  1  &amp;amp;  2 &amp;amp;  2 &amp;amp; -2\\
  -1 &amp;amp; -2 &amp;amp; -2 &amp;amp; 2
\end{bmatrix}
\end{equation*}&lt;/div&gt;
&lt;p&gt;If you take the &lt;span class="caps"&gt;VAA&lt;/span&gt; and your opinions are the&amp;nbsp;vector &lt;span class="math"&gt;\(\boldsymbol{y} = (-1, -2, 2, -2, 0, 0)\)&lt;/span&gt;, then your distance to &lt;span class="caps"&gt;AP&lt;/span&gt;&amp;nbsp;is &lt;span class="math"&gt;\(13\)&lt;/span&gt;, to&amp;nbsp;Sp &lt;span class="math"&gt;\(14\)&lt;/span&gt;, to&amp;nbsp;Frp &lt;span class="math"&gt;\(5\)&lt;/span&gt;, and to&amp;nbsp;H &lt;span class="math"&gt;\(4\)&lt;/span&gt;.
In other words, you are likely on the political right and should consider voting for Frp or&amp;nbsp;H.&lt;/p&gt;
&lt;p&gt;The&amp;nbsp;distance &lt;span class="math"&gt;\(d_j\)&lt;/span&gt; between your&amp;nbsp;opinions &lt;span class="math"&gt;\(\boldsymbol{y}\)&lt;/span&gt; and a&amp;nbsp;party &lt;span class="math"&gt;\(j\)&lt;/span&gt;&amp;rsquo;s opinions is given by the sum of the distances for each&amp;nbsp;statement &lt;span class="math"&gt;\(i\)&lt;/span&gt;:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
d_j =  \sum_i | X_{ij} - y_i |
\end{equation*}&lt;/div&gt;
&lt;h3 id="distances-p-norms-and-weighting"&gt;Distances, &lt;span class="math"&gt;\(p\)&lt;/span&gt;-norms, and&amp;nbsp;weighting&lt;/h3&gt;
&lt;p&gt;We can generalize to an&amp;nbsp;arbitrary &lt;span class="math"&gt;\(p\)&lt;/span&gt;-norm and add a&amp;nbsp;weighting &lt;span class="math"&gt;\(w_i\)&lt;/span&gt;:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
d_j = \left( \sum_i w_i | X_{ij} - y_i |^p \right)^{(1/p)}
\end{equation*}&lt;/div&gt;
&lt;p&gt;
The&amp;nbsp;weights &lt;span class="math"&gt;\(w_i\)&lt;/span&gt; are typically equal&amp;nbsp;to &lt;span class="math"&gt;\(1\)&lt;/span&gt; for most statements,&amp;nbsp;while &lt;span class="math"&gt;\(w_i = 2\)&lt;/span&gt; for core issues or issues that are particularly important to you.
For issues where you have no opinion, we can&amp;nbsp;set &lt;span class="math"&gt;\(w_i = 0\)&lt;/span&gt;.
The value chosen&amp;nbsp;for &lt;span class="math"&gt;\(p\)&lt;/span&gt; depends on how you want to measure&amp;nbsp;distance.&lt;/p&gt;
&lt;p&gt;If you believe&amp;nbsp;that &lt;span class="math"&gt;\(\boldsymbol{y}\)&lt;/span&gt; is closer to&amp;nbsp;party &lt;span class="math"&gt;\(A\)&lt;/span&gt; in the example below, you should&amp;nbsp;choose &lt;span class="math"&gt;\(p=2\)&lt;/span&gt;.
If you&amp;nbsp;think &lt;span class="math"&gt;\(\boldsymbol{y}\)&lt;/span&gt; is closer&amp;nbsp;to &lt;span class="math"&gt;\(B\)&lt;/span&gt;, you should&amp;nbsp;choose &lt;span class="math"&gt;\(p=1\)&lt;/span&gt;.
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\boldsymbol{y} &amp;amp;= (0, 0, 0, 0) \\
A &amp;amp;= (1, 1, 1, 1) \\
B &amp;amp;= (0, 0, 0, 3) 
\end{align*}&lt;/div&gt;
&lt;p&gt;
This is a subjective assessment, and personally, I lean&amp;nbsp;toward &lt;span class="math"&gt;\(p=1\)&lt;/span&gt;.
Both of &lt;span class="caps"&gt;NRK&lt;/span&gt; and &lt;span class="caps"&gt;VG&lt;/span&gt; do the same; they both&amp;nbsp;use &lt;span class="math"&gt;\(p=1\)&lt;/span&gt; in their&amp;nbsp;VAAs.&lt;/p&gt;
&lt;p&gt;The calculations ultimately result in a list of parties, sorted by&amp;nbsp;distance:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;1. H   (distance 4 )
2. Frp (distance 5 )
3. AP  (distance 13)
4. Sp  (distance 14)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Such a list, sorted by distance, is one possible way to explain the user&amp;rsquo;s results.
But it&amp;rsquo;s not the only option; the next section covers another type of&amp;nbsp;explanation.&lt;/p&gt;
&lt;h2 id="explanation-your-position-as-a-combination-of-party-positions"&gt;Explanation: Your position as a combination of party&amp;nbsp;positions&lt;/h2&gt;
&lt;p&gt;The parties&amp;rsquo; vectors set directions in the space of opinions.
After taking a &lt;span class="caps"&gt;VAA&lt;/span&gt;, you also get a position in this space.
(&amp;ldquo;Ditt standpunkt&amp;rdquo; means &amp;ldquo;Your standpoint&amp;rdquo; or &amp;ldquo;Your position&amp;rdquo; in&amp;nbsp;English.)&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 350px; width: 95%;"
src="https://tommyodland.com/images/articles/valgomat_design/vectors_parties_with_user.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;In the figure above, your position&amp;nbsp;is &lt;span class="math"&gt;\(\boldsymbol{y} = (1, -2)\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;One way to reach your position with vector arithmetic is to take &lt;span class="caps"&gt;MDG&lt;/span&gt;&amp;rsquo;s position and add KrF&amp;rsquo;s.
This can be observed geometrically in the figure above, or&amp;nbsp;algebraically:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align}
\text{MDG} + \text{KrF} = \boldsymbol{y} \\
(2, -1) + (-1, -1) &amp;amp;= (1, -2)
\end{align}&lt;/div&gt;
&lt;h3 id="drafting-an-optimization-model"&gt;Drafting an optimization&amp;nbsp;model&lt;/h3&gt;
&lt;p&gt;Here is an alternative idea to a distance-based explanation: we express your position as a combination of party positions.
We are looking for a combination of parties that describes you by taking us as close to your position as possible.
In other words, we are search for a&amp;nbsp;vector &lt;span class="math"&gt;\(\boldsymbol{\beta}\)&lt;/span&gt; that solves the optimization&amp;nbsp;problem
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align}
&amp;amp; \underset{\boldsymbol{\beta}}{\text{minimize}}   &amp;amp;&amp;amp; \| X \boldsymbol{\beta} - \boldsymbol{y}  \|.
\end{align}&lt;/div&gt;
&lt;p&gt;This model has some practical&amp;nbsp;challenges:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It allows complicated explanations in the form of long&amp;nbsp;vectors &lt;span class="math"&gt;\(\boldsymbol{\beta}\)&lt;/span&gt;.
For example, we can&amp;nbsp;reach &lt;span class="math"&gt;\(\boldsymbol{y}\)&lt;/span&gt; through the linear&amp;nbsp;combination
&lt;div class="math"&gt;\begin{align}
\text{V} + \text{R} + \text{H} + \text{Ap}  + \text{MDG} = \boldsymbol{y}.
\end{align}&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;The model allows negative directions&amp;nbsp;because &lt;span class="math"&gt;\(\boldsymbol{\beta}\)&lt;/span&gt; can consist of negative numbers. This is difficult to interpret. For&amp;nbsp;example, &lt;span class="math"&gt;\(-\text{R} = \boldsymbol{y}\)&lt;/span&gt; in the example above, but you naturally want to know who you agree with, rather than being told that you disagree with the&amp;nbsp;party &lt;span class="math"&gt;\(\text{R}\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;The model allows explanations where the sum of the parties&amp;rsquo;&amp;nbsp;contributions &lt;span class="math"&gt;\(\boldsymbol{\beta}\)&lt;/span&gt; exceeds &lt;span class="math"&gt;\(1\)&lt;/span&gt;. This is the case&amp;nbsp;in &lt;span class="math"&gt;\(\text{MDG} + \text{KrF} = \boldsymbol{y}\)&lt;/span&gt; and in the long explanation above. Again, it is difficult to interpret such an&amp;nbsp;answer.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All these problems can be solved if we limit ourselves to &lt;em&gt;convex combinations&lt;/em&gt;.&lt;/p&gt;
&lt;h3 id="final-optimization-model"&gt;Final optimization&amp;nbsp;model&lt;/h3&gt;
&lt;p&gt;We fix the problems in the above model by adding some constraints.
Rather than look for any &lt;em&gt;linear combination&lt;/em&gt; of parties, we look for the &lt;em&gt;shortest convex combination&lt;/em&gt;:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align}
&amp;amp; \underset{\boldsymbol{\beta}}{\text{minimize}}   
&amp;amp;&amp;amp; \| X \boldsymbol{\beta} - \boldsymbol{y}  \|
+ \epsilon \|  \boldsymbol{\beta} \|
  \\
&amp;amp; \text{subject to} &amp;amp;&amp;amp; \beta_j \geq 0 &amp;amp;&amp;amp; \\
&amp;amp;  &amp;amp;&amp;amp; \sum_j \beta_j = 1. &amp;amp;&amp;amp; 
\end{align}&lt;/div&gt;
&lt;p&gt;
The regularization&amp;nbsp;term &lt;span class="math"&gt;\(\epsilon \|  \boldsymbol{\beta} \|\)&lt;/span&gt; means that we prefer simple (short) explanations.
The two constraints&amp;nbsp;on &lt;span class="math"&gt;\(\boldsymbol{\beta}\)&lt;/span&gt; are the definition of a convex combination, and ensure that we avoid negative directions and sums that&amp;nbsp;exceed &lt;span class="math"&gt;\(1\)&lt;/span&gt;.
The&amp;nbsp;sum &lt;span class="math"&gt;\(\sum_j \beta_j\)&lt;/span&gt; must&amp;nbsp;equal &lt;span class="math"&gt;\(1\)&lt;/span&gt;, rather than be less&amp;nbsp;than &lt;span class="math"&gt;\(1\)&lt;/span&gt;, because&amp;nbsp;if &lt;span class="math"&gt;\(\boldsymbol{y} = (0, 1)\)&lt;/span&gt;, &lt;span class="math"&gt;\(A = (0, 1)\)&lt;/span&gt;,&amp;nbsp;and &lt;span class="math"&gt;\(B = (0, 2)\)&lt;/span&gt;, we&amp;nbsp;expect &lt;span class="math"&gt;\(\boldsymbol{\beta} = (1, 0)\)&lt;/span&gt; as the answer,&amp;nbsp;not &lt;span class="math"&gt;\(\boldsymbol{\beta} = (0, 0.5)\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;With&amp;nbsp;the &lt;span class="math"&gt;\(2\)&lt;/span&gt;-norm on both terms in the objective function, the solution to the optimization problem&amp;nbsp;becomes:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align}
0.69 \, \text{V} + 0.23 \, \text{H} + 0.08 \, \text{Frp} = \boldsymbol{y}.
\end{align}&lt;/div&gt;
&lt;p&gt;
If you go back to the figure and compare the geometry with this solution, you&amp;rsquo;ll see that the solution makes sense.
That said, there are often no single correct approach in mathematical modeling, and other variations of such a model might be&amp;nbsp;appropriate.&lt;/p&gt;
&lt;h3 id="the-optimization-model-on-a-larger-example"&gt;The optimization model on a larger&amp;nbsp;example&lt;/h3&gt;
&lt;p&gt;Let&amp;rsquo;s run the optimization model on the same example as&amp;nbsp;before:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
[X \mid \boldsymbol{y}] =
\begin{bmatrix}
  \text{AP} &amp;amp;  \text{Sp} &amp;amp; \text{Frp} &amp;amp; \text{H} &amp;amp; \boldsymbol{y} \\
  -1 &amp;amp;  1 &amp;amp; -2 &amp;amp; -1 &amp;amp; -1\\
  2  &amp;amp;  1 &amp;amp; -2 &amp;amp; -2 &amp;amp; -2\\
  -2 &amp;amp; -2 &amp;amp;  2 &amp;amp;  2 &amp;amp; 2\\
  1  &amp;amp; -1 &amp;amp; -2 &amp;amp; -2 &amp;amp; -2 \\
  1  &amp;amp;  2 &amp;amp;  2 &amp;amp; -2 &amp;amp; 0\\
  -1 &amp;amp; -2 &amp;amp; -2 &amp;amp; 2 &amp;amp; 0
\end{bmatrix} 
\end{align*}&lt;/div&gt;
&lt;p&gt;The answer is given below and corresponds well with the ranking we obtained&amp;nbsp;earlier.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;1. H   (51%)
2. Frp (45%)
4. Sp  (4%)
3. AP  (0%)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="the-difference-between-distance-based-and-combination-based-explanation"&gt;The difference between distance-based and combination-based&amp;nbsp;explanation&lt;/h3&gt;
&lt;p&gt;We have seen two ways to explain a user&amp;rsquo;s results in a &lt;span class="caps"&gt;VAA&lt;/span&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Distance-based explanation&lt;/strong&gt; - ranks parties by distance from the&amp;nbsp;user&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Combination-based explanation&lt;/strong&gt; - the user is expressed as the nearest convex combination of&amp;nbsp;parties&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The difference between the explanations is illustrated in the figure below.
&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 350px; width: 95%;"
src="https://tommyodland.com/images/articles/valgomat_design/opt_model_vs_distance.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Distance-based explanation says&amp;nbsp;that &lt;span class="math"&gt;\(\boldsymbol{y}\)&lt;/span&gt; is closest&amp;nbsp;to &lt;span class="math"&gt;\(B\)&lt;/span&gt;,&amp;nbsp;then &lt;span class="math"&gt;\(A\)&lt;/span&gt;, and&amp;nbsp;finally &lt;span class="math"&gt;\(C\)&lt;/span&gt;. Distance-based explanation is &lt;em&gt;mutually independent&lt;/em&gt;; the distance&amp;nbsp;to &lt;span class="math"&gt;\(B\)&lt;/span&gt; does not affect the distance&amp;nbsp;to &lt;span class="math"&gt;\(A\)&lt;/span&gt;.&amp;nbsp;If &lt;span class="math"&gt;\(B\)&lt;/span&gt; had disappeared, the distance&amp;nbsp;to &lt;span class="math"&gt;\(A\)&lt;/span&gt; would remain&amp;nbsp;unchanged.&lt;/li&gt;
&lt;li&gt;Combination-based explanation expresses the&amp;nbsp;position &lt;span class="math"&gt;\(\boldsymbol{y}\)&lt;/span&gt; as a convex combination. The answer in the figure above&amp;nbsp;becomes &lt;span class="math"&gt;\(\boldsymbol{\beta} = (\beta_A, \beta_B, \beta_C) = (0, 0.7, 0.3)\)&lt;/span&gt;. The optimization model&amp;rsquo;s explanation is &lt;em&gt;mutually dependent&lt;/em&gt;.&amp;nbsp;If &lt;span class="math"&gt;\(B\)&lt;/span&gt; had disappeared, the explanation would have relied&amp;nbsp;on &lt;span class="math"&gt;\(A\)&lt;/span&gt;, but&amp;nbsp;when &lt;span class="math"&gt;\(B\)&lt;/span&gt; exists, &lt;span class="math"&gt;\(A\)&lt;/span&gt; is not needed at all to&amp;nbsp;explain &lt;span class="math"&gt;\(\boldsymbol{y}\)&lt;/span&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The figure below shows an example where the explanations become completely different.
&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 350px; width: 95%;"
src="https://tommyodland.com/images/articles/valgomat_design/opt_model_vs_distance_different_result.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Measured by&amp;nbsp;distance, &lt;span class="math"&gt;\(C\)&lt;/span&gt; is closer&amp;nbsp;to &lt;span class="math"&gt;\(\boldsymbol{y}\)&lt;/span&gt; than&amp;nbsp;both &lt;span class="math"&gt;\(A\)&lt;/span&gt; and &lt;span class="math"&gt;\(B\)&lt;/span&gt;.&amp;nbsp;Yet &lt;span class="math"&gt;\(\boldsymbol{y}\)&lt;/span&gt; is explained&amp;nbsp;using &lt;span class="math"&gt;\(\boldsymbol{\beta} = (\beta_A, \beta_B, \beta_C) = (0.5, 0.5, 0)\)&lt;/span&gt; without&amp;nbsp;using &lt;span class="math"&gt;\(C\)&lt;/span&gt;.
Shortest distance tells you which single party is closest.
The optimization model tells you which coalition of parties is closest to&amp;nbsp;you.&lt;/p&gt;
&lt;h1 id="part-3-how-should-a-vaa-present-questions-to-the-user"&gt;Part 3: How should a &lt;span class="caps"&gt;VAA&lt;/span&gt; present questions to the&amp;nbsp;user?&lt;/h1&gt;
&lt;p&gt;In this section, we present an algorithm that selects statements that discriminate well between parties.
There may be two reasons for wanting&amp;nbsp;this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;We want to use a selection of statements.&lt;/strong&gt; If parties have answered many questions, we may want to limit ourselves to a smaller selection in the &lt;span class="caps"&gt;VAA&lt;/span&gt;. How can we choose a small subset of good statements that differentiate between&amp;nbsp;parties?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Early termination.&lt;/strong&gt; We want the user to be able to end the &lt;span class="caps"&gt;VAA&lt;/span&gt; after any number of questions. How do we ensure that the user has been presented with a diverse set of statements even if they do not answer every&amp;nbsp;question?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To motivate the algorithm, we start by looking at the same example as earlier in the&amp;nbsp;article.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 550px; width: 95%;"
src="https://tommyodland.com/images/articles/valgomat_design/vectors_statements_with_trivial_direction.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;If we ask about a railway to Tromsø or whether the county municipality should be abolished, we gather no information because the user&amp;rsquo;s answer does not discriminate between parties.
The subspace spanned by the&amp;nbsp;vector &lt;span class="math"&gt;\(\boldsymbol{e} = (1, 1)\)&lt;/span&gt; consists of such &lt;em&gt;trivial statements&lt;/em&gt;.
The best statement to present to the user is therefore &amp;ldquo;Drop the ferry-free E39 along the west coast,&amp;rdquo; since this statement lies furthest&amp;nbsp;from &lt;span class="math"&gt;\(\operatorname{span} (\{ \boldsymbol{e} \} )\)&lt;/span&gt;.&lt;/p&gt;
&lt;h2 id="algorithm-for-selecting-statements-in-sequence"&gt;Algorithm for selecting statements in&amp;nbsp;sequence&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s generalize from two dimensions to an arbitrary number of&amp;nbsp;dimensions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Start with the trivial subspace spanned by the&amp;nbsp;statement &lt;span class="math"&gt;\(\boldsymbol{e} = (1, 1, \ldots, 1)\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;The first statement selected is the one that is furthest&amp;nbsp;from &lt;span class="math"&gt;\(\operatorname{span} (\{ \boldsymbol{e} \} )\)&lt;/span&gt;. This&amp;nbsp;is &lt;span class="math"&gt;\(\boldsymbol{v}_1 = \arg \max_j \| P_S \boldsymbol{v}_j - \boldsymbol{v}_j \|_2\)&lt;/span&gt;,&amp;nbsp;where &lt;span class="math"&gt;\(P_S\)&lt;/span&gt; is a projection matrix that projects a vector&amp;nbsp;onto &lt;span class="math"&gt;\(S\)&lt;/span&gt;. The formula for such a projection matrix&amp;nbsp;is &lt;span class="math"&gt;\(P_S = S S^{+} = S (S^T S)^{-1} S^T\)&lt;/span&gt;, where the columns of the&amp;nbsp;matrix &lt;span class="math"&gt;\(S\)&lt;/span&gt; are the vectors that span the space we want to project onto (here&amp;nbsp;just &lt;span class="math"&gt;\(\boldsymbol{e}\)&lt;/span&gt;).&lt;/li&gt;
&lt;li&gt;The next statement selected should be far from both the trivial&amp;nbsp;subspace &lt;span class="math"&gt;\(\boldsymbol{e}\)&lt;/span&gt; and far from the previously selected&amp;nbsp;statement &lt;span class="math"&gt;\(\boldsymbol{v}_1\)&lt;/span&gt;. So we choose the statement that is furthest&amp;nbsp;from &lt;span class="math"&gt;\(\operatorname{span} (\{ \boldsymbol{e}, \boldsymbol{v}_1 \} )\)&lt;/span&gt;. In mathematical notation, this&amp;nbsp;becomes &lt;span class="math"&gt;\(\boldsymbol{v}_2 = \arg \max_j \| P_S \boldsymbol{v}_j - \boldsymbol{v}_j \|_2\)&lt;/span&gt;,&amp;nbsp;where &lt;span class="math"&gt;\(S\)&lt;/span&gt; in this iteration is a matrix with&amp;nbsp;columns &lt;span class="math"&gt;\(\boldsymbol{e}\)&lt;/span&gt; and &lt;span class="math"&gt;\(\boldsymbol{v}_1\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;We continue like this, selecting statements that are furthest from those we have already chosen.&amp;nbsp;If &lt;span class="math"&gt;\(\operatorname{rank} \operatorname{span} (\{ \boldsymbol{e}, \boldsymbol{v}_1, \ldots, \boldsymbol{v}_n \} )\)&lt;/span&gt; becomes equal to the dimensionality of the space (number of parties), then all remaining statements are in the space. Then the&amp;nbsp;distance &lt;span class="math"&gt;\(\| P_S \boldsymbol{v}_j - \boldsymbol{v}_j \|_2 \approx 0\)&lt;/span&gt; for all remaining&amp;nbsp;statements &lt;span class="math"&gt;\(\boldsymbol{v}_j\)&lt;/span&gt;. The solution is to treat the selected statements as a &lt;span class="caps"&gt;FIFO&lt;/span&gt; queue: we remove the first selected&amp;nbsp;statement &lt;span class="math"&gt;\(\boldsymbol{v}_1\)&lt;/span&gt; and try again. Then the next statement we select is always far from the most recently selected&amp;nbsp;statements.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Python code for the algorithm is included at the end of the article.
The figure below illustrates the concept in three dimensions.
The next statement selected is always the one that is furthest from the space spanned by the trivial direction and previously selected statements.
(In english, the first figure says &amp;ldquo;Start with (1, 1, 1)&amp;rdquo;, the next says &amp;ldquo;Choose first statement&amp;rdquo; and the third says &amp;ldquo;Choose second&amp;nbsp;statement&amp;rdquo;.)&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 800px; width: 99%;"
src="https://tommyodland.com/images/articles/valgomat_design/pivoted_QR_visualization.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;h2 id="results-bad-statements"&gt;Results - bad&amp;nbsp;statements&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s look at an example with statements that are similar to each&amp;nbsp;other.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 550px; width: 95%;"
src="https://tommyodland.com/images/articles/valgomat_design/statements_bad_order.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;The first two statements are very similar.
The third statement about Erna is on the opposite side of the vector space but still does not provide much new information because it is close to the space spanned by the first two statements.
In other words: the question about Erna thus reveals little new information, despite the statement being on the opposite side of the vector&amp;nbsp;space.&lt;/p&gt;
&lt;h2 id="results-good-statements"&gt;Results - good&amp;nbsp;statements&lt;/h2&gt;
&lt;p&gt;Here is a selection of statements that are different from each other, identified by the algorithm&amp;nbsp;above.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 550px; width: 95%;"
src="https://tommyodland.com/images/articles/valgomat_design/statements_orthogonal_order.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;The first statement is far from trivial because half of the parties disagree and the other half agree.
The second statement is answered differently by six parties and differs from both the trivial statement and the first statement.
The algorithm continues to select statements that are consistently different from what the &lt;span class="caps"&gt;VAA&lt;/span&gt; user has already been presented&amp;nbsp;with.&lt;/p&gt;
&lt;h1 id="summary-and-references"&gt;Summary and&amp;nbsp;references&lt;/h1&gt;
&lt;p&gt;Voting advice applications quantify political opinions.
Party responses can be represented as a matrix, where the rows (statements) and columns (parties) can be interpreted as vectors.
Such a geometric interpretation gives us valuable insight: we can define what it means for a person to be close to a party, and we can formalize what it means for a statement to discriminate between&amp;nbsp;parties.&lt;/p&gt;
&lt;p&gt;The idea of viewing parties as vectors and ranking users by proximity is obvious, and &lt;a href="https://nrkbeta.no/2019/07/03/slik-snekret-vi-en-valgomat-for-hele-landet/"&gt;for the 2019 municipal election, &lt;span class="caps"&gt;NRK&lt;/span&gt;&amp;nbsp;used&lt;/a&gt; &lt;span class="math"&gt;\(p=1\)&lt;/span&gt; as a metric.
(The fact that NRKbeta&amp;rsquo;s article in the link above describes middle school mathematics in phrases like &amp;ldquo;explain the calculation to me who doesn&amp;rsquo;t have math anxiety&amp;rdquo; and &amp;ldquo;I get a headache!&amp;rdquo; is sad.)
The idea of describing a user as a convex combination of parties is my own.
In practice, it is more difficult to implement in code and more challenging for an average user to understand.
Simple algorithms have inherent value, and distance calculation provides a simple and understandable&amp;nbsp;explanation.&lt;/p&gt;
&lt;p&gt;The idea of viewing statements as vectors and selecting statements that are far from each other is my own.
The inspiration comes from &lt;a href="https://en.wikipedia.org/wiki/QR_decomposition"&gt;&lt;span class="caps"&gt;QR&lt;/span&gt; decomposition with pivoting&lt;/a&gt;, which is an algorithm for orthogonalizing a matrix.
The pivot columns in &lt;span class="caps"&gt;QR&lt;/span&gt; can be seen as a greedy approximation of D-optimality in &lt;a href="https://en.wikipedia.org/wiki/Optimal_experimental_design"&gt;optimal experimental design&lt;/a&gt;, which maximizes the volume of a high-dimensional parallelepiped.
The differences between my algorithm and &lt;span class="caps"&gt;QR&lt;/span&gt; decomposition are that (1) we always stay away from the trivial&amp;nbsp;statement &lt;span class="math"&gt;\(\boldsymbol{e} = \boldsymbol{1}\)&lt;/span&gt; and that (2) we use a &lt;span class="caps"&gt;FIFO&lt;/span&gt; queue.
Again I believe simplicitiy has inherent value: I like that this algorithm does &lt;em&gt;not&lt;/em&gt; adapt the sequence of questions to what the user responded, since I do not like the idea of a personalized &lt;span class="caps"&gt;VAA&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;This article focused mostly on mathematics, but a &lt;span class="caps"&gt;VAA&lt;/span&gt; must of course contain politically relevant statements, good graphic design, potentially the ability to skip statements or weight statements up, subjective analyses of the results, and so&amp;nbsp;on.&lt;/p&gt;
&lt;h2 id="code"&gt;Code&lt;/h2&gt;
&lt;p&gt;Here is a simple implementation of the algorithm that finds statements that are far from each other.
I have not emphasized runtime, numerical stability, caching of calculations, etc.
The code is certainly good enough to showcase the idea, but for larger datasets, it should be&amp;nbsp;rewritten.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;collections&lt;/span&gt;  &lt;span class="c1"&gt;# Python 3.12&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;numpy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;np&lt;/span&gt;  &lt;span class="c1"&gt;# NumPy 2.1.3&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;away_from_last_k&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Yields the column indices of A that are as far away from a continually&lt;/span&gt;
&lt;span class="sd"&gt;    updated subspace spanned by (1, 1, 1, ...) and a First-In-First-Out (FIFO)&lt;/span&gt;
&lt;span class="sd"&gt;    queue of length k. When a column index is yielded, it is added to the queue.&lt;/span&gt;

&lt;span class="sd"&gt;    Parameters&lt;/span&gt;
&lt;span class="sd"&gt;    ----------&lt;/span&gt;
&lt;span class="sd"&gt;    A : np.ndarray&lt;/span&gt;
&lt;span class="sd"&gt;        A matrix (2D array) with columns [v1 | v2 | ... ].&lt;/span&gt;
&lt;span class="sd"&gt;    k : int, optional&lt;/span&gt;
&lt;span class="sd"&gt;        Numbers of elements to keep in the FIFO queue. None means no limit.&lt;/span&gt;
&lt;span class="sd"&gt;    verbose : bool, optional&lt;/span&gt;
&lt;span class="sd"&gt;        Whether to print information. The default is False.&lt;/span&gt;

&lt;span class="sd"&gt;    Examples&lt;/span&gt;
&lt;span class="sd"&gt;    --------&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; A = np.array([[3, 0, 0], [0, 2, 0], [0, 0, 1], [2.5, 0, 0]]).T&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; list(away_from_last_k(A, k=None))&lt;/span&gt;
&lt;span class="sd"&gt;    [0, 1, 3, 2]&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; list(away_from_last_k(A, k=0)) #  Distance from span({(1, 1, 1)}) only&lt;/span&gt;
&lt;span class="sd"&gt;    [0, 3, 1, 2]&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; list(away_from_last_k(A, k=1))&lt;/span&gt;
&lt;span class="sd"&gt;    [0, 1, 3, 2]&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;subspace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deque&lt;/span&gt;&lt;span class="p"&gt;([],&lt;/span&gt; &lt;span class="n"&gt;maxlen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Vector in FIFO subspace&lt;/span&gt;
    &lt;span class="n"&gt;ones&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ones&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;  &lt;span class="c1"&gt;# Vector of ones, always in subspace&lt;/span&gt;
    &lt;span class="n"&gt;remaining&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# Remaining column indices of A&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;distance_from_subspace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tuple_&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tuple_&lt;/span&gt;
        &lt;span class="n"&gt;S&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vstack&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;ones&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subspace&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;  &lt;span class="c1"&gt;# Full space as col. mat.&lt;/span&gt;
        &lt;span class="n"&gt;P_S&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pinv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Projection matrix onto span(S)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;P_S&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Distance between v and span(S)&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;remaining&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;inds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remaining&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Col idx of &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remaining&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; remaining vectors: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;inds&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Vectors in subspace (excl. ones): &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subspace&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Get idx of col vector furthest from S = [ones | subspace]&lt;/span&gt;
        &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remaining&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;distance_from_subspace&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;maxdist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;distance_from_subspace&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;remaining&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;maxdist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Distance was 0. Popping from subspace.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;subspace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;popleft&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Col idx &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; furthest from subspace. Dist: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;maxdist&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.6f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;subspace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remaining&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Mon, 07 Apr 2025 00:00:00 +0200</pubDate><guid>tag:tommyodland.com,2025-04-07:/articles/2025/how-do-voting-advice-applications-work</guid><category>articles</category><category>mathematics</category></item><item><title>Hvordan fungerer en valgomat?</title><link>https://tommyodland.com/articles/2025/hvordan-fungerer-en-valgomat</link><description>&lt;p&gt;Denne artikkelen presenterer matematikken bak valgomater. 
Den består av tre&amp;nbsp;hoveddeler:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Del 1: Valgomater og&amp;nbsp;geometri&lt;/li&gt;
&lt;li&gt;Del 2: Hvordan forklarer en valgomat&amp;nbsp;resultatene?&lt;/li&gt;
&lt;li&gt;Del 3: Hvordan bør en valgomat presentere spørsmål for&amp;nbsp;brukeren?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Artikkelen begynner på lavt matematisk nivå og blir progressivt mer krevende.
En fordel med dette er at det er noe å lære for alle, uansett nivå.
Ulempen er at artikkelen ikke har én tydelig&amp;nbsp;målgruppe.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span class="caps"&gt;PS&lt;/span&gt;:&lt;/strong&gt; I 2023 &lt;a href="https://tommyodland.com/articles/2023/politikk-og-meningsrommet"&gt;visualiserte jeg partiene i kommunevalget&lt;/a&gt;. 
Fokuset i den artikkelen var innsikt i partienes politikk. 
Denne artikkelen retter seg mer mot design av&amp;nbsp;valgomater.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span class="caps"&gt;PSS&lt;/span&gt;:&lt;/strong&gt; Idéene i denne artikkelen er implementert i min egen &lt;a href="https://tommyodland.com/tools/valgomat2025.html"&gt;Valgomat for Stortingsvalget 2025&lt;/a&gt;.&lt;/p&gt;
&lt;h1 id="del-1-valgomater-og-geometri"&gt;Del 1: Valgomater og&amp;nbsp;geometri&lt;/h1&gt;
&lt;h2 id="meninger-og-matriser"&gt;Meninger og&amp;nbsp;matriser&lt;/h2&gt;
&lt;p&gt;For å lage en valgomat spør man politiske partier hvor enige de er i ulike påstander.
Resultatet kvantifiseres ved at svar som &amp;ldquo;Helt enig!&amp;rdquo; blir oversatt til tall.
Svarene kan deretter visualiseres i en tabell med en rad per påstand og en kolonne per parti.
Nedenfor er &amp;ldquo;Helt enig&amp;rdquo; oversatt&amp;nbsp;til &lt;span class="math"&gt;\(2\)&lt;/span&gt; og &amp;ldquo;Helt uenig&amp;rdquo; oversatt&amp;nbsp;til &lt;span class="math"&gt;\(-2\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 550px; width: 95%;"
src="https://tommyodland.com/images/articles/valgomat_design/intro_figure.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Tabellen utgjør en&amp;nbsp;matrise &lt;span class="math"&gt;\(X\)&lt;/span&gt;.
Radene og kolonnene har en geometrisk tolkning som &lt;em&gt;vektorer&lt;/em&gt;.
En vektor er en liste med tall,&amp;nbsp;e.g. &lt;span class="math"&gt;\((1, 2)\)&lt;/span&gt;, og kan tolkes geometrisk som en pil eller som et&amp;nbsp;punkt.&lt;/p&gt;
&lt;h2 id="pastander-og-partier"&gt;Påstander og&amp;nbsp;partier&lt;/h2&gt;
&lt;h3 id="pastander-som-vektorer"&gt;Påstander som&amp;nbsp;vektorer&lt;/h3&gt;
&lt;p&gt;For å visualisere påstander og partier som vektorer må vi holde oss til to eller tre dimensjoner.
Høyere dimensjoner lar seg ikke&amp;nbsp;visualisere.&lt;/p&gt;
&lt;p&gt;La oss studere geometrisk hva partiene &lt;span class="caps"&gt;SV&lt;/span&gt; og Sp mener om et utvalg&amp;nbsp;påstander.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 750px; width: 95%;"
src="https://tommyodland.com/images/articles/valgomat_design/vectors_statements.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Oppe til høyre i figuren finner vi påstander som begge partiene er enige i.
Nede til høyre finner vi påstander som &lt;span class="caps"&gt;SV&lt;/span&gt; er enige i, men som Sp er uenige i.
Vi kan gjøre tilsvarende observasjoner for de to andre&amp;nbsp;kvadrantene.&lt;/p&gt;
&lt;h3 id="hvilke-pastander-er-gode-og-darlige"&gt;Hvilke påstander er gode og&amp;nbsp;dårlige?&lt;/h3&gt;
&lt;p&gt;En god påstand lar oss skille partiene fra&amp;nbsp;hverandre:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I figuren ovenfor er &amp;ldquo;Bygg jernbane nordover til Tromsø&amp;rdquo; en dårlig påstand. Uansett om du er enig eller uenig, avklarer ikke påstanden hvilket av de to partiene du er nærmest politisk. Trivielle påstander, som &amp;ldquo;Et rettferdig samfunn er bra&amp;rdquo;, er alle partiene enige i. Derfor gir de heller ingen informasjon. Det samme gjelder trivielle påstander som alle partiene er uenige i, for eksempel &amp;ldquo;Fylkeskommunen bør avskaffes&amp;rdquo; i figuren&amp;nbsp;ovenfor. &lt;/li&gt;
&lt;li&gt;&lt;span class="dquo"&gt;&amp;ldquo;&lt;/span&gt;Dropp ferjefri E39 langs Vestlandet&amp;rdquo; er en god påstand fordi den &lt;em&gt;diskriminerer&lt;/em&gt; mellom partiene. Om du er enig ligger du nærmere &lt;span class="caps"&gt;SV&lt;/span&gt; politisk. Er du derimot uenig ligger du nær&amp;nbsp;Sp.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I to dimensjoner (to partier) er påstander gode dersom de ligger langt fra underrommet som spennes av&amp;nbsp;vektoren &lt;span class="math"&gt;\(\boldsymbol{e} = (1, 1)\)&lt;/span&gt;, altså den diagonale&amp;nbsp;linja &lt;span class="math"&gt;\(y=x\)&lt;/span&gt;.
På slutten av denne artikkelen skal vi generalisere denne idéen for å konstruere en algoritme som finner en god rekkefølge å stille spørsmål&amp;nbsp;på.&lt;/p&gt;
&lt;h3 id="partier-som-vektorer"&gt;Partier som&amp;nbsp;vektorer&lt;/h3&gt;
&lt;p&gt;Ovenfor betraktet vi radene (påstandene) i tabellen som vektorer.
La oss nå se på det komplementære vektorrommet der kolonnene (partiene) er vektorer.
Vi velger to tilfeldige påstander: en om ulv og en om statens eierskap i&amp;nbsp;bedrifter.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 350px; width: 95%;"
src="https://tommyodland.com/images/articles/valgomat_design/vectors_parties.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;To partier ligner hverandre dersom de ligger nær hverandre i meningsrommet.
Vi kan for eksempel måle avstand som distanse på den horisontale aksen pluss distanse på den vertikale&amp;nbsp;aksen.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Partiene R og &lt;span class="caps"&gt;AP&lt;/span&gt; er nær hverandre. Distansen&amp;nbsp;er &lt;span class="math"&gt;\(0 + 1 = 1\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Partiene R og V er langt fra hverandre. Distansen&amp;nbsp;er &lt;span class="math"&gt;\(3 + 4 = 7\)&lt;/span&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Distansen i dette rommet er nøkkelen til hvordan en valgomat avgjør hvilke partier du er mest enig med når du tar valgomaten.
Det er dette vi nå skal ta for oss i del&amp;nbsp;2.&lt;/p&gt;
&lt;h1 id="del-2-hvordan-forklarer-en-valgomat-resultatene"&gt;Del 2: Hvordan forklarer en valgomat&amp;nbsp;resultatene?&lt;/h1&gt;
&lt;h2 id="forklaring-rangering-av-partiene-etter-avstand"&gt;Forklaring: rangering av partiene etter&amp;nbsp;avstand&lt;/h2&gt;
&lt;p&gt;Ovenfor så vi at partienes likhet kan kvantifiseres ved å se på avstanden mellom partiene.
Vi visualiserte dette i to dimensjoner, men beregningen er den samme i et vilkårlig antall&amp;nbsp;dimensjoner.&lt;/p&gt;
&lt;p&gt;Her er en matrise med tallene fra den første figuren i denne&amp;nbsp;artikkelen:&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
X =
\begin{bmatrix}
  \text{AP} &amp;amp;  \text{Sp} &amp;amp; \text{Frp} &amp;amp; \text{H}\\
  -1 &amp;amp;  1 &amp;amp; -2 &amp;amp; -1\\
  2  &amp;amp;  1 &amp;amp; -2 &amp;amp; -2\\
  -2 &amp;amp; -2 &amp;amp;  2 &amp;amp;  2\\
  1  &amp;amp; -1 &amp;amp; -2 &amp;amp; -2\\
  1  &amp;amp;  2 &amp;amp;  2 &amp;amp; -2\\
  -1 &amp;amp; -2 &amp;amp; -2 &amp;amp; 2
\end{bmatrix}
\end{equation*}&lt;/div&gt;
&lt;p&gt;Hvis du tar valgomaten og dine meninger er&amp;nbsp;vektoren &lt;span class="math"&gt;\(\boldsymbol{y} = (-1, -2, 2, -2, 0, 0)\)&lt;/span&gt;, så blir din avstand til &lt;span class="caps"&gt;AP&lt;/span&gt; &lt;span class="math"&gt;\(13\)&lt;/span&gt;,&amp;nbsp;Sp &lt;span class="math"&gt;\(14\)&lt;/span&gt;,&amp;nbsp;Frp &lt;span class="math"&gt;\(5\)&lt;/span&gt; og&amp;nbsp;H &lt;span class="math"&gt;\(4\)&lt;/span&gt;.
Du ligger med andre ord sannsynligvis på høyresiden politisk, og bør vurdere å stemme Frp eller&amp;nbsp;H.&lt;/p&gt;
&lt;p&gt;Distansen &lt;span class="math"&gt;\(d_j\)&lt;/span&gt; mellom dine&amp;nbsp;meninger &lt;span class="math"&gt;\(\boldsymbol{y}\)&lt;/span&gt; og et&amp;nbsp;parti &lt;span class="math"&gt;\(j\)&lt;/span&gt; sine meninger er gitt av summen av avstandene mellom hver&amp;nbsp;påstand &lt;span class="math"&gt;\(i\)&lt;/span&gt;:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
d_j =  \sum_i | X_{ij} - y_i |
\end{equation*}&lt;/div&gt;
&lt;h3 id="avstander-p-normer-og-vekting"&gt;Avstander, &lt;span class="math"&gt;\(p\)&lt;/span&gt;-normer og&amp;nbsp;vekting&lt;/h3&gt;
&lt;p&gt;Vi kan generalisere til en&amp;nbsp;vilkårlig &lt;span class="math"&gt;\(p\)&lt;/span&gt;-norm og legge til en&amp;nbsp;vekting &lt;span class="math"&gt;\(w_i\)&lt;/span&gt;:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
d_j = \left( \sum_i w_i | X_{ij} - y_i |^p \right)^{(1/p)}
\end{equation*}&lt;/div&gt;
&lt;p&gt;
Vektene &lt;span class="math"&gt;\(w_i\)&lt;/span&gt; er gjerne&amp;nbsp;lik &lt;span class="math"&gt;\(1\)&lt;/span&gt; for de fleste påstander,&amp;nbsp;mens &lt;span class="math"&gt;\(w_i = 2\)&lt;/span&gt; for kjernesaker eller saker som er spesielt viktige for deg.
For saker der du ikke har noen mening kan vi&amp;nbsp;sette &lt;span class="math"&gt;\(w_i = 0\)&lt;/span&gt;.
Verdien som velges&amp;nbsp;for &lt;span class="math"&gt;\(p\)&lt;/span&gt; er avhengig av hvordan man vil måle&amp;nbsp;distanse.&lt;/p&gt;
&lt;p&gt;Dersom du mener&amp;nbsp;at &lt;span class="math"&gt;\(\boldsymbol{y}\)&lt;/span&gt; er nærmere&amp;nbsp;partiet &lt;span class="math"&gt;\(A\)&lt;/span&gt; i eksempelet nedenfor bør du&amp;nbsp;velge &lt;span class="math"&gt;\(p=2\)&lt;/span&gt;.
Mener du derimot&amp;nbsp;at &lt;span class="math"&gt;\(\boldsymbol{y}\)&lt;/span&gt; er&amp;nbsp;nærmere &lt;span class="math"&gt;\(B\)&lt;/span&gt; bør du&amp;nbsp;velge &lt;span class="math"&gt;\(p=1\)&lt;/span&gt;.
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\boldsymbol{y} &amp;amp;= (0, 0, 0, 0) \\
A &amp;amp;= (1, 1, 1, 1) \\
B &amp;amp;= (0, 0, 0, 3) 
\end{align*}&lt;/div&gt;
&lt;p&gt;
Dette er en subjektiv vurdering, og personlig lener jeg&amp;nbsp;mot &lt;span class="math"&gt;\(p=1\)&lt;/span&gt;.
Det samme gjør både &lt;span class="caps"&gt;NRK&lt;/span&gt; og &lt;span class="caps"&gt;VG&lt;/span&gt;; begge&amp;nbsp;bruker &lt;span class="math"&gt;\(p=1\)&lt;/span&gt; i sine&amp;nbsp;valgomater.&lt;/p&gt;
&lt;p&gt;Beregningene resulterer til slutt i en liste av partier, sortert etter&amp;nbsp;distanse:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;1. H   (avstand 4 )
2. Frp (avstand 5 )
3. AP  (avstand 13)
4. Sp  (avstand 14)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;En slik liste, sortert etter avstand, er én mulig måte å forklare brukerens resultater på.
Men det er ikke den eneste; neste seksjon tar for seg en annen type&amp;nbsp;forklaring.&lt;/p&gt;
&lt;h2 id="forklaring-ditt-standpunkt-som-en-kombinasjon-av-partienes-standpunkter"&gt;Forklaring: Ditt standpunkt som en kombinasjon av partienes&amp;nbsp;standpunkter&lt;/h2&gt;
&lt;p&gt;Partienes vektorer setter retning i rommet av meninger.
Etter å ha tatt en valgomat får du også en plassering i dette&amp;nbsp;rommet.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 350px; width: 95%;"
src="https://tommyodland.com/images/articles/valgomat_design/vectors_parties_with_user.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;I figuren ovenfor er ditt&amp;nbsp;standpunkt &lt;span class="math"&gt;\(\boldsymbol{y} = (1, -2)\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Én måte å nå ditt standpunkt på med vektorregning er å ta MDGs standpunkt og legge på KrFs.
Dette kan observeres geometrisk i figuren ovenfor, eller&amp;nbsp;algebraisk:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align}
\text{MDG} + \text{KrF} = \boldsymbol{y} \\
(2, -1) + (-1, -1) &amp;amp;= (1, -2)
\end{align}&lt;/div&gt;
&lt;h3 id="utkast-til-optimeringsmodell"&gt;Utkast til&amp;nbsp;optimeringsmodell&lt;/h3&gt;
&lt;p&gt;Her er en alternativ idé til distansebasert forklaring: vi uttrykker ditt standpunkt som en kombinasjon av partienes standpunkter.
Vi leter da etter en kombinasjon av partier som beskriver deg ved at de tar oss så nært ditt standpunkt som mulig.
Med andre ord er vi på jakt etter en&amp;nbsp;vektor &lt;span class="math"&gt;\(\boldsymbol{\beta}\)&lt;/span&gt; som løser&amp;nbsp;optimeringsproblemet
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align}
&amp;amp; \underset{\boldsymbol{\beta}}{\text{minimize}}   &amp;amp;&amp;amp; \| X \boldsymbol{\beta} - \boldsymbol{y}  \|.
\end{align}&lt;/div&gt;
&lt;p&gt;Denne modellen har noen praktiske&amp;nbsp;utfordringer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Den tillater kompliserte forklaringer i form av lange&amp;nbsp;vektorer &lt;span class="math"&gt;\(\boldsymbol{\beta}\)&lt;/span&gt;.
For eksempel kan vi nå frem&amp;nbsp;til &lt;span class="math"&gt;\(\boldsymbol{y}\)&lt;/span&gt; ved den lineære&amp;nbsp;kombinasjonen
&lt;div class="math"&gt;\begin{align}
\text{V} + \text{R} + \text{H} + \text{Ap}  + \text{MDG} = \boldsymbol{y}.
\end{align}&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;Modellen tillater negative retninger&amp;nbsp;fordi &lt;span class="math"&gt;\(\boldsymbol{\beta}\)&lt;/span&gt; kan bestå av negative tall. Dette er vanskelig å tolke. For eksempel&amp;nbsp;er &lt;span class="math"&gt;\(-R = \boldsymbol{y}\)&lt;/span&gt; i eksempelet ovenfor, men du ønsker naturligvis å vite hvem du er enig med, heller enn å få vite at du er uenig med&amp;nbsp;Rødt.&lt;/li&gt;
&lt;li&gt;Modellen tillater forklaringer der summen av partienes&amp;nbsp;bidrag &lt;span class="math"&gt;\(\boldsymbol{\beta}\)&lt;/span&gt; overskrider &lt;span class="math"&gt;\(1\)&lt;/span&gt;. Dette er tilfellet&amp;nbsp;i &lt;span class="math"&gt;\(\text{MDG} + \text{KrF} = \boldsymbol{y}\)&lt;/span&gt; og i den lange forklaringen ovenfor. Igjen er det vanskelig å tolke et slikt&amp;nbsp;svar.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Alle disse problemene kan løses om vi begrenser oss til &lt;em&gt;konvekse kombinasjoner&lt;/em&gt;.&lt;/p&gt;
&lt;h3 id="endelig-optimeringsmodell"&gt;Endelig&amp;nbsp;optimeringsmodell&lt;/h3&gt;
&lt;p&gt;Vi fikser problemene til modellen ovenfor ved å utvide den.
Heller enn å leter etter en hvilken som helst lineær kombinasjon av partier, leter vi etter den korteste konvekse&amp;nbsp;kombinasjonen:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align}
&amp;amp; \underset{\boldsymbol{\beta}}{\text{minimize}}   
&amp;amp;&amp;amp; \| X \boldsymbol{\beta} - \boldsymbol{y}  \|
+ \alpha \|  \boldsymbol{\beta} \|
  \\
&amp;amp; \text{subject to} &amp;amp;&amp;amp; \beta_j \geq 0 &amp;amp;&amp;amp; \\
&amp;amp;  &amp;amp;&amp;amp; \sum_j \beta_j = 1. &amp;amp;&amp;amp; 
\end{align}&lt;/div&gt;
&lt;p&gt;
Begge noremen ovenfor&amp;nbsp;er &lt;span class="math"&gt;\(2\)&lt;/span&gt;-normer.
Setter&amp;nbsp;vi &lt;span class="math"&gt;\(\alpha = \epsilon\)&lt;/span&gt; (et lite positivt tall) i regulariseringsleddet, så vil modellen foretrekke løsninger der elementene&amp;nbsp;i &lt;span class="math"&gt;\(\boldsymbol{\beta}\)&lt;/span&gt; jevnt over er små.&amp;nbsp;Dersom &lt;span class="math"&gt;\(\alpha \to \infty\)&lt;/span&gt; så&amp;nbsp;velges &lt;span class="math"&gt;\(\boldsymbol{\beta} \propto \boldsymbol{1}\)&lt;/span&gt;,&amp;nbsp;dersom &lt;span class="math"&gt;\(\alpha = -\epsilon\)&lt;/span&gt; vil modellen foretrekke løsninger som bruker så få partier som mulig, og&amp;nbsp;dersom &lt;span class="math"&gt;\(\alpha \to -\infty\)&lt;/span&gt; er vi tilbake til en typisk valgomat som velger ett parti.
Merk at&amp;nbsp;når &lt;span class="math"&gt;\(\alpha &amp;lt; 0\)&lt;/span&gt; så er ikke problemet konvekst.
Vi&amp;nbsp;setter &lt;span class="math"&gt;\(\alpha = \epsilon\)&lt;/span&gt; i resten av&amp;nbsp;artikkelen.&lt;/p&gt;
&lt;p&gt;De to bibetingelsene&amp;nbsp;på &lt;span class="math"&gt;\(\boldsymbol{\beta}\)&lt;/span&gt; er definisjonen på en konveks kombinasjon, og gjør at vi unngår negative retninger og summer som&amp;nbsp;overskrider &lt;span class="math"&gt;\(1\)&lt;/span&gt;.&amp;nbsp;Summen &lt;span class="math"&gt;\(\sum_j \beta_j\)&lt;/span&gt; må være&amp;nbsp;lik &lt;span class="math"&gt;\(1\)&lt;/span&gt;, heller enn mindre&amp;nbsp;enn &lt;span class="math"&gt;\(1\)&lt;/span&gt;, fordi&amp;nbsp;dersom &lt;span class="math"&gt;\(\boldsymbol{y} = (0, 1)\)&lt;/span&gt;, &lt;span class="math"&gt;\(A = (0, 1)\)&lt;/span&gt; og &lt;span class="math"&gt;\(B = (0, 2)\)&lt;/span&gt; så forventer&amp;nbsp;vi &lt;span class="math"&gt;\(\boldsymbol{\beta} = (1, 0)\)&lt;/span&gt; som svar,&amp;nbsp;ikke &lt;span class="math"&gt;\(\boldsymbol{\beta} = (0, 0.5)\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Med &lt;span class="math"&gt;\(2\)&lt;/span&gt;-norm på begge leddene i objektivfunksjonen blir løsningen på&amp;nbsp;optimeringsproblemet:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align}
0.69 \, \text{V} + 0.23 \, \text{H} + 0.08 \, \text{Frp} = \boldsymbol{y}.
\end{align}&lt;/div&gt;
&lt;p&gt;
Om du går tilbake til figuren og sammenligner geometrien med denne løsningen, ser du at løsningen er fornuftig.
Når det er sagt er det ingen absolutt fasit i matematisk modellering og andre variasjoner av en slik modell kan være&amp;nbsp;hensiktsmessige.&lt;/p&gt;
&lt;h3 id="optimeringsmodellen-pa-et-strre-eksempel"&gt;Optimeringsmodellen på et større&amp;nbsp;eksempel&lt;/h3&gt;
&lt;p&gt;La oss kjøre optimeringsmodellen på samme eksempel som&amp;nbsp;tidligere:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
[X \mid \boldsymbol{y}] =
\begin{bmatrix}
  \text{AP} &amp;amp;  \text{Sp} &amp;amp; \text{Frp} &amp;amp; \text{H} &amp;amp; \boldsymbol{y} \\
  -1 &amp;amp;  1 &amp;amp; -2 &amp;amp; -1 &amp;amp; -1\\
  2  &amp;amp;  1 &amp;amp; -2 &amp;amp; -2 &amp;amp; -2\\
  -2 &amp;amp; -2 &amp;amp;  2 &amp;amp;  2 &amp;amp; 2\\
  1  &amp;amp; -1 &amp;amp; -2 &amp;amp; -2 &amp;amp; -2 \\
  1  &amp;amp;  2 &amp;amp;  2 &amp;amp; -2 &amp;amp; 0\\
  -1 &amp;amp; -2 &amp;amp; -2 &amp;amp; 2 &amp;amp; 0
\end{bmatrix} 
\end{align*}&lt;/div&gt;
&lt;p&gt;Svaret er gitt nedenfor, og samsvarer godt med rangeringen som vi fikk&amp;nbsp;tidligere.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;1. H   (51 %)
2. Frp (45 %)
4. Sp  ( 4 %)
3. AP  ( 0 %)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="forskjellen-pa-distansebasert-og-kombinasjonsbasert-forklaring"&gt;Forskjellen på distansebasert og kombinasjonsbasert&amp;nbsp;forklaring&lt;/h3&gt;
&lt;p&gt;Vi har sett to måter å forklare en brukers resultater på i en&amp;nbsp;valgomat:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Distansebasert forklaring&lt;/strong&gt; - rangerer partiene etter distanse fra brukeren. Svarer på spørsmålet &amp;ldquo;Hvilket enkeltparti er nærmest&amp;nbsp;meg?&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kombinasjonsbasert forklaring&lt;/strong&gt; - brukeren uttrykkes som den nærmeste konvekse kombinasjonen av partiene. Svarer på spørsmålet &amp;ldquo;Hvilken koalisjon av partier er nærmest&amp;nbsp;meg?&amp;rdquo;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Forskjellen mellom forklaringene er illustrert i figuren nedenfor.
&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 350px; width: 95%;"
src="https://tommyodland.com/images/articles/valgomat_design/opt_model_vs_distance.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Distansebasert forklaring sier&amp;nbsp;at &lt;span class="math"&gt;\(\boldsymbol{y}\)&lt;/span&gt; er&amp;nbsp;nærmest &lt;span class="math"&gt;\(B\)&lt;/span&gt;,&amp;nbsp;deretter &lt;span class="math"&gt;\(A\)&lt;/span&gt; og til&amp;nbsp;slutt &lt;span class="math"&gt;\(C\)&lt;/span&gt;. Distansebasert forklaring er &lt;em&gt;innbyrdes uavhengig&lt;/em&gt;; avstanden&amp;nbsp;til &lt;span class="math"&gt;\(B\)&lt;/span&gt; påvirker ikke avstanden&amp;nbsp;til &lt;span class="math"&gt;\(A\)&lt;/span&gt;.&amp;nbsp;Dersom &lt;span class="math"&gt;\(B\)&lt;/span&gt; hadde forsvunnet ville avstanden&amp;nbsp;til &lt;span class="math"&gt;\(A\)&lt;/span&gt; vært&amp;nbsp;uendret.&lt;/li&gt;
&lt;li&gt;Kombinasjonsbasert forklaring uttrykker&amp;nbsp;standpunktet &lt;span class="math"&gt;\(\boldsymbol{y}\)&lt;/span&gt; som en konveks kombinasjon. Svaret i figuren ovenfor&amp;nbsp;blir &lt;span class="math"&gt;\(\boldsymbol{\beta} = (\beta_A, \beta_B, \beta_C) = (0, 0.7, 0.3)\)&lt;/span&gt;. Optimeringsmodellens forklaring er &lt;em&gt;innbyrdes avhengig&lt;/em&gt;.&amp;nbsp;Dersom &lt;span class="math"&gt;\(B\)&lt;/span&gt; hadde forsvunnet ville forklaringen lent seg&amp;nbsp;på &lt;span class="math"&gt;\(A\)&lt;/span&gt;, men&amp;nbsp;når &lt;span class="math"&gt;\(B\)&lt;/span&gt; finnes trengs&amp;nbsp;ikke &lt;span class="math"&gt;\(A\)&lt;/span&gt; i det hele tatt for å&amp;nbsp;forklare &lt;span class="math"&gt;\(\boldsymbol{y}\)&lt;/span&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Figuren nedenfor viser et eksempel der forklaringene blir helt ulike.
&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 350px; width: 95%;"
src="https://tommyodland.com/images/articles/valgomat_design/opt_model_vs_distance_different_result.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Målt i distanse&amp;nbsp;er &lt;span class="math"&gt;\(C\)&lt;/span&gt; nærmere &lt;span class="math"&gt;\(\boldsymbol{y}\)&lt;/span&gt; enn&amp;nbsp;både &lt;span class="math"&gt;\(A\)&lt;/span&gt; og &lt;span class="math"&gt;\(B\)&lt;/span&gt;.
Likevel&amp;nbsp;forklares &lt;span class="math"&gt;\(\boldsymbol{y}\)&lt;/span&gt; ved hjelp&amp;nbsp;av &lt;span class="math"&gt;\(\boldsymbol{\beta} = (\beta_A, \beta_B, \beta_C) = (0.5, 0.5, 0)\)&lt;/span&gt; uten å&amp;nbsp;bruke &lt;span class="math"&gt;\(C\)&lt;/span&gt;.
Korteste distanse forteller deg hvilket enkeltparti som er nærmest.
Optimeringsmodellen forteller deg hvilken kombinasjon av partier som står deg&amp;nbsp;nærmest.&lt;/p&gt;
&lt;h1 id="del-3-hvordan-br-en-valgomat-presentere-sprsmal-for-brukeren"&gt;Del 3: Hvordan bør en valgomat presentere spørsmål for&amp;nbsp;brukeren?&lt;/h1&gt;
&lt;p&gt;I denne seksjonen presenterer vi en algoritme som velger påstander som diskriminerer godt mellom partiene.
Det kan være to grunner til at man ønsker&amp;nbsp;dette:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Man skal bruke et utvalg påstander.&lt;/strong&gt; Om partiene har svart på mange spørsmål, ønsker vi gjerne å begrense oss til et utvalg. Hvordan velger vi gode påstander som differensierer mellom&amp;nbsp;partiene?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tidlig avslutning.&lt;/strong&gt; Man ønsker at brukeren skal kunne avslutte valgomaten etter et vilkårlig antall spørsmål. Hvordan garanterer vi at brukeren har blitt presentert for et allsidig sett med&amp;nbsp;påstander?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For å motivere algoritmen begynner vi med å se på samme eksempel som tidligere i&amp;nbsp;artikkelen.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 550px; width: 95%;"
src="https://tommyodland.com/images/articles/valgomat_design/vectors_statements_with_trivial_direction.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Spør vi om jernbane til Tromsø eller om fylkeskommunen bør avskaffes, innhenter vi ingen informasjon fordi brukerens svar ikke diskriminerer mellom partiene.
Underrommet som spennes av&amp;nbsp;vektoren &lt;span class="math"&gt;\(\boldsymbol{e} = (1, 1)\)&lt;/span&gt; består av slike &lt;em&gt;trivielle påstander&lt;/em&gt;.
Den beste påstanden å presentere for brukeren er derimot &amp;ldquo;Dropp ferjefri E39 langs Vestlandet&amp;rdquo;, ettersom denne påstanden ligger lengst i&amp;nbsp;fra &lt;span class="math"&gt;\(\operatorname{span} (\{ \boldsymbol{e} \} )\)&lt;/span&gt;.&lt;/p&gt;
&lt;h2 id="algoritme-for-a-velge-pastander-i-rekkeflge"&gt;Algoritme for å velge påstander i&amp;nbsp;rekkefølge&lt;/h2&gt;
&lt;p&gt;La oss generalisere fra to dimensjoner til et vilkårlig antall&amp;nbsp;dimensjoner:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Start med det trivielle underrommet som spennes av&amp;nbsp;påstanden &lt;span class="math"&gt;\(\boldsymbol{e} = (1, 1, \ldots, 1)\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Den første påstanden som velges er den som er lengst&amp;nbsp;fra &lt;span class="math"&gt;\(\operatorname{span} (\{ \boldsymbol{e} \} )\)&lt;/span&gt;. Dette&amp;nbsp;er &lt;span class="math"&gt;\(\boldsymbol{v}_1 = \arg \max_j \| P_S \boldsymbol{v}_j - \boldsymbol{v}_j \|_2\)&lt;/span&gt;,&amp;nbsp;der &lt;span class="math"&gt;\(P_S\)&lt;/span&gt; er en projeksjonsmatrise som projiserer en vektor&amp;nbsp;på &lt;span class="math"&gt;\(S\)&lt;/span&gt;. Formelen for en slik projeksjonsmatrise&amp;nbsp;er &lt;span class="math"&gt;\(P_S = S S^{+} = S (S^T S)^{-1} S^T\)&lt;/span&gt;, der kolonnene i&amp;nbsp;matrisen &lt;span class="math"&gt;\(S\)&lt;/span&gt; er vektorene som spenner rommet vi ønsker å projisere på (her&amp;nbsp;bare &lt;span class="math"&gt;\(\boldsymbol{e}\)&lt;/span&gt;).&lt;/li&gt;
&lt;li&gt;Den neste påstanden som velges skal være langt fra både det trivielle&amp;nbsp;underrommet &lt;span class="math"&gt;\(\boldsymbol{e}\)&lt;/span&gt;, og langt fra den forrige valgte&amp;nbsp;påstanden &lt;span class="math"&gt;\(\boldsymbol{v}_1\)&lt;/span&gt;. Vi velger altså påstanden som er lengst&amp;nbsp;fra &lt;span class="math"&gt;\(\operatorname{span} (\{ \boldsymbol{e}, \boldsymbol{v}_1 \} )\)&lt;/span&gt;. I matematisk notasjon blir&amp;nbsp;dette &lt;span class="math"&gt;\(\boldsymbol{v}_2 = \arg \max_j \| P_S \boldsymbol{v}_j - \boldsymbol{v}_j \|_2\)&lt;/span&gt;,&amp;nbsp;der &lt;span class="math"&gt;\(S\)&lt;/span&gt; i denne iterasjonen er en matrise med&amp;nbsp;kolonner &lt;span class="math"&gt;\(\boldsymbol{e}\)&lt;/span&gt; og &lt;span class="math"&gt;\(\boldsymbol{v}_1\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Slik fortsetter vi og velger påstander som er lengst fra de vi allerede har valgt.&amp;nbsp;Dersom &lt;span class="math"&gt;\(\operatorname{rank} \operatorname{span} (\{ \boldsymbol{e}, \boldsymbol{v}_1, \ldots, \boldsymbol{v}_n \} )\)&lt;/span&gt; blir lik dimensjonaliteten til rommet (antall partier), så er alle gjenværende påstander i rommet. Da blir&amp;nbsp;distansen &lt;span class="math"&gt;\(\| P_S \boldsymbol{v}_j - \boldsymbol{v}_j \|_2 \approx 0\)&lt;/span&gt; for alle gjenværende&amp;nbsp;påstander &lt;span class="math"&gt;\(\boldsymbol{v}_j\)&lt;/span&gt;. Løsningen er å behandle de valgte påstandene som en &lt;span class="caps"&gt;FIFO&lt;/span&gt;-kø: vi fjerner den første valgte&amp;nbsp;påstanden &lt;span class="math"&gt;\(\boldsymbol{v}_1\)&lt;/span&gt; og prøver på nytt. Da er neste påstand vi velger alltid langt fra de siste valgte&amp;nbsp;påstandene.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Python-kode for algoritmen er vedlagt nederst i artikkelen.
Figuren nedenfor illustrerer konseptet i tre dimensjoner.
Den neste påstanden som velges er alltid den som er lengst i fra rommet som spennes av den trivielle retningen og tidligere valgte&amp;nbsp;påstander.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 800px; width: 99%;"
src="https://tommyodland.com/images/articles/valgomat_design/pivoted_QR_visualization.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;h2 id="resultater-darlige-pastander"&gt;Resultater - dårlige&amp;nbsp;påstander&lt;/h2&gt;
&lt;p&gt;La oss se på et eksempel med påstander som innbyrdes ligner på&amp;nbsp;hverandre.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 550px; width: 95%;"
src="https://tommyodland.com/images/articles/valgomat_design/statements_bad_order.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;De første to påstandene er veldig like.
Den tredje påstanden om Erna er på motsatt side av vektorrommet, men gir likevel ikke mye ny informasjon fordi det er nær rommet som spennes av de første to spørsmålene.
Spørsmålet om Erna avdekker altså lite ny informasjon, på tross av at påstanden er på motsatt side av&amp;nbsp;vektorrommet.&lt;/p&gt;
&lt;h2 id="resultater-gode-pastander"&gt;Resultater - gode&amp;nbsp;påstander&lt;/h2&gt;
&lt;p&gt;Her er et utvalg påstander som er ulike hverandre, avdekket av algoritmen&amp;nbsp;ovenfor.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 550px; width: 95%;"
src="https://tommyodland.com/images/articles/valgomat_design/statements_orthogonal_order.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Den første påstanden er langt fra triviell, fordi halvparten av partiene er uenige og den andre halvparten er enige.
Den andre påstanden er ulikt besvart av seks partier, og skiller seg både fra den trivielle påstanden og den første påstanden.
Slik fortsetter algoritmen med å velge påstander som hele tiden er annerledes enn det brukeren av valgomaten har blitt presentert&amp;nbsp;for.&lt;/p&gt;
&lt;h1 id="oppsummering-og-referanser"&gt;Oppsummering og&amp;nbsp;referanser&lt;/h1&gt;
&lt;p&gt;Valgomater kvantifiserer politiske meninger.
Partienes svar kan representeres som en matrise, der radene (påstander) og kolonnene (partier) kan tolkes vektorer.
En slik geometrisk tolkning gir oss verdifull innsikt: vi kan definere hva det vil si at en person er nær et parti og vi kan formalisere hva det vil si at en påstand diskriminerer mellom&amp;nbsp;partier.&lt;/p&gt;
&lt;p&gt;Idéen som å se på partier som vektorer og rangere brukeren etter nærhet er rimelig åpenbar, og &lt;a href="https://nrkbeta.no/2019/07/03/slik-snekret-vi-en-valgomat-for-hele-landet/"&gt;for kommunevalget 2019 brukte &lt;span class="caps"&gt;NRK&lt;/span&gt;&lt;/a&gt; &lt;span class="math"&gt;\(p=1\)&lt;/span&gt; som metrikk.
(At NRKbetas artikkel pakker ungdomsskolematematikk inn i fraser som &amp;ldquo;forklar utregningen for meg som ikke har matteskrekk&amp;rdquo; og &amp;ldquo;Jeg får vondt i hodet!&amp;rdquo; er sørgelig.)
Idéen om å beskrive en bruker som en konveks kombinasjon av partier er min egen.
I praksis er det vanskeligere å implementere i kode, samt mer krevende å forstå for en gjennomsnittlig bruker.
Enkle algoritmer har iboende verdi og distanse-beregning gir en enkel og forståelig&amp;nbsp;forklaring.&lt;/p&gt;
&lt;p&gt;Idéen om å se på påstander som vektorer og velge ut påstander som er langt fra hverandre er min egen.
Inspirasjonen kommer fra &lt;a href="https://en.wikipedia.org/wiki/QR_decomposition"&gt;&lt;span class="caps"&gt;QR&lt;/span&gt;-dekomposisjon med pivotering&lt;/a&gt;, som er en algoritme for å ortogonalisere en matrise.
Pivot-kolonnene i &lt;span class="caps"&gt;QR&lt;/span&gt; kan sees på som en grådig approksimasjon av D-optimalitet i &lt;a href="https://en.wikipedia.org/wiki/Optimal_experimental_design"&gt;optimal eksperimentdesign&lt;/a&gt;, som maksimerer volumet av en høydimensjonell parallellepiped.
Forskjellene mellom min algoritme og &lt;span class="caps"&gt;QR&lt;/span&gt;-dekomposisjon er at (1) vi alltid holder oss borte fra den trivielle&amp;nbsp;påstanden &lt;span class="math"&gt;\(\boldsymbol{e} = \boldsymbol{1}\)&lt;/span&gt; og at (2) vi bruker en &lt;span class="caps"&gt;FIFO&lt;/span&gt;-kø.&lt;/p&gt;
&lt;p&gt;Denne artikkelen fokuserte mest på matematikk, men en valgomat må selvsagt ha politisk relevante påstander, godt grafisk design, potensielt mulighet for å hoppe over påstander eller vekte påstander opp, subjektive analyser av resultatene, og så&amp;nbsp;videre.&lt;/p&gt;
&lt;!--
Matematikken i denne artikkelen er hovedsaklig lineær algebra, med litt optimering.
Tre bøker verdt å lese er:

- [Linear Algebra and Its Applications](https://www.amazon.com/Linear-Algebra-Its-Applications-4th/dp/0030105676) av Strang er en god introduksjon. Seksjon 3.3 om "Projections and Least Squares" er spesielt relevant for denne artikkelen.
- [Introduction to Applied Linear Algebra](https://www.amazon.com/Introduction-Applied-Linear-Algebra-Matrices/dp/1316518965) av Boyd og Vandenberghe er en god oppfølger. Kapittel 12 "Least squares" er relevant.
- [Matrix Computations](https://www.amazon.com/Computations-Hopkins-Studies-Mathematical-Sciences/dp/1421407949) av Golub og Van Loan er de-facto oppslagsverket for beregninger. Seksjon 5.4 "Other Orthogonal Factorizations" er relevant.

--&gt;

&lt;h2 id="kode"&gt;Kode&lt;/h2&gt;
&lt;p&gt;Her er en enkel implementasjon av algoritmen som finner påstander som er langt fra hverandre.
Jeg har ikke vektlagt kjøretid, numerisk stabilitet, caching av beregninger, etc.
Koden er absolutt god nok for bruksområdet, men for større datasett bør man skrive&amp;nbsp;om.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;collections&lt;/span&gt;  &lt;span class="c1"&gt;# Python 3.12&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;numpy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;np&lt;/span&gt;  &lt;span class="c1"&gt;# NumPy 2.1.3&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;away_from_last_k&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Yields the column indices of A that are as far away from a continually&lt;/span&gt;
&lt;span class="sd"&gt;    updated subspace spanned by (1, 1, 1, ...) and a First-In-First-Out (FIFO)&lt;/span&gt;
&lt;span class="sd"&gt;    queue of length k. When a column index is yielded, it is added to the queue.&lt;/span&gt;

&lt;span class="sd"&gt;    Parameters&lt;/span&gt;
&lt;span class="sd"&gt;    ----------&lt;/span&gt;
&lt;span class="sd"&gt;    A : np.ndarray&lt;/span&gt;
&lt;span class="sd"&gt;        A matrix (2D array) with columns [v1 | v2 | ... ].&lt;/span&gt;
&lt;span class="sd"&gt;    k : int, optional&lt;/span&gt;
&lt;span class="sd"&gt;        Numbers of elements to keep in the FIFO queue. None means no limit.&lt;/span&gt;
&lt;span class="sd"&gt;    verbose : bool, optional&lt;/span&gt;
&lt;span class="sd"&gt;        Whether to print information. The default is False.&lt;/span&gt;

&lt;span class="sd"&gt;    Examples&lt;/span&gt;
&lt;span class="sd"&gt;    --------&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; A = np.array([[3, 0, 0], [0, 2, 0], [0, 0, 1], [2.5, 0, 0]]).T&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; list(away_from_last_k(A, k=None))&lt;/span&gt;
&lt;span class="sd"&gt;    [0, 1, 3, 2]&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; list(away_from_last_k(A, k=0)) #  Distance from span({(1, 1, 1)}) only&lt;/span&gt;
&lt;span class="sd"&gt;    [0, 3, 1, 2]&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; list(away_from_last_k(A, k=1))&lt;/span&gt;
&lt;span class="sd"&gt;    [0, 1, 3, 2]&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;subspace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deque&lt;/span&gt;&lt;span class="p"&gt;([],&lt;/span&gt; &lt;span class="n"&gt;maxlen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Vectors in FIFO subspace&lt;/span&gt;
    &lt;span class="n"&gt;ones&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ones&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;  &lt;span class="c1"&gt;# Vector of ones, always in subspace&lt;/span&gt;
    &lt;span class="n"&gt;remaining&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# Remaining column indices of A&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;distance_from_subspace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tuple_&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tuple_&lt;/span&gt;
        &lt;span class="n"&gt;S&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vstack&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;ones&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subspace&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;  &lt;span class="c1"&gt;# Full space as col. mat.&lt;/span&gt;
        &lt;span class="n"&gt;P_S&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pinv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Projection matrix onto span(S)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;P_S&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Distance between v and span(S)&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;remaining&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;inds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remaining&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Col idx of &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remaining&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; remaining vectors: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;inds&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Vectors in subspace (excl. ones): &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subspace&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Get idx of col vector furthest from S = [ones | subspace]&lt;/span&gt;
        &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remaining&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;distance_from_subspace&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;maxdist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;distance_from_subspace&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;remaining&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;maxdist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Distance was 0. Popping from subspace.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;subspace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;popleft&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Col idx &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; furthest from subspace. Dist: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;maxdist&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.6f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;subspace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remaining&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Mon, 07 Apr 2025 00:00:00 +0200</pubDate><guid>tag:tommyodland.com,2025-04-07:/articles/2025/hvordan-fungerer-en-valgomat</guid><category>articles</category><category>mathematics</category></item><item><title>Rettferdig fordeling av boliglån med ulik eierandel</title><link>https://tommyodland.com/articles/2025/rettferdig-fordeling-av-boliglan-med-ulik-eierandel</link><description>&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 450px; width: 95%;"
src="https://tommyodland.com/images/unsplash/house_keys.jpg"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Anta at to personer skal kjøpe en bolig sammen, men har ulik egenkapital og/eller ulik betalingsevne på boliglånet.
Hva er en økonomisk rettferdig&amp;nbsp;fordeling?&lt;/p&gt;
&lt;h2 id="ulik-egenkapital-lik-fordeling-av-lanet"&gt;Ulik egenkapital, lik fordeling av&amp;nbsp;lånet&lt;/h2&gt;
&lt;p&gt;I DNBs artikkel &amp;ldquo;&lt;a href="https://www.dnb.no/dnbnyheter/no/din-okonomi/del-boliglanet-rettferdig"&gt;del boliglånet rettferdig&lt;/a&gt;&amp;rdquo; blir følgende fordeling foreslått som rettferdig dersom partene ikke har lik egenkapital.
Alle tall er i tusen&amp;nbsp;kroner.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Johan&lt;/th&gt;
&lt;th&gt;Andrea&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Andel av lån&lt;/td&gt;
&lt;td&gt;1 500&lt;/td&gt;
&lt;td&gt;1 500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Egenkapital&lt;/td&gt;
&lt;td&gt;800&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total andel av kjøpesum&lt;/td&gt;
&lt;td&gt;2 300&lt;/td&gt;
&lt;td&gt;1 700&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Andel ift. totalsum&lt;/td&gt;
&lt;td&gt;2 300 / 4 000&lt;/td&gt;
&lt;td&gt;1 700 / 4 000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Eierandel&lt;/td&gt;
&lt;td&gt;57,5 %&lt;/td&gt;
&lt;td&gt;42,5 %&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Men dette kan oppleves som urettferdig.
For å forstå hvorfor gjør vi eksempelet mer ekstremt&amp;mdash;hva om Johan har mye mer penger enn&amp;nbsp;Andrea?&lt;/p&gt;
&lt;p&gt;Da kan vi få en fordeling av lån og egenkapital som for eksempel ser slik&amp;nbsp;ut:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Johan&lt;/th&gt;
&lt;th&gt;Andrea&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Andel av lån&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Egenkapital&lt;/td&gt;
&lt;td&gt;3 800&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total andel av kjøpesum&lt;/td&gt;
&lt;td&gt;3 900&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Andel ift. totalsum&lt;/td&gt;
&lt;td&gt;3 900 / 4 000&lt;/td&gt;
&lt;td&gt;100 / 4 000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Eierandel&lt;/td&gt;
&lt;td&gt;97,5 %&lt;/td&gt;
&lt;td&gt;2,5 %&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Johan har gått inn med 97,5 %, og skal få 97,5 % av salget.
Men Andrea har likevel gjort et kupp! 
Selv om hun får lite når boligen selges, så &lt;em&gt;hun bor i praksis nesten gratis&lt;/em&gt;.
Hun bruker tross alt halve boligen i hverdagen, men betaler nesten ikke noe for&amp;nbsp;det.&lt;/p&gt;
&lt;h2 id="en-mer-rettferdig-fordeling"&gt;En mer rettferdig&amp;nbsp;fordeling?&lt;/h2&gt;
&lt;p&gt;For å korrigere for skjevheten som oppstår når Andrea disponerer halve boligen kan vi anse henne som delvis å være leietaker.
Hun betaler da leie for den andelen av hennes 50 % bruksrett som ikke er dekket av eierandelen.
Utregningen ser slik ut i&amp;nbsp;Python:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Egenkapital for begge personene&lt;/span&gt;
&lt;span class="n"&gt;egenkapital_A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3_800&lt;/span&gt;
&lt;span class="n"&gt;egenkapital_B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;egenkapital&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;egenkapital_A&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;egenkapital_B&lt;/span&gt;

&lt;span class="c1"&gt;# Andel av lån for begge personene&lt;/span&gt;
&lt;span class="n"&gt;lån_A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
&lt;span class="n"&gt;lån_B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
&lt;span class="n"&gt;lån&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lån_A&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;lån_B&lt;/span&gt;

&lt;span class="c1"&gt;# Utregninger&lt;/span&gt;
&lt;span class="n"&gt;boligverdi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;egenkapital&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;lån&lt;/span&gt;
&lt;span class="n"&gt;eierskap_A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;egenkapital_A&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;lån_A&lt;/span&gt;
&lt;span class="n"&gt;eierskap_B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;egenkapital_B&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;lån_B&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Boligens verdi: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;boligverdi&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.2f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Belåningsgrad: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;lån&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;boligverdi&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.2%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Person A eier &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;eierskap_A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;boligverdi&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.1%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Person B eier &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;eierskap_B&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;boligverdi&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.1%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Person A skal betale &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;lån_A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lån&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.1%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; av lånet hver måned&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Person B skal betale &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;lån_B&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lån&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.1%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; av lånet hver måned&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Total leiepris per måned for tilsvarende bolig&lt;/span&gt;
&lt;span class="n"&gt;leiepris&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;
&lt;span class="n"&gt;leieprosent_A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eierskap_A&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;boligverdi&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;leieprosent_B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eierskap_B&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;boligverdi&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;leieprosent_A&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Person A er &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;leieprosent_A&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.1%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; leietaker&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Person A betaler &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;leieprosent_A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;leiepris&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.2f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; til person B i leie&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;leieprosent_B&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Person B er &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;leieprosent_B&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.1%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; leietaker&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Person B betaler &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;leieprosent_B&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;leiepris&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.2f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; til person A i leie&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Kjører man koden blir&amp;nbsp;resultatet:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Boligens verdi: 4000.00
Belåningsgrad: 5.00%

Person A eier 97.5%
Person B eier 2.5%

Person A skal betale 50.0% av lånet hver måned
Person B skal betale 50.0% av lånet hver måned

Person B er 95.0% leietaker
Person B betaler 7.12 til person A i leie
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I dette eksempelet ville en tilsvarende bolig kostet 15 tusen å leie per måned.
Andrea disponerer halve boligen, og av denne halvdelen leier hun i praksis 95 % og eier 5 %.
Hun eier altså 5 % av sin 50 % bruksandel, som blir 0,05 * 0,5 = 2,5 % av boligens totalverdi.
&lt;strong&gt;Hun må betale 7,12 tusen per måned i leie til&amp;nbsp;Johan.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="det-originale-eksempelet"&gt;Det originale&amp;nbsp;eksempelet&lt;/h3&gt;
&lt;p&gt;La oss gå tilbake til det originale eksempelet der Andrea går inn med 200 tusen i egenkapital og Johan går inn med 800 tusen.
Tallene ser da slik&amp;nbsp;ut:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;egenkapital_A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt;
&lt;span class="n"&gt;egenkapital_B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
&lt;span class="n"&gt;lån_A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1500&lt;/span&gt;
&lt;span class="n"&gt;lån_B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1500&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Utregningen blir nå at Andrea eier 42,5 % av boligen.
Men med en antatt månedlig leiekostnad på 15 tusen må hun både (1) betale ned halvparten av lånet per måned og (2) betale &lt;strong&gt;1,13 tusen kroner i leie til Johan&lt;/strong&gt; ettersom han gikk inn med en større andel&amp;nbsp;egenkapital.&lt;/p&gt;
&lt;h3 id="et-ekstremt-eksempel"&gt;Et ekstremt&amp;nbsp;eksempel&lt;/h3&gt;
&lt;p&gt;Hva om Andrea ikke går inn med noe penger i det hele&amp;nbsp;tatt?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;egenkapital_A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1_000&lt;/span&gt;
&lt;span class="n"&gt;egenkapital_B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;lån_A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3_000&lt;/span&gt;
&lt;span class="n"&gt;lån_B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Da må Andrea betale 7,5 tusen i leie til Johan hver måned, fordi det er halvparten av boligens antatte leiekostnad.
Hadde vi fulgt DNBs logikk ville hun bodd&amp;nbsp;gratis.&lt;/p&gt;
&lt;h2 id="konklusjon"&gt;Konklusjon&lt;/h2&gt;
&lt;p&gt;DNBs eksempel tar ikke hensyn til at parten som eier den minste andelen i boligen disponerer halve boligen.
For å korrigere for dette kan vi anse parten som eier minst som å delvis være leietaker.
Om leietakeren eier 42,5 % av boligen men 50 % av lånet, slik som i DNBs eksempel, så får hun 42,5 % av salgssummen og betaler 42,5 % av vedlikeholdskostnadene.
Hun betaler også 50 % av lånet, pluss leie til den andre&amp;nbsp;parten.&lt;/p&gt;
&lt;p&gt;Dersom egenkapital er begrensningen, er en annen mulighet at den ene parten låner penger av den andre parten.
Da kan begge gå inn med 50 % i både egenkapital og lån, men det er tatt opp et privat lån på siden.
I praksis kan det oppleves kjipt å være delvis leietaker, spesielt om eierbrøken uansett er tilnærmet lik 50 %.
Da er det nok mer praktisk å følge DNBs&amp;nbsp;utregning.&lt;/p&gt;
&lt;p&gt;Rent matematisk er lærdommen at man bør konstruere ekstreme eksempler for å sjekke om en utregning gir mening.
DNBs utregning feiler på denne testen og er åpenbart utrettferdig når eierbrøken blir for&amp;nbsp;skjev.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Se også &amp;ldquo;&lt;a href="https://pengeverkstedet.no/bolig/slik-deler-dere-boliglanet-og-utgiftene-riktig/"&gt;Slik deler dere boliglånet og utgiftene riktig&lt;/a&gt;&amp;rdquo; hos Pengeverkstedet. Artikkelen sier at totalen er lik egenkapital pluss lån, og korrigerer ikke for at hver part disponerer halvparten av&amp;nbsp;boligen.&lt;/li&gt;
&lt;li&gt;Om du vil ha en utfordring kan du forsøke å generalisere logikken ovenfor. Hva blir utregningen om mer enn to personer går sammen med ulik egenkapital og/eller ulik betalingsevne på&amp;nbsp;lån?&lt;/li&gt;
&lt;/ul&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Fri, 07 Mar 2025 00:00:00 +0100</pubDate><guid>tag:tommyodland.com,2025-03-07:/articles/2025/rettferdig-fordeling-av-boliglan-med-ulik-eierandel</guid><category>articles</category><category>mathematics</category></item><item><title>Strømstøtte, fastpris, makspris og insentiver</title><link>https://tommyodland.com/articles/2025/stromstotte-fastpris-makspris-og-insentiver</link><description>&lt;p&gt;Strømprisen har tatt stor plass i media de siste årene.
Arbeiderpartiet foreslår nå å bytte ut &lt;strong&gt;strømstøtte&lt;/strong&gt; med &lt;strong&gt;fastpris&lt;/strong&gt;, mens Fremskrittspartiet og Kristelig Folkeparti vil ha &lt;strong&gt;makspris&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;I denne artikkelen skal vi visualisere disse ordningene, fikse de svake insentivene ved hjelp av litt enkel matematikk på &lt;span class="caps"&gt;VGS&lt;/span&gt;-nivå, og se at politikernes tenkemåte rundt strømpriser har fellestrekk med hvordan de tenker på f.eks.&amp;nbsp;fraværsgrensa.&lt;/p&gt;
&lt;h2 id="konstant-pris-liner-pris-og-insentiver"&gt;Konstant pris, lineær pris og&amp;nbsp;insentiver&lt;/h2&gt;
&lt;p&gt;I Oslo kommune er det to måter å fastsette &lt;a href="https://www.oslo.kommune.no/vann-og-avlop/tilknytningsgebyr-arsgebyr-og-vannmaler/vann-og-avlopsgebyrer/#toc-1"&gt;vann- og avløpsgebyrer&lt;/a&gt;&amp;nbsp;på:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Konstant pris&lt;/strong&gt; basert på en arealberegning, som er uavhengig av&amp;nbsp;forbruk&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lineær pris&lt;/strong&gt; proporsjonal med forbruk (pluss en&amp;nbsp;fastpris)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I motsetning til vannpris, er strømpris alltid basert på kundens forbruk.
Om prisen var konstant, ville det ikke vært noe økonomisk insentiv for å isolere, holde temperaturen nede, kjøpe varmepumpe, eller spare strøm på andre&amp;nbsp;måter.&lt;/p&gt;
&lt;p&gt;Strømprisen har en annen egenskap som vannprisen ikke har; den er ustabil over tid.
Prisen varierer innad i døgnet, innad året, og fra år til&amp;nbsp;år.&lt;/p&gt;
&lt;h2 id="dagens-strmsttte"&gt;Dagens&amp;nbsp;strømstøtte&lt;/h2&gt;
&lt;p&gt;Figuren nedenfor viser prisen per kvartal de siste ti årene. 
Dataene er fra &lt;a href="https://www.nrk.no/norge/sjekk-hvor-mange-tusenlapper-du-kan-spare-med-ny-fastpris-1.17243502"&gt;NRKs.no&lt;/a&gt; / &lt;span class="caps"&gt;SSB&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 800px; width: 98%;"
src="https://tommyodland.com/images/articles/fastpris_strom/Strømstøttestatendekker90avprisenover73øre.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Dagens strømstøtte-ordning er også vist i figuren.
Opp til 73 øre betaler man alt selv, deretter betaler man 10% av overskytende.
La oss kalle spotprisen&amp;nbsp;for &lt;span class="math"&gt;\(x\)&lt;/span&gt; og nettoprisen som kunden betaler&amp;nbsp;for &lt;span class="math"&gt;\(y\)&lt;/span&gt;.
Da kan formelen for nettopris skrives&amp;nbsp;som&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
y(x) = \min\left( x, 73 + 0.1 (x - 73) \right).
\end{equation*}&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Edit:&lt;/strong&gt; Fra og med 1. januar 2025 er strømstøtten &lt;a href="https://www.hvakosterstrommen.no/artikler/slik-fungerer-stromstotten"&gt;75 øre&lt;/a&gt;, ikke 73&amp;nbsp;øre.&lt;/p&gt;
&lt;h2 id="fastpris"&gt;Fastpris&lt;/h2&gt;
&lt;p&gt;Arbeidetpartiet lanserte januar 2025 forslag om &lt;a href="https://www.nrk.no/norge/dette-er-store-sin-plan-for-a-kuppe-straumveljarane-1.17240357"&gt;fastprisavtaler på 40 øre/kWh&lt;/a&gt;.
En slik avtale er visualisert i &lt;a href="https://www.nrk.no/norge/sjekk-hvor-mange-tusenlapper-du-kan-spare-med-ny-fastpris-1.17243502"&gt;denne artikkelen&lt;/a&gt; og gjenskapt i figuren nedenfor.
Formelen er enkel; uansett hva&amp;nbsp;spotprisen &lt;span class="math"&gt;\(x\)&lt;/span&gt; er, blir&amp;nbsp;nettoprisen &lt;span class="math"&gt;\(y\)&lt;/span&gt; lik &lt;span class="math"&gt;\(40\)&lt;/span&gt; øre/kWh.&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
y(x) = 40
\end{equation*}&lt;/div&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 800px; width: 98%;"
src="https://tommyodland.com/images/articles/fastpris_strom/Fastprispå40ørekWh.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;h2 id="makspris"&gt;Makspris&lt;/h2&gt;
&lt;p&gt;Frp og KrF ønsker &lt;a href="https://www.nrk.no/norge/full-sprik-om-stromstotten-1.17207031"&gt;100 prosent kompensasjon for priser over 50 øre/kWh&lt;/a&gt;, altså en makspris.
Formelen&amp;nbsp;blir&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
y(x) = \min\left(x, 50 \right),
\end{equation*}&lt;/div&gt;
&lt;p&gt;og resultatet av en slik regel er vist i figuren&amp;nbsp;nedenfor.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 800px; width: 98%;"
src="https://tommyodland.com/images/articles/fastpris_strom/Maksprispå50ørekWh.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;h2 id="insentiver-og-den-deriverte"&gt;Insentiver og den&amp;nbsp;deriverte&lt;/h2&gt;
&lt;p&gt;Fastprisforslaget får &lt;a href="https://www.dn.no/politikk/strom/strompris/strompriser/sjefokonom-om-fastprisordning-kan-ha-klare-negative-bieffekter/2-1-1773088"&gt;kritikk for å ta bort insentiver&lt;/a&gt;.
Kritikken kommer fra professorer, økonomer, og fra Støre selv, som tidligere uttalte&amp;nbsp;at:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;ldquo;&lt;/span&gt;Makspris på strøm løser ikke dette problemet, makspris gjør ikke noe med gassprisene og det fyller heller ikke&amp;nbsp;vannmagasinene.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Dette er riktig, men vi må huske at det er snakk om en &lt;strong&gt;fastpris per kilowattime&lt;/strong&gt;, ikke en &lt;strong&gt;fastpris per måned&lt;/strong&gt;.
En fastpris per kilowattime insentiverer til lavt forbruk; kutter man forbruket til halvparten, betaler man også halvparten.
Derimot forsvinner insentiver for å bruke strøm når den er billig, heller enn når den er dyr, under en slik&amp;nbsp;ordning.&lt;/p&gt;
&lt;p&gt;Politikere klamrer seg ofte til harde grenser som fastpriser og makspriser når de vil regulere et marked.
Ofte tenker de at det må gå en hard grense mellom regulering og frie markeder i seg selv.
Her er noen eksempler der harde grenser kunne vært myknet&amp;nbsp;opp:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Skoleopptak.&lt;/strong&gt; Politikerne i Oslo har i mange år ment at skoleopptak enten må være helt karakterbasert, eller basert på ren loddtrekning. En mellomting er mulig med &lt;a href="https://tommyodland.com/articles/2020/fritt-skolevalg-del-2-karakterer"&gt;vektet loddtrekning&lt;/a&gt;, som jeg skrev om i&amp;nbsp;2020.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fraværsgrensa.&lt;/strong&gt; En fraværsprosent på 9% er uproblematisk, men med 11% fravær &lt;a href="https://snl.no/frav%C3%A6rsgrense"&gt;mister man hele standpunktkarakteren&lt;/a&gt;. En mykere overgang ville vært mulig ved å redusere karakteren noe før man mister hele&amp;nbsp;karakteren.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Arbeidsavklaringspenger (&lt;span class="caps"&gt;AAP&lt;/span&gt;).&lt;/strong&gt; Du kvalifiserer om du er 50% syk, men ikke om du er 49%&amp;nbsp;syk.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Toll.&lt;/strong&gt; Frem til 1. januar 2024 var det &lt;a href="https://www.toll.no/no/bedrift/nyheter-for-naeringslivet/na-avvikles-siste-del-av-350-kronersgrensen"&gt;350-kronersgrense&lt;/a&gt; for toll- og avgiftsfritak på varer kjøpt fra&amp;nbsp;utlandet.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ofte er harde grenser både nødvendig, enkelt og praktisk; for eksempel 18-års aldersgrense på alkohol.
Andre ganger kan harde grenser skape perverse insentiver og uheldige tilpasninger, som når elever strategisk planlegger fravær rett under grensen.
Da bør vi vurdere mer gradvise overganger som bedre reflekterer de underliggende realitetene.
Dette er en enkel form for &lt;a href="https://en.wikipedia.org/wiki/Mechanism_design"&gt;mekanismedesign&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I våre&amp;nbsp;modeller &lt;span class="math"&gt;\(y(x)\)&lt;/span&gt; er insentiver analogt med den &lt;a href="https://snl.no/derivasjon_-_matematikk"&gt;deriverte&lt;/a&gt;, som er stigningen til funksjonen.
En flat funksjon dreper koblingen mellom input og output, som her er bruttopris og nettopris.
For å insentivere strømforbruk når det er billig, kan vi glatte ut Frp og KrFs&amp;nbsp;makspris.&lt;/p&gt;
&lt;h2 id="glatt-makspris"&gt;Glatt&amp;nbsp;makspris&lt;/h2&gt;
&lt;p&gt;Vi kan glatte ut makspris-forslaget til Frp og KrF for å beholde insentiver, og samtidig garantere at prisen aldri overstiger 50 øre/kWh.
Insentivene blir svake, men det er bedre enn ingen&amp;nbsp;ting.&lt;/p&gt;
&lt;p&gt;Vi starter med&amp;nbsp;softplus-funksjonen &lt;span class="math"&gt;\(s(x; \alpha) = \log \left(1 + \exp\left( \alpha x \right) \right) / \alpha\)&lt;/span&gt;, skifter og skalerer&amp;nbsp;til
&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
y(x) = 50 - s(50 - x; \alpha)
\end{equation*}&lt;/div&gt;
&lt;p&gt;
og velger en&amp;nbsp;fornuftig &lt;span class="math"&gt;\(\alpha\)&lt;/span&gt;-verdi for å kontrollere hvor glatt funksjonen blir.
Resultatet er vist i figuren&amp;nbsp;nedenfor.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 800px; width: 98%;"
src="https://tommyodland.com/images/articles/fastpris_strom/Mykmaksprispå50ørekWh.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;h2 id="logaritmisk-pris"&gt;Logaritmisk&amp;nbsp;pris&lt;/h2&gt;
&lt;p&gt;Enda sterkere insentiver får man om man ikke begrenser seg til en makspris på 50 øre/kWh.
Det politikerne egentlig ønsker å gjøre er å skjerme forbrukerne mot den enorme prisvariasjonen.
Grunnen til at de strekker seg mot fastpris og makspris heller enn en logaritme, kan være at de ikke vet hva en logaritme er, eller tror at befolkningen ikke vet hva en logaritme&amp;nbsp;er.&lt;/p&gt;
&lt;p&gt;For å begrense svingningene, men samtidig beholde insentiver i større grad enn med makspris, kan vi bruke en logaritmisk pris.
Formelen kan skrives&amp;nbsp;som
&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
y(x) = \log \left( 1 + \alpha x \right) / \alpha ,
\end{equation*}&lt;/div&gt;
&lt;p&gt;
der &lt;span class="math"&gt;\(\alpha\)&lt;/span&gt;-verdien kontrollerer styrken.
En logaritmisk funksjon vil begrense prisen kraftig, men la kunden føle toppene mer enn med makspris.
Logaritmen er en enkel funksjonell form som oppnår ønsket effekt, men mange andre funksjoner kan også oppnå lignende effekter og være vel så&amp;nbsp;gode.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 800px; width: 98%;"
src="https://tommyodland.com/images/articles/fastpris_strom/Logaritmiskpris.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;h2 id="oppsummering"&gt;Oppsummering&lt;/h2&gt;
&lt;p&gt;Hvordan strømmarkedet skal fungere er en politisk beslutning, og de fleste problemene er uten tvil politisk skapt heller enn noe som kan fikses med matematikk.
Når det er sagt er det fremdeles interessant å analysere politikernes tankemåte matematisk, og det er nok lurt å lytte til økonomenes betraktninger. 
Politikere tyr ofte til harde grenser som fastpris og makspris, men vi har sett at enkel matematikk kan gi oss mer nyanserte&amp;nbsp;løsninger.&lt;/p&gt;
&lt;p&gt;Vi skisserte to prismekanismer som både beskytter forbrukerne mot ekstreme prissvingninger og samtidig bevarer insentiver for fornuftig strømbruk: en myk makspris og en logaritmisk pris.
Samme tankegang kunne forbedret andre områder der harde grenser skaper uheldige&amp;nbsp;virkninger.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Tue, 04 Feb 2025 00:00:00 +0100</pubDate><guid>tag:tommyodland.com,2025-02-04:/articles/2025/stromstotte-fastpris-makspris-og-insentiver</guid><category>articles</category><category>mathematics</category></item><item><title>Beer tasting</title><link>https://tommyodland.com/articles/2025/beer-tasting</link><description>&lt;p&gt;In this article we&amp;rsquo;ll analyze data from a blind test.
Blind tests are fun experiments that can determine, for instance, if family members or friends can distinguish&amp;nbsp;between:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pepsi and&amp;nbsp;Cola&lt;/li&gt;
&lt;li&gt;Red and white&amp;nbsp;wines&lt;/li&gt;
&lt;li&gt;Brand-name and generic&amp;nbsp;foods&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Analysis of such data is interesting, especially when you&amp;rsquo;ve designed the experiment and collected the data yourself.
You might learn some statistics along the way, and that knowledge could prove useful in industry settings as well as in everyday&amp;nbsp;situations.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 90%; max-width:400px;"
src="https://tommyodland.com/images/articles/beer_tasting/beer2.jpg"&gt;&lt;/p&gt;
&lt;h2 id="the-beer-tasting-experiment"&gt;The beer tasting&amp;nbsp;experiment&lt;/h2&gt;
&lt;p&gt;A friend wanted to determine her true beer preferences, so we decided to run a blind&amp;nbsp;test:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We bought 6 different brands of&amp;nbsp;beer.&lt;/li&gt;
&lt;li&gt;She tasted each brand on 3 different&amp;nbsp;occasions.&lt;/li&gt;
&lt;li&gt;The ordering of the 18 tastings was&amp;nbsp;randomized.&lt;/li&gt;
&lt;li&gt;Each of the 18 tastings was conducted on a separate&amp;nbsp;day.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In every tasting, the unknown beer brand was given a rating on a scale from 1 to 10.
Knowledge of which brands were given which rating was not disclosed until the experiment was finished.
The results of the full experiment are shown in the figure below, and the full dataset is available at the end of this&amp;nbsp;article.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 90%; max-width:600px;"
src="https://tommyodland.com/images/articles/beer_tasting/beer_ratings.png"&gt;&lt;/p&gt;
&lt;p&gt;The Oslo-based beer brand &amp;ldquo;Schous&amp;rdquo; is the winner, with an average rating&amp;nbsp;of &lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
(7 + 7 + 8)/3 = 22/3 \approx 7.33
\end{align*}&lt;/div&gt;
&lt;h2 id="can-my-friend-tell-beers-apart"&gt;Can my friend tell beers&amp;nbsp;apart?&lt;/h2&gt;
&lt;p&gt;Let us examine the figure above a bit more critically.
The ratings are somewhat consistent, in the sense that they do not look like random integers between 1 and 10.
However, it is surprising that the very same beer is awarded both the lowest observed rating (3) and the highest observed rating&amp;nbsp;(8).&lt;/p&gt;
&lt;p&gt;This raises the question: &lt;strong&gt;can my friend consistently tell beers apart?&lt;/strong&gt;
Let&amp;rsquo;s test this with a &lt;a href="https://tommyodland.com/articles/2023/three-monte-carlo-permutation-tests"&gt;permutation hypothesis test&lt;/a&gt;.
We&amp;rsquo;ll test if the explained&amp;nbsp;variance &lt;span class="math"&gt;\(r^2\)&lt;/span&gt; in the dataset is higher than what could expect if knowledge of beer brands contained no&amp;nbsp;information.&lt;/p&gt;
&lt;p&gt;Let &lt;span class="math"&gt;\(y_i\)&lt;/span&gt; be the rating at&amp;nbsp;tasting &lt;span class="math"&gt;\(i\)&lt;/span&gt;.
Defint the&amp;nbsp;variable &lt;span class="math"&gt;\(x_{ij}\)&lt;/span&gt; to be a one-hot encoding of the brands: it is equal&amp;nbsp;to &lt;span class="math"&gt;\(1\)&lt;/span&gt; if beer&amp;nbsp;brand &lt;span class="math"&gt;\(j\)&lt;/span&gt; was tasted in&amp;nbsp;tasting &lt;span class="math"&gt;\(i\)&lt;/span&gt;, otherwise it is equal&amp;nbsp;to &lt;span class="math"&gt;\(0\)&lt;/span&gt;.
We&amp;rsquo;ll assume a linear model with&amp;nbsp;structure
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
y_i = \sum_j \beta_j x_{ij} + \epsilon_i,
\end{align*}&lt;/div&gt;
&lt;p&gt;
where &lt;span class="math"&gt;\(\epsilon_i\)&lt;/span&gt; is normally distributed noise.
The&amp;nbsp;coefficients &lt;span class="math"&gt;\(\beta_j\)&lt;/span&gt; have a natural interpretation: they are the average ratings of each&amp;nbsp;brand &lt;span class="math"&gt;\(j\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Fitting this model to the data gives an explained&amp;nbsp;variance &lt;span class="math"&gt;\(r^2 = 0.375\)&lt;/span&gt;.
The beer brand explains around one third of the total variane in the ratings.
To put this number into perspective, consider&amp;nbsp;that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A perfectly consistent beer-rater would&amp;nbsp;achieve &lt;span class="math"&gt;\(r^2 = 1\)&lt;/span&gt; on a training set (and on a test set), since the beer brand would explain their ratings perfectly and account for all&amp;nbsp;variation.&lt;/li&gt;
&lt;li&gt;A completely random beer-rater would on average&amp;nbsp;achieve &lt;span class="math"&gt;\(r^2 \approx 0\)&lt;/span&gt; on a test set, since the beer brand would not explain the ratings at all. However, on a training set they would&amp;nbsp;achieve &lt;span class="math"&gt;\(r^2 &amp;gt; 0\)&lt;/span&gt; since there are six degrees of freedom in the brand&amp;nbsp;coefficients &lt;span class="math"&gt;\(\beta_j\)&lt;/span&gt;, and these will overfit and capture some of the&amp;nbsp;variance.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let the null&amp;nbsp;hypothesis &lt;span class="math"&gt;\(H_0\)&lt;/span&gt; be that the beer brands have no effect on the ratings at all.
Permuting the rows of the data set randomly (so there is no relationship between brands and ratings) produces the following distribution&amp;nbsp;of &lt;span class="math"&gt;\(r^2\)&lt;/span&gt; under the null hypothesis.
&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 90%; max-width:600px;"
src="https://tommyodland.com/images/articles/beer_tasting/beer_tasting_r2_scores.png"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If &lt;span class="math"&gt;\(H_0\)&lt;/span&gt; is true (brands have no effect), we will on average observe&amp;nbsp;an &lt;span class="math"&gt;\(r^2\)&lt;/span&gt; of&amp;nbsp;around &lt;span class="math"&gt;\(0.292\)&lt;/span&gt;. The variation&amp;nbsp;in &lt;span class="math"&gt;\(r^2\)&lt;/span&gt;-values in the permuted data sets is large, and anything&amp;nbsp;between &lt;span class="math"&gt;\(0.08\)&lt;/span&gt; and &lt;span class="math"&gt;\(0.57\)&lt;/span&gt; is relatively common (coverage of&amp;nbsp;90%).&lt;/li&gt;
&lt;li&gt;The&amp;nbsp;observed &lt;span class="math"&gt;\(r^2\)&lt;/span&gt; is &lt;span class="math"&gt;\(0.375\)&lt;/span&gt;, which is certainly higher than the average, but by no means&amp;nbsp;extreme.&lt;/li&gt;
&lt;li&gt;The &lt;span class="math"&gt;\(p\)&lt;/span&gt;-value, which is the probability of observing a value&amp;nbsp;of &lt;span class="math"&gt;\(0.375\)&lt;/span&gt; or higher, given&amp;nbsp;that &lt;span class="math"&gt;\(H_0\)&lt;/span&gt; is true, is&amp;nbsp;around &lt;span class="math"&gt;\(0.289\)&lt;/span&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Scientists typically&amp;nbsp;reject &lt;span class="math"&gt;\(H_0\)&lt;/span&gt; if&amp;nbsp;the &lt;span class="math"&gt;\(p\)&lt;/span&gt;-value is lower&amp;nbsp;than &lt;span class="math"&gt;\(0.05\)&lt;/span&gt;.
Here&amp;nbsp;the &lt;span class="math"&gt;\(p\)&lt;/span&gt;-value is much larger; this level of consistency in the ratings is well within the realm of what could happen even if my friend cannot tell beers apart.
It might be that any apparent consistency is attributable to chance rather than genuine tasting&amp;nbsp;ability.&lt;/p&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;The brand &amp;ldquo;Schous&amp;rdquo; is the winner, but there is a concerning amount of variation in the ratings within each brand.
It&amp;rsquo;s not clear that my friend can consistently tell beers apart in this experiment.&amp;nbsp;The &lt;span class="math"&gt;\(p\)&lt;/span&gt;-value is so high that any scientific paper would report a &amp;ldquo;no effect&amp;rdquo; conclusion, failing to&amp;nbsp;discard &lt;span class="math"&gt;\(H_0\)&lt;/span&gt;.
That being said, I am also reluctant to decisively claim that my friend cannot tell beers apart.&amp;nbsp;While &lt;span class="math"&gt;\(H_0\)&lt;/span&gt; is mathematically convenient, it imposes a somewhat one-sided burden of&amp;nbsp;proof.&lt;/p&gt;
&lt;p&gt;People&amp;rsquo;s tasting abilities may be less consistent than they believe.
The paper &lt;a href="https://www.cambridge.org/core/journals/journal-of-wine-economics/article/abs/an-examination-of-judge-reliability-at-a-major-us-wine-competition/15EF999FBAC63C5FB6DCF1C2F4BB6655"&gt;An Examination of Judge Reliability at a major &lt;span class="caps"&gt;U.S.&lt;/span&gt; Wine Competition&lt;/a&gt; investigates this to some extent.
A larger, more involved experiment might be needed to figure out if my friend can tell beers apart.
For now, the data suggests Schous remains the preferred&amp;nbsp;choice.&lt;/p&gt;
&lt;h2 id="dataset"&gt;Dataset&lt;/h2&gt;
&lt;p&gt;Here is the dataset used in this article&amp;nbsp;in &lt;code&gt;.csv&lt;/code&gt; format:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;order,beer_brand,rating
1,Frydenlund,3
2,CB,7
3,Pokal,5
4,Tuborg,3
5,Frydenlund,5
6,Tuborg,8
7,Frydenlund,5
8,Hansa,6
9,CB,7
10,CB,5
11,Pokal,3
12,Schous,7
13,Hansa,3
14,Schous,8
15,Hansa,8
16,Tuborg,4
17,Schous,7
18,Pokal,5
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="python-code"&gt;Python&amp;nbsp;code&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s a sketch of Python code to perform the statistical test.
Python 3.12.8 was used with Pandas 2.2.3, NumPy 1.26.4 and scikit-learn&amp;nbsp;1.5.2. &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pandas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sklearn.preprocessing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OneHotEncoder&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sklearn.linear_model&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LinearRegression&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;numpy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;np&lt;/span&gt;

&lt;span class="c1"&gt;# Read dataset&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dataset.csv&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Create X and y&lt;/span&gt;
&lt;span class="n"&gt;encoder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OneHotEncoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;drop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;encoder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fit_transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;beer_brand&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;
&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rating&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;

&lt;span class="c1"&gt;# Fit linear model, compute r2&lt;/span&gt;
&lt;span class="n"&gt;linreg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LinearRegression&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fit_intercept&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;linreg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;r2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;linreg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;r^2=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r2&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.3f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# r^2=0.375&lt;/span&gt;

&lt;span class="c1"&gt;# Randomly permute data and fit models&lt;/span&gt;
&lt;span class="n"&gt;rng&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default_rng&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;r2_scores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;simulation&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;999&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;y_permuted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permutation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;linreg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_permuted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r2_scores&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;linreg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_permuted&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;p_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r2_scores&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;r2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;p_value=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p_value&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.3f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# p_value=0.311&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Fri, 10 Jan 2025 00:00:00 +0100</pubDate><guid>tag:tommyodland.com,2025-01-10:/articles/2025/beer-tasting</guid><category>articles</category><category>statistics</category></item><item><title>Papers read in 2024</title><link>https://tommyodland.com/articles/2024/papers-read-in-2024</link><description>&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto;"
src="https://tommyodland.com/images/unsplash/unsplash_16_3_1600px_bookshelf.jpg"&gt;&lt;/p&gt;
&lt;p&gt;I try to read a few math books every year, but the more I learn, the more overlap I see in their content.
In 2024 I decided to organize with a group of friends and read papers&amp;nbsp;instead.&lt;/p&gt;
&lt;p&gt;We read 17 papers together, and I skimmed many more to decide on which 17 to read in the group.
Below is the list of papers, which might be useful if you want to learn some new data science related conceps, or if you want you organize a reading club of your&amp;nbsp;own.&lt;/p&gt;
&lt;h2 id="lessons-learned"&gt;Lessons&amp;nbsp;learned&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s what I&amp;nbsp;learned:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Most papers are not ideal for learning; the authors are not great writers and they have limited space to convey their&amp;nbsp;ideas.&lt;/li&gt;
&lt;li&gt;Most practitioners should read books rather than papers to gain general&amp;nbsp;knowledge.&lt;/li&gt;
&lt;li&gt;Finding papers that are on an appropriate technical level and of interest to everyone in a group is&amp;nbsp;difficult.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Before reading papers, it might be useful to read some &lt;em&gt;guides on reading papers&lt;/em&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://ccr.sigcomm.org/online/files/p83-keshavA.pdf"&gt;How to Read a Paper&lt;/a&gt;&amp;nbsp;(2007)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cs.jhu.edu/~jason/advice/how-to-read-a-paper.html"&gt;How to Read a Technical Paper&lt;/a&gt;&amp;nbsp;(2009)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jtleek/readingpapers"&gt;A guide to reading scientific papers &lt;/a&gt;&amp;nbsp;(2016)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://saiamrit.github.io/technical-blog/research/reading_papers/2021/07/31/read-papers.html"&gt;How to read Machine Learning and Deep Learning Research papers&lt;/a&gt;&amp;nbsp;(2021)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here is the list of papers that we read.
&lt;em&gt;Honorable mentions&lt;/em&gt; are papers that I was interested in, but ended up not choosing for the group.
Papers that I for some reason enjoyed more than others get a star&amp;nbsp;(⭐).&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="data-science"&gt;Data&amp;nbsp;Science&lt;/h2&gt;
&lt;p&gt;We chose to start with two papers on the culture of data science and practical tips for data science&amp;nbsp;projects.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://doi.org/10.1080/10618600.2017.1384734"&gt;50 Years of Data Science&lt;/a&gt;&lt;/strong&gt;. David Donoho&amp;nbsp;(2017)&lt;/li&gt;
&lt;li&gt;⭐ &lt;strong&gt;&lt;a href="https://doi.org/10.1371/journal.pcbi.1005510"&gt;Good enough practices in scientific computing&lt;/a&gt;&lt;/strong&gt;. Wilson et al.&amp;nbsp;(2017)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="honorable-mentions"&gt;Honorable&amp;nbsp;mentions&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://homes.cs.washington.edu/~pedrod/papers/cacm12.pdf"&gt;A few useful things to know about machine learning&lt;/a&gt;&lt;/strong&gt;. Pedro Domingos&amp;nbsp;(2012)&lt;/li&gt;
&lt;li&gt;⭐ &lt;strong&gt;&lt;a href="https://doi.org/10.1214/ss/1009213726"&gt;Statistical Modeling: The Two Cultures&lt;/a&gt;&lt;/strong&gt;. Leo Breiman&amp;nbsp;(2001)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="recommendation-engines"&gt;Recommendation&amp;nbsp;engines&lt;/h2&gt;
&lt;p&gt;We looked into recommendation engines: the Netflix price, matrix factorization and extensions to neural&amp;nbsp;networks.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://doi.org/10.1109/MC.2009.263"&gt;Matrix Factorization Techniques for Recommender Systems&lt;/a&gt;&lt;/strong&gt;. Koren et al.&amp;nbsp;(2009)&lt;/li&gt;
&lt;li&gt;⭐ &lt;strong&gt;&lt;a href="https://doi.org/10.1109/ICDM.2010.127"&gt;Factorization Machines&lt;/a&gt;&lt;/strong&gt;. Steffen Rendle&amp;nbsp;(2010)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://doi.org/10.1145/3038912.3052569"&gt;Neural Collaborative Filtering&lt;/a&gt;&lt;/strong&gt;. He et al. (2017) [&lt;a href="https://arxiv.org/abs/1708.05031"&gt;arXiv&lt;/a&gt;]&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="honorable-mentions_1"&gt;Honorable&amp;nbsp;mentions&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://ieeexplore.ieee.org/document/4781121"&gt;Collaborative Filtering for Implicit Feedback Datasets&lt;/a&gt;&lt;/strong&gt;. Hu et al.&amp;nbsp;(2008)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://arxiv.org/abs/1708.05027"&gt;Neural Factorization Machines for Sparse Predictive Analytics&lt;/a&gt;&lt;/strong&gt;. He et al.&amp;nbsp;(2017)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="continuous-optimization"&gt;Continuous&amp;nbsp;optimization&lt;/h2&gt;
&lt;p&gt;These articles cover practical methods for solving optimization&amp;nbsp;problems.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://arxiv.org/abs/1609.04747"&gt;An overview of gradient descent optimization algorithms&lt;/a&gt;&lt;/strong&gt;. Sebastian Ruder&amp;nbsp;(2016)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://tminka.github.io/papers/logreg/minka-logreg.pdf"&gt;A comparison of numerical optimizers for logistic regression&lt;/a&gt;&lt;/strong&gt;. Thomas P. Minka&amp;nbsp;(2003)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://doi.org/10.1109/JSTSP.2007.910971"&gt;An Interior-Point Method for Large-Scale l1-Regularized Least Squares&lt;/a&gt;&lt;/strong&gt;. Kim et al.&amp;nbsp;(2007)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="honorable-mentions_2"&gt;Honorable&amp;nbsp;mentions&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://arxiv.org/abs/0711.1612"&gt;Enhancing Sparsity by Reweighted L1 Minimization&lt;/a&gt;&lt;/strong&gt;. Candes et al.&amp;nbsp;(2007)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="operations-research"&gt;Operations&amp;nbsp;research&lt;/h2&gt;
&lt;p&gt;In the age of data science, operations research is&amp;nbsp;underrated.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://doi.org/10.1287/inte.2016.0875"&gt;&lt;span class="caps"&gt;UPS&lt;/span&gt; Optimizes Delivery Routes&lt;/a&gt;&lt;/strong&gt;. Holland et al.&amp;nbsp;(2017)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://pubsonline.informs.org/doi/10.1287/inte.2017.0897"&gt;Increasing the Responsiveness of Firefighter Services &amp;hellip;&lt;/a&gt;&lt;/strong&gt;. van den Berg et al.&amp;nbsp;(2017)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://dl.acm.org/doi/10.5555/2887007.2887103"&gt;Data analysis and optimization for (citi)bike sharing&lt;/a&gt;&lt;/strong&gt;. O&amp;rsquo;Mahony et al.&amp;nbsp;(2015)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="honorable-mentions_3"&gt;Honorable&amp;nbsp;mentions&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://doi.org/10.1287/inte.1060.0252"&gt;Defending Critical Infrastructure&lt;/a&gt;&lt;/strong&gt;. Brown et al.&amp;nbsp;(2006)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="information-and-compression"&gt;Information and&amp;nbsp;compression&lt;/h2&gt;
&lt;p&gt;Information theory has a rich history and is the foundation of many modern&amp;nbsp;techniques.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://people.math.harvard.edu/~ctm/home/text/others/shannon/entropy/entropy.pdf"&gt;A Mathematical Theory of Communication&lt;/a&gt;&lt;/strong&gt;. Claude E. Shannon&amp;nbsp;(1948)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://dl.acm.org/doi/10.1145/214762.214771"&gt;Arithmetic coding for data compression&lt;/a&gt;&lt;/strong&gt;. Witten et al.&amp;nbsp;(1987)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="statistics"&gt;Statistics&lt;/h2&gt;
&lt;p&gt;More statistics never hurts, and seeing how practitioners attack problems is always&amp;nbsp;interesting.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://www.duo.uio.no/handle/10852/10284"&gt;Should the Olympic sprint skaters run 500 meter twice?&lt;/a&gt;&lt;/strong&gt;. Nils Lid Hjort&amp;nbsp;(1994)&lt;/li&gt;
&lt;li&gt;⭐ &lt;strong&gt;&lt;a href="https://arxiv.org/abs/1701.02434"&gt;A Conceptual Introduction to Hamiltonian Monte Carlo&lt;/a&gt;&lt;/strong&gt;. Michael Betancourt&amp;nbsp;(2017)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://arxiv.org/abs/1206.1901"&gt;&lt;span class="caps"&gt;MCMC&lt;/span&gt; using Hamiltonian dynamics&lt;/a&gt;&lt;/strong&gt;. Radford M. Neal&amp;nbsp;(2012)&lt;/li&gt;
&lt;li&gt;⭐ &lt;strong&gt;&lt;a href="https://peerj.com/articles/6876/"&gt;Hierarchical generalized additive models in ecology&lt;/a&gt;&lt;/strong&gt;. Pedersen​ et al.&amp;nbsp;(2019)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="honorable-mentions_4"&gt;Honorable&amp;nbsp;mentions&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://library.mpib-berlin.mpg.de/ft/gg/GG_Mindless_2004.pdf"&gt;Mindless statistics&lt;/a&gt;&lt;/strong&gt;. Gerd Gigerenzer&amp;nbsp;(2004)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://efron.ckirby.su.domains/papers/2019PredictEstimatAttribut.pdf"&gt;Prediction, Estimation, and Attribution&lt;/a&gt;&lt;/strong&gt;. Bradley Efron&amp;nbsp;(2020)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://bayes.wustl.edu/MacKay/alpha.pdf"&gt;Hyperparameters: Optimize, or Integrate Out?&lt;/a&gt;&lt;/strong&gt; David &lt;span class="caps"&gt;J.C.&lt;/span&gt; MacKay&amp;nbsp;(1996)&lt;/li&gt;
&lt;/ul&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Sat, 14 Dec 2024 00:00:00 +0100</pubDate><guid>tag:tommyodland.com,2024-12-14:/articles/2024/papers-read-in-2024</guid><category>articles</category><category>datascience</category></item><item><title>Solving NRK’s game “Former”</title><link>https://tommyodland.com/articles/2024/solving-nrks-game-former</link><description>&lt;p&gt;&lt;span class="caps"&gt;NRK&lt;/span&gt; published &lt;a href="https://www.nrk.no/former-1.17105310"&gt;a fun game&lt;/a&gt; on their website called &amp;ldquo;Former&amp;rdquo; (English: &amp;ldquo;Shapes&amp;rdquo;).
The gameplay is straightforward: the player clicks on a cell on the board, and as a result every connected cell (up, down, left and right) with the same color vanishes.
The surrounding shapes fall down and the player is ready for the next&amp;nbsp;move.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 431px;"
src="https://tommyodland.com/images/articles/nrk_game/nrk_game_gameplay.gif"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;The goal is to make all shapes vanish in as few clicks as&amp;nbsp;possible.&lt;/p&gt;
&lt;p&gt;In this article we formulate the game as a graph search problem, show how to reduce the size of the graph and attempt to solve it using various graph search algorithms.
&lt;strong&gt;The full code is available at &lt;a href="https://github.com/tommyod/NRK-former-game"&gt;github.com/tommyod/&lt;span class="caps"&gt;NRK&lt;/span&gt;-former-game&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="games-as-directed-graphs"&gt;Games as directed&amp;nbsp;graphs&lt;/h2&gt;
&lt;p&gt;Games like these can be represented as directed graphs: nodes represent game states and directed edges represent moves (clicks).
The initial node is the first board we see and the terminal node is the empty&amp;nbsp;board.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Any path from the initial node to the terminal node is a&amp;nbsp;solution.&lt;/li&gt;
&lt;li&gt;The optimal solution is the shortest solution&amp;nbsp;path.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The original game has 9 rows and 7 columns, which leads to a huge graph and therefore a huge search space.
In the game instance shown in the animation&amp;nbsp;above:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There are 39 possible first&amp;nbsp;moves&lt;/li&gt;
&lt;li&gt;There are 1,446 possible sequences of two&amp;nbsp;moves&lt;/li&gt;
&lt;li&gt;There are 51,162 possible sequences of three&amp;nbsp;moves&lt;/li&gt;
&lt;li&gt;There are 1,730,312 possible sequences of four&amp;nbsp;moves&lt;/li&gt;
&lt;li&gt;There are 55,950,299 possible sequences of five&amp;nbsp;moves&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We count one move as one click on any cell in a connected group of cells of the same color.
The search space continues to grow as we search for sequences of moves.
The branching factor is roughly 35, so at depth 10 we can expect there to be&amp;nbsp;around &lt;span class="math"&gt;\(35^{10} \approx 10^{15}\)&lt;/span&gt; nodes.&lt;/p&gt;
&lt;h2 id="a-simpler-instance"&gt;A simpler&amp;nbsp;instance&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s examine a simple 2-color instance on a board with 2 rows and 2 columns.
Below is the full search space for such a problem&amp;nbsp;instance.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 780px;"
src="https://tommyodland.com/images/articles/nrk_game/nrk_game_1.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;The initial node is at the top and the terminal node is at the bottom of the figure.
There are 24 nodes in total.
The game can be solved in 3 or 4 moves, depending on which path we choose.
The original game instance has billions of such nodes, and solving it comes down to searching the graph&amp;nbsp;efficiently.&lt;/p&gt;
&lt;h2 id="reducing-the-search-space"&gt;Reducing the search&amp;nbsp;space&lt;/h2&gt;
&lt;p&gt;The first observation we make is that &lt;strong&gt;a click on any group of connected colors leads to the same node&lt;/strong&gt;.
It does not matter which specific cell we click as long as it&amp;rsquo;s connected to a group of the same color, so we might as well only consider the top-left cell in any group of connected&amp;nbsp;colors.&lt;/p&gt;
&lt;p&gt;The second observation is that &lt;strong&gt;any permutation of colors represents the same state&lt;/strong&gt;.
If there&amp;nbsp;are &lt;span class="math"&gt;\(4\)&lt;/span&gt; colors, there&amp;nbsp;are &lt;span class="math"&gt;\(4! = 4 \times 3 \times 2 \times 1 = 24\)&lt;/span&gt; ways to permute them.
Applying this observation we can reduce the search space from 24 to 12 nodes in this specific problem&amp;nbsp;instance.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
src="https://tommyodland.com/images/articles/nrk_game/nrk_game_2.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;The third observation is that &lt;strong&gt;horizontal flips represent the same state&lt;/strong&gt;.
If we&amp;rsquo;ve solved the game at some&amp;nbsp;node &lt;span class="math"&gt;\(n\)&lt;/span&gt;, then we&amp;rsquo;ve also solved the&amp;nbsp;node &lt;span class="math"&gt;\(n'\)&lt;/span&gt; with flipped columns.
There are two such states (flipped and not flipped), so this can only reduce the search space by a factor of 2.
By applying this observation we can reduce the search space down to 8&amp;nbsp;nodes.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 520px;"
src="https://tommyodland.com/images/articles/nrk_game/nrk_game_3.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Based on these observations we can define a &lt;em&gt;canonical representation&lt;/em&gt; of each game state (node) where we re-label the colors and flip the game.
There is a tradeoff&amp;nbsp;between:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;the computation time spent transforming a game state into canonical&amp;nbsp;form&lt;/li&gt;
&lt;li&gt;the storage of game states as we search the&amp;nbsp;graph&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Re-coloring reduces the size of the search space by quite a bit, so we&amp;rsquo;ll apply this transformation.
We&amp;rsquo;ll skip the flipping and full&amp;nbsp;canonicalization.&lt;/p&gt;
&lt;h2 id="optimal-solutions"&gt;Optimal&amp;nbsp;solutions&lt;/h2&gt;
&lt;p&gt;The simplest algorithm that is guaranteed to produce an optimal solution is &lt;a href="https://en.wikipedia.org/wiki/Breadth-first_search"&gt;breadth-first search&lt;/a&gt; (&lt;span class="caps"&gt;BFS&lt;/span&gt;).
It first explores all nodes one move away from the initial node, then all nodes two moves away, then all nodes three nodes away, and so forth.
When we get to an empty board we know that no shorter path&amp;nbsp;exists.&lt;/p&gt;
&lt;p&gt;Unfortunately, &lt;span class="caps"&gt;BFS&lt;/span&gt; holds too many nodes in memory and explores &lt;em&gt;every path&lt;/em&gt;.
A smarter approach is to use &lt;a href="https://en.wikipedia.org/wiki/A%2A_search_algorithm"&gt;A* search&lt;/a&gt;.
All nodes are put in a &lt;a href="https://en.wikipedia.org/wiki/Priority_queue"&gt;priority queue&lt;/a&gt;, and we continually pop off the node with the smallest value&amp;nbsp;of&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
f(n) = g(n) + h(n),
\end{equation*}&lt;/div&gt;
&lt;p&gt;where &lt;span class="math"&gt;\(n\)&lt;/span&gt; is a&amp;nbsp;node, &lt;span class="math"&gt;\(g(n)\)&lt;/span&gt; is the length of the path so far&amp;nbsp;and &lt;span class="math"&gt;\(h(n)\)&lt;/span&gt; is an &lt;em&gt;admissible heuristic&lt;/em&gt; that estimates the total remaining&amp;nbsp;path.&lt;/p&gt;
&lt;h3 id="admissible-heuristics"&gt;Admissible&amp;nbsp;heuristics&lt;/h3&gt;
&lt;p&gt;If the&amp;nbsp;heuristic &lt;span class="math"&gt;\(h\)&lt;/span&gt; never overestimates the length of the remaining path, then A* is guaranteed to return an optimal solution.
Such a heuristic is called&amp;nbsp;admissible.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An obvious&amp;nbsp;admissible &lt;span class="math"&gt;\(h\)&lt;/span&gt; is the unique number of colors left on the board. This never overestimates the number of remaining moves needed, because at least one click is required to remove all cells of a given&amp;nbsp;color.&lt;/li&gt;
&lt;li&gt;An even&amp;nbsp;better &lt;span class="math"&gt;\(h\)&lt;/span&gt; is described in the code below. It&amp;rsquo;s based on the following idea: if any given color appears on both sides of a column not containing that color, then at least two moves are needed to get rid of that&amp;nbsp;color.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;consecutive_groups&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;iterable&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Count how many consecutive groups of True there are.&lt;/span&gt;

&lt;span class="sd"&gt;    Examples&lt;/span&gt;
&lt;span class="sd"&gt;    --------&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; consecutive_groups([False, True, True, False, False, True, True])&lt;/span&gt;
&lt;span class="sd"&gt;    2&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;itertools&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;iterable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;a_star_heuristic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;board&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Board&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;A lower bound on how many moves are needed to solve.&lt;/span&gt;

&lt;span class="sd"&gt;    This function examines each color in turn. For each color, count whether&lt;/span&gt;
&lt;span class="sd"&gt;    it appears in each column. For each group of columns, separated by&lt;/span&gt;
&lt;span class="sd"&gt;    columns where the color does not appear, we need at least one click.&lt;/span&gt;

&lt;span class="sd"&gt;    Examples&lt;/span&gt;
&lt;span class="sd"&gt;    --------&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; board = Board([[1, 2, 1],&lt;/span&gt;
&lt;span class="sd"&gt;    ...                [2, 2, 2],&lt;/span&gt;
&lt;span class="sd"&gt;    ...                [1, 2, 1]])&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; a_star_heuristic(board)&lt;/span&gt;
&lt;span class="sd"&gt;    3&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; board = Board([[1, 3, 1],&lt;/span&gt;
&lt;span class="sd"&gt;    ...                [2, 3, 1],&lt;/span&gt;
&lt;span class="sd"&gt;    ...                [1, 3, 1]])&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; a_star_heuristic(board)&lt;/span&gt;
&lt;span class="sd"&gt;    4&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="c1"&gt;# Transpose the grid&lt;/span&gt;
    &lt;span class="n"&gt;matrix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;board&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

    &lt;span class="c1"&gt;# Get the unique integers larger than zero in the matrix&lt;/span&gt;
    &lt;span class="n"&gt;unique_integers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;matrix&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# For every integer, see if it exists in each column&lt;/span&gt;
    &lt;span class="n"&gt;integer_in_col&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(((&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;unique_integers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Count groups of integers separated by other integers&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;consecutive_groups&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;integer&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;integer_in_col&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Unfortunately, even with this heuristic A* is too slow for boards of a decent size.
The figure below shows solution times for A* and &lt;span class="caps"&gt;BFS&lt;/span&gt; as a function of board size.
Solution times grow exponentially.
Whether we re-label colors or not seems to have little practical impact on solution&amp;nbsp;times.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 90%; max-width: 580px;"
src="https://tommyodland.com/images/articles/nrk_game/random_boards_to_optimality.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;h2 id="good-enough-solutions-in-limited-time"&gt;Good-enough solutions in limited&amp;nbsp;time&lt;/h2&gt;
&lt;p&gt;A heuristic graph search algorithm (not to be confused with the heuristic&amp;nbsp;function &lt;span class="math"&gt;\(h\)&lt;/span&gt; in A* search) is an algorithm that attempts to find a good solution.
With heuristics, there are no guarantees that the optimal solution is&amp;nbsp;found.&lt;/p&gt;
&lt;p&gt;Some simple heuristics&amp;nbsp;are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Random search.&lt;/strong&gt; Always choose a random&amp;nbsp;move.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Greedy best-first search.&lt;/strong&gt; Always choose the move that clears the most&amp;nbsp;cells.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Randomized best-first search.&lt;/strong&gt; Choose a move at random, but use weighted sampling and weigh the moves by the number of cells they remove.&amp;nbsp;Setting &lt;code&gt;weight = cells_removed**power&lt;/code&gt; for&amp;nbsp;some &lt;code&gt;power &amp;gt;= 0&lt;/code&gt; is one way to parametrize this graph search.&amp;nbsp;If &lt;code&gt;power = 0&lt;/code&gt; then the search behaves like random search, and&amp;nbsp;if &lt;code&gt;power&lt;/code&gt; is very large then it behaves like greedy best-first search.&amp;nbsp;Setting &lt;code&gt;power=None&lt;/code&gt; means using no&amp;nbsp;randomness.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The figure below shows what happens when we solve the&amp;nbsp;large &lt;span class="math"&gt;\(9 \times 7\)&lt;/span&gt; board 100 times using different values&amp;nbsp;of &lt;code&gt;power.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 90%; max-width: 580px;"
src="https://tommyodland.com/images/articles/nrk_game/randomized_best_first_search.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Setting &lt;code&gt;power=5&lt;/code&gt; and &lt;code&gt;power=10&lt;/code&gt; both yield solution solution paths with 17&amp;nbsp;moves.&lt;/p&gt;
&lt;p&gt;We implement three algorithms that yield good-enough solutions quickly.
They are called &lt;strong&gt;anytime beam search&lt;/strong&gt;, &lt;strong&gt;heuristic search&lt;/strong&gt; and &lt;strong&gt;Monte Carlo search&lt;/strong&gt;, and all are more clever than simply repeating randomized best-first search many&amp;nbsp;times.&lt;/p&gt;
&lt;h3 id="anytime-beam-search"&gt;Anytime beam&amp;nbsp;search&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Beam_search"&gt;Beam search&lt;/a&gt; is a simple&amp;nbsp;algorithm:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Start at the root, expand all children. Keep only&amp;nbsp;the &lt;code&gt;beam_width&lt;/code&gt; children that are promising, for instance those that have the largest values&amp;nbsp;of &lt;code&gt;cells_cleared / num_moves&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;From&amp;nbsp;the &lt;code&gt;beam_width&lt;/code&gt; nodes under consideration, expand all children. Filter them again and keep only&amp;nbsp;the &lt;code&gt;beam_width&lt;/code&gt; most promising&amp;nbsp;ones.&lt;/li&gt;
&lt;li&gt;Repeat until a solution is&amp;nbsp;found.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;One disadvantage of beam search is that for large values&amp;nbsp;of &lt;code&gt;beam_width&lt;/code&gt; we have to wait a while until we get a solution.
An &lt;a href="https://en.wikipedia.org/wiki/Anytime_algorithm"&gt;anytime algorithm&lt;/a&gt; is one that yields good-enough solutions quickly, and if it runs for longer it improves on the solution.
To make beam search into an anytime algorithm we run it&amp;nbsp;for &lt;code&gt;beam_width=1, 2, 4, 8, ...&lt;/code&gt;, keeping track of the best solution seen so far and yielding as better solutions are&amp;nbsp;found.&lt;/p&gt;
&lt;h3 id="heuristic-search"&gt;Heuristic&amp;nbsp;search&lt;/h3&gt;
&lt;p&gt;This algorithm yields better and better solutions as they are found.
If it runs long enough, it will eventually find an optimal solution.
In practice it will run out of memory before it&amp;rsquo;s guaranteed to explore every path, so there are no guarantees.
It works roughly like&amp;nbsp;this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Start by running a single deterministic best-first search. This gives a quick upper&amp;nbsp;bound &lt;code&gt;best_solution_length_so_far&lt;/code&gt; on the optimal solution that we can use to prune solutions with&amp;nbsp;later.&lt;/li&gt;
&lt;li&gt;Sort the priority queue by a non-admissible heuristic function, for&amp;nbsp;instance &lt;code&gt;cells_cleared / num_moves&lt;/code&gt;. Always consider moves that maximize this function, hoping they lead to a good&amp;nbsp;solution.&lt;/li&gt;
&lt;li&gt;Let &lt;code&gt;h(n)&lt;/code&gt; be the admissible heuristic function discussed earlier.&amp;nbsp;If &lt;code&gt;num_moves + h(n) &amp;gt;= best_solution_length_so_far&lt;/code&gt;, then we can prune&amp;nbsp;node &lt;code&gt;n&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="monte-carlo-tree-search"&gt;Monte Carlo tree&amp;nbsp;search&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Monte_Carlo_tree_search"&gt;Monte Carlo tree search&lt;/a&gt; also yields better and better solutions as they are discovered.
Like the algorithm above, it also eventually finds an optimal solution but provides no guarantees when run for a short time.
It works roughly like&amp;nbsp;this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Start with a deterministic best-first search to establish an upper bound used for&amp;nbsp;pruning.&lt;/li&gt;
&lt;li&gt;Randomly sample nodes in the explored tree, going down guided by the so-called &lt;em&gt;upper confidence bound&lt;/em&gt;, which trades of exploration and&amp;nbsp;exploitation.&lt;/li&gt;
&lt;li&gt;When a new unexplored node is reached, run a randomized best-first search from the node down to the bottom of the tree to score the node. If this results in a new best solution, update the upper bound. Propagate the&amp;nbsp;score &lt;code&gt;cells_cleared / num_moves&lt;/code&gt; up in the explored tree to the root. This score affects the upper confidence bound used in the next&amp;nbsp;iteration.&lt;/li&gt;
&lt;li&gt;Prune by using the admissible&amp;nbsp;heuristic &lt;code&gt;h(n)&lt;/code&gt; as the tree is&amp;nbsp;explored.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Running all algorithms for around four minutes on the game instance of&amp;nbsp;size &lt;span class="math"&gt;\(9 \times 7\)&lt;/span&gt;, we get the following&amp;nbsp;results.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 90%; max-width: 580px;"
src="https://tommyodland.com/images/articles/nrk_game/heuristic_searches_board_no_16.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Anytime beam search finds the best-known solution from &lt;span class="caps"&gt;NRK&lt;/span&gt; in two&amp;nbsp;seconds.&lt;/li&gt;
&lt;li&gt;Heuristic search finds a solution of length 17 in around two minutes, but is unable to discover any better solutions in the remaining&amp;nbsp;time.&lt;/li&gt;
&lt;li&gt;Monte Carlo search finds a solution of length 14 in around twenty&amp;nbsp;seconds.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here is the solution sequence for the 12 move solution (click to&amp;nbsp;enlarge).&lt;/p&gt;
&lt;p&gt;&lt;a href="https://tommyodland.com/images/articles/nrk_game/best_solution_found_no_16.png"&gt;
&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 720px;"
src="https://tommyodland.com/images/articles/nrk_game/best_solution_found_board_no_16.png"
class="img-responsive"&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The &lt;span class="caps"&gt;NRK&lt;/span&gt; record was 12 moves, and we managed to solve the board in 12 moves.
On this particular instance, running for longer does not lead to further improvements.
On other instances we are not always so lucky; sometimes we are unable to find the best known solution, at least in a few minutes of compute&amp;nbsp;time.&lt;/p&gt;
&lt;h2 id="summary-notes-and-references"&gt;Summary, notes and&amp;nbsp;references&lt;/h2&gt;
&lt;p&gt;The &lt;span class="caps"&gt;NRK&lt;/span&gt; game &amp;ldquo;Former&amp;rdquo; can be formulated as a graph search problem, but the graph is huge.
There are billions and billions of nodes in the graph, so complete enumeration is out of the question.
While A* search is effective similar puzzles (e.g. the &lt;a href="https://en.wikipedia.org/wiki/15_puzzle"&gt;15 puzzle&lt;/a&gt;), here it fails because we were unable to design a great admissible heuristic&amp;nbsp;function.&lt;/p&gt;
&lt;p&gt;Heuristic search algorithms are still able to produce good-enough solutions in a short time.
We &lt;a href="https://github.com/tommyod/NRK-former-game"&gt;implemented&lt;/a&gt; three such algorithms with different flavors: &lt;strong&gt;anytime beam search&lt;/strong&gt;, &lt;strong&gt;heuristic search&lt;/strong&gt; and &lt;strong&gt;Monte Carlo search&lt;/strong&gt;.
In the end we managed to find the record solution of 12 moves on this instance.
These algorithms can be combined: heuristic search and Monte Carlo search perform better if an upper bound is established quickly, so running beam search as a first step could&amp;nbsp;help.&lt;/p&gt;
&lt;p&gt;The game is similar to &lt;a href="https://en.wikipedia.org/wiki/SameGame"&gt;SameGame&lt;/a&gt;, and the paper &lt;a href="https://dke.maastrichtuniversity.nl/m.winands/documents/CGSameGame.pdf"&gt;Single-Player Monte-Carlo Tree Search&lt;/a&gt; proposes Monte Carlo search for this game.
An implementation by Odin at &lt;a href="https://github.com/odinhg/nrk-former-sp-mcts"&gt;github.com/odinhg/nrk-former-sp-mcts&lt;/a&gt; implements this algorithm.
Another paper is &lt;a href="https://liacs.leidenuniv.nl/~kosterswa/samegame.pdf"&gt;Solving SameGame and its Chessboard Variant&lt;/a&gt;, which also uses Monte Carlo search.
The &lt;a href="https://github.com/chausner/sgbust"&gt;sgbust&lt;/a&gt; program is a SameGame solver written in C++ that uses beam search.
A good introductory reference for game solving is Chapter 3 &amp;ldquo;Solving Problems by Searching&amp;rdquo; in &lt;a href="https://www.amazon.com/Artificial-Intelligence-A-Modern-Approach/dp/0134610997"&gt;Artificial Intelligence: A Modern Approach&lt;/a&gt;.
Another avenue to go down is reinforcement learning, see for instance the book &lt;a href="https://www.amazon.com/Reinforcement-Learning-second-Introduction-Computation-ebook/dp/B08BSYL7R1"&gt;Reinforcement Learning: An Introduction&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thanks to Andreas for introducing me to the game, and to Eivind and Gunvor who independently discovered the admissible&amp;nbsp;heuristic.&lt;/p&gt;
&lt;h2 id="appendix-more-boards"&gt;Appendix: more&amp;nbsp;boards&lt;/h2&gt;
&lt;p&gt;Above we considered the &lt;span class="caps"&gt;NRK&lt;/span&gt; game instance for the 16th of November.
Below we show performance on more game instances from four other&amp;nbsp;days.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 90%; max-width: 580px;"
src="https://tommyodland.com/images/articles/nrk_game/heuristic_searches_board_no_26.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 90%; max-width: 580px;"
src="https://tommyodland.com/images/articles/nrk_game/heuristic_searches_board_no_25.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 90%; max-width: 580px;"
src="https://tommyodland.com/images/articles/nrk_game/heuristic_searches_board_no_24.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 90%; max-width: 580px;"
src="https://tommyodland.com/images/articles/nrk_game/heuristic_searches_board_no_23.png"
class="img-responsive"&gt;&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Tue, 26 Nov 2024 00:00:00 +0100</pubDate><guid>tag:tommyodland.com,2024-11-26:/articles/2024/solving-nrks-game-former</guid><category>articles</category><category>algorithms</category><category>optimization</category></item><item><title>The average value</title><link>https://tommyodland.com/articles/2024/the-average-value</link><description>&lt;p&gt;Presentation slides and video of a talk I gave about notions of averages (one-parameter models), loss functions and simple machine learning&amp;nbsp;problems.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://tommyodland.com/files/presentations/avg.pdf"&gt;&lt;span class="caps"&gt;PDF&lt;/span&gt; slides from the&amp;nbsp;talk&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=ZgcBQKFX4dg"&gt;YouTube&amp;nbsp;recording&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;iframe width="680" height="382" src="https://www.youtube.com/embed/ZgcBQKFX4dg?si=o4KVi_yBmMhirv9g" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
&gt;&lt;/iframe&gt;

&lt;p&gt;Below is a transcript with slides, if you prefer reading over&amp;nbsp;listening.&lt;/p&gt;
&lt;hr&gt;
&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#the-average-value"&gt;The average&amp;nbsp;value&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#table-of-contents"&gt;Table of&amp;nbsp;contents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-pythagorean-averages"&gt;The Pythagorean&amp;nbsp;averages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-p-norm"&gt;The&amp;nbsp;\(p\)-norm&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#huber-mellowmax-and-quantiles"&gt;Huber, mellowmax and&amp;nbsp;quantiles&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#eta-for-file-downloads"&gt;&lt;span class="caps"&gt;ETA&lt;/span&gt; for file&amp;nbsp;downloads&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#ranking-products-by-ratings"&gt;Ranking products by&amp;nbsp;ratings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#summary"&gt;Summary&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h2 id="the-average-value"&gt;The average&amp;nbsp;value&lt;/h2&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-01.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Hello, my name is Tommy and this is a recording of a talk I gave at an &lt;span class="caps"&gt;IT&lt;/span&gt; conference. 
The title is &amp;ldquo;&lt;em&gt;The average value&lt;/em&gt;&amp;rdquo; and the intended audience is intermediate-level data scientists.
In this recording, I&amp;rsquo;ll go through the presentation informally.
I&amp;rsquo;ll assume you have some mathematical background and are interested in data science.
If you want to look at these slides or check the references, you can download them from &lt;a href="https://tommyodland.com/files/presentations/avg.pdf"&gt;this &lt;span class="caps"&gt;URL&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-02.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;The average value&amp;mdash;this is the equation for it, and if that was all there was to say, the talk would end here. 
This slide introduces some notation, but obviously to make a presentation out of this, we need to generalize away from this&amp;nbsp;equation.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-03.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ll do that by looking at the optimization problem that this equation&amp;mdash;the arithmetic average&amp;mdash;solves.
That&amp;rsquo;s the least squares optimization problem. 
In general, in machine learning, when we do regression tasks, it&amp;rsquo;s very common that we have squared loss and some kind of&amp;nbsp;function &lt;span class="math"&gt;\(f\)&lt;/span&gt; where we want to compute the optimal parameters&amp;nbsp;for &lt;span class="math"&gt;\(f\)&lt;/span&gt;.&amp;nbsp;Here, &lt;span class="math"&gt;\(f\)&lt;/span&gt; could be a tree-based model, linear regression, or neural network, and somehow we find the best&amp;nbsp;parameters &lt;span class="math"&gt;\(\theta\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;However, there is typically no closed-form solution.
For the arithmetic average there is a closed-form equation, but for many more complicated models, you use something like gradient descent or Newton&amp;rsquo;s method.
You end up with a result, but there&amp;rsquo;s no equation you can use to just compute the results&amp;mdash;it&amp;rsquo;s an iterative procedure.
In many problems, there&amp;rsquo;s no guarantee that you have a global minimum or that you always end up with the same&amp;nbsp;one.&lt;/p&gt;
&lt;p&gt;Basically, this talk isn&amp;rsquo;t really about the average value&amp;mdash;&lt;em&gt;it&amp;rsquo;s about setting the&amp;nbsp;model &lt;span class="math"&gt;\(f\)&lt;/span&gt; to be a single parameter model&lt;/em&gt;.
We&amp;nbsp;set &lt;span class="math"&gt;\(f(x,\theta) = \theta\)&lt;/span&gt; and ask ourselves &amp;ldquo;what else can we do?&amp;rdquo;.
If we fix the model to be the simplest possible model we can think of, what else can we do? 
We can tweak the loss&amp;nbsp;function!&lt;/p&gt;
&lt;h2 id="table-of-contents"&gt;Table of&amp;nbsp;contents&lt;/h2&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-04.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the table of contents for the&amp;nbsp;presentation:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;First, we&amp;rsquo;ll look at the Pythagorean averages&amp;mdash;the arithmetic average, geometric average, and harmonic average&amp;mdash;and see that there are loss functions that induce these averages in a very natural&amp;nbsp;way&lt;/li&gt;
&lt;li&gt;Then we&amp;rsquo;ll look at&amp;nbsp;the &lt;span class="math"&gt;\(p\)&lt;/span&gt;-norm, which you might know about&amp;mdash;it&amp;rsquo;s a way to measure distance, and we can use that to get the notion of the median and the&amp;nbsp;midpoint&lt;/li&gt;
&lt;li&gt;We&amp;rsquo;ll examine Huber, Mellox, and quantiles, which are one-parameter models that go further away from the Pythagorean averages&amp;nbsp;and &lt;span class="math"&gt;\(p\)&lt;/span&gt;-norm&lt;/li&gt;
&lt;li&gt;Finally, we&amp;rsquo;ll look at some real-world examples aimed at developers: (1) &lt;span class="caps"&gt;ETA&lt;/span&gt; for file downloads and (2) Ranking products by ratings on&amp;nbsp;websites&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="the-pythagorean-averages"&gt;The Pythagorean&amp;nbsp;averages&lt;/h2&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-05.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s start with the Pythagorean&amp;nbsp;averages.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-06.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;The arithmetic average is the most common average, and it minimizes squared loss.
There&amp;rsquo;s an assumption that if you&amp;rsquo;re twice as far off, it&amp;rsquo;s four times worse because you&amp;rsquo;re squaring the residuals.
It has really nice mathematical properties&amp;mdash;basically, if you have squared loss, you have a quadratic, so when you differentiate it, you get something linear that you can solve immediately.
Newton&amp;rsquo;s method can solve this in one iteration&amp;mdash;you can one-shot these squared&amp;nbsp;problems!&lt;/p&gt;
&lt;p&gt;This figure is important to understand&amp;mdash;it shows for each data point (1, 3, and 6) the loss, and then shows the sum of these three losses over the three data points.
That&amp;rsquo;s the solid blue line, and this is the loss function.
If you minimize it, you get the arithmetic average, shown in the dashed&amp;nbsp;line.&lt;/p&gt;
&lt;p&gt;One final comment in the footnote: how do we get this squared loss in the first place?
One way is to look at the normal distribution, which is essentially an exponential to the power of squares.
But you also end up with averages in Poisson models and binomial models, so there are many types of assumptions about data that lead to arithmetic&amp;nbsp;averages.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-07.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;The geometric average minimizes multiplicative loss.
If you take the logarithm of the data and the logarithm of the model and compare those, you get the multiplicative loss.
Here, being one order of magnitude below is equally bad as being one order of magnitude above.
The geometric average is like the arithmetic average but with multiplication&amp;mdash;it&amp;rsquo;s the number that you have to multiply with&amp;nbsp;itself &lt;span class="math"&gt;\(n\)&lt;/span&gt; times to get the product of the&amp;nbsp;data.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-08.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;The harmonic average minimizes relative loss, where you measure errors relative to the true value.
This is equivalent to doing weighted least squares where the weights are given&amp;nbsp;by &lt;span class="math"&gt;\(1/y_i\)&lt;/span&gt;.
This particular loss&amp;mdash;the relative loss&amp;mdash;is not the only loss function that induces this notion of average.
The loss in the footnotes will also lead to the same harmonic average, but the relative loss one is more natural and commonly&amp;nbsp;used.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-09.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s look at these three loss functions in a different problem setting.
Suppose you have resources to distribute&amp;mdash;maybe you have bakeries and predictions for tomorrow&amp;rsquo;s bread sales.
You have a central bakery where you bake bread and want to distribute it to cover every&amp;nbsp;store.&lt;/p&gt;
&lt;p&gt;Unfortunately, you don&amp;rsquo;t have enough bread to cover every store, so you need to distribute according to some rule.
You would like your&amp;nbsp;distribution &lt;span class="math"&gt;\(\theta\)&lt;/span&gt; to be as close as possible to the demand of each&amp;nbsp;store &lt;span class="math"&gt;\(y\)&lt;/span&gt;, so you use a loss function.
Depending on which loss function you use, you get three different&amp;nbsp;distributions.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not sure what the correct answer would be&amp;mdash;it would probably depend on the bakeries&amp;rsquo; goals.
Would you use squared loss, multiplicative loss, or relative loss in the real world?
I&amp;rsquo;ll leave that up to you to think&amp;nbsp;about.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-10.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ve seen several loss functions, and it might be interesting to discuss how you would choose between them.
Every loss function has the property that if your prediction is very close to the target, it&amp;rsquo;s good, and if it&amp;rsquo;s far off, it&amp;rsquo;s&amp;nbsp;bad.&lt;/p&gt;
&lt;p&gt;How do you choose between loss functions? 
One thing I like to do is consider the null space&amp;mdash;by that, I mean considering which types of errors are equally bad and whether that makes sense in your application. 
For instance, if the true&amp;nbsp;values &lt;span class="math"&gt;\(y\)&lt;/span&gt; are 2 and&amp;nbsp;8: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Squared errors would imply that going two up in the positive direction on either prediction is equally&amp;nbsp;bad&lt;/li&gt;
&lt;li&gt;Multiplicative errors mean that predicting 4 (8/2) or 16 (8*2) is equally&amp;nbsp;bad&lt;/li&gt;
&lt;li&gt;Relative errors mean that subtracting 2 from the first prediction or adding 8 to the second one is equally bad, because those represent equal units of true&amp;nbsp;values&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here are some practical&amp;nbsp;examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you&amp;rsquo;re trying to predict medicine dosages, you probably want to use relative or multiplicative errors, since different drugs work on different dosage&amp;nbsp;scales&lt;/li&gt;
&lt;li&gt;If you&amp;rsquo;re predicting economic outcomes or investments, you probably don&amp;rsquo;t want to use multiplicative or relative errors because money isn&amp;rsquo;t relative&amp;mdash;one unit of money is the same whether it comes from a small project or a huge&amp;nbsp;project&lt;/li&gt;
&lt;li&gt;For financial predictions, you might want to use absolute errors rather than squared errors, because being twice as far off means losing twice as much, not four times as much (unless you want to consider the utility of money, but let&amp;rsquo;s skip that for&amp;nbsp;now)&lt;/li&gt;
&lt;li&gt;When people look at interest rates or summarize computer performance benchmarks (CPUs, etc.), they almost always use the geometric&amp;nbsp;average&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="the-p-norm"&gt;The &lt;span class="math"&gt;\(p\)&lt;/span&gt;-norm&lt;/h2&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-11.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Having finished the first part about Pythagorean averages, let&amp;rsquo;s talk about&amp;nbsp;the &lt;span class="math"&gt;\(p\)&lt;/span&gt;-norm. &lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-12.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;re going to generalize this equation in a different direction by solving this optimization problem for different values&amp;nbsp;of &lt;span class="math"&gt;\(p\)&lt;/span&gt; instead&amp;nbsp;of &lt;span class="math"&gt;\(p=2\)&lt;/span&gt; (which gives us the arithmetic&amp;nbsp;average).&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-13.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;If we&amp;nbsp;set &lt;span class="math"&gt;\(p\)&lt;/span&gt; equal to one, we get the absolute error.
This is an absolute value function placed on each data point, and when you sum it up, it&amp;rsquo;s piecewise linear.
If there&amp;rsquo;s an odd number of data points, there&amp;rsquo;s a unique maximum; if there&amp;rsquo;s an even number of data points, it&amp;rsquo;s flat between the two middle points&amp;mdash;there&amp;rsquo;s no unique&amp;nbsp;minimum.&lt;/p&gt;
&lt;p&gt;The median has an interesting property: if I were to change the data point 6 and move it up to 8 or down to 4, as long as it doesn&amp;rsquo;t cross 3, the median stays the same.
The median as a function completely ignores the location of every data point except for the middle one.
Obviously, if 6 crosses 3, then something happens with the function, but it&amp;rsquo;s essentially blind to many of the data&amp;nbsp;points.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-14.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Choosing &lt;span class="math"&gt;\(p=1.5\)&lt;/span&gt; is one of many ways to create a function between a median and an average.
It&amp;rsquo;s also the first optimization problem we&amp;rsquo;ve seen where there is no closed-form solution&amp;mdash;here you would use an iterative optimization routine to find the&amp;nbsp;minimum.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-15.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Choosing &lt;span class="math"&gt;\(p=2\)&lt;/span&gt; is the squared error, which we&amp;rsquo;ve seen&amp;nbsp;before.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-16.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;As you&amp;nbsp;increase &lt;span class="math"&gt;\(p\)&lt;/span&gt; further, the functions become more and more&amp;nbsp;sharp.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-17.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;You get to an interesting property in the limit&amp;nbsp;as &lt;span class="math"&gt;\(p\)&lt;/span&gt; becomes very large: minimizing&amp;nbsp;the &lt;span class="math"&gt;\(p\)&lt;/span&gt;-norm becomes equivalent to minimizing the maximum.
If you want to minimize the maximum distance, you want to make the worst case as good as possible, and you do that by placing yourself at the midpoint.
This is one of the measures of centrality you might have learned in high school&amp;mdash;the&amp;nbsp;midpoint.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-18.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;How can we combine these Pythagorean losses&amp;nbsp;and &lt;span class="math"&gt;\(p\)&lt;/span&gt;-norms?
Let&amp;rsquo;s put them in a&amp;nbsp;table:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mean absolute&amp;nbsp;error&lt;/li&gt;
&lt;li&gt;Mean squared&amp;nbsp;error&lt;/li&gt;
&lt;li&gt;Maximum error (not used much in machine learning but used in engineering where you want to optimize the worst case, like in airplane&amp;nbsp;design)&lt;/li&gt;
&lt;li&gt;Mean squared logarithmic&amp;nbsp;error&lt;/li&gt;
&lt;li&gt;Mean absolute percentage&amp;nbsp;error&lt;/li&gt;
&lt;li&gt;Weighted mean squared&amp;nbsp;error&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some combinations aren&amp;rsquo;t listed but could be imagined.
For instance,&amp;nbsp;with &lt;span class="math"&gt;\(p=1\)&lt;/span&gt; and multiplicative error, you&amp;rsquo;d get something like the mean absolute logarithmic error.
The point is that you can construct and combine these&amp;nbsp;functions.&lt;/p&gt;
&lt;p&gt;How would you do that?
You can first look at an individual data point and consider what kind of loss function you&amp;rsquo;re interested in, then use&amp;nbsp;a &lt;span class="math"&gt;\(p\)&lt;/span&gt;-norm to combine them.&amp;nbsp;The &lt;span class="math"&gt;\(p\)&lt;/span&gt;-norm is a way to combine the individual&amp;nbsp;losses.&lt;/p&gt;
&lt;p&gt;Think about what properties you want in your&amp;nbsp;application:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;With absolute error, residuals&amp;nbsp;of &lt;span class="math"&gt;\((4,0)\)&lt;/span&gt; are as bad&amp;nbsp;as &lt;span class="math"&gt;\((3,1)\)&lt;/span&gt; and &lt;span class="math"&gt;\((2,2)\)&lt;/span&gt;. With money, this makes sense&amp;mdash;if you&amp;rsquo;re off by four, you probably don&amp;rsquo;t care how it&amp;rsquo;s distributed if it&amp;rsquo;s money&amp;nbsp;lost&lt;/li&gt;
&lt;li&gt;It also means&amp;nbsp;that &lt;span class="math"&gt;\((9,0)\)&lt;/span&gt; is better&amp;nbsp;than &lt;span class="math"&gt;\((5,5)\)&lt;/span&gt;, even though errors&amp;nbsp;in &lt;span class="math"&gt;\((5,5)\)&lt;/span&gt; are more&amp;nbsp;balanced&lt;/li&gt;
&lt;li&gt;Maximum error just looks at the largest value,&amp;nbsp;so &lt;span class="math"&gt;\((9,0)\)&lt;/span&gt; would be as bad&amp;nbsp;as &lt;span class="math"&gt;\((9,5)\)&lt;/span&gt; and &lt;span class="math"&gt;\((9,9)\)&lt;/span&gt;,&amp;nbsp;while &lt;span class="math"&gt;\((8,8)\)&lt;/span&gt; would be better&amp;nbsp;than &lt;span class="math"&gt;\((9,0)\)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="huber-mellowmax-and-quantiles"&gt;Huber, mellowmax and&amp;nbsp;quantiles&lt;/h2&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-19.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Now let&amp;rsquo;s look at even more esoteric notions of averages, maxima, minima, and everything in&amp;nbsp;between.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-20.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;One issue with squared errors is that they can be overly sensitive to outliers.
One remedy for this is to use the smooth Huber loss, which acts like a square when you&amp;rsquo;re close to zero but like the absolute value when you zoom out.
This makes it less sensitive to outliers while still maintaining a unique minimum even with an even number of data&amp;nbsp;points.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-21.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s an opposite problem that might occur in some applications: squared errors are very flat around zero, so if you have a small residual and you square it, it becomes even smaller.
If you&amp;rsquo;re regularizing coefficients (like the &lt;span class="caps"&gt;LASSO&lt;/span&gt; does) and want to drive them all the way to zero, it&amp;rsquo;s very nice to have something that acts like the absolute value close to zero.
The reverse Huber has this effect&amp;mdash;it&amp;rsquo;s like the absolute value when you&amp;rsquo;re close to zero and acts like a square if you&amp;rsquo;re far&amp;nbsp;away.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-22.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Mellowmax and mellowmin are smooth generalizations of maximum and minimum.
They are functions&amp;nbsp;that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;As &lt;span class="math"&gt;\(\alpha\)&lt;/span&gt; goes to infinity, the function approaches the&amp;nbsp;maximum&lt;/li&gt;
&lt;li&gt;As &lt;span class="math"&gt;\(\alpha\)&lt;/span&gt; goes to negative infinity, the function approaches the&amp;nbsp;minimum&lt;/li&gt;
&lt;li&gt;Around zero, the function approaches the arithmetic&amp;nbsp;average&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you want something that acts in between the arithmetic average and the maximum, mellowmax is a nice way to do it.
The maximum only depends on the maximum value in the dataset, but sometimes when you want to minimize the maximum, you want to bring all values down.
One way to do that is to drive down the maximum value, but as a function, the maximum is blind to every other value apart from the largest one.
If you use mellowmax instead, it&amp;rsquo;s actually a bit sensitive to other values, which can better inform the optimization&amp;nbsp;routine.&lt;/p&gt;
&lt;p&gt;Sometimes it&amp;rsquo;s very nice to relax these functions that are piecewise linear or have derivatives that are zero for most inputs.
The median only depends on one or two values for most inputs, and if you want something more smooth, this is a nice&amp;nbsp;option.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-27.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s quickly cover quantiles&amp;mdash;I assume you know about them.
They&amp;rsquo;re a way to be somewhere between the median and the minimum/maximum.
They put an absolute value function on every data point but scale it up and down on the left and&amp;nbsp;right.&lt;/p&gt;
&lt;p&gt;A small generalization: there&amp;rsquo;s no reason we have to use the absolute value&amp;mdash;we could use squared residuals too.&amp;nbsp;If &lt;span class="math"&gt;\(p=2\)&lt;/span&gt;, you would call it expectile, but in general, you don&amp;rsquo;t &lt;em&gt;have&lt;/em&gt; to&amp;nbsp;use &lt;span class="math"&gt;\(p=1\)&lt;/span&gt; or &lt;span class="math"&gt;\(p=2\)&lt;/span&gt;.
You could in principle use any value&amp;nbsp;for &lt;span class="math"&gt;\(p\)&lt;/span&gt;.&lt;/p&gt;
&lt;h2 id="eta-for-file-downloads"&gt;&lt;span class="caps"&gt;ETA&lt;/span&gt; for file&amp;nbsp;downloads&lt;/h2&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-28.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s look at our first practical example: &lt;span class="caps"&gt;ETA&lt;/span&gt; for file&amp;nbsp;downloads. &lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-29.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Suppose you&amp;rsquo;re a developer writing a routine that will download files, copy files, or install software.
The computer is doing chunks of work, and you have the time that each chunk takes.
You know the amount of work done and how much work will be done in total (like the total megabytes for all files to download).
You want to estimate how long this will&amp;nbsp;take.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-30.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;To solve this, we need to&amp;nbsp;handle:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Non-equidistant arrivals (times or pieces of&amp;nbsp;work)&lt;/li&gt;
&lt;li&gt;We&amp;rsquo;ll assume a linear model is reasonable (parameters can change&amp;mdash;if someone else starts downloading, your speed might drop, but after it drops we assume it&amp;rsquo;s still linear with a different&amp;nbsp;rate)&lt;/li&gt;
&lt;li&gt;Very importantly, when a new data point arrives, we must be able to update our model in constant time&amp;mdash;we can&amp;rsquo;t iterate over all previous data points, as there could be&amp;nbsp;millions&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-32.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s one approach: we can compute the average rate of work. 
With two pieces of work (two files downloaded), we can compute the slope for each piece (work done over time elapsed). 
By minimizing this function, we&amp;nbsp;get &lt;span class="math"&gt;\(\theta\)&lt;/span&gt;, which is the average amount of work done per&amp;nbsp;time.&lt;/p&gt;
&lt;p&gt;I weight this&amp;nbsp;by &lt;span class="math"&gt;\(\Delta t\)&lt;/span&gt; (time) because when I solve that optimization problem, it leads to an equation with nice symmetry&amp;nbsp;in &lt;span class="math"&gt;\(\theta\)&lt;/span&gt;.
It should make intuitive sense that downloading a file in 1 second contains some information, but downloading the next file in 30 seconds contains much more information&amp;mdash;so&amp;nbsp;large &lt;span class="math"&gt;\(\Delta t\)&lt;/span&gt; should be weighted more&amp;nbsp;heavily.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-31.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;We&amp;nbsp;use &lt;span class="math"&gt;\(w_i\)&lt;/span&gt; to weigh the most recent data more heavily. 
We can split it up like this: the most recent data point is attached at the end, so we have the previous estimate and some new data. 
We can quickly update by keeping track of our previous computation and adding to&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-32.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;We then take a linear model constrained to go through the last data point seen.
We have one degree of freedom left, which we use on the slope with this estimate.
We set the weights proportional to some number to the power&amp;nbsp;of &lt;span class="math"&gt;\(i\)&lt;/span&gt; over &lt;span class="math"&gt;\(\gamma\)&lt;/span&gt;,&amp;nbsp;where &lt;span class="math"&gt;\(\gamma\)&lt;/span&gt; is the half-life.
This parameterized the weights so the most recent data is weighted more&amp;nbsp;heavily.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-36.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s how it&amp;nbsp;performs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If the speed changes when we see more data, obviously it&amp;rsquo;s not a very good&amp;nbsp;model&lt;/li&gt;
&lt;li&gt;If it&amp;rsquo;s linear all the way through, it&amp;rsquo;s quite&amp;nbsp;accurate&lt;/li&gt;
&lt;li&gt;With some deviation from linearity, it picks up an average rate somewhere in&amp;nbsp;between&lt;/li&gt;
&lt;li&gt;With noise, it goes through the last data point and picks up an average rate that&amp;rsquo;s quite&amp;nbsp;close&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As more data arrives, it will actually improve because it&amp;rsquo;ll pick up on new regimes with different rates of change.
This is a simple algorithm&amp;mdash;very fast and accurate in many real-world cases.
It depends on the application, but it&amp;rsquo;s probably more advanced than many file &lt;span class="caps"&gt;ETA&lt;/span&gt; estimates I&amp;rsquo;ve seen, which tend to be jittery and jump around too&amp;nbsp;much.&lt;/p&gt;
&lt;h2 id="ranking-products-by-ratings"&gt;Ranking products by&amp;nbsp;ratings&lt;/h2&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-38.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Our final example is ranking products by&amp;nbsp;ratings.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-39.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;This is a fascinating real-world example of how you can use averages effectively.
I looked at Norwegian e-commerce websites where users can give ratings.
All these websites use a &lt;strong&gt;naive average rating&lt;/strong&gt;, which produces some strange&amp;nbsp;results:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The average of a single 5 is 5, but the average of many 5s and a few 4s would be slightly less than&amp;nbsp;5&lt;/li&gt;
&lt;li&gt;This means a product with one 5-star rating would rank higher than a product with hundreds of 5-star ratings and a few 4-star&amp;nbsp;ratings&lt;/li&gt;
&lt;li&gt;I would trust the product with more ratings to be better, as one rating doesn&amp;rsquo;t give much certainty about true&amp;nbsp;quality&lt;/li&gt;
&lt;li&gt;Many websites also place products with no ratings at the very bottom, which I think is wrong&amp;mdash;a new product with no ratings is likely average, not the worst&amp;nbsp;product&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I didn&amp;rsquo;t find any Norwegian e-commerce websites that do this correctly (I didn&amp;rsquo;t look at international ones).
One of the few sites that gets this right is the IMDb Top 250 list&amp;mdash;they account for the number of ratings and use an equation similar to what we&amp;rsquo;re going to&amp;nbsp;deduce.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-40.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;We want our average rating to have two&amp;nbsp;properties:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It should take into account the number of&amp;nbsp;ratings&lt;/li&gt;
&lt;li&gt;A product without any ratings is likely typical, not really&amp;nbsp;bad&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here&amp;rsquo;s a crucial idea: what are we really doing when we&amp;rsquo;re trying to rank products?
&lt;strong&gt;We&amp;rsquo;re going to frame this as a prediction problem&amp;mdash;we&amp;rsquo;re predicting the next rating.&lt;/strong&gt;
If you were to buy this product and rate it, what would your rating be?
That would be the next rating for this&amp;nbsp;product.&lt;/p&gt;
&lt;p&gt;So we&amp;rsquo;re casting it as a prediction problem and saying that ranking products is equivalent to predicting the next rating and sorting by that prediction.
We&amp;rsquo;ll do this&amp;nbsp;by:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Introducing a prior&amp;nbsp;average &lt;span class="math"&gt;\(\bar{y}\)&lt;/span&gt; and a pseudo&amp;nbsp;count &lt;span class="math"&gt;\(\alpha\)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Solving this optimization&amp;nbsp;problem&lt;/li&gt;
&lt;li&gt;The solution shows the meaning of the pseudo count&amp;mdash;it&amp;rsquo;s added&amp;nbsp;to &lt;span class="math"&gt;\(n\)&lt;/span&gt; as dummy ratings, and the value of these dummy ratings&amp;nbsp;is &lt;span class="math"&gt;\(\bar{y}\)&lt;/span&gt; (the prior average&amp;nbsp;rating)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-41.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;To determine the&amp;nbsp;hyperparameters &lt;span class="math"&gt;\(\bar{y}\)&lt;/span&gt; and &lt;span class="math"&gt;\(\alpha\)&lt;/span&gt;, we need to do cross-validation.
I assume you know about leave-one-out and k-fold cross-validation.
We&amp;rsquo;re going to do &amp;ldquo;leave-next-out&amp;rdquo;&amp;nbsp;cross-validation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It&amp;rsquo;s very important that your cross-validation procedure be as similar as possible to how you want to use the model.&lt;/strong&gt;
Since we defined our notion of ranking as predicting the next value, we&amp;rsquo;ll simulate that&amp;nbsp;exactly:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Start with no ratings, try to predict the first&amp;nbsp;one&lt;/li&gt;
&lt;li&gt;Get to know the first rating, look at the squared&amp;nbsp;residual&lt;/li&gt;
&lt;li&gt;Use that first data point to predict the second&amp;nbsp;one&lt;/li&gt;
&lt;li&gt;Get to know the second data point and&amp;nbsp;compare&lt;/li&gt;
&lt;li&gt;Continue this&amp;nbsp;process&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is essentially a time series split where you learn one more data point at a time.
Both of these can be computed in linear time for these simple models, so we can do this very&amp;nbsp;quickly.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-42.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s apply it to a real dataset.
There&amp;rsquo;s a Norwegian website called Legelisten that ranks&amp;nbsp;doctors.&lt;/p&gt;
&lt;p&gt;I found 17 doctors with 212 total ratings.
I used our average with some weights, parameterizing those weights by the&amp;nbsp;half-life &lt;span class="math"&gt;\(\bar{y}\)&lt;/span&gt;.
Using leave-next-out cross-validation, I&amp;nbsp;found:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Optimal number of pseudo-ratings is&amp;nbsp;around &lt;span class="math"&gt;\(\alpha=2\)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Optimal half-life is&amp;nbsp;around &lt;span class="math"&gt;\(\gamma =7\)&lt;/span&gt; years&lt;/li&gt;
&lt;li&gt;Prior rating&amp;nbsp;is &lt;span class="math"&gt;\(\bar{y}=3.8\)&lt;/span&gt; (on a scale from 1 to&amp;nbsp;5)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The loss when using the arithmetic average as our predictor was 1.77.
With this regularized temporal average, it&amp;rsquo;s 1.55&amp;mdash;a very nice reduction in loss that leads to better predictions of the next&amp;nbsp;rating.&lt;/p&gt;
&lt;p&gt;You might ask why not make this into a big machine learning project using all kinds of covariates, but I don&amp;rsquo;t think people would like that.
If you rank doctors using information about their gender and age, it&amp;rsquo;s probably not good.
This solution is something people will understand and won&amp;rsquo;t feel discriminates unfairly based on age, gender, or other factors.
It&amp;rsquo;s still super simple, just more nuanced and created from thinking through the problem more&amp;nbsp;carefully.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-43.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Looking at the results (blue is the old website rating, green is my proposed&amp;nbsp;rating):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The third doctor: One person rated this doctor with a 5. The old ranking puts them first; we say no&amp;mdash;one rating isn&amp;rsquo;t much, so we pull them down. The doctor with many 5s deserves to be ranked&amp;nbsp;higher.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The tenth doctor is interesting because they&amp;rsquo;re pulled away from the grand average. Typically without temporal weighting, you pull towards the grand average, but this doctor has gotten more positive reviews lately&amp;mdash;lots of 5s and 4s, 44 ratings in total. This doctor has improved over the years, so the half-life is forgiving the older low ratings and pulling up towards the more recent&amp;nbsp;5s.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The seventeenth doctor hasn&amp;rsquo;t been rated once. The old ranking would place them last; we say no&amp;mdash;with no ratings, we believe you&amp;rsquo;re in the middle of the pack. We place them there, certainly better than someone with just many 1-star&amp;nbsp;ratings.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-44.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s summarize the key&amp;nbsp;points.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"
src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-45.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Only simple problems have closed-form solutions.&lt;/strong&gt; Don&amp;rsquo;t worry so much about how to solve things when you start modeling&amp;mdash;look at what properties you want to model instead. Solving is left to an optimization&amp;nbsp;algorithm.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;There&amp;rsquo;s a good reason why people like squared errors&lt;/strong&gt;&amp;mdash;they have nice mathematical properties. Not everything I showed today can be solved efficiently for bigger&amp;nbsp;models.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;In machine learning, the solution depends on both the model and the loss function.&lt;/strong&gt; Many data scientists focus heavily on the model, discussing which is better in detail. While the model is important, choosing the wrong loss function can be just as problematic. For instance, if errors are truly relative in your application but you choose &lt;span class="caps"&gt;RMSE&lt;/span&gt;, or you have an economic application but use the wrong loss function, you&amp;rsquo;re probably modeling the whole thing&amp;nbsp;incorrectly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Adapt your approach to the problem.&lt;/strong&gt; Combine existing ideas but don&amp;rsquo;t reinvent&amp;mdash;there are many good books and sources. People have thought about amazing things, so read up on that. If you find yourself inventing something you believe is totally new to solve a practical problem, you&amp;rsquo;re probably reinventing something&amp;mdash;do a literature&amp;nbsp;search.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Always start simple.&lt;/strong&gt; Never use models you don&amp;rsquo;t understand&amp;mdash;you&amp;rsquo;ll have a hard time explaining them to others, and they might behave in ways you don&amp;rsquo;t fully understand. There&amp;rsquo;s still plenty of room to have fun with simple models. Even today, some e-commerce sites are billion-dollar businesses and don&amp;rsquo;t implement averages&amp;nbsp;correctly.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The final comment I want to make is that if you go back to the first slide and remove the restriction of the one-parameter model, everything we talked about still holds.
All the loss functions and modeling approaches are still relevant, but by choosing one-parameter models, I was able to create nice plots and talk about things simply.
Many of these concepts are still relevant for other models, even though computationally it&amp;rsquo;s not always easy to&amp;nbsp;optimize.&lt;/p&gt;
&lt;p&gt;I really hope you enjoyed my presentation.
Thanks a lot for watching, and again, you can download the slides and look at the references if you want to.
Thank&amp;nbsp;you.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Sun, 10 Nov 2024 00:00:00 +0100</pubDate><guid>tag:tommyodland.com,2024-11-10:/articles/2024/the-average-value</guid><category>articles</category><category>datascience</category></item><item><title>Smooth taxes without brackets</title><link>https://tommyodland.com/articles/2024/smooth-taxes-without-brackets</link><description>&lt;p&gt;I recently implemented a calculator for Norwegian income tax, and it was harder than I anticipated.
&lt;strong&gt;The implementation required around 30 lines of code and 20 constants&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 95%; max-width:600px;"
src="https://tommyodland.com/images/articles/taxes/norwegian_income_tax_2024_marginal.png"&gt;&lt;/p&gt;
&lt;p&gt;In this article we use the term &amp;ldquo;income tax&amp;rdquo; to refer to everything the government deducts from gross income.
To compute income tax, one must take into account the &lt;a href="https://www.skatteetaten.no/en/rates/bracket-tax/"&gt;brackets&lt;/a&gt;, the &lt;a href="https://www.skatteetaten.no/en/rates/national-insurance-contributions/"&gt;national insurance contributions&lt;/a&gt;, the &lt;a href="https://www.skatteetaten.no/en/rates/minimum-standard-deduction/"&gt;minimum standard deduction&lt;/a&gt;, the &lt;a href="https://www.skatteetaten.no/en/rates/personal-allowance/"&gt;personal allowance&lt;/a&gt;, and so&amp;nbsp;forth.&lt;/p&gt;
&lt;p&gt;In this article we&amp;rsquo;ll simplify the income tax calculation to &lt;strong&gt;one line of code and two constants&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ll start by looking empirically at the Norwegian income tax, then we&amp;rsquo;ll study properties that a tax scheme should have.
In the end we&amp;rsquo;ll construct a smooth tax function with two adjustable&amp;nbsp;parameters &lt;span class="math"&gt;\(H\)&lt;/span&gt; and &lt;span class="math"&gt;\(k\)&lt;/span&gt;:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
T(x) = 
\frac{H(e^{-kx} + kx - 1)}{k}.
\end{align*}&lt;/div&gt;
&lt;p&gt;
We&amp;rsquo;ll show that this function has good properties and that for suitable values&amp;nbsp;of &lt;span class="math"&gt;\(H\)&lt;/span&gt; and &lt;span class="math"&gt;\(k\)&lt;/span&gt; it approximates the current income tax very well.
Below is a figure showing the average tax rate and the marginal tax rate induced by the&amp;nbsp;function &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt; above.
You&amp;rsquo;ll understand this figure better as you read the next sections.
&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 95%; max-width:600px;"
src="https://tommyodland.com/images/articles/taxes/norwegian_income_tax_2024_smooth.png"&gt;&lt;/p&gt;
&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;h3 id="the-tax-function-tx"&gt;The tax&amp;nbsp;function &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;Let &lt;span class="math"&gt;\(T: \mathbb{R}_+ \to \mathbb{R}_+\)&lt;/span&gt; be the income tax as a function of gross&amp;nbsp;income &lt;span class="math"&gt;\(x\)&lt;/span&gt;.
For instance, if Alice earns a gross income&amp;nbsp;of &lt;span class="math"&gt;\(x=100\)&lt;/span&gt; units of money, then she might have to&amp;nbsp;pay &lt;span class="math"&gt;\(T(100) = 20\)&lt;/span&gt; in taxes, leaving her with a net income&amp;nbsp;of &lt;span class="math"&gt;\(x - T(x) = 100 - 20 = 80\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;If we have such a&amp;nbsp;function &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt;, then we can deduce three other interesting functions that answer specific&amp;nbsp;questions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The net&amp;nbsp;income &lt;span class="math"&gt;\(x - T(x)\)&lt;/span&gt;: &amp;ldquo;How much money do I have left after paying&amp;nbsp;taxes?&amp;rdquo;&lt;/li&gt;
&lt;li&gt;The average tax&amp;nbsp;rate &lt;span class="math"&gt;\(T(x) / x\)&lt;/span&gt;: &amp;ldquo;What percentage of my total income do I pay in&amp;nbsp;taxes?&amp;rdquo;&lt;/li&gt;
&lt;li&gt;The marginal tax&amp;nbsp;rate &lt;span class="math"&gt;\(T'(x) \approx T(x+1) - T(x)\)&lt;/span&gt;: &amp;ldquo;If I earn one more unit of money, what fraction of that unit of money goes to&amp;nbsp;taxes?&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="proportional-and-progressive-tax"&gt;Proportional and progressive&amp;nbsp;tax&lt;/h3&gt;
&lt;p&gt;A tax scheme is called &lt;em&gt;proportional&lt;/em&gt; if the tax rate is a fixed percentage.
In our notation, this would mean&amp;nbsp;that &lt;span class="math"&gt;\(T(x) = cx\)&lt;/span&gt; for some&amp;nbsp;constant &lt;span class="math"&gt;\(c\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;A tax scheme is called &lt;em&gt;progressive&lt;/em&gt; if those who earn more pay a higher percentage in taxes.
In addition to progressive tax schemes, we can define &lt;em&gt;non-regressive&lt;/em&gt; and &lt;em&gt;strictly progressive&lt;/em&gt; tax&amp;nbsp;schemes.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Non-regressive&lt;/strong&gt;: the average tax&amp;nbsp;rate &lt;span class="math"&gt;\(T(x)/x\)&lt;/span&gt; never&amp;nbsp;decreases.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Progessive&lt;/strong&gt;: the average tax&amp;nbsp;rate &lt;span class="math"&gt;\(T(x)/x\)&lt;/span&gt; never decreases, and it &lt;em&gt;increases at least once&lt;/em&gt; in the&amp;nbsp;domain.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Strictly progressive&lt;/strong&gt;: the average tax&amp;nbsp;rate &lt;span class="math"&gt;\(T(x)/x\)&lt;/span&gt; always&amp;nbsp;increases.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Mathematically, these requirements correspond to conditions on the derivatives of the average tax rate.
For instance, a non-regressive tax&amp;nbsp;function &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt; has the constraint&amp;nbsp;that
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align}
\label{progressive} \tag{1}
\left( \frac{T(x)}{x} \right)' \geq 0 \Rightarrow T'(x) - \frac{T(x)}{x} \geq 0.
\end{align}&lt;/div&gt;
&lt;p&gt;
The second inequality comes from applying the product rule of differentiation.
Having established some notation and concepts that we&amp;rsquo;ll use later, we will now empirically examine the Norwegian income&amp;nbsp;tax.&lt;/p&gt;
&lt;h2 id="the-norwegian-income-tax"&gt;The Norwegian income&amp;nbsp;tax&lt;/h2&gt;
&lt;p&gt;The Norwegian income tax has several deductions, a few if-then-else rules and a (mostly) increasing marginal tax rate.
Once all the rules are implemented and combined, the result can be visualized as a function.
The implementation was tested against the calculator &lt;a href="https://www.smartepenger.no/kalkulatorer/3786-skatteberegning-2024"&gt;Skatteberegning 2024&lt;/a&gt;, and in the background the income distribution from 2022 is&amp;nbsp;shown.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 95%; max-width:600px;"
src="https://tommyodland.com/images/articles/taxes/norwegian_income_tax_2024.png"&gt;&lt;/p&gt;
&lt;p&gt;The jumps on the marginal tax&amp;nbsp;rate &lt;span class="math"&gt;\(T'(x)\)&lt;/span&gt; make the figure a little disorderly, but notice&amp;nbsp;that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It&amp;rsquo;s progressive, since the average tax&amp;nbsp;rate &lt;span class="math"&gt;\(T(x)/x\)&lt;/span&gt; never decreases and it does&amp;nbsp;increase.&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s not strictly progressive&amp;nbsp;because &lt;span class="math"&gt;\(T(x)/x\)&lt;/span&gt; has two flat&amp;nbsp;regions.&lt;/li&gt;
&lt;li&gt;The limit evaluates&amp;nbsp;to &lt;span class="math"&gt;\(\lim_{x \to \infty} T(x) / x = 0.474\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;The marginal tax rate is discontinuous, and actually jumps down&amp;nbsp;once.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The government often changes the effective tax rates: they adjust deductions, the lower and upper limits on the brackets and rates within each&amp;nbsp;bracket.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 95%; max-width:600px;"
src="https://tommyodland.com/images/articles/taxes/norwegian_income_tax_2023_2024.png"&gt;&lt;/p&gt;
&lt;p&gt;To get an idea of how erratic these changes are, consider the highest bracket that extends to infinity.
In 2020 the lower limit of the highest bracket was 999 550, then in 2021 it was raised to 1 021 550. It doubled to 2 000 000 in 2022, was reduced by half a million to 1 500 000 in 2023 and cut further to 1 350 001 in&amp;nbsp;2024.&lt;/p&gt;
&lt;h2 id="properties-that-smooth-taxes-should-have"&gt;Properties that smooth taxes should&amp;nbsp;have&lt;/h2&gt;
&lt;p&gt;To implement the Norwegian income tax I had to read up on tax rules from several sources online.
I also had to test the implementation against an online calculator multiple times before I got it right.
&lt;strong&gt;Wouldn&amp;rsquo;t it be nice&amp;nbsp;if &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt; was just a simple formula?&lt;/strong&gt;
No jumps and kinks&amp;mdash;just a smooth, simple&amp;nbsp;function.&lt;/p&gt;
&lt;p&gt;For instance, given a&amp;nbsp;constant &lt;span class="math"&gt;\(k &amp;gt;0\)&lt;/span&gt;, what&amp;nbsp;if &lt;span class="math"&gt;\(T(x) = x(1 - e^{-xk})\)&lt;/span&gt;?
That way the average tax rate&amp;nbsp;becomes &lt;span class="math"&gt;\(T(x)/x = 1 - e^{-xk}\)&lt;/span&gt;, which seems reasonable&amp;hellip;&amp;nbsp;right?&lt;/p&gt;
&lt;p&gt;It turns out that this is a seriously bad idea, as we can see in this figure:
&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 95%; max-width:700px;"
src="https://tommyodland.com/images/articles/taxes/exponential_tax.png"&gt;&lt;/p&gt;
&lt;p&gt;The net&amp;nbsp;income &lt;span class="math"&gt;\(x - T(x) = x - x(1 - e^{-xk})\)&lt;/span&gt; is maximized&amp;nbsp;at &lt;span class="math"&gt;\(x^{\star} = 1/k\)&lt;/span&gt;.
There is no incentive to earn more money than this, since you &lt;em&gt;lose money by earning more&lt;/em&gt;.
This is a terrible property for a tax system to have&amp;mdash;back to the drawing&amp;nbsp;board!&lt;/p&gt;
&lt;h3 id="property-1-good-incentives"&gt;Property 1: Good&amp;nbsp;incentives&lt;/h3&gt;
&lt;p&gt;Let&amp;rsquo;s set a simple rule: earning more should always mean keeping more after taxes.
In other words, if we increase our gross income&amp;nbsp;from &lt;span class="math"&gt;\(x\)&lt;/span&gt; to &lt;span class="math"&gt;\(x + \epsilon\)&lt;/span&gt;, then we should end up with more net income after paying&amp;nbsp;taxes:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\underbrace{(x + \epsilon) - T(x + \epsilon)}_{\text{net income with $\epsilon$ more}} &amp;gt; \underbrace{x - T(x)}_{\text{net income}}
\end{align*}&lt;/div&gt;
&lt;p&gt;Rearranging this expression we find that the condition is equivalent&amp;nbsp;to
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\frac{T(x + \epsilon) - T(x)}{\epsilon} &amp;lt; 1,
\end{align*}&lt;/div&gt;
&lt;p&gt;
and in the&amp;nbsp;limit &lt;span class="math"&gt;\(\epsilon \to 0\)&lt;/span&gt; the requirement says that the marginal tax&amp;nbsp;rate &lt;span class="math"&gt;\(T'(x)\)&lt;/span&gt; must&amp;nbsp;obey &lt;span class="math"&gt;\(T'(x) &amp;lt; 1\)&lt;/span&gt;.
If this inequality is satisfied, then higher pre-tax income always leads to higher post-tax income.
In other words, there&amp;rsquo;s always an incentive to increase one&amp;rsquo;s gross&amp;nbsp;income &lt;span class="math"&gt;\(x\)&lt;/span&gt;.&lt;/p&gt;
&lt;h3 id="property-2-progressive-taxation"&gt;Property 2: Progressive&amp;nbsp;taxation&lt;/h3&gt;
&lt;p&gt;Another natural constraint to impose is&amp;nbsp;that &lt;span class="math"&gt;\(T'(x)\)&lt;/span&gt; should be non-decreasing, since then the tax scheme must be non-regressive.
To see why this is true, note that&amp;nbsp;if &lt;span class="math"&gt;\(T'(x)\)&lt;/span&gt; is non-decreasing and non-negative,&amp;nbsp;then &lt;span class="math"&gt;\(T(x) = \int_0^{x} T'(\tau) \, d \tau \leq x T'(x)\)&lt;/span&gt;.
Rearranging this inequality&amp;nbsp;gives &lt;span class="math"&gt;\(T'(x) \geq T(x)/ x\)&lt;/span&gt;, which is an inequality that we have seen before in Equation \eqref{progressive} in the introduction.
It is exactly what is needed for a tax scheme to be&amp;nbsp;non-regressive.&lt;/p&gt;
&lt;p&gt;Note that the converse is not true: a tax scheme can be non-regressive even&amp;nbsp;if &lt;span class="math"&gt;\(T'(x)\)&lt;/span&gt; decreases.
For instance, the Norwegian marginal tax does decrease at one point, but the tax is still non-regressive overall.&amp;nbsp;If &lt;span class="math"&gt;\(T'(x)\)&lt;/span&gt; is not only non-decreasing, but also &lt;em&gt;increases&lt;/em&gt; at some point, then the resulting tax scheme is guaranteed to be&amp;nbsp;progressive.&lt;/p&gt;
&lt;p&gt;Due to these constraints on the marginal tax&amp;nbsp;rate &lt;span class="math"&gt;\(T'(x)\)&lt;/span&gt;, it&amp;rsquo;s easier to find good&amp;nbsp;functions &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt; if we start&amp;nbsp;with &lt;span class="math"&gt;\(T'(x)\)&lt;/span&gt; and integrate to&amp;nbsp;obtain &lt;span class="math"&gt;\(T(x) = \int_0^{x} T'(\tau) \, d \tau\)&lt;/span&gt;.&lt;/p&gt;
&lt;h2 id="constructing-tax-functions"&gt;Constructing tax&amp;nbsp;functions&lt;/h2&gt;
&lt;p&gt;Recall that the marginal tax&amp;nbsp;rate &lt;span class="math"&gt;\(T'(x)\)&lt;/span&gt; says how much we&amp;rsquo;ll be taxed on the next unit of money earned.
For&amp;nbsp;instance, &lt;span class="math"&gt;\(T'(x=100 \, 000) = 0.25\)&lt;/span&gt; in the Norwegian income tax, so if we&amp;nbsp;earn &lt;span class="math"&gt;\(100\, 000\)&lt;/span&gt; &lt;span class="caps"&gt;NOK&lt;/span&gt; the next unit of money will be taxed&amp;nbsp;by &lt;span class="math"&gt;\(0.25\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Proposing a function.&lt;/strong&gt;
Let&amp;rsquo;s again consider the&amp;nbsp;function &lt;span class="math"&gt;\(1 - e^{-xk}\)&lt;/span&gt;, but this time as the marginal tax&amp;nbsp;rate &lt;span class="math"&gt;\(T'(x)\)&lt;/span&gt; instead of as an average tax&amp;nbsp;rate:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
T'(x) = 
1 - e^{-xk}.
\end{align*}&lt;/div&gt;
&lt;p&gt;
This function satisfies both our properties: it&amp;rsquo;s always less than one, and it never decreases.
If we integrate it we&amp;nbsp;obtain
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
T(x) = \int_0^{x} T'(\tau) \, d \tau = \int_0^{x} (1 - e^{-\tau k}) \, d \tau
=
\frac{e^{-kx} - 1}{k} + x.
\end{align*}&lt;/div&gt;
&lt;p&gt;
Notice that our proposed marginal tax&amp;nbsp;rate &lt;span class="math"&gt;\(1 - e^{-xk}\)&lt;/span&gt; is zero&amp;nbsp;when &lt;span class="math"&gt;\(x=0\)&lt;/span&gt; and approaches one&amp;nbsp;as &lt;span class="math"&gt;\(x\to \infty\)&lt;/span&gt;.
The interpretation is that there is no tax initially as one starts to earn income, but if one approaches infinite income then units of money earned toward infinity will be taxed more and&amp;nbsp;more.&lt;/p&gt;
&lt;p&gt;Generalizing ever so slightly, we can change the marginal tax rate&amp;nbsp;to &lt;span class="math"&gt;\(T'(x) = H - (H-L)e^{-k x}\)&lt;/span&gt;.
This way the marginal tax starts at a low&amp;nbsp;value &lt;span class="math"&gt;\(L\)&lt;/span&gt; and converges to a high&amp;nbsp;value &lt;span class="math"&gt;\(H\)&lt;/span&gt;.
Integrating, we obtain the tax&amp;nbsp;function
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
T(x) = \int_0^{x} T'(\tau) \, d \tau = \int_0^{x} H - (H-L)e^{-k \tau} \, d \tau
=
\frac{(H-L) e^{-kx} + H(kx - 1) + L}{k}.
\end{align*}&lt;/div&gt;
&lt;p&gt;
The current policy today in Norway is that the first units of money earned are not taxed.
Therefore, let us&amp;nbsp;set &lt;span class="math"&gt;\(L=0\)&lt;/span&gt; and optimize&amp;nbsp;for &lt;span class="math"&gt;\(H\)&lt;/span&gt; and &lt;span class="math"&gt;\(k\)&lt;/span&gt; by curve fitting to the Norwegian tax&amp;nbsp;rate:&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 95%; max-width:600px;"
src="https://tommyodland.com/images/articles/taxes/norwegian_income_tax_2024_exponential_interpolated.png"&gt;&lt;/p&gt;
&lt;p&gt;The smooth model based&amp;nbsp;on &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt; is shown in dashed lines.
The error is never more than 5300 &lt;span class="caps"&gt;NOK&lt;/span&gt;, so this approximation is pretty good.
It&amp;rsquo;s easy to implement if you want to play around with it in Excel or&amp;nbsp;Python:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;numpy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;np&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;T&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Norwegian income tax for 2024. &lt;/span&gt;
&lt;span class="sd"&gt;    Max error &amp;lt; 5300 NOK, mean absolute error ~1400.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.4772&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.8745e-06&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;H&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;Income tax computation is quite complex.
To implement it one needs around 30 lines of code with deductions, rules and tax brackets, all defined using around 20&amp;nbsp;constants.&lt;/p&gt;
&lt;p&gt;In this post we examined smooth tax&amp;nbsp;functions &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt;. One such function&amp;nbsp;is
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
T(x; H, k) = 
\frac{H(e^{-kx} + kx - 1)}{k},
\end{align*}&lt;/div&gt;
&lt;p&gt;
which mimics the overall behavoir of the current income tax calculation with good&amp;nbsp;accuracy.&lt;/p&gt;
&lt;p&gt;Maybe it could be an alternative to today&amp;rsquo;s system?
After all, a single formula is easy to implement compared to the current rule set with various deductions, constants and tax brackets.
Everyone has access to the exponential function on their phones or computers.
A simple function makes it harder for politicians to obfuscate the tax system by introducing new rules and conditions.
They could still modify the tax rate by modifying constants such&amp;nbsp;as &lt;span class="math"&gt;\(H\)&lt;/span&gt; and &lt;span class="math"&gt;\(k\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;One argument against a smooth function is that the average person does not understand the mathematics of it.
This might be true, but then again: the average person probably does not understand what a bracketed marginal tax system is&amp;nbsp;either.&lt;/p&gt;
&lt;h3 id="keep-it-simple"&gt;Keep it&amp;nbsp;simple!&lt;/h3&gt;
&lt;p&gt;If I was a benevolent dictator, I&amp;nbsp;would:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Simplify tax formulas and other computations performed by the government as much as&amp;nbsp;possible&lt;/li&gt;
&lt;li&gt;Favor simple, smooth functions (2-3 parameters) over discretizations and&amp;nbsp;bracketing&lt;/li&gt;
&lt;li&gt;Require government agencies that use formulas to publish them in math and code, with&amp;nbsp;tests&lt;/li&gt;
&lt;li&gt;Ensure that properties such as good incentives are baked into the&amp;nbsp;models&lt;/li&gt;
&lt;li&gt;Run an exhaustive test suite to guarantee good incentives across all combinations of gross income and all manner of government payouts, ensuring that citizens are never punished for earning more and working more (this &lt;a href="https://www.nav.no/no/nav-og-samfunn/kunnskap/analyser-fra-nav/nyheter/1-av-4-dagpengemottakere-kan-tape-pa-a-jobbe-en-dag"&gt;does happen&lt;/a&gt;, and it might be a sign of overly complex systems that have not been thought through&amp;nbsp;carefully)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="notes-and-references"&gt;Notes and&amp;nbsp;references&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Continuous tax brackets were discussed on the &lt;a href="https://money.stackexchange.com/questions/125745/why-is-income-tax-generally-not-in-a-continuous-spectrum"&gt;Personal Finance &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; Money Stack Exchange&lt;/a&gt; back in&amp;nbsp;2020.&lt;/li&gt;
&lt;li&gt;The paper &lt;a href="https://docs.iza.org/dp11493.pdf"&gt;Smooth Income Tax Schedules: Derivation and Consequences&lt;/a&gt; by Schwarz et al discusses how various countries approach taxation and propose using smooth&amp;nbsp;functions.&lt;/li&gt;
&lt;li&gt;A Quora post with the fantastic title &lt;a href="https://ajydvfgtczonwiqu.quora.com/The-US-Federal-Tax-Code-is-More-Complicated-Than-Calculus-So-Lets-Use-Calculus"&gt;The &lt;span class="caps"&gt;US&lt;/span&gt; Federal Tax Code is More Complicated Than Calculus. So Let&amp;rsquo;s Use Calculus&lt;/a&gt; also looks at smooth functions. The author proposes shifting and scaling a function of the&amp;nbsp;form &lt;span class="math"&gt;\(T'(x) = 1 - 1/(x + 1)\)&lt;/span&gt;, whereas we shifted and&amp;nbsp;scaled &lt;span class="math"&gt;\(T'(x) = 1 - e^{-x}\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Any sensible cumulative probability density function can be used to&amp;nbsp;construct &lt;span class="math"&gt;\(T'(x)\)&lt;/span&gt;, offering a flexible approach to designing smooth tax functions. I chose the exponential function because it&amp;rsquo;s relatively simple to work&amp;nbsp;with.&lt;/li&gt;
&lt;/ul&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Wed, 16 Oct 2024 00:00:00 +0200</pubDate><guid>tag:tommyodland.com,2024-10-16:/articles/2024/smooth-taxes-without-brackets</guid><category>articles</category><category>mathematics</category></item><item><title>Arithmetic coding in Python</title><link>https://tommyodland.com/articles/2024/arithmetic-coding-in-python</link><description>&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Arithmetic_coding"&gt;Arithmetic coding&lt;/a&gt; is a lossless data compression algorithm.
It encodes a sequence of symbols into a sequence of bits.
After reading the 1987 paper &amp;ldquo;&lt;a href="https://doi.org/10.1145/214762.214771"&gt;Arithmetic coding for data compression&lt;/a&gt;&amp;rdquo; I decided to implement the algorithm in clean, modern&amp;nbsp;Python.&lt;/p&gt;
&lt;p&gt;In this article I&amp;rsquo;ll demonstrate how to use my Python implementation, how well arithmetic coding works when the symbol frequency is skewed, and how to encode &amp;ldquo;Crime and Punishment.&amp;rdquo;
We also compare arithmetic coding with the more well-known &lt;a href="https://en.wikipedia.org/wiki/Huffman_coding"&gt;Huffman coding&lt;/a&gt;.
The implementation is available at: &lt;a href="https://github.com/tommyod/arithmetic-coding"&gt;github.com/tommyod/arithmetic-coding&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="a-simple-example"&gt;A simple&amp;nbsp;example&lt;/h2&gt;
&lt;p&gt;Here we create&amp;nbsp;a &lt;code&gt;message&lt;/code&gt; to encode and put an End Of Message (&lt;span class="caps"&gt;EOM&lt;/span&gt;)&amp;nbsp;symbol &lt;code&gt;"&amp;lt;EOM&amp;gt;"&lt;/code&gt; at the end of it.
We also need to set up a table of symbol frequencies.
Arithmetic coding exploits the frequent symbols and assigns fewer bits to them, though unlike Huffman encoding arithmetic encoding does not actually use a fixed bit sequence per&amp;nbsp;symbol.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;collections&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;arithmetic_coding&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ArithmeticEncoder&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;A&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;B&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;A&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;A&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;A&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;lt;EOM&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;frequencies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;encoder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ArithmeticEncoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frequencies&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;frequencies&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;bits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;bits&lt;/span&gt;
&lt;span class="go"&gt;[1, 1, 0, 0, 0, 0, 1, 1]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We can decode these bits back to symbols and we get the original message&amp;nbsp;back.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bits&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="go"&gt;[&amp;#39;A&amp;#39;, &amp;#39;B&amp;#39;, &amp;#39;A&amp;#39;, &amp;#39;A&amp;#39;, &amp;#39;A&amp;#39;, &amp;#39;&amp;lt;EOM&amp;gt;&amp;#39;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This examples encodes the message to 8 bits.
A naive approach would be to assign a unique 2-bit sequence to each symbol,&amp;nbsp;e.g., &lt;code&gt;{"A": 0b00, "B": 0b01, "&amp;lt;EOM&amp;gt;": 0b10}&lt;/code&gt;.
Since the message contains 6 symbols, we would&amp;nbsp;need &lt;code&gt;6 * 2 = 12&lt;/code&gt; bits with this naive&amp;nbsp;approach.&lt;/p&gt;
&lt;h2 id="infrequent-symbols"&gt;Infrequent&amp;nbsp;symbols&lt;/h2&gt;
&lt;p&gt;To see how the encoder exploits the frequency of symbols, consider this example.
We&amp;nbsp;initialize &lt;code&gt;ArithmeticEncoder(bits=32)&lt;/code&gt; because we need more internal bits in the buffer of the encoder to represent low-frequency&amp;nbsp;symbols.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;A&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;B&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1_000_000&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;lt;EOM&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;frequencies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;encoder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ArithmeticEncoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frequencies&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;frequencies&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;bits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;43&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Amazingly, a message with a million symbols is encoded to only 43 bits!
Having seen how efficient arithmetic coding is with skewed frequencies, let&amp;rsquo;s apply it to a real-world&amp;nbsp;example.&lt;/p&gt;
&lt;h2 id="encoding-crime-and-punishment"&gt;Encoding &amp;ldquo;Crime and&amp;nbsp;Punishment&amp;rdquo;&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s download the English translation of &amp;ldquo;&lt;a href="https://www.gutenberg.org/ebooks/2554"&gt;Crime and Punishment&lt;/a&gt;&amp;rdquo; by Fyodor&amp;nbsp;Dostoevsky.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;requests&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;string&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://www.gutenberg.org/cache/epub/2554/pg2554.txt&amp;quot;&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;symbols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;printable&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbols&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;lt;EOM&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;1164281&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;For this example, we&amp;rsquo;ll use the actual frequencies from the text, though in practice one might use a more general frequency table for the English&amp;nbsp;language.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;frequencies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frequencies&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;83&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;frequencies&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;e&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;frequencies&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;q&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="go"&gt;(104735, 766)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="naive-encoding"&gt;Naive&amp;nbsp;encoding&lt;/h3&gt;
&lt;p&gt;The Python code above shows that there are 83 distinct printable symbols in the book.&amp;nbsp;Since &lt;span class="math"&gt;\(2^7 = 128\)&lt;/span&gt;, a naive approach would be to use seven bits for each symbol, regardless of its frequency.
We would then&amp;nbsp;encode &lt;code&gt;"A"&lt;/code&gt; to &lt;code&gt;0b0000000&lt;/code&gt;, &lt;code&gt;"B"&lt;/code&gt; to &lt;code&gt;0b0000001&lt;/code&gt;, &lt;code&gt;"C"&lt;/code&gt; to &lt;code&gt;0b0000010&lt;/code&gt; and so forth.
This approach would encode the book&amp;nbsp;using &lt;code&gt;1164281 * 7 = 8149967&lt;/code&gt; bits.&lt;/p&gt;
&lt;h3 id="arithmetic-encoding"&gt;Arithmetic&amp;nbsp;encoding&lt;/h3&gt;
&lt;p&gt;Instead of using the naive approach, the arithmetic encoder exploits that symbols&amp;nbsp;like &lt;code&gt;"e"&lt;/code&gt; are much more frequent than symbols&amp;nbsp;like &lt;code&gt;"q"&lt;/code&gt;.
In around three seconds we&amp;rsquo;re able to encode all the 1.05 million symbols in the&amp;nbsp;book:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;encoder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ArithmeticEncoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frequencies&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;frequencies&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;bits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;5259420&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The result has around 5.26 million bits, and the bits/symbol ratio is&amp;nbsp;4.52.&lt;/p&gt;
&lt;h3 id="shannon-entropy-as-a-lower-bound-on-bits"&gt;Shannon entropy as a lower bound on&amp;nbsp;bits&lt;/h3&gt;
&lt;p&gt;The Shannon entropy, which is a lower bound on how many bits we need on average to encode a message of this length with these frequencies,&amp;nbsp;is
&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
-\sum_i f_i \log_2 \left( p_i \right),
\end{equation*}&lt;/div&gt;
&lt;p&gt;
where &lt;span class="math"&gt;\(f_i\)&lt;/span&gt; is the frequency (count) of&amp;nbsp;symbol &lt;span class="math"&gt;\(i\)&lt;/span&gt; and &lt;span class="math"&gt;\(p_i = f_i / \sum_j f_j\)&lt;/span&gt; is the probability of&amp;nbsp;symbol &lt;span class="math"&gt;\(i\)&lt;/span&gt;.
The bound only applies if we assume that each symbol is independent of the others&amp;mdash;if we assume higher-order statistics, higher compression ratios might be&amp;nbsp;possible.&lt;/p&gt;
&lt;p&gt;Computing this value for &amp;ldquo;Crime and Punishment&amp;rdquo;, we find that the best we can hope for&amp;nbsp;is&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;math&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frequencies&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;probs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;frequencies&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;shannon_bound&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frequencies&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;probs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; 
&lt;span class="gp"&gt;... &lt;/span&gt;                    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;frequencies&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;shannon_bound&lt;/span&gt;
&lt;span class="go"&gt;5259418.898940693&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="huffman-encoding"&gt;Huffman&amp;nbsp;encoding&lt;/h3&gt;
&lt;p&gt;We also use the &lt;a href="https://github.com/soxofaan/dahuffman"&gt;dahuffman&lt;/a&gt; Python library to produce a Huffman encoding, which maps each symbol to a unique code value of variable length.
Notice how common symbols&amp;nbsp;like &lt;code&gt;"e"&lt;/code&gt; are mapped to short code values, while rare symbols&amp;nbsp;like &lt;code&gt;"B"&lt;/code&gt; correspond to long code&amp;nbsp;values.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dahuffman&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HuffmanCodec&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;codec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;HuffmanCodec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_frequencies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frequencies&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;codec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print_code_table&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="go"&gt;Bits Code                    Value Symbol&lt;/span&gt;
&lt;span class="go"&gt;   3 000                         0 &amp;#39;e&amp;#39;&lt;/span&gt;
&lt;span class="go"&gt;   4 0010                        2 &amp;#39;h&amp;#39;&lt;/span&gt;
&lt;span class="go"&gt;   4 0011                        3 &amp;#39;i&amp;#39;&lt;/span&gt;
&lt;span class="go"&gt;   5 01000                       8 &amp;#39;u&amp;#39;&lt;/span&gt;
&lt;span class="go"&gt;   9 010010000                 144 &amp;#39;R&amp;#39;&lt;/span&gt;
&lt;span class="go"&gt;  10 0100100010                290 &amp;#39;Y&amp;#39;&lt;/span&gt;
&lt;span class="go"&gt;  10 0100100011                291 &amp;#39;B&amp;#39;&lt;/span&gt;
&lt;span class="go"&gt;  ...&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;symbol_to_bitsize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;bitsize&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bitsize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;                     &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;codec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_code_table&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbol_to_bitsize&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;symbol&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;5298110&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Huffman encodes the book to around 5.3 million&amp;nbsp;bits.&lt;/p&gt;
&lt;h3 id="arithmetic-coder-with-an-adaptive-model"&gt;Arithmetic coder with an adaptive&amp;nbsp;model&lt;/h3&gt;
&lt;p&gt;If we do not want to assume known frequencies, we can use a simple adaptive model.
The frequency count for each symbol is initialized to one.
As the encoder processes symbols it increments the counts to update the probability model.
The decoder mimics this process exactly, but in reverse.
We still need to know all distinct symbols that the encoder and decoder expects to see beforehand though (unless we iterate over the data&amp;nbsp;twice).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;symbols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;encoder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ArithmeticEncoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbols&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;bits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;5260166&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;decoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bits&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;decoded&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;
&lt;span class="go"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="a-comparison-table"&gt;A comparison&amp;nbsp;table&lt;/h3&gt;
&lt;p&gt;Comparing all the methods we&amp;rsquo;ve mentioned, we get the following&amp;nbsp;table.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left;"&gt;Compression Method              &lt;/th&gt;
&lt;th style="text-align: left;"&gt;Bits Used    &lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Naive encoding (7 bits/symbol)  &lt;/td&gt;
&lt;td style="text-align: left;"&gt;8,149,967    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Huffman encoding                &lt;/td&gt;
&lt;td style="text-align: left;"&gt;5,298,110    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Arithmetic encoding (adaptive)  &lt;/td&gt;
&lt;td style="text-align: left;"&gt;5,260,166    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Arithmetic encoding (fixed)    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;5,259,420    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Shannon entropy bound          &lt;/td&gt;
&lt;td style="text-align: left;"&gt;5,259,418.9  &lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Naively encoding each symbol using a fixed bit length is wasteful, since it does not account for the fact that some symbols are much more frequent than others.
Huffman encoding improves greatly upon this, but arithmetic encoding is even better and is able to get within two bits of the Shannon&amp;nbsp;bound.&lt;/p&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;Arithmetic coding maps a sequence of symbols into a sequence of bits.
Unlike Huffman coding, there is no one-to-one correspondence between a single symbol and a single bit sequence.
Both methods exploit the fact that some symbols are typically more common than&amp;nbsp;others.&lt;/p&gt;
&lt;p&gt;With arithmetic coding there are two types of probability models: fixed and adaptive.
A fixed model uses a static frequency count, computed by either looping the data in advance or by observing a larger corpus of text.
A dynamic model starts out with equal probabilities for all symbols, and adapts to the specific message as it iterates through the symbols.
Regardless of which model is used, the logic of the decoder must exactly match the logic of the&amp;nbsp;encoder.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Thu, 19 Sep 2024 00:00:00 +0200</pubDate><guid>tag:tommyodland.com,2024-09-19:/articles/2024/arithmetic-coding-in-python</guid><category>articles</category><category>algorithms</category></item><item><title>Turnusplaner for sykepleiere</title><link>https://tommyodland.com/articles/2024/turnusplaner-for-sykepleiere</link><description>&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width:450px; max-width:90%;"
src="https://tommyodland.com/images/articles/nurse_shift_scheduling/nurse.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Det er mange triste saker om dårlige turnusplaner i media. Her er et utvalg fra &lt;span class="caps"&gt;NRK&lt;/span&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.nrk.no/ostfold/tilsyn-sjekker-13-timersvakter-1.6805229"&gt;Tilsyn sjekker 13-timers&amp;nbsp;vakter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.nrk.no/vestfoldogtelemark/nesten-umulig-a-fa-en-fulltidsstilling-ved-sykehus-1.14109851"&gt;Nesten umulig å få en fulltidsstilling ved&amp;nbsp;sykehus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.nrk.no/tromsogfinnmark/sykepleiere-ma-jobbe-44-lordager-i-aret_-_-det-er-et-overtramp-1.13406864"&gt;Sykepleiere må jobbe 44 lørdager i året: - Det er et&amp;nbsp;overtramp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.nrk.no/mr/jordmor-og-fodsel_-mange-sjukehus-slit-med-a-fa-tak-i-nok-vikarar-1.15502465"&gt;Får ikkje tak i nok vikarar – jordmorforeninga fryktar kollaps i&amp;nbsp;sommar&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.nrk.no/innlandet/sykepleier-ma-jobbe-dobbelt-i-jula-1.15300049"&gt;Line (34) jobbet 60 timer forrige uke - må belage seg på doble skift i&amp;nbsp;jula&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Men det er også håp.
I &lt;a href="https://sykepleien.no/2019/01/vi-fordeler-de-ubekvemme-vaktene-likt"&gt;en artikkel fra sykepleien.no&lt;/a&gt; forteller Marita om hennes &amp;ldquo;matematiske turnus&amp;rdquo;.
Hun fordeler ubekvemme vakter tilnærmet likt på sykepleierne ut fra stillingsprosent.
Dette virker som en helt åpenbar og fornuftig&amp;nbsp;tilnærming.&lt;/p&gt;
&lt;p&gt;Vi kan ta Maritas idé enda lengre og faktisk bruke matematikk til å lage en turnusplan.
I denne artikkelen lager vi fem matematiske optimeringsmodeller med økende kompleksitet og antall krav til&amp;nbsp;turnusplanen.&lt;/p&gt;
&lt;h2 id="nok-sykepleiere-pa-jobb"&gt;Nok sykepleiere på&amp;nbsp;jobb&lt;/h2&gt;
&lt;p&gt;Det første kravet er å ha nok folk på jobb.
I vårt fiktive eksempel tar vi utgangspunkt i 22 sykepleiere og tre typer vakter: dagvakt, aftenvakt og nattevakt.
Ifølge &lt;a href="https://www.nsf.no/arbeidsvilkar/turnus"&gt;Norsk Sykepleierforbund&lt;/a&gt; har en sykepleier rett til å se turnusen senest 14 dager i forkant.
Vi lager derfor en 28 dagers turnus for å være på den sikre siden.
Grunnbemanningen vi ønsker å oppnå er 8 personer på dagvakt, 4 på aftenvakt og 2 på nattevakt.
I helgene er det lavere&amp;nbsp;bemanning.&lt;/p&gt;
&lt;p&gt;Algoritmen bygger opp fra en tom turnusplan og evaluerer 100 000 forslag.
Dette tar noen sekunder, og animasjonen nedenfor viser resultatet.
Vi forsøker kun å dekke behovet for antall ansatte på jobb.
Hver rad er en sykepleier og hver kolonne er en vakt.
Tre kolonner utgjør et døgn, og det er totalt 28&amp;nbsp;døgn.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://tommyodland.com/images/articles/nurse_shift_scheduling/nurse_schedule_1.gif" target="_blank"&gt;
&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width:98%;"
src="https://tommyodland.com/images/articles/nurse_shift_scheduling/nurse_schedule_1.gif"&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Vi oppnår perfekt dekning: vår bemanning matcher ønsket bemanning på hver eneste&amp;nbsp;vakt.&lt;/p&gt;
&lt;h2 id="fylle-opp-stillingsprosenter"&gt;Fylle opp&amp;nbsp;stillingsprosenter&lt;/h2&gt;
&lt;p&gt;Vaktplanen ovenfor innfrir grunnbemanningen, men ignorerer stillingsprosentene.
Av våre 22 sykepleiere skal 6 jobbe 100%, 11 jobbe 80% og 5 jobbe 60%.
Fordelingen av antall timer er vist til venstre i&amp;nbsp;figuren.&lt;/p&gt;
&lt;p&gt;Vi skal nå forsøke å innfri to målsetninger&amp;nbsp;samtidig:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Det skal være nok sykepleiere på jobb hver&amp;nbsp;dag.&lt;/li&gt;
&lt;li&gt;Over perioden på 28 dager skal hver sykepleier jobbe riktig antall&amp;nbsp;vakter.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://tommyodland.com/images/articles/nurse_shift_scheduling/nurse_schedule_2.gif" target="_blank"&gt;
&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width:98%;"
src="https://tommyodland.com/images/articles/nurse_shift_scheduling/nurse_schedule_2.gif"&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Disse to målsetningene er ofte i konflikt, og det er også tilfellet her.
Det er umulig å både (1) alltid ha riktig antall folk på jobb og (2) innfri alle stillingsprosenter helt&amp;nbsp;nøyaktig.&lt;/p&gt;
&lt;p&gt;Disse konflikterende målene kan skape gnisninger i arbeidslivet, som da &lt;a href="https://www.nrk.no/tromsogfinnmark/sykepleiere-ma-jobbe-44-lordager-i-aret_-_-det-er-et-overtramp-1.13406864"&gt;sykepleierne i Nesseby følte seg presset&lt;/a&gt; inn i en turnus med korte arbeidsdager og mange helgevakter.
Hovedtillitsvalgt uttalte at det er &amp;ldquo;flere sykepleiere som ser etter andre jobber, og det blir vanskelig å rekruttere sykepleiere hit i en sånn type&amp;nbsp;turnus&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Gode turnuser er viktig for arbeidsmiljøet til sykepleierne og for samfunnet generelt.
Matematikk kan ikke fjerne den iboende konflikten i målsetningene, men den kan finne beste trade-off.
Med andre ord: om man må ofre stillingsprosentene noe, bør man kunne garantere at man ofrer minst&amp;nbsp;mulig!&lt;/p&gt;
&lt;h2 id="friperioder-mellom-vakter"&gt;Friperioder mellom&amp;nbsp;vakter&lt;/h2&gt;
&lt;p&gt;Ser vi nøye på turnusen ovenfor oppdager vi at den ikke er særlig god.
Den har eksempelvis 48 doble vakter.
&lt;a href="https://www.nsf.no/arbeidsvilkar/turnus"&gt;Norsk Sykepleierforbund&lt;/a&gt; skriver om turnus&amp;nbsp;at:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Det bør ikke være mindre enn 11 timer fri mellom&amp;nbsp;arbeidsperiodene.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Dette har vi på ingen måte innfridd til nå.
Det er ikke noe i vår matematiske modell som forhindrer doble vakter, eller tre vakter på rad for den saks&amp;nbsp;skyld.&lt;/p&gt;
&lt;p&gt;Vi kan modellere dette matematisk ved å straffe doble vakter og vakter med bare én friperiode på 8 timer mellom.
Resultatene blir umiddelbart&amp;nbsp;bedre:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://tommyodland.com/images/articles/nurse_shift_scheduling/nurse_schedule_3.gif" target="_blank"&gt;
&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width:98%;"
src="https://tommyodland.com/images/articles/nurse_shift_scheduling/nurse_schedule_3.gif"&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Forrige turnus hadde 48 doble vakter og 54 vakter med bare én friperiode.
Denne turnusen har null doble vakter og null vakter med bare én friperiode!
Visuelt får vi mer &amp;ldquo;luft&amp;rdquo; i&amp;nbsp;turnusen.&lt;/p&gt;
&lt;h2 id="frrest-mulig-nattevakter-pa-rad"&gt;Færrest mulig nattevakter på&amp;nbsp;rad&lt;/h2&gt;
&lt;p&gt;I &lt;a href="https://sykepleien.no/2019/02/etter-testing-reduserer-sein-tidlig-vaktene-pa-hele-sykehuset"&gt;en sak om helsefremmende turnuser&lt;/a&gt; fra sykepleien.no kan vi lese at et av kriteriene for en god turnus&amp;nbsp;er:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Færrest mulig nattevakter på rad (maks to i uken og maks tre i en helg), og lengst mulig fri etter&amp;nbsp;nattevakten.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Vi forenkler dette kravet litt og legger inn en straff for to nattevakter på&amp;nbsp;rad.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://tommyodland.com/images/articles/nurse_shift_scheduling/nurse_schedule_4.gif" target="_blank"&gt;
&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width:98%;"
src="https://tommyodland.com/images/articles/nurse_shift_scheduling/nurse_schedule_4.gif"&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Ovenfor er det null doble nattevakter, mens i forrige turnus var det 6 doble&amp;nbsp;nattevakter.&lt;/p&gt;
&lt;h2 id="lik-fordeling-av-antall-aftenvakter-og-nattevakter"&gt;Lik fordeling av antall aftenvakter og&amp;nbsp;nattevakter&lt;/h2&gt;
&lt;p&gt;Til sist innfrir vi et av Maritas krav til en matematisk turnus.
&lt;a href="https://sykepleien.no/2019/01/vi-fordeler-de-ubekvemme-vaktene-likt"&gt;Hun sier at&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Vi har lagt vekt på å fordele de ubekvemme vaktene tilnærmet likt på alle, ut fra stillingsprosent. Det vil for eksempel si at alle med 100 prosent stilling har likt antall seinvakter og likt antall&amp;nbsp;nattevakter.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Det er ikke enkelt å se i figuren, men ovenfor jobber en av sykepleierne 1 natt og en annen hele 5 netter, selv om de har samme stillingsprosent.
Turnusen nedenfor forsøker å redusere forskjeller mellom sykepleierne, og resultatet er at det maksimalt er én nattevakt i forskjell mellom sykepleierne som har samme&amp;nbsp;stillingsprosent.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://tommyodland.com/images/articles/nurse_shift_scheduling/nurse_schedule_5.gif" target="_blank"&gt;
&lt;img
style="display: block; margin-left: auto; margin-right: auto;  max-width:98%;"
src="https://tommyodland.com/images/articles/nurse_shift_scheduling/nurse_schedule_5.gif"&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Teller vi antall nattevakter for de 11 sykepleierne med 80% stilling får&amp;nbsp;vi:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fordeling i forrige turnus: [1 1 2 2 3 3 3 3 4 5&amp;nbsp;5]&lt;/li&gt;
&lt;li&gt;Fordeling i denne turnusen: [2 2 2 2 2 2 3 3 3 3&amp;nbsp;3]&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Denne turnusen er mye mer balansert.
Dette gjelder for alle stillingsprosenter, og for både nattevakter og&amp;nbsp;aftenvakter.&lt;/p&gt;
&lt;h3 id="oppsummert-har-turnusen-ovenfor-seks-gode-egenskaper"&gt;Oppsummert har turnusen ovenfor seks gode&amp;nbsp;egenskaper&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Faktisk bemanning matcher perfekt med ønsket&amp;nbsp;bemanning&lt;/li&gt;
&lt;li&gt;Sykepleierne jobber svært nær sin&amp;nbsp;stillingsprosent&lt;/li&gt;
&lt;li&gt;Det er ingen doble&amp;nbsp;vakter&lt;/li&gt;
&lt;li&gt;Alle vakter har minst to friperioder mellom&amp;nbsp;seg&lt;/li&gt;
&lt;li&gt;Det er ingen doble&amp;nbsp;nattevakter&lt;/li&gt;
&lt;li&gt;Nattevaktene og kveldsvaktene er jevnt&amp;nbsp;fordelt&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Det tar et par sekunder å finne en slik turnus på min laptop.
Resultatene blir litt bedre om vi lar algoritmen kjøre lengre.
En større turnus (flere sykepleiere, eller lengre enn 28 dager) vil kreve mer&amp;nbsp;regnekraft.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width:400px; max-width:90%;"
src="https://tommyodland.com/images/articles/nurse_shift_scheduling/national-cancer-institute.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Vi kunne ha fortsatt å legge til flere egenskaper.
For eksempel kan vi fordele helgevaktene likt mellom sykepleierne.
Det er også tenkelig at ulike sykepleiere har ulike ønsker.
Noen misliker kanskje doble nattevakter, men andre synes det er greit.
Enkelte foretrekker å jobbe kveldsvakter, andre dagvakter.
Slike personlige preferanser kunne ha vært lagt til i&amp;nbsp;modellen.&lt;/p&gt;
&lt;h2 id="oppsummering-og-referanser"&gt;Oppsummering og&amp;nbsp;referanser&lt;/h2&gt;
&lt;p&gt;Vaktplaner kan være en kilde til konflikt og frustrasjon, men problemstillingen er utrolig tilgivelig rent matematisk.
Datastrukturene er enkle og grunnleggende algoritmer (her &lt;em&gt;simulert størkning&lt;/em&gt;) gir gode resultater på få&amp;nbsp;sekunder.&lt;/p&gt;
&lt;p&gt;Målet var å undersøke noenlunde realistiske problemer med fiktive data.
Det ville vært umulig å løse alle bemanningsproblemer med én modell.
Praktisk turnusplanlegging varierer mellom ulike avdelinger innad i samme sykehus, mellom ulike sykehus, og mellom f.eks. et eldrehjem, en legevakt og et sykehus.
Hver for seg kan disse problemstillingene løses med matematikk, men slike modeller må ofte&amp;nbsp;spesialtilpasses.&lt;/p&gt;
&lt;p&gt;Det har blitt skrevet forskningsartikler om matematisk optimering av sykepleieres vaktplaner siden 1960-tallet.
Artikkelen &lt;a href="https://link.springer.com/article/10.1023/B:JOSH.0000046076.75950.0b"&gt;The State of the Art of Nurse Rostering&lt;/a&gt; gir en oversikt.
Det trengs neppe mer forskning, men heller at man i større grad tar kjente metoder i&amp;nbsp;bruk.&lt;/p&gt;
&lt;p&gt;En av de store fordelene med modeller er at de danner grunnlag for diskusjon.
Hovedformålet er innsikt, ikke nødvendigvis et helt riktig svar.
I praksis vil ofte en ekspert se over resultatene, gjøre endringer, og kjøre modellen flere&amp;nbsp;ganger.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Her er en video som viser hvordan simulert størkning finner en løsning på et større&amp;nbsp;problem:&lt;/strong&gt;&lt;/p&gt;
&lt;iframe 
width="804" height="305"
style="aspect-ratio: 16 / 9; height: auto; width: 100%;"
src="https://www.youtube.com/embed/k95aBjq4_a8?si=6xDWxxxx2VRA1a7t" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen&gt;&lt;/iframe&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Thu, 08 Aug 2024 00:00:00 +0200</pubDate><guid>tag:tommyodland.com,2024-08-08:/articles/2024/turnusplaner-for-sykepleiere</guid><category>articles</category><category>optimization</category></item><item><title>The reverse pseudo-Huber loss function</title><link>https://tommyodland.com/articles/2024/the-reverse-pseudo-huber-loss-function</link><description>&lt;p&gt;In this article we introduce the reverse pseudo-Huber&amp;nbsp;function
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
R(x) &amp;amp;= \frac{1}{2} \left( \lvert x \rvert \sqrt{x^2 + 1} + \ln \left( \lvert x \rvert + \sqrt{x^2 + 1} \right)  \right),
\end{align*}&lt;/div&gt;
&lt;p&gt;
which acts like the absolute value&amp;nbsp;when &lt;span class="math"&gt;\(|x|\)&lt;/span&gt; is around zero and like a square&amp;nbsp;as &lt;span class="math"&gt;\(|x|\)&lt;/span&gt; becomes&amp;nbsp;large.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="introduction-to-pseudo-huber-loss"&gt;Introduction to pseudo-Huber&amp;nbsp;loss&lt;/h3&gt;
&lt;p&gt;The pseudo-Huber&amp;nbsp;function &lt;span class="math"&gt;\(H(x) = \sqrt{x^2 + 1} - 1\)&lt;/span&gt; is a smooth approximation to the &lt;a href="https://en.wikipedia.org/wiki/Huber_loss"&gt;Huber loss&lt;/a&gt;.
It acts&amp;nbsp;like &lt;span class="math"&gt;\(x^2/2\)&lt;/span&gt; near zero and&amp;nbsp;like &lt;span class="math"&gt;\(\lvert x \rvert\)&lt;/span&gt; near infinity.
A typical use-case is &lt;em&gt;robust regression&lt;/em&gt;, where we want to employ a squared loss but simultaneously limit the influence of outliers.
Below we plot the pseudo-Huber function along with the two functions it&amp;nbsp;approximates:&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 450px; width: 95%;"
src="https://tommyodland.com/images/articles/smooth_reverse_huber/pseudo_huber_loss.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;The equation for the pseudo-Huber and its derivative&amp;nbsp;is
&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
H(x) = \sqrt{x^2 + 1} - 1
\qquad
H'(x) = \frac{x}{\sqrt{x^2 + 1}}.
\end{equation*}&lt;/div&gt;
&lt;h3 id="the-reverse-pseudo-huber"&gt;The reverse&amp;nbsp;pseudo-Huber&lt;/h3&gt;
&lt;p&gt;What about the reverse case? 
What if we want a function that acts&amp;nbsp;like &lt;span class="math"&gt;\(\lvert x \rvert\)&lt;/span&gt; near &lt;span class="math"&gt;\(x=0\)&lt;/span&gt; and&amp;nbsp;like &lt;span class="math"&gt;\(x^2/2\)&lt;/span&gt;  near &lt;span class="math"&gt;\(x = \pm \infty\)&lt;/span&gt;?
I found no such function in the literature when searching, though it would not surprise me if someone has deduced it&amp;nbsp;already.&lt;/p&gt;
&lt;p&gt;I propose the following equation for the reverse pseudo-Huber and its&amp;nbsp;derivative:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
R(x) &amp;amp;= \frac{1}{2} \left( \lvert x \rvert \sqrt{x^2 + 1} + \ln \left( \lvert x \rvert + \sqrt{x^2 + 1} \right)  \right) \\
R'(x) &amp;amp;=  \operatorname{sign}(x)  \sqrt{x^2 + 1}
\end{align*}&lt;/div&gt;
&lt;p&gt;Here&amp;rsquo;s a plot of the reverse&amp;nbsp;pseudo-Huber:&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 450px; width: 95%;"
src="https://tommyodland.com/images/articles/smooth_reverse_huber/reverse_pseudo_huber_loss.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;h3 id="deriving-the-reverse-pseudo-huber"&gt;Deriving the reverse&amp;nbsp;pseudo-Huber&lt;/h3&gt;
&lt;p&gt;To derive the reverse pseudo-Huber, I plotted the&amp;nbsp;derivative &lt;span class="math"&gt;\(H'(x)\)&lt;/span&gt; for &lt;span class="math"&gt;\(x&amp;gt;0\)&lt;/span&gt;.
The&amp;nbsp;function &lt;span class="math"&gt;\(H'(x)\)&lt;/span&gt; acts&amp;nbsp;like &lt;span class="math"&gt;\(\lvert x \rvert\)&lt;/span&gt; near &lt;span class="math"&gt;\(x=0\)&lt;/span&gt; and&amp;nbsp;like &lt;span class="math"&gt;\(1\)&lt;/span&gt; near &lt;span class="math"&gt;\(x = \infty\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Our goal is to find a function with the opposite behavoir&amp;mdash;it should act&amp;nbsp;like &lt;span class="math"&gt;\(1\)&lt;/span&gt; near &lt;span class="math"&gt;\(x=0\)&lt;/span&gt; and&amp;nbsp;like &lt;span class="math"&gt;\(\lvert x \rvert\)&lt;/span&gt; near &lt;span class="math"&gt;\(x = \infty\)&lt;/span&gt;.
But this is exactly the behavoir&amp;nbsp;of &lt;span class="math"&gt;\(H(x)\)&lt;/span&gt; if we simply add one to&amp;nbsp;it!&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 650px; width: 95%;"
src="https://tommyodland.com/images/articles/smooth_reverse_huber/reverse_pseudo_huber_loss_derivation.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Therefore we guess&amp;nbsp;that &lt;span class="math"&gt;\(R(x) = \int_0^x \sqrt{\tau^2 + 1} \, d\tau\)&lt;/span&gt; would be a proposal for a function&amp;nbsp;when &lt;span class="math"&gt;\(x&amp;gt;0\)&lt;/span&gt;.
We mirror it across the vertical axis to obtain symmetry.
It turns out that this guess is very good&amp;nbsp;indeed!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It has exactly the desired behavoir&amp;nbsp;at &lt;span class="math"&gt;\(x=0\)&lt;/span&gt; and &lt;span class="math"&gt;\(x = \pm \infty\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;The&amp;nbsp;functions &lt;span class="math"&gt;\(H(x)\)&lt;/span&gt; and &lt;span class="math"&gt;\(R(x)\)&lt;/span&gt; act as tight lower and upper bounds on both the&amp;nbsp;functions &lt;span class="math"&gt;\(\lvert x \rvert\)&lt;/span&gt; and &lt;span class="math"&gt;\(x^2/2\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;The&amp;nbsp;proposed &lt;span class="math"&gt;\(R(x)\)&lt;/span&gt; has a similar functional form&amp;nbsp;as &lt;span class="math"&gt;\(H(x)\)&lt;/span&gt;, since it&amp;rsquo;s more or less the&amp;nbsp;integral.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; max-width: 750px; width: 95%;"
src="https://tommyodland.com/images/articles/smooth_reverse_huber/reverse_pseudo_huber_loss_zoomed_out.png"
class="img-responsive"&gt;&lt;/p&gt;
&lt;p&gt;Just like we can scale the pseudo-Huber&amp;nbsp;as &lt;span class="math"&gt;\(c H(x / c)\)&lt;/span&gt; for &lt;span class="math"&gt;\(c &amp;gt; 0\)&lt;/span&gt;, we can scale the reverse pseudo-Huber&amp;nbsp;as &lt;span class="math"&gt;\(c^2 R(x / c)\)&lt;/span&gt;.
The reason we scale with the square is&amp;nbsp;that &lt;span class="math"&gt;\(H(x)\)&lt;/span&gt; acts&amp;nbsp;like &lt;span class="math"&gt;\(\lvert x \rvert\)&lt;/span&gt;,&amp;nbsp;while &lt;span class="math"&gt;\(R(x)\)&lt;/span&gt; acts&amp;nbsp;like &lt;span class="math"&gt;\(x^2 / 2\)&lt;/span&gt; as &lt;span class="math"&gt;\(x \to \pm \infty\)&lt;/span&gt;.
To undo the scaling&amp;nbsp;as &lt;span class="math"&gt;\(x \to \pm \infty\)&lt;/span&gt; we need to&amp;nbsp;multiply &lt;span class="math"&gt;\(H(x)\)&lt;/span&gt; and &lt;span class="math"&gt;\(R(x)\)&lt;/span&gt; by &lt;span class="math"&gt;\(c\)&lt;/span&gt; and &lt;span class="math"&gt;\(c^2\)&lt;/span&gt; respectively.&lt;/p&gt;
&lt;h3 id="other-approximations-to-huber-loss"&gt;Other approximations to Huber&amp;nbsp;loss&lt;/h3&gt;
&lt;p&gt;Two alternative loss functions that act&amp;nbsp;like &lt;span class="math"&gt;\(x^2/2\)&lt;/span&gt; near &lt;span class="math"&gt;\(x=0\)&lt;/span&gt; and&amp;nbsp;like &lt;span class="math"&gt;\(\lvert x \rvert\)&lt;/span&gt; near &lt;span class="math"&gt;\(x = \pm \infty\)&lt;/span&gt; are
&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
\ln \cosh x \quad \text{and} \quad x \tanh \left( x/2 \right).
\end{equation*}&lt;/div&gt;
&lt;p&gt;
I was unable to construct reverse functions for these two.
Applying the same trick as above does not work if we want to avoid special functions, since the integral depends on the &lt;a href="https://en.wikipedia.org/wiki/Dilogarithm"&gt;Dilogarithm&lt;/a&gt;&amp;nbsp;function.&lt;/p&gt;
&lt;p&gt;Thanks to my friend Floris for discussions on the contents of this&amp;nbsp;article.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Fri, 05 Jul 2024 00:00:00 +0200</pubDate><guid>tag:tommyodland.com,2024-07-05:/articles/2024/the-reverse-pseudo-huber-loss-function</guid><category>articles</category><category>mathematics</category></item><item><title>Lønna til norske utviklere i 2024</title><link>https://tommyodland.com/articles/2024/lonna-til-norske-utviklere-i-2024</link><description>&lt;p&gt;&lt;strong&gt;Denne artikkelen ble også publisert som et &lt;a href="https://www.kode24.no/artikkel/hva-er-viktigst-for-norske-utvikleres-lonn-tommy-har-regna/81507930"&gt;leserinnlegg på kode24.no&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Kode24 har publisert data fra sin spørreundersøkelse om lønn.
Her kommer en analyse av tallene.
Resultatet er ganske likt som i &lt;a href="https://tommyodland.com/articles/2023/lonna-til-norske-utviklere-i-2023"&gt;min analyse av 2023-tallene&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Det er ti variabler i datasettet.
Variablene er ikke bare korrelerte med lønna, de er også korrelerte med hverandre.
Eksempelvis har arkitekter høy lønn, men de har også lang erfaring.
Hvor mye av lønna bør tilskrives fagfeltet heller enn erfaringen?
For å svare på dette skal vi dekomponere variablene ved hjelp av en Generalisert Additiv Modell (&lt;span class="caps"&gt;GAM&lt;/span&gt;).&lt;/p&gt;
&lt;p&gt;Vi begynner med konklusjonen av analysen, som er oppsummert i figuren nedenfor.
Seks variabler er kategoriske og fire er numeriske.
Effekten av hver variabel er vist som en differanse fra modellens konstantledd, som er en lønn på 970 tusen kroner (&lt;span class="caps"&gt;TNOK&lt;/span&gt;).
Usikkerheten i effektene er også vist i&amp;nbsp;figuren.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://tommyodland.com/images/articles/kode24_lonn_2024/lønn_kode24_full_GAM.png" target="_blank"&gt;
&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 780px;"
src="https://tommyodland.com/images/articles/kode24_lonn_2024/lønn_kode24_full_GAM.png"
class="img-responsive"&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Antall personer i hver kategori er vist i parentes.
Frilans har en koeffisient på 250 &lt;span class="caps"&gt;TNOK&lt;/span&gt;, men ble kuttet i figuren for å vise helheten&amp;nbsp;bedre.&lt;/p&gt;
&lt;p&gt;Vi skal være forsiktige med å tolke et slikt datasett og tilhørende analyser med stor sikkerhet.
Det er mange feilkilder: dataene er selvrapporterte, kode24-lesere er ikke nødvendigvis representative for &lt;span class="caps"&gt;IT&lt;/span&gt;-folk generelt, den statistiske modellen gjør visse antagelser og det er usikkerhet i effektene som modellen plukker&amp;nbsp;opp.&lt;/p&gt;
&lt;p&gt;La oss ta noen skritt tilbake og jobbe oss frem til resultatet ovenfor steg for steg, samtidig som vi gjør syv&amp;nbsp;observasjoner.&lt;/p&gt;
&lt;h3 id="1-medianlnna-er-850-tnok"&gt;(1) Medianlønna er 850 &lt;span class="caps"&gt;TNOK&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;Vi begynner med å plotte fordelingen av lønna.
Tvilsomme datapunkter som sannsynligvis var feilrapporterte har blitt fjernet.
Figuren nedenfor viser at medianlønna er 850 &lt;span class="caps"&gt;TNOK&lt;/span&gt; og at halvparten har lønn mellom 700 og 1010 &lt;span class="caps"&gt;TNOK&lt;/span&gt;.
På bunnen av figuren er 200 tilfeldige personer&amp;nbsp;vist.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://tommyodland.com/images/articles/kode24_lonn_2024/kode24_lønn_fordeling_persentiler.png" target="_blank"&gt;
&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 99%; max-width: 500px;"
src="https://tommyodland.com/images/articles/kode24_lonn_2024/kode24_lønn_fordeling_persentiler.png"
class="img-responsive"&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="2-en-additiv-struktur-forklarer-datasettet-godt"&gt;(2) En additiv struktur forklarer datasettet&amp;nbsp;godt&lt;/h3&gt;
&lt;p&gt;Vi skal nå bruke et utvalg fra datasettet til å forklare hvordan den additive modellen fungerer.
Figuren til venstre nedenfor viser utviklere fordelt i 15 grupper basert på fag og sted.
Innad i hver gruppe er medianlønna vist.
Totalt sett er medianlønna 850 &lt;span class="caps"&gt;TNOK&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Arkitekter i Oslo har en medianlønn på 1150 &lt;span class="caps"&gt;TNOK&lt;/span&gt;.
Både stedet (Oslo) og faget (arkitekt) tilsier at gruppen skal tjene godt.
Hvor mye av den høye lønna bør da tilskrives hver&amp;nbsp;variabel?&lt;/p&gt;
&lt;p&gt;&lt;a href="https://tommyodland.com/images/articles/kode24_lonn_2024/kode24_lønn_kategorisk_additiv.png" target="_blank"&gt;
&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 99%; max-width: 550px;"
src="https://tommyodland.com/images/articles/kode24_lonn_2024/kode24_lønn_kategorisk_additiv.png"
class="img-responsive"&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;For å svare på dette bruker vi en additiv modell.
Vi assosierer en numerisk koeffisient med hvert sted og fagfelt, som deretter legges på medianen 850 &lt;span class="caps"&gt;TNOK&lt;/span&gt; for å estimere lønna.
Stedet Oslo får koeffisienten 90 &lt;span class="caps"&gt;TNOK&lt;/span&gt; og fagfeltet arkitektur får koeffisienten 220 &lt;span class="caps"&gt;TNOK&lt;/span&gt;.
Modellen estimerer derfor at arkitekter i Oslo tjener 850 + 90 + 220 = 1160 &lt;span class="caps"&gt;TNOK&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Arkitekter i Oslo tjener i størst grad godt fordi de er arkitekter, ikke fordi de bor i Oslo.
Modellen lar oss estimere hva en typisk arkitekt i Troms og Finnmark ville tjent, selv om ingen slike personer finnes i datasettet.
Den additive modellstrukturen er enkel, forståelig og beskriver datasettet&amp;nbsp;godt.&lt;/p&gt;
&lt;h3 id="3-vi-br-vre-forsiktige-med-arsakssammenhenger"&gt;(3) Vi bør være forsiktige med&amp;nbsp;årsakssammenhenger&lt;/h3&gt;
&lt;p&gt;Ovenfor sa vi at lønna &amp;ldquo;skyldes&amp;rdquo; en variabel og at noen tjente godt &amp;ldquo;fordi&amp;rdquo; de er arkitekter.
Denne språkbruken er i kontekst av en dekomposisjon av variabler i et datasett, og er ikke ment som en kausal tolkning av&amp;nbsp;virkeligheten.&lt;/p&gt;
&lt;p&gt;Vi vet at korrelasjon ikke er det samme som kausalitet, men betyr det at vi ikke kan si noe om årsakssammenhenger?
Statistikere har lenge drøftet nyansene i dette spørsmålet, og det finnes ingen enkel&amp;nbsp;fasit.&lt;/p&gt;
&lt;p&gt;Virkeligheten er kompleks og effektene som en modell finner kan skyldes både korrelasjon og kausalitet.
Det er ikke nødvendigvis galt å spekulere i årsakssammenhenger, men husk at en statistisk modell alene ikke sier noe om årsak&amp;mdash;bruk sunn fornuft og vær forsiktig med bastante&amp;nbsp;konklusjoner.&lt;/p&gt;
&lt;h3 id="4-variabler-er-korrelerte"&gt;(4) Variabler er&amp;nbsp;korrelerte&lt;/h3&gt;
&lt;p&gt;La oss undersøke hvordan en additiv modell kan dekomponere en numerisk variabel (erfaring) og en kategorisk variabel (stilling).
Her er et eksempel der variablene er sterkt&amp;nbsp;korrelerte:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;En typisk person med &amp;ldquo;junior&amp;rdquo; i stillingstittelen har ett års erfaring og tjener 630 &lt;span class="caps"&gt;TNOK&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;En typisk person med &amp;ldquo;senior&amp;rdquo; i stillingstittelen har åtte års erfaring og tjener 945 &lt;span class="caps"&gt;TNOK&lt;/span&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I hvor stor grad skyldes lønnsforskjellen stillingstittelen kontra erfaringen?
Modellen antar en numerisk koeffisient per stillingstittel og en glatt funksjon for&amp;nbsp;erfaring.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://tommyodland.com/images/articles/kode24_lonn_2024/kode24_lønn_stilling_erfaring.png" target="_blank"&gt;
&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 99%; max-width: 550px;"
src="https://tommyodland.com/images/articles/kode24_lonn_2024/kode24_lønn_stilling_erfaring.png"
class="img-responsive"&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Modellantagelsen er at lønnsøkningen som funksjon av erfaring er den samme, og at stillingstittelen gir et påslag i form av en koeffisient.
Gitt lik erfaring anslår modellen at en typisk senior tjener 80 - (-80) = 160 &lt;span class="caps"&gt;TNOK&lt;/span&gt; mer enn en junior.
Om vi ikke korrigerer for erfaring er differansen mellom en senior og en junior lik 945 - 630 = 315 &lt;span class="caps"&gt;TNOK&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Tidligere så vi hvordan modellen dekomponerte to kategoriske variabler (sted og fag).
Her ser vi hvordan modellen dekomponerer en kategorisk variabel (stilling) fra en numerisk variabel (erfaring).
Den endelige modellen dekomponerer alle de ti variablene&amp;nbsp;samtidig.&lt;/p&gt;
&lt;h3 id="5-estimater-er-usikre"&gt;(5) Estimater er&amp;nbsp;usikre&lt;/h3&gt;
&lt;p&gt;Modellen estimerer effekten av alle variablene samtidig.
Usikkerheten i estimatene påvirkes av spredningen i lønna, antall svar i spørreundersøkelsen og hvor mange koeffisienter som estimeres&amp;nbsp;samtidig.&lt;/p&gt;
&lt;p&gt;Figuren nedenfor viser usikkerheten i de glatte funksjonene som estimerer effekten av numeriske variabler på lønna.
Når erfaringen øker blir usikkerheten større, fordi det er få personer med lang erfaring i datasettet.
Denne typen usikkerhet forekommer i alle estimater, også i effekten av kategoriske variabler som sted og&amp;nbsp;fagfelt.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://tommyodland.com/images/articles/kode24_lonn_2024/spline_all.png" target="_blank"&gt;
&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 99%; max-width: 750px;"
src="https://tommyodland.com/images/articles/kode24_lonn_2024/spline_all.png"
class="img-responsive"&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;At det er lite usikkerhet forbundet med effekten av erfaring de første ti årene betyr at vi er sikre på den generelle trenden.
Det betyr ikke nødvendigvis at vi med sikkerhet kan predikere lønna til et&amp;nbsp;enkeltindivid.&lt;/p&gt;
&lt;h3 id="6-noen-fa-variabler-gir-gode-prediksjoner"&gt;(6) Noen få variabler gir gode&amp;nbsp;prediksjoner&lt;/h3&gt;
&lt;p&gt;La oss bruke en modell til å predikere lønna til enkeltindivider.
Vi bruker Mean Absolute Error (&lt;span class="caps"&gt;MAE&lt;/span&gt;) som mål på prediksjonsfeil og undersøker tre ulike&amp;nbsp;modeller:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;En enkel modell som bare har et konstantledd og alltid gjetter&amp;nbsp;medianen&lt;/li&gt;
&lt;li&gt;Vår forklarbare additive modell (&lt;span class="caps"&gt;GAM&lt;/span&gt;), med ulike antall&amp;nbsp;variabler&lt;/li&gt;
&lt;li&gt;En Gradient Boosting (&lt;span class="caps"&gt;GB&lt;/span&gt;)&amp;nbsp;maskinlæringsmodell&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;En modell som bare bruker et konstantledd (medianen) har en typisk feil på 213.7 &lt;span class="caps"&gt;TNOK&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Om vi får bruke én variabel i en &lt;span class="caps"&gt;GAM&lt;/span&gt; bør vi velge erfaring, som reduserer feilen til 148.2 &lt;span class="caps"&gt;TNOK&lt;/span&gt;.
Får vi legge til enda en variabel bør vi velge jobb, som tar feilen ned til 140.5 &lt;span class="caps"&gt;TNOK&lt;/span&gt;.
Slik fortsetter vi til alle variablene er med og feilen er 129.1 &lt;span class="caps"&gt;TNOK&lt;/span&gt;.
Å ta med kjønn forverrer modellen litt ettersom den overtilpasser og generaliserer&amp;nbsp;dårligere.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://tommyodland.com/images/articles/kode24_lonn_2024/kode24_lønn_variabler_MAE.png" target="_blank"&gt;
&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 99%; max-width: 650px;"
src="https://tommyodland.com/images/articles/kode24_lonn_2024/kode24_lønn_variabler_MAE.png"
class="img-responsive"&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;GB&lt;/span&gt; har en typisk feil på 119.3 &lt;span class="caps"&gt;TNOK&lt;/span&gt;, som er litt bedre enn &lt;span class="caps"&gt;GAM&lt;/span&gt;, ettersom &lt;span class="caps"&gt;GB&lt;/span&gt; ikke er begrenset til en additiv struktur.
Formålet med analysen bør diktere modellvalget, og her er hovedformålet innsikt og forståelse.
Vi bruker derfor &lt;span class="caps"&gt;GAM&lt;/span&gt; heller enn &lt;span class="caps"&gt;GB&lt;/span&gt;, selv om det også er mulig å trekke mye innsikt ut av black-box modeller som &lt;span class="caps"&gt;GB&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Noen få variabler er alt vi trenger for å predikere rimelig godt.
Modellene halverer prediksjonsfeilen sammenlignet med å bruke medianen.
Den resterende halvparten av lønnsforskjellene ikke er forklart av variablene i&amp;nbsp;datasettet.&lt;/p&gt;
&lt;h3 id="7-erfaring-er-viktigst"&gt;(7) Erfaring er&amp;nbsp;viktigst&lt;/h3&gt;
&lt;p&gt;Det finnes mange mål på hvor viktige variabler er i en modell.
En metode er å stille følgende spørsmål: gitt at vi kan bygge en modell med kun én variabel, hvilken bør vi velge?
Dette gjorde vi i forrige seksjon, og svaret var&amp;nbsp;erfaring.&lt;/p&gt;
&lt;p&gt;En annen metode er &lt;em&gt;permutation importance&lt;/em&gt;.
Litt forenklet er idéen å først trene en modell på alle variablene.
Deretter bruker vi den til å predikere, men vi lar folk lyve om én variabel.
Hvor mye prediksjonskraft taper vi dersom folk er&amp;nbsp;uærlige?&lt;/p&gt;
&lt;p&gt;Resultatene er vist i figuren nedenfor.
Om du lyver om erfaring vil det gå kraftig ut over modellens evne til å predikere lønna di.
Om du lyver om de andre variablene har derimot mindre å si, og disse variablene kan derfor anses som mindre&amp;nbsp;viktige.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://tommyodland.com/images/articles/kode24_lonn_2024/kode24_lønn_variabler_permutation_importance.png" target="_blank"&gt;
&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 99%; max-width: 600px;"
src="https://tommyodland.com/images/articles/kode24_lonn_2024/kode24_lønn_variabler_permutation_importance.png"
class="img-responsive"&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Det enkleste argumentet for at erfaring er viktig er å se på figuren som oppsummerer modellen.
Forskjellen mellom kort og lang erfaring er omtrent en halv million kroner i årslønn, som er langt mer enn effekten av noen annen&amp;nbsp;variabel.&lt;/p&gt;
&lt;h3 id="oppsummering-og-referanser"&gt;Oppsummering og&amp;nbsp;referanser&lt;/h3&gt;
&lt;p&gt;Datasett som dette bør tolkes forsiktig, men vi kan likevel lære mye av å analysere dataene.
Nåtidens fokus på maskinlæring og &lt;span class="caps"&gt;AI&lt;/span&gt; kan gi inntrykk av at formålet med alle modeller er prediksjon.
Her var derimot hovedfokus innsikt, og vi forklarte halvparten av lønnsforskjellene ved hjelp av ti variabler.
Jeg brukte min egen Python-pakke &lt;a href="https://github.com/tommyod/generalized-additive-models"&gt;generalized-additive-models&lt;/a&gt; til&amp;nbsp;analysen.&lt;/p&gt;
&lt;p&gt;Jeg håper du fikk forståelse for hvilke faktorer som er med på å påvirke lønn, samtidig som du forhåpentligvis lærte litt om statistisk modellering.
Denne gangen var problemstillingen lønn, men metodene og modellene er generelle.
I dag har vi mer data, kraftigere maskiner og flere mennesker som analyserer data enn noen gang&amp;mdash;statistikk er derfor viktigere enn&amp;nbsp;noensinne.&lt;/p&gt;
&lt;p&gt;Om man vil lese populærvitenskapelige introduksjoner til statistikk kan jeg&amp;nbsp;anbefale:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com/Flaws-Fallacies-Statistical-Thinking-Mathematics-ebook/dp/B00A62Y1X4/"&gt;Flaws and Fallacies in Statistical Thinking&lt;/a&gt;, av&amp;nbsp;Campbell&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com/Book-Why-Science-Cause-Effect-ebook/dp/B075CR9QBJ/"&gt;The Book of Why&lt;/a&gt;, av&amp;nbsp;Pearl&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com/Lady-Tasting-Tea-Statistics-Revolutionized/dp/0805071342"&gt;The Lady Tasting Tea&lt;/a&gt;, av&amp;nbsp;Salsburg&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ønsker man faglitteratur for modelling ville jeg startet&amp;nbsp;med:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com/Regression-Stories-Analytical-Methods-Research/dp/1107676517/"&gt;Regression and Other Stories&lt;/a&gt;, av&amp;nbsp;Gelman&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com/Statistical-Rethinking-Bayesian-Examples-Chapman/dp/036713991X/"&gt;Statistical Rethinking&lt;/a&gt;, av&amp;nbsp;McElreath&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="lnnskalkulator"&gt;Lønnskalkulator&lt;/h2&gt;
&lt;p&gt;Jeg hjalp også kode24 med å lage en &lt;a href="https://kodejobb.no/lonn"&gt;lønnskalkulator&lt;/a&gt;.
Modellen ligner på den ovenfor, men er ikke helt lik.
Man trenger en enkel formel som kan implementeres i frontend, så jeg satte opp en lineær regresjonsmodell på et utvalg&amp;nbsp;variabler:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align}
\text{lønn} &amp;amp;= w_0 + w_1 \log\left( \alpha + \text{erfaring} \right) + w_2 \text{utdanning} \\
&amp;amp;+ w_\text{jobb} 
+ w_\text{region}
+ w_\text{fag}
+ w_\text{tittel}
\end{align}&lt;/div&gt;
&lt;p&gt;
Modellen ble trent for å&amp;nbsp;minimere &lt;span class="math"&gt;\(\ell_1\)&lt;/span&gt;-norm (absoluttverdi-feil) heller&amp;nbsp;enn &lt;span class="math"&gt;\(\ell_2\)&lt;/span&gt;-norm (kvadratsum-feil), som betyr at det er en &lt;em&gt;medianregresjon&lt;/em&gt;.
Kryssvalidering ble brukt for å regularisere&amp;nbsp;koeffisientene &lt;span class="math"&gt;\(\boldsymbol{w}\)&lt;/span&gt; med &lt;span class="math"&gt;\(\ell_1\)&lt;/span&gt;-norm og til å velge&amp;nbsp;hyperparameter &lt;span class="math"&gt;\(\alpha\)&lt;/span&gt;.
For å få&amp;nbsp;et &lt;span class="math"&gt;\(90\%\)&lt;/span&gt; intervall rundt prediksjonen trente jeg to kvantilregresjoner på residualene: én&amp;nbsp;på &lt;span class="math"&gt;\(5\%\)&lt;/span&gt; og én&amp;nbsp;på &lt;span class="math"&gt;\(95\%\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://tommyodland.com/images/articles/kode24_lonn_2024/kode24_lønn_linear_residuals.png" target="_blank"&gt;
&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 99%; max-width: 600px;"
src="https://tommyodland.com/images/articles/kode24_lonn_2024/kode24_lønn_linear_residuals.png"
class="img-responsive"&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Tilnærmingen er noe enkel, men fungerer godt.
Resultatet er en formel som gir typisk lønn og&amp;nbsp;et &lt;span class="math"&gt;\(90\%\)&lt;/span&gt;-intervall som beskriver spredningen.
Denne lineære modellen har en &lt;span class="caps"&gt;MAE&lt;/span&gt; på 130.7 &lt;span class="caps"&gt;TNOK&lt;/span&gt;, som kan sammenlignes med Gradient Boosting (119.3 &lt;span class="caps"&gt;TNOK&lt;/span&gt;) og et modell med bare konstantledd (213.7 &lt;span class="caps"&gt;TNOK&lt;/span&gt;).
Et utvalg variabler i en lineær modell oppnår&amp;nbsp;altså &lt;span class="math"&gt;\((213.7 - 130.7) / (213.7 - 119.3) = 88\%\)&lt;/span&gt; av hva som er mulig med en kompleks maskinlæringsmodell.
Det er ikke ille med tanke på hvor strukturelt enkel modellen&amp;nbsp;er.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Sun, 09 Jun 2024 00:00:00 +0200</pubDate><guid>tag:tommyodland.com,2024-06-09:/articles/2024/lonna-til-norske-utviklere-i-2024</guid><category>articles</category><category>datascience</category></item><item><title>Tags for optimal information retrieval - part 1: motivation</title><link>https://tommyodland.com/articles/2024/tags-for-optimal-information-retrieval-part-1-motivation</link><description>&lt;p&gt;This is part one of a two-article series.
I recommend reading them in&amp;nbsp;order:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://tommyodland.com/articles/2024/tags-for-optimal-information-retrieval-part-1-motivation"&gt;Tags for optimal information retrieval - part 1:&amp;nbsp;motivation&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="https://tommyodland.com/articles/2024/tags-for-optimal-information-retrieval-part-2-theory"&gt;Tags for optimal information retrieval - part 2:&amp;nbsp;theory&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;In information management systems &lt;em&gt;tags&lt;/em&gt; are one of several ways to arrange and filter &lt;em&gt;items&lt;/em&gt;.
The goal of using tags is to help users navigate and retrieve information efficiently.
Here are two examples of real-world datasets with&amp;nbsp;tags:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Persons and skills.&lt;/strong&gt; The items are persons and the tags are their skills.&amp;nbsp;Tag &lt;span class="math"&gt;\(i\)&lt;/span&gt; is applied to&amp;nbsp;person &lt;span class="math"&gt;\(j\)&lt;/span&gt; if the person has the skill. The tag &amp;ldquo;data science&amp;rdquo; might apply to&amp;nbsp;me.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Movies and keywords.&lt;/strong&gt; The items are movies and the tags are keywords that describe the movies.&amp;nbsp;Tag &lt;span class="math"&gt;\(i\)&lt;/span&gt; is applied to&amp;nbsp;movie &lt;span class="math"&gt;\(j\)&lt;/span&gt; if the keyword is relevant for the movie. As an example, the tag &amp;ldquo;superhero&amp;rdquo; could be applied to the movie&amp;nbsp;&amp;ldquo;Ant-Man.&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The figure below shows an example of a tag structure.
We visualize tags in rows and items in columns.
If you had to pick two out of the four tags to describe the items, which two would you choose to help your users navigate the items?
The answer is not&amp;nbsp;obvious.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 95%; max-width:300px;"
src="https://tommyodland.com/images/articles/most_descriptive_tags/tags_intro_figure.png"&gt;&lt;/p&gt;
&lt;p&gt;The plan for this article is to discuss what a descriptive set of tags looks like and create an objective function that can be used to mathematically evaluate and choose descriptive tags.
We&amp;rsquo;ll also apply the proposed method to the two real-world datasets introduced above and study the&amp;nbsp;results.&lt;/p&gt;
&lt;h2 id="what-does-a-descriptive-set-of-tags-look-like"&gt;What does a descriptive set of tags look&amp;nbsp;like?&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s investigate which properties a good tag structure might have.
It&amp;rsquo;s always a good idea to fetch a pen and paper and play with a few simple ideas when attacking problems like these.
Here are my own initial thoughts about the&amp;nbsp;problem.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No tag should cover very few or very many items.&lt;/strong&gt;
A tag that covers all or almost all items is hardly descriptive.
For instance a movie tag &amp;ldquo;color-tv&amp;rdquo; does not appear worthwhile, since almost all films are shot in color.
Conversely, extremely specific tags are also quite worthless, for instance a tag like &amp;ldquo;romantic horror filmed&amp;nbsp;outdoors.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Equal group size.&lt;/strong&gt;
Tags group the items, and in a descriptive tag structure each group should have roughly the same size.
If we had to choose between the two sets of tags below, we would prefer the more balanced set of tags on the right.
Notice that equal group size and equal tag size are not identical concepts&amp;mdash;two tags could each apply to the same number of items but also overlap on some items, inducing a third group of a different&amp;nbsp;size.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 95%; max-width:550px;"
src="https://tommyodland.com/images/articles/most_descriptive_tags/tags_equal_sizes.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Balanced coverage.&lt;/strong&gt;
We define the &lt;em&gt;coverage&lt;/em&gt; of an item as the number of tags that are applied to it.
The figure below shows full one-coverage, two-coverage and three-coverage.
In general some items might have large coverage while others have little or no coverage.
We might wish for balanced coverage&amp;mdash;there is no good reason for some items to have no tags while others have very many&amp;nbsp;tags.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 99%; max-width:750px;"
src="https://tommyodland.com/images/articles/most_descriptive_tags/tags_coverage.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Binary trees are in a sense optimal.&lt;/strong&gt;
To compress as much information as possible into a set of tags, a binary tree structure is optimal.
Using a binary&amp;nbsp;tree, &lt;span class="math"&gt;\(m\)&lt;/span&gt; tags&amp;nbsp;induce &lt;span class="math"&gt;\(2^m\)&lt;/span&gt; distinct groups onto the items.
However, the groups induced by binary trees have very unbalanced coverage.
Some items are covered by zero tags, while others will be covered by&amp;nbsp;all &lt;span class="math"&gt;\(m\)&lt;/span&gt; tags.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 99%; max-width:260px;"
src="https://tommyodland.com/images/articles/most_descriptive_tags/tags_binary_tree.png"&gt;&lt;/p&gt;
&lt;p&gt;Binary trees are optimal if the information management system shows exactly the set of items corresponding to the tags applied &lt;em&gt;and not applied&lt;/em&gt;.
But information systems rarely do&amp;mdash;if you go to a website with tags and you apply no tags, you&amp;rsquo;ll see &lt;em&gt;every item&lt;/em&gt;, not &lt;em&gt;the items with no tags&lt;/em&gt;.
For our purposes, binary trees are not the answer&amp;mdash;their optimality does not correspond to how tags are typically used in information&amp;nbsp;systems.&lt;/p&gt;
&lt;h2 id="the-objective-function-minimize-number-of-items-viewed"&gt;The objective function: minimize number of items&amp;nbsp;viewed&lt;/h2&gt;
&lt;p&gt;It is tempting to start working with the properties above directly.
We could search for a set of tags that (1) induces groups of roughly equal size and (2) has roughly equal coverage of all items.
But going down this path would be a modeling&amp;nbsp;mistake.&lt;/p&gt;
&lt;p&gt;Instead of trying to satisfy several ad-hoc properties at once in a messy multi-objective problem, we should pause and think&amp;mdash;what is it that we&amp;rsquo;re trying to accomplish here,&amp;nbsp;really?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The goal of using tags is to help users retrieve information as efficiently as possible.&lt;/strong&gt;
Let&amp;rsquo;s pursue this thought and create a unified objective function.
To do so, we must first understand how tags are used in more&amp;nbsp;detail.&lt;/p&gt;
&lt;h3 id="tags-and-filters"&gt;Tags and&amp;nbsp;filters&lt;/h3&gt;
&lt;p&gt;Go to a few websites that use tags and look for an item: an apartment, a movie, or a new dishwasher.
You&amp;rsquo;ll quickly realize that tags are used differently depending on the website and&amp;nbsp;context:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Tags as one-of-many filters over mutually exclusive outcomes (&amp;ldquo;cat&amp;rdquo; or&amp;nbsp;&amp;ldquo;dog&amp;rdquo;)&lt;/li&gt;
&lt;li&gt;Tags as &lt;span class="caps"&gt;OR&lt;/span&gt;-filters (selecting tags &amp;ldquo;a&amp;rdquo; and &amp;ldquo;b&amp;rdquo; shows the union &amp;ldquo;a &lt;span class="caps"&gt;OR&lt;/span&gt;&amp;nbsp;b&amp;rdquo;)&lt;/li&gt;
&lt;li&gt;Tags as &lt;span class="caps"&gt;AND&lt;/span&gt;-filters (selecting tags &amp;ldquo;a&amp;rdquo; and &amp;ldquo;b&amp;rdquo; shows the intersection &amp;ldquo;a &lt;span class="caps"&gt;AND&lt;/span&gt;&amp;nbsp;b&amp;rdquo;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;a href="https://archive.ics.uci.edu/datasets"&gt;&lt;span class="caps"&gt;UC&lt;/span&gt; Irvine Machine Learning Repository&lt;/a&gt; uses &lt;span class="caps"&gt;OR&lt;/span&gt;-filters for tags related to the &amp;ldquo;data types&amp;rdquo; filter on their datasets.
There are 665 datasets in total, and if we activate the &amp;ldquo;Tabular&amp;rdquo; tag we filter down to 48 datasets.
If we add the &amp;ldquo;Text&amp;rdquo; tag we &lt;em&gt;increase&lt;/em&gt; the search result to 121 datasets.
Adding more tags enlarges the result set by widening the&amp;nbsp;selection.&lt;/p&gt;
&lt;p&gt;The Norwegian marketplace &lt;a href="https://www.finn.no/"&gt;&lt;span class="caps"&gt;FINN&lt;/span&gt;.no&lt;/a&gt; uses &lt;span class="caps"&gt;AND&lt;/span&gt;-filters for real estate properties.
When we activate the &amp;ldquo;Elevator&amp;rdquo; tag and the &amp;ldquo;Fireplace&amp;rdquo; tag, we are presented with housing that has both an elevator and a fireplace.
Adding more tags narrows the selection and &lt;em&gt;decreases&lt;/em&gt; the size of the result&amp;nbsp;set.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The objective function:&lt;/strong&gt; We assume that we&amp;rsquo;re using tags with &lt;span class="caps"&gt;AND&lt;/span&gt;-filters, and that we&amp;rsquo;re looking for a randomly selected, but specific item. The goal is to &lt;em&gt;minimize the number of items viewed&lt;/em&gt; on average before we find the item we&amp;rsquo;re looking&amp;nbsp;for.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A &lt;em&gt;view&lt;/em&gt; means an item considered; like going through a deck of cards one-by-one and asking &amp;ldquo;is this my card?&amp;rdquo;.
Each item considered this way counts as a view, and each view costs time for the person searching through the&amp;nbsp;items.&lt;/p&gt;
&lt;h3 id="example-computation"&gt;Example&amp;nbsp;computation&lt;/h3&gt;
&lt;p&gt;The equation for the objective function, which we will present shortly, is a bit opaque.
To make the equation more understandable we introduce it with an&amp;nbsp;example.&lt;/p&gt;
&lt;p&gt;Consider the following example&amp;nbsp;with &lt;span class="math"&gt;\(m=3\)&lt;/span&gt; tags&amp;nbsp;and &lt;span class="math"&gt;\(n=6\)&lt;/span&gt; items.
The tag structure is described by a binary matrix, with&amp;nbsp;entry &lt;span class="math"&gt;\(1\)&lt;/span&gt; if&amp;nbsp;tag &lt;span class="math"&gt;\(i\)&lt;/span&gt; is applied to&amp;nbsp;item &lt;span class="math"&gt;\(j\)&lt;/span&gt;.
Zeros are omitted for&amp;nbsp;readability.
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
 \begin{bmatrix}
1 &amp;amp; 1 &amp;amp;  &amp;amp;  &amp;amp;  &amp;amp; 1 \\
1 &amp;amp;  &amp;amp; 1 &amp;amp; 1 &amp;amp; 1 &amp;amp; 1 \\
 &amp;amp;  &amp;amp;  &amp;amp;  &amp;amp; 1 &amp;amp; 1 \\
\end{bmatrix}
\end{align*}&lt;/div&gt;
&lt;p&gt;
Suppose we&amp;rsquo;re looking for the first item with&amp;nbsp;tags &lt;span class="math"&gt;\([1, 1, 0]\)&lt;/span&gt;.
We know exactly what we&amp;rsquo;re looking for, so we apply tags one and two to our &lt;span class="caps"&gt;AND&lt;/span&gt;-filter.
The result of filtering all items on tags one and two is the result&amp;nbsp;set &lt;span class="math"&gt;\(\{[1, 1, 0], [1, 1, 1]\}\)&lt;/span&gt;.
It is coincidental that we happen to be looking for the first item in this set.
We want the objective to be invariant to permutations of the columns, and we assume that we have no control of the order of the filtered results.
If we&amp;rsquo;re looking for the first item we&amp;rsquo;ll view one item, which is the item we wanted to find.
If we&amp;rsquo;re looking for the second item we&amp;rsquo;ll view two items before we find what we&amp;rsquo;re looking for.
On average we&amp;nbsp;view &lt;span class="math"&gt;\((1 + 2)/2 = 3/2\)&lt;/span&gt; items.&lt;/p&gt;
&lt;p&gt;Now suppose we&amp;rsquo;re looking for the second item, with&amp;nbsp;tags &lt;span class="math"&gt;\([1, 0, 0]\)&lt;/span&gt;.
Again we know exactly what we&amp;rsquo;re looking for, so we apply the first tag to filter the items.
The result set&amp;nbsp;is &lt;span class="math"&gt;\(\{[1, 0, 0], [1, 0, 0], [1, 1, 1]\}\)&lt;/span&gt;, with three items.
The ordering is arbitrary and on average we&amp;nbsp;view &lt;span class="math"&gt;\((1+2+3)/3 = 2\)&lt;/span&gt; items before finding what we&amp;rsquo;re looking&amp;nbsp;for.&lt;/p&gt;
&lt;p&gt;We continue this way for every item.
If we&amp;rsquo;re looking for the third item we&amp;rsquo;ll&amp;nbsp;view &lt;span class="math"&gt;\(3\)&lt;/span&gt; items on average before we find&amp;nbsp;it, &lt;span class="math"&gt;\(3\)&lt;/span&gt; for the&amp;nbsp;fourth, &lt;span class="math"&gt;\(3/2\)&lt;/span&gt; for the fifth&amp;nbsp;and &lt;span class="math"&gt;\(1\)&lt;/span&gt; for the&amp;nbsp;sixth.&lt;/p&gt;
&lt;p&gt;Averaging over all the six items which we might be looking for, we&amp;rsquo;ll&amp;nbsp;view
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\frac{3/2 + 2 + 3 + 3 + 3/2 + 1}{6} = 2
\end{align*}&lt;/div&gt;
&lt;p&gt;
items on average before we find a specific item, if the item we&amp;rsquo;re looking for was drawn uniformly at&amp;nbsp;random.&lt;/p&gt;
&lt;h3 id="equation-and-code"&gt;Equation and&amp;nbsp;code&lt;/h3&gt;
&lt;p&gt;We now formalize the logic above into an equation and into code.
Consider a binary&amp;nbsp;matrix &lt;span class="math"&gt;\(T \in \{0,1\}^{m \times n}\)&lt;/span&gt; consisting&amp;nbsp;of &lt;span class="math"&gt;\(m\)&lt;/span&gt; tags&amp;nbsp;and &lt;span class="math"&gt;\(n\)&lt;/span&gt; items.
An equation that implements the computation above&amp;nbsp;is
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
f(T) = \frac{1}{2} + \frac{1}{2n} \sum_{j=1}^{n} \sum_{k=1}^{n} \prod_{i=1}^{m} \left[ T_{ik} \geq T_{ij} \right].
\end{align*}&lt;/div&gt;
&lt;p&gt;
The square bracket is the &lt;a href="https://en.wikipedia.org/wiki/Iverson_bracket"&gt;Iverson bracket&lt;/a&gt;, which evaluates&amp;nbsp;to &lt;span class="math"&gt;\(1\)&lt;/span&gt; if the argument inside it is true&amp;nbsp;and &lt;span class="math"&gt;\(0\)&lt;/span&gt; otherwise.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s untangle the equation a little.
Given&amp;nbsp;items &lt;span class="math"&gt;\(j\)&lt;/span&gt; and &lt;span class="math"&gt;\(k\)&lt;/span&gt;, the&amp;nbsp;expression &lt;span class="math"&gt;\(\prod_{i=1}^{m} \left[ T_{ik} \geq T_{ij} \right]\)&lt;/span&gt; equals &lt;span class="math"&gt;\(1\)&lt;/span&gt; if applying filters for tags associated with&amp;nbsp;item &lt;span class="math"&gt;\(j\)&lt;/span&gt; retains&amp;nbsp;item &lt;span class="math"&gt;\(k\)&lt;/span&gt;.
If&amp;nbsp;item &lt;span class="math"&gt;\(k\)&lt;/span&gt; is excluded by the filter, then the expression becomes zero.
We can think of the product as the &lt;span class="caps"&gt;AND&lt;/span&gt; function applied to a set of logical implications computed using the &amp;ldquo;greater than or equal to&amp;rdquo; function.
The total number of items in the result set when we apply tags corresponding to&amp;nbsp;item &lt;span class="math"&gt;\(j\)&lt;/span&gt; is&amp;nbsp;therefore &lt;span class="math"&gt;\(\sum_{k=1}^{n} \prod_{i=1}^{m} \left[ T_{ik} \geq T_{ij} \right]\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Assume that applying filters for&amp;nbsp;item &lt;span class="math"&gt;\(j\)&lt;/span&gt; leads to a result set&amp;nbsp;with &lt;span class="math"&gt;\(\ell\)&lt;/span&gt; items.
On average we then have to&amp;nbsp;view
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\frac{1 + 2 + 3 + \cdots + \ell}{\ell} =
\frac{\ell (\ell + 1)/2}{\ell} = \frac{\ell + 1}{2}
\end{align*}&lt;/div&gt;
&lt;p&gt;
items before finding the specific item we&amp;rsquo;re looking&amp;nbsp;for.&lt;/p&gt;
&lt;p&gt;Combining these observations together and re-arranging a little leads to the objective&amp;nbsp;function &lt;span class="math"&gt;\(f\)&lt;/span&gt; above.
The idea is perhaps more easily understood through code.
Below is a Python snippet that implements the objective function&amp;nbsp;above.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;numpy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;np&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;items_viewed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Assume we&amp;#39;re looking for a random item and we apply&lt;/span&gt;
&lt;span class="sd"&gt;    the correct AND-filters. How many items must we look at&lt;/span&gt;
&lt;span class="sd"&gt;    at on average before finding it?&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

    &lt;span class="n"&gt;views&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;  &lt;span class="c1"&gt;# Average items viewed before finding each item&lt;/span&gt;

    &lt;span class="c1"&gt;# Loop over each item (column) in T&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
        &lt;span class="c1"&gt;# Applying the AND-filter, this is the number of items found&lt;/span&gt;
        &lt;span class="n"&gt;num_found&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[:,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c1"&gt;# On average we look at (1 + 2 + ... + num_found) / num_found&lt;/span&gt;
        &lt;span class="c1"&gt;# items, and (1 + 2 + ... + k) / k = (k + 1)/2&lt;/span&gt;
        &lt;span class="n"&gt;views&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;num_found&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;views&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Average over every item&lt;/span&gt;


&lt;span class="c1"&gt;# An example where T has shape (tags, items)&lt;/span&gt;
&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; 
              &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; 
              &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;
&lt;span class="n"&gt;items_viewed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 2.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The function runs&amp;nbsp;in &lt;span class="math"&gt;\(\mathcal{O}(mn^2)\)&lt;/span&gt; time.&lt;/p&gt;
&lt;h3 id="a-penalty-for-the-number-of-tags"&gt;A penalty for the number of&amp;nbsp;tags&lt;/h3&gt;
&lt;p&gt;The objective&amp;nbsp;function &lt;span class="math"&gt;\(f\)&lt;/span&gt; never penalizes adding more tags to the system (rows to the&amp;nbsp;matrix &lt;span class="math"&gt;\(T\)&lt;/span&gt;).
A thousand tags for a thousand items is perfectly &lt;span class="caps"&gt;OK&lt;/span&gt;.
We will now introduce a penalty for the number of tags.
This makes the model more realistic and helps us choose an optimal subset of tags without having to specify the size of the subset&amp;nbsp;beforehand.&lt;/p&gt;
&lt;p&gt;Recall&amp;nbsp;that &lt;span class="math"&gt;\(f(T)\)&lt;/span&gt; is the expected number of items we have to view before we find the item we&amp;rsquo;re looking for, averaged over all possible items that we could be looking for in the first place.
We can also penalize viewing the tags themselves.
After all, in a database we have to view all&amp;nbsp;the &lt;span class="math"&gt;\(m\)&lt;/span&gt; tags to decide whether or not to apply each tag.
Just like viewing items, this represents a cost of time&amp;nbsp;wasted.&lt;/p&gt;
&lt;p&gt;The simplest way to modify the objective to account for the number of tags is to penalize viewing a tag roughly equally to viewing an&amp;nbsp;item:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
g(T; \alpha) = f(T) + \alpha m = \frac{1}{2} + \frac{1}{2n} \sum_{j=1}^{n} \sum_{k=1}^{n} \prod_{i=1}^{m} \left[ T_{ij} \geq T_{ik} \right] + \alpha m
\end{align*}&lt;/div&gt;
&lt;p&gt;
The&amp;nbsp;number &lt;span class="math"&gt;\(\alpha \geq 0\)&lt;/span&gt; is the relative cost of viewing a tag compared to an item.
We&amp;nbsp;set &lt;span class="math"&gt;\(\alpha=1\)&lt;/span&gt; unless we explicitly state&amp;nbsp;otherwise.&lt;/p&gt;
&lt;p&gt;While &lt;span class="math"&gt;\(f\)&lt;/span&gt; can answer questions like &amp;ldquo;which ten tags out of&amp;nbsp;these &lt;span class="math"&gt;\(m\)&lt;/span&gt; are the best ones?&amp;rdquo;, the&amp;nbsp;function &lt;span class="math"&gt;\(g\)&lt;/span&gt; does not require choosing the number of tags beforehand.&amp;nbsp;Therefore &lt;span class="math"&gt;\(g\)&lt;/span&gt; can answer &amp;ldquo;which subset of&amp;nbsp;these &lt;span class="math"&gt;\(m\)&lt;/span&gt; tags is the best&amp;nbsp;one?&amp;rdquo;&lt;/p&gt;
&lt;h3 id="example-gallery"&gt;Example&amp;nbsp;gallery&lt;/h3&gt;
&lt;p&gt;To get a feeling for the objective function, here are a few&amp;nbsp;matrices &lt;span class="math"&gt;\(T\)&lt;/span&gt; and the&amp;nbsp;objectives &lt;span class="math"&gt;\(f(T)\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 99%; max-width:750px;"
src="https://tommyodland.com/images/articles/most_descriptive_tags/descriptive_tags_examples.png"&gt;&lt;/p&gt;
&lt;p&gt;With no tags the objective&amp;nbsp;is &lt;span class="math"&gt;\(1/2 + n/2 = 1/2 + 12/2 = 6.5\)&lt;/span&gt;.
If one tag is available, the optimal structure is to cover half the items with it.
If two tags are available, the optimal structure is that each tag covers half the items with no overlap.
If three tags are available, there are several minima.
One of them is a structure where each tag covers a third of the items with no overlap.
Another allows symmetric overlaps between pairs of two tags, but no overlap between all three&amp;nbsp;tags.&lt;/p&gt;
&lt;p&gt;We discuss theoretical properties of the optimal tag structure in &lt;a href="https://tommyodland.com/articles/2024/tags-for-optimal-information-retrieval-part-2-theory"&gt;part two of this article series&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="computational-solution-finding-an-optimal-subset"&gt;Computational solution: finding an optimal&amp;nbsp;subset&lt;/h2&gt;
&lt;p&gt;Given a dataset as a&amp;nbsp;matrix &lt;span class="math"&gt;\(T \in \{0,1\}^{m \times n}\)&lt;/span&gt;, we want to find a subset of&amp;nbsp;the &lt;span class="math"&gt;\(m\)&lt;/span&gt; tags that&amp;nbsp;minimize &lt;span class="math"&gt;\(g\)&lt;/span&gt;.&amp;nbsp;Typically &lt;span class="math"&gt;\(m\)&lt;/span&gt; is large and we wish to eliminate tags that are&amp;nbsp;redundant.&lt;/p&gt;
&lt;p&gt;We encode the solution as a binary&amp;nbsp;vector &lt;span class="math"&gt;\(\mathbf{x} \in \{0,1\}^{m}\)&lt;/span&gt;.&amp;nbsp;If &lt;span class="math"&gt;\(x_i = 1\)&lt;/span&gt;, then&amp;nbsp;tag &lt;span class="math"&gt;\(i\)&lt;/span&gt; is chosen.
In Python we&amp;nbsp;use &lt;span class="math"&gt;\(\mathbf{x}\)&lt;/span&gt; to subset the rows&amp;nbsp;of &lt;span class="math"&gt;\(T\)&lt;/span&gt;, and penalize the number of tags&amp;nbsp;chosen &lt;span class="math"&gt;\(\sum_i x_i\)&lt;/span&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alpha&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Given a binary vector x, evaluate the solution.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;items_viewed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;:])&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;alpha&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Finding the&amp;nbsp;optimal &lt;span class="math"&gt;\(\mathbf{x}\)&lt;/span&gt; is a combinatorial optimization problem with a large solution space.
There&amp;nbsp;are &lt;span class="math"&gt;\(2^m\)&lt;/span&gt; possible solutions, but even in such unfathomably large search spaces we can often find high-quality solutions.
A number of heuristic algorithms can be used: greedy algorithms, simulated annealing, tabu search, genetic algorithms, variable neighborhood search, and so&amp;nbsp;forth.&lt;/p&gt;
&lt;h3 id="greedy-forward-selection"&gt;Greedy forward&amp;nbsp;selection&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Greedy_algorithm"&gt;Greedy forward selection&lt;/a&gt; starts with an all-zero solution&amp;nbsp;vector &lt;span class="math"&gt;\(\mathbf{x} = \mathbf{0}\)&lt;/span&gt;.
The first iteration considers every tag, and picks the one that leads to the lowest objective.
Subsequent iterations consider every tag not already chosen, and after each iteration one new tag is added to the solution.
The algorithm continues to add tags in this manner until the objective no longer&amp;nbsp;improves.&lt;/p&gt;
&lt;p&gt;Each of the outer iterations requires&amp;nbsp;evaluating &lt;span class="math"&gt;\(\mathcal{O}(m)\)&lt;/span&gt; tags, and each evaluation of the objective function&amp;nbsp;takes &lt;span class="math"&gt;\(\mathcal{O}(mn^2)\)&lt;/span&gt; time.
The number of outer iterations is at&amp;nbsp;most &lt;span class="math"&gt;\(\mathcal{O}(m)\)&lt;/span&gt;, so the worst case complexity&amp;nbsp;is &lt;span class="math"&gt;\(\mathcal{O}(m^2 n^2)\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Greedy forward selection performs well in practice, but it&amp;rsquo;s not guaranteed to find an optimal solution to this problem.
An alternative approach would be a greedy backward algorithm&amp;mdash;starting&amp;nbsp;with &lt;span class="math"&gt;\(\mathbf{x} = \mathbf{1}\)&lt;/span&gt; and iteratively removing&amp;nbsp;tags.&lt;/p&gt;
&lt;h3 id="simulated-annealing"&gt;Simulated&amp;nbsp;annealing&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Simulated_annealing"&gt;Simulated annealing&lt;/a&gt; starts with an all-zero&amp;nbsp;vector &lt;span class="math"&gt;\(\mathbf{x} = \mathbf{0}\)&lt;/span&gt;.
In each iteration we choose a random element&amp;nbsp;in &lt;span class="math"&gt;\(\mathbf{x}\)&lt;/span&gt; and flip it from zero to one or vice versa.
If this new candidate solution is better, or if the temperature criterion kicks in, we keep the candidate solution and try to improve upon&amp;nbsp;it.&lt;/p&gt;
&lt;h2 id="results-on-real-world-datasets"&gt;Results on real-world&amp;nbsp;datasets&lt;/h2&gt;
&lt;p&gt;We now consider a few real-world datasets.
For each dataset we run (1) greedy forward selection and (2) simulated annealing&amp;nbsp;with &lt;span class="math"&gt;\(\sqrt{n}m\)&lt;/span&gt; iterations.&lt;/p&gt;
&lt;h3 id="dataset-consultants-and-roles"&gt;Dataset: consultants and&amp;nbsp;roles&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://www.folq.no/"&gt;Folq&lt;/a&gt; is a Norwegian company that matches independent software consultants with projects.
One of the ways consultants describe themselves is by using binary tags for roles.
Examples of roles are &amp;ldquo;Tech Lead&amp;rdquo;, &amp;ldquo;Scrummaster&amp;rdquo; and &amp;ldquo;Data Scientist.&amp;rdquo;
Most roles are Norwegian words, but many of them are not too far from their English&amp;nbsp;counterparts.&lt;/p&gt;
&lt;p&gt;The dataset&amp;nbsp;contains &lt;span class="math"&gt;\(m=48\)&lt;/span&gt; roles (tags)&amp;nbsp;and &lt;span class="math"&gt;\(n=2255\)&lt;/span&gt; consultants (items).
Are&amp;nbsp;all &lt;span class="math"&gt;\(48\)&lt;/span&gt; roles needed, or could some of them be removed?
Let&amp;rsquo;s see what the model has to say about the&amp;nbsp;matter.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Greedy.&lt;/strong&gt; The best objective achieved&amp;nbsp;was &lt;span class="math"&gt;\(99.57\)&lt;/span&gt;,&amp;nbsp;and &lt;span class="math"&gt;\(29\)&lt;/span&gt; out&amp;nbsp;of &lt;span class="math"&gt;\(48\)&lt;/span&gt; roles were&amp;nbsp;chosen.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Simulated annealing.&lt;/strong&gt; The best objective achieved&amp;nbsp;was &lt;span class="math"&gt;\(100.43\)&lt;/span&gt;,&amp;nbsp;and &lt;span class="math"&gt;\(30\)&lt;/span&gt; out&amp;nbsp;of &lt;span class="math"&gt;\(48\)&lt;/span&gt; roles were&amp;nbsp;chosen.&lt;/p&gt;
&lt;p&gt;The figure below shows how roles were chosen in each iteration of the forward greedy algorithm.
A subset of&amp;nbsp;approximately &lt;span class="math"&gt;\(30\)&lt;/span&gt; roles is all we need to efficiently find consultants in this dataset.
Notice the diminishing returns adding more and more roles.
It seems&amp;nbsp;like &lt;span class="math"&gt;\(15\)&lt;/span&gt; roles or so does a perfectly okay&amp;nbsp;job.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 99%; max-width:650px;"
src="https://tommyodland.com/images/articles/most_descriptive_tags/folq_role_items_added.png"&gt;&lt;/p&gt;
&lt;p&gt;It should go without saying that mathematical models must be interpreted with care. 
Highly relevant tags could exist but not be in this dataset, because no one created them in the first place.
Conversely, tags could be chosen by the model but still be nonsensical in the real world (for instance a tag for &amp;ldquo;blonde hair&amp;rdquo; for consultants).
Models are often helpful, but work best alongside humans who understand the domain at&amp;nbsp;hand.&lt;/p&gt;
&lt;p&gt;Below is the full tag structure.
The tags on the top were added first by the greedy solution.
The items are sorted as binary vectors.
Beware that even though the tag &amp;ldquo;Fullstackutvikler&amp;rdquo; (full stack developer) was added first by the greedy algorithm, it is not necessarily the most important tag in the context of all other chosen&amp;nbsp;tags.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://tommyodland.com/images/articles/most_descriptive_tags/folq_role_items_matrix.png" target="_blank"&gt;
&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 99%; max-width:750px;"
src="https://tommyodland.com/images/articles/most_descriptive_tags/folq_role_items_matrix.png"&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The following roles were discarded:
&lt;span class="caps"&gt;AI&lt;/span&gt;-rådgiver,
&lt;span class="caps"&gt;AI&lt;/span&gt;-utvikler,
&lt;span class="caps"&gt;CISO&lt;/span&gt;,
Content Manager,
Endringsleder,
Informasjonsarkitekt,
Organisasjonsutvikler,
Penetrasjonstester,
Personvernrådgiver,
Produkteier,
Produktleder,
Prosessleder,
Security governance manager,
Sikkerhetsanalytiker,
Sikkerhetsarkitekt,
Sikkerhetsrevisor,
Sikkerhetsutvikler,
Smidig coach and&amp;nbsp;Virksomhetsarkitekt.
 &lt;/p&gt;
&lt;h3 id="dataset-consultants-and-skills"&gt;Dataset: consultants and&amp;nbsp;skills&lt;/h3&gt;
&lt;p&gt;Folq also allows consultants to describe themselves using skills, such as &amp;ldquo;&lt;span class="caps"&gt;SQL&lt;/span&gt;&amp;rdquo;, &amp;ldquo;Java&amp;rdquo;, &amp;ldquo;Agile&amp;rdquo; and &amp;ldquo;&lt;span class="caps"&gt;UI&lt;/span&gt;-design.&amp;rdquo;
This dataset&amp;nbsp;contains &lt;span class="math"&gt;\(m=291\)&lt;/span&gt; skills (tags)&amp;nbsp;and &lt;span class="math"&gt;\(n=2255\)&lt;/span&gt; consultants&amp;nbsp;(items).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Greedy.&lt;/strong&gt; The best objective achieved&amp;nbsp;was &lt;span class="math"&gt;\(58.02\)&lt;/span&gt;,&amp;nbsp;and &lt;span class="math"&gt;\(38\)&lt;/span&gt; out&amp;nbsp;of &lt;span class="math"&gt;\(291\)&lt;/span&gt; skills were&amp;nbsp;chosen.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Simulated annealing.&lt;/strong&gt; The best objective achieved&amp;nbsp;was &lt;span class="math"&gt;\(57.01\)&lt;/span&gt;,&amp;nbsp;and &lt;span class="math"&gt;\(38\)&lt;/span&gt; out&amp;nbsp;of &lt;span class="math"&gt;\(291\)&lt;/span&gt; skills were&amp;nbsp;chosen.&lt;/p&gt;
&lt;p&gt;Simulated annealing found a solution that was slightly better than the greedy solution.
The figure below shows which roles were chosen in each iteration of the forward greedy&amp;nbsp;algorithm.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 99%; max-width:650px;"
src="https://tommyodland.com/images/articles/most_descriptive_tags/folq_skill_items_added.png"&gt;&lt;/p&gt;
&lt;p&gt;Below is the full tag structure.
The tags on the top were added first by the greedy&amp;nbsp;algorithm.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://tommyodland.com/images/articles/most_descriptive_tags/folq_skill_items_matrix.png" target="_blank"&gt;
&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 99%; max-width:750px;"
src="https://tommyodland.com/images/articles/most_descriptive_tags/folq_skill_items_matrix.png"&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="dataset-movies-and-keywords"&gt;Dataset: movies and&amp;nbsp;keywords&lt;/h3&gt;
&lt;p&gt;We will now consider a dataset from the GitHub repository &lt;a href="https://github.com/maazh/IMDB-Movie-Dataset-Analysis/"&gt;&lt;span class="caps"&gt;IMDB&lt;/span&gt;-Movie-Dataset-Analysis&lt;/a&gt;.
It&amp;rsquo;s quite large, so we select a random subset of movies.
After filtering, the dataset&amp;nbsp;has &lt;span class="math"&gt;\(m=3341\)&lt;/span&gt; keywords (tags)&amp;nbsp;and &lt;span class="math"&gt;\(n=2000\)&lt;/span&gt; movies&amp;nbsp;(items).&lt;/p&gt;
&lt;p&gt;We&amp;nbsp;set &lt;span class="math"&gt;\(\alpha=10\)&lt;/span&gt; on this dataset, since very many tags are chosen&amp;nbsp;if &lt;span class="math"&gt;\(\alpha=1\)&lt;/span&gt;.
This dataset has a sub-optimal structure with little overlap between&amp;nbsp;tags.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Greedy.&lt;/strong&gt; The best objective achieved&amp;nbsp;was &lt;span class="math"&gt;\(859.39\)&lt;/span&gt;,&amp;nbsp;and &lt;span class="math"&gt;\(17\)&lt;/span&gt; out&amp;nbsp;of &lt;span class="math"&gt;\(3341\)&lt;/span&gt; keywords were&amp;nbsp;chosen.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Simulated annealing.&lt;/strong&gt; We do not bother with simulated annealing on this dataset, since there is so little overlap between the tags. The greedy algorithm does a much better job in reasonable&amp;nbsp;time.&lt;/p&gt;
&lt;p&gt;The objective decreases very slowly as keywords are added.
The keywords are not very descriptive, in the sense that they won&amp;rsquo;t help a user find a movie&amp;nbsp;quickly.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 99%; max-width:650px;"
src="https://tommyodland.com/images/articles/most_descriptive_tags/imdb_keywords_items_added.png"&gt;&lt;/p&gt;
&lt;p&gt;The tag structure reveals why the objective decreases so slowly.
A tag structure such as this is sub-optimal&amp;mdash;each tag covers a small set of movies, and there is little overlap between the&amp;nbsp;tags.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://tommyodland.com/images/articles/most_descriptive_tags/imdb_keywords_items_matrix.png" target="_blank"&gt;
&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 99%; max-width:750px;"
src="https://tommyodland.com/images/articles/most_descriptive_tags/imdb_keywords_items_matrix.png"&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The same movies are also tagged with genres.
There&amp;nbsp;are &lt;span class="math"&gt;\(m=20\)&lt;/span&gt; movie genres,&amp;nbsp;and &lt;span class="math"&gt;\(11\)&lt;/span&gt; are selected by our greedy selection if we run it&amp;nbsp;with &lt;span class="math"&gt;\(\alpha=10\)&lt;/span&gt;.
The objective function&amp;nbsp;is &lt;span class="math"&gt;\(234.12\)&lt;/span&gt; if we use genres&amp;mdash;much better than&amp;nbsp;the &lt;span class="math"&gt;\(859.39\)&lt;/span&gt; achieved with keywords.
Genres result in a lower objective function value while also using fewer&amp;nbsp;tags.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://tommyodland.com/images/articles/most_descriptive_tags/imdb_genres_items_matrix.png" target="_blank"&gt;
&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 99%; max-width:750px;"
src="https://tommyodland.com/images/articles/most_descriptive_tags/imdb_genres_items_matrix.png"&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="summary-and-references"&gt;Summary and&amp;nbsp;references&lt;/h2&gt;
&lt;p&gt;Tags should help users retrieve information efficiently.
We assumed that a user is looking for a particular item, and that he knows which tags to apply.
Then we asked: &amp;ldquo;which tags will, on average, minimize time spent searching for the item?&amp;rdquo;
We formulated this as a mathematical optimization&amp;nbsp;problem.&lt;/p&gt;
&lt;p&gt;For concrete datasets we solve the problem using greedy forward selection or simulated annealing. 
This works reasonably well for small-to-medium sized datasets.
It does not scale to large datasets, since evaluating the objective function&amp;nbsp;takes &lt;span class="math"&gt;\(\mathcal{O}(mn^2)\)&lt;/span&gt; time,&amp;nbsp;where &lt;span class="math"&gt;\(m\)&lt;/span&gt; is the number of chosen tags&amp;nbsp;and &lt;span class="math"&gt;\(n\)&lt;/span&gt; is the number of&amp;nbsp;items.&lt;/p&gt;
&lt;p&gt;Minimizing the worst case&amp;ndash;the maximum number of views&amp;mdash;is also an option, but generally harder due to discontinuities in the objective function.
An interesting question is whether the objective function can be evaluated more efficiently, either by using previous computations or by exploiting the subset structure of the tags.
A more in-depth analysis of optimal tagging structures is &lt;a href="https://tommyodland.com/articles/2024/tags-for-optimal-information-retrieval-part-2-theory"&gt;presented in a follow-up article&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="literature-review"&gt;Literature&amp;nbsp;review&lt;/h3&gt;
&lt;p&gt;Wikipedia has entries on &lt;a href="https://en.wikipedia.org/wiki/Subject_indexing"&gt;subject indexing&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/Tag_(metadata)"&gt;tagging&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/Taxonomy"&gt;taxonomies&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Information_retrieval"&gt;information retrieval&lt;/a&gt;&amp;mdash;but I found no research into optimal tag&amp;nbsp;structures.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://stackoverflow.com/questions/3432083/algorithm-for-covering-population-with-minimum-tags"&gt;This&lt;/a&gt; and &lt;a href="https://stackoverflow.com/questions/3895824/algorithm-to-find-fewest-number-of-tags-that-encompass-all-items"&gt;this&lt;/a&gt; question on Stack Overflow ask about the minimum number of tags that cover all items.
This is the &lt;span class="caps"&gt;NP&lt;/span&gt;-hard &lt;a href="https://en.wikipedia.org/wiki/Set_cover_problem"&gt;set cover problem&lt;/a&gt;, but solving it does not lead to efficient information retrieval&amp;mdash;the number of items covered by each tag is not accounted&amp;nbsp;for.&lt;/p&gt;
&lt;p&gt;In the paper &amp;ldquo;&lt;a href="https://arxiv.org/abs/2104.01028"&gt;Limiting Tags Fosters Efficiency&lt;/a&gt;,&amp;rdquo; the authors use the conditional entropy of items given tags as a proxy for retrieval efficiency.
The optimal solution is then one unique tag per item.
A system with that many tags is obviously only &amp;ldquo;efficient&amp;rdquo; in a very specific and somewhat theoretical&amp;nbsp;sense.&lt;/p&gt;
&lt;p&gt;The idea in the paper &amp;ldquo;&lt;a href="https://kobra.uni-kassel.de/bitstream/handle/123456789/2009040826905/StummeMiningAssociationRules2006.pdf"&gt;Mining Association Rules in Folksonomies&lt;/a&gt;&amp;rdquo; could be used on tags to create a hierarchical structure out of non-hierarchical tags, probably with varying results.
The papers &amp;ldquo;&lt;a href="https://dl.acm.org/doi/abs/10.1145/1135777.1135869"&gt;Improved Annotation of the Blogosphere via Autotagging and Hierarchical Clustering&lt;/a&gt;&amp;rdquo; appears to be in the same&amp;nbsp;vein.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Fri, 31 May 2024 00:00:00 +0200</pubDate><guid>tag:tommyodland.com,2024-05-31:/articles/2024/tags-for-optimal-information-retrieval-part-1-motivation</guid><category>articles</category><category>optimization</category></item><item><title>Tags for optimal information retrieval - part 2: theory</title><link>https://tommyodland.com/articles/2024/tags-for-optimal-information-retrieval-part-2-theory</link><description>&lt;p&gt;This is part two of a two-article series.
I recommend reading them in&amp;nbsp;order:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://tommyodland.com/articles/2024/tags-for-optimal-information-retrieval-part-1-motivation"&gt;Tags for optimal information retrieval - part 1:&amp;nbsp;motivation&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="https://tommyodland.com/articles/2024/tags-for-optimal-information-retrieval-part-2-theory"&gt;Tags for optimal information retrieval - part 2:&amp;nbsp;theory&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;In this article we investigate optimal tag structures.
In the &lt;a href="https://tommyodland.com/articles/2024/tags-for-optimal-information-retrieval-part-1-motivation"&gt;previous article&lt;/a&gt; we answered &amp;ldquo;which subset of a given set&amp;nbsp;of &lt;span class="math"&gt;\(m\)&lt;/span&gt; tags is best?&amp;rdquo;, and we will now focus on the continuous problem&amp;nbsp;&amp;ldquo;given &lt;span class="math"&gt;\(m\)&lt;/span&gt; tags, how should they be structured if we have freedom to arrange them as we&amp;nbsp;wish?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Consider again the case where we&amp;rsquo;re looking for a specific &lt;em&gt;item&lt;/em&gt;.
The item is uniformly distributed on the&amp;nbsp;interval &lt;span class="math"&gt;\([0, 1]\)&lt;/span&gt;.
Assume that we&amp;nbsp;have &lt;span class="math"&gt;\(m\)&lt;/span&gt; &lt;em&gt;tags&lt;/em&gt; that we can cover the interval with, to help us find the item as quickly as&amp;nbsp;possible.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 95%; max-width:350px;"
src="https://tommyodland.com/images/articles/most_descriptive_tags/tags_2_tag_continuous.png"&gt;&lt;/p&gt;
&lt;p&gt;Each combination of tags induces a sub-interval.
In the figure above, the two&amp;nbsp;tags &lt;span class="math"&gt;\(1\)&lt;/span&gt; and &lt;span class="math"&gt;\(2\)&lt;/span&gt; induce intervals with&amp;nbsp;lengths &lt;span class="math"&gt;\(p_1\)&lt;/span&gt;, &lt;span class="math"&gt;\(p_{12}\)&lt;/span&gt; and &lt;span class="math"&gt;\(p_2\)&lt;/span&gt;.
The goal is to choose the tag structure (interval lengths) to minimize the total expected length of interval searched as we look for an item.
As in the &lt;a href="https://tommyodland.com/articles/2024/tags-for-optimal-information-retrieval-part-1-motivation"&gt;previous article&lt;/a&gt;, we assume that we are searching using an &lt;span class="caps"&gt;AND&lt;/span&gt;-filter.&lt;/p&gt;
&lt;h3 id="small-problems"&gt;Small&amp;nbsp;problems&lt;/h3&gt;
&lt;p&gt;To get a feeling for the problem, we&amp;rsquo;ll consider the&amp;nbsp;cases &lt;span class="math"&gt;\(m=1, 2, 3\)&lt;/span&gt; in detail before&amp;nbsp;generalizing.&lt;/p&gt;
&lt;h4 id="a-single-tag"&gt;A single&amp;nbsp;tag&lt;/h4&gt;
&lt;p&gt;Consider first a single tag.
The tag covers an interval of&amp;nbsp;length &lt;span class="math"&gt;\(0 \leq p_1 \leq 1\)&lt;/span&gt;, as shown in the figure below.
Our goal is to find the optimal interval&amp;nbsp;length &lt;span class="math"&gt;\(p_1\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 95%; max-width:350px;"
src="https://tommyodland.com/images/articles/most_descriptive_tags/tags_1_tag_continuous.png"&gt;
There are two outcomes: either the item we&amp;rsquo;re looking for is in the interval covered&amp;nbsp;by &lt;span class="math"&gt;\(p_1\)&lt;/span&gt; or it is not.
The probability that the item we&amp;rsquo;re looking for is in the interval&amp;nbsp;is &lt;span class="math"&gt;\(p_1\)&lt;/span&gt;, since it is drawn uniformly&amp;nbsp;from &lt;span class="math"&gt;\([0, 1]\)&lt;/span&gt;.
If the item is in the&amp;nbsp;interval &lt;span class="math"&gt;\(p_1\)&lt;/span&gt;, then on average we will look through half the interval&amp;nbsp;length &lt;span class="math"&gt;\(p_1/2\)&lt;/span&gt; before we find it.
On the other hand, the probability that the item is &lt;em&gt;not&lt;/em&gt; in the interval&amp;nbsp;is &lt;span class="math"&gt;\(1 - p_1\)&lt;/span&gt;.
In that case, we have to look through the entire&amp;nbsp;interval &lt;span class="math"&gt;\([0, 1]\)&lt;/span&gt; since we cannot apply any tags.
On average we find it after searching half the interval,&amp;nbsp;so &lt;span class="math"&gt;\(1/2\)&lt;/span&gt; is the expected length of interval&amp;nbsp;searched.&lt;/p&gt;
&lt;p&gt;Combining these two cases, the objective function that measures the expected total length of interval searched&amp;nbsp;is
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
f(p_1) = p_1 \times (p_1)/2 + (1 - p_1) \times (1)/2.
\end{align*}&lt;/div&gt;
&lt;p&gt;
We minimize this function subject to the&amp;nbsp;constraint &lt;span class="math"&gt;\(0 \leq p_1 \leq 1\)&lt;/span&gt;.
The minimizer&amp;nbsp;is &lt;span class="math"&gt;\(p_1^\star = 1/2\)&lt;/span&gt;,&amp;nbsp;and &lt;span class="math"&gt;\(f(p_1^\star) = 3/8\)&lt;/span&gt;.&lt;/p&gt;
&lt;h4 id="two-tags"&gt;Two&amp;nbsp;tags&lt;/h4&gt;
&lt;p&gt;Now consider two tags.
There are three intervals covered by at least one&amp;nbsp;tag: &lt;span class="math"&gt;\(p_1\)&lt;/span&gt; is the interval covered by only&amp;nbsp;tag &lt;span class="math"&gt;\(1\)&lt;/span&gt;, &lt;span class="math"&gt;\(p_2\)&lt;/span&gt; is the interval covered by only&amp;nbsp;tag &lt;span class="math"&gt;\(2\)&lt;/span&gt; and &lt;span class="math"&gt;\(p_{12}\)&lt;/span&gt; is the interval covered by both tags.
The figure below shows the&amp;nbsp;situation:&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 95%; max-width:350px;"
src="https://tommyodland.com/images/articles/most_descriptive_tags/tags_2_tag_continuous.png"&gt;&lt;/p&gt;
&lt;p&gt;Again we sum probabilities times outcomes.
Each outcome is the expected interval searched before we find the item.
Notice that if the item is in the interval covered&amp;nbsp;by &lt;span class="math"&gt;\(p_1\)&lt;/span&gt; and we apply the filter for&amp;nbsp;tag &lt;span class="math"&gt;\(1\)&lt;/span&gt;, then an &lt;span class="caps"&gt;AND&lt;/span&gt;-filter will present us with all items covered by&amp;nbsp;tag &lt;span class="math"&gt;\(1\)&lt;/span&gt;, irrespective of whether or not they are also covered by&amp;nbsp;tag &lt;span class="math"&gt;\(2\)&lt;/span&gt;.
This means that we have to search through both&amp;nbsp;intervals &lt;span class="math"&gt;\(p_1\)&lt;/span&gt; and &lt;span class="math"&gt;\(p_{12}\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;The objective function&amp;nbsp;becomes:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
f(p_1, p_2, p_{12}) 
&amp;amp;= p_1 \times (p_1 + p_{12})/2 \\
&amp;amp;+ p_2 \times (p_2 + p_{12})/2 \\
&amp;amp;+ p_{12} \times (p_{12})/2 \\
&amp;amp;+ (1 - p_1 - p_2 - p_{12}) \times (1)/2 
\end{align*}&lt;/div&gt;
&lt;p&gt;
We must minimize this function, subject&amp;nbsp;to &lt;span class="math"&gt;\(p_1 + p_2 + p_{12} \leq 1\)&lt;/span&gt; and &lt;span class="math"&gt;\(p_1, p_2, p_{12} \geq 0\)&lt;/span&gt;.
The minimizer&amp;nbsp;is &lt;span class="math"&gt;\((p_1^\star, p_2^\star, p_{12}^\star)  = (1/2, 1/2, 0)\)&lt;/span&gt;, which leads to the objective function&amp;nbsp;value &lt;span class="math"&gt;\(1/4\)&lt;/span&gt;.&lt;/p&gt;
&lt;h4 id="three-tags"&gt;Three&amp;nbsp;tags&lt;/h4&gt;
&lt;p&gt;A pattern starts to emerge.
The case of three tags can be visualized as&amp;nbsp;follows.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 95%; max-width:350px;"
src="https://tommyodland.com/images/articles/most_descriptive_tags/tags_3_tag_continuous.png"&gt;&lt;/p&gt;
&lt;p&gt;There&amp;nbsp;are &lt;span class="math"&gt;\(2^m-1\)&lt;/span&gt; intervals covered by at least one tag.
The objective function&amp;nbsp;becomes:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
f(p_1, p_2, \ldots, p_{123}) 
&amp;amp;= p_1 \times (p_1 + p_{12} + p_{13} + p_{123})/2 \\
&amp;amp;+ p_2 \times (p_2 + p_{12} + p_{23} + p_{123})/2 \\
&amp;amp;+ p_3 \times (p_3 + p_{13} + p_{23} + p_{123})/2 \\
&amp;amp;+ p_{12} \times (p_{12} + p_{123})/2 \\
&amp;amp;+ p_{13} \times (p_{13} + p_{123})/2 \\
&amp;amp;+ p_{23} \times (p_{23} + p_{123})/2 \\
&amp;amp;+ p_{123} \times ( p_{123})/2 \\
&amp;amp;+ (1 - p_1 - p_2- p_3- p_{12}- p_{13}- p_{23}- p_{123}) \times (1)/2
\end{align*}&lt;/div&gt;
&lt;p&gt;The constraints for the minimization problem&amp;nbsp;are &lt;span class="math"&gt;\(\sum_i p_i \leq 1\)&lt;/span&gt; and &lt;span class="math"&gt;\(p_i \geq 0\)&lt;/span&gt; for all&amp;nbsp;tags &lt;span class="math"&gt;\(i\)&lt;/span&gt;.
While&amp;nbsp;the &lt;span class="math"&gt;\(m=1\)&lt;/span&gt; and &lt;span class="math"&gt;\(m=2\)&lt;/span&gt; cases have unique minimizers, here the solution is a convex&amp;nbsp;combination
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
(1-\beta) \times (1/3, 1/3, 1/3, 0, 0, 0, 0) + \beta \times(0, 0, 0, 1/3, 1/3, 1/3, 0)
\end{align*}&lt;/div&gt;
&lt;p&gt;
for&amp;nbsp;any &lt;span class="math"&gt;\(0 \leq \beta \leq 1\)&lt;/span&gt;.&lt;/p&gt;
&lt;h3 id="general-problem-with-m-tags"&gt;General problem&amp;nbsp;with &lt;span class="math"&gt;\(m\)&lt;/span&gt; tags&lt;/h3&gt;
&lt;p&gt;In the general problem, we sum over all children in the powerset of tags.
The powerset can be visualized as a &lt;a href="https://en.wikipedia.org/wiki/Hasse_diagram"&gt;Hasse diagram&lt;/a&gt;, shown below&amp;nbsp;for &lt;span class="math"&gt;\(m=4\)&lt;/span&gt; tags.
&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 95%; max-width:500px;"
src="https://tommyodland.com/images/articles/most_descriptive_tags/tags_hasse_diagram.png"&gt;
The general function&amp;nbsp;for &lt;span class="math"&gt;\(m\)&lt;/span&gt; tags&amp;nbsp;takes &lt;span class="math"&gt;\(2^m-1\)&lt;/span&gt; variables as inputs.&amp;nbsp;Let &lt;span class="math"&gt;\(P(m)\)&lt;/span&gt; be the intervals corresponding to the powerset&amp;nbsp;of &lt;span class="math"&gt;\(m\)&lt;/span&gt; tags, for&amp;nbsp;instance &lt;span class="math"&gt;\(P(3) = \{p_{1}, p_{12}, p_{2}, p_{13}, p_{123}, p_{23}, p_{3}\}\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;The general function considers each interval in the powerset in turn, and loops down through the Hasse diagram to all children (supersets).
The function&amp;nbsp;is
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
f(\boldsymbol{p}) 
&amp;amp;= \sum_{p_i \in P(m)} p_i \times (p_i + \operatorname{children}(p_i))/2 \\
&amp;amp;+ (1 - \sum_{p_i \in P(m)} p_i)/2.
\end{align*}&lt;/div&gt;
&lt;p&gt;
As always, the function must be minimized subject to&amp;nbsp;constraints &lt;span class="math"&gt;\(p_i \geq 0\)&lt;/span&gt; and &lt;span class="math"&gt;\(\sum_{p_j \in P(m)} p_i \leq 1\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Another way to construct this function is to use a recursive definition of the power sets, ordering the variables as&amp;nbsp;follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="math"&gt;\(m=1\)&lt;/span&gt; yields the ordered&amp;nbsp;set &lt;span class="math"&gt;\(P(1) = \{p_{1}\}\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(m=2\)&lt;/span&gt; yields the ordered&amp;nbsp;set &lt;span class="math"&gt;\(P(2) = \{p_{1}, p_{12}, p_{2}\}\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(m=3\)&lt;/span&gt; yields the ordered&amp;nbsp;set &lt;span class="math"&gt;\(P(3) = \{p_{1}, p_{12}, p_{2}, p_{13}, p_{123}, p_{23}, p_{3}\}\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;In&amp;nbsp;general &lt;span class="math"&gt;\(P(m) = S_{m-1} \cup \{ p_i \cup m \mid p_i \in S_{m-1} \} \cup \{ p_m \}\)&lt;/span&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Given this ordering of the variables, we can write the objective function as a quadratic&amp;nbsp;form
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
f(\boldsymbol{p}) = \frac{1}{2} \boldsymbol{p}^T N_{k} \boldsymbol{p} - \frac{1}{2} \boldsymbol{1}^T\boldsymbol{p} + \frac{1}{2},
\end{align*}&lt;/div&gt;
&lt;p&gt;
where the non-symmetric&amp;nbsp;matrix &lt;span class="math"&gt;\(N_{k} \in \{0, 1\}^{2^m-1}\)&lt;/span&gt; is recursively defined&amp;nbsp;as
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
N_1 = \begin{bmatrix}1\end{bmatrix}
\quad
N_{k+1}= \begin{bmatrix}
N_k &amp;amp; N_k &amp;amp;  \\
 &amp;amp; N_k &amp;amp;  \\
 &amp;amp; \mathbf{1}^T &amp;amp; 1 \\
\end{bmatrix}.
\end{align*}&lt;/div&gt;
&lt;h3 id="reducing-the-number-of-variables-with-symmetry"&gt;Reducing the number of variables with&amp;nbsp;symmetry&lt;/h3&gt;
&lt;p&gt;We make three observations about the objective&amp;nbsp;function:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The&amp;nbsp;function &lt;span class="math"&gt;\(f(\boldsymbol{p})\)&lt;/span&gt; is invariant to permutations of values of variables that represent the same cardinality elements in the power set (the same cover of the items). For instance,&amp;nbsp;when &lt;span class="math"&gt;\(m=2\)&lt;/span&gt; the&amp;nbsp;solutions &lt;span class="math"&gt;\((p_1, p_2, p_{12}) = (1/3, 1/2, 0)\)&lt;/span&gt; and &lt;span class="math"&gt;\((p_1, p_2, p_{12}) = (1/2, 1/3, 0)\)&lt;/span&gt; yield identical objective function&amp;nbsp;values.&lt;/li&gt;
&lt;li&gt;If only the variables that represent a given cardinarily in the power set are non-zero, then the optimal solution is to let them be equal to each other. In other words, if we consider only one level in the Hasse diagram, then the optimal solution is to spread out the coverage equally across all variables associated with that level. For instance, if we only consider variables that represent cardinality three in&amp;nbsp;the &lt;span class="math"&gt;\(m=4\)&lt;/span&gt; problem,&amp;nbsp;then &lt;span class="math"&gt;\(p_{123} = p_{124} = p_{134} = p_{234} = 1/4\)&lt;/span&gt; is&amp;nbsp;optimal.&lt;/li&gt;
&lt;li&gt;If we consider sets of variables with a given cardinality, i.e., a single level in the Hasse diagram, then the &lt;em&gt;level with the most variables&lt;/em&gt; is the optimal level.&amp;nbsp;With &lt;span class="math"&gt;\(m\geq2\)&lt;/span&gt; tags this is&amp;nbsp;level &lt;span class="math"&gt;\(\lfloor m/2 \rfloor\)&lt;/span&gt;, which&amp;nbsp;has &lt;span class="math"&gt;\(\binom{m}{\lfloor m/2 \rfloor}\)&lt;/span&gt; variables representing unique intervals all&amp;nbsp;having &lt;span class="math"&gt;\(\lfloor m/2 \rfloor\)&lt;/span&gt;-coverage. This will be explained in more detail later in the&amp;nbsp;article.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We conjecture that it suffices to check solutions where each variable that represents the same cardinality elements in the power set is equal, for instance&amp;nbsp;if &lt;span class="math"&gt;\(m=3\)&lt;/span&gt; then we&amp;nbsp;assume &lt;span class="math"&gt;\(p_{1} = p_{2} = p_{3}\)&lt;/span&gt; and &lt;span class="math"&gt;\(p_{12} = p_{13} = p_{23}\)&lt;/span&gt;.
By this symmetry argument, we reduce the number of variables&amp;nbsp;from &lt;span class="math"&gt;\(2^m\)&lt;/span&gt; to &lt;span class="math"&gt;\(m\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Let &lt;span class="math"&gt;\(q_i\)&lt;/span&gt; be the value of every variable that&amp;nbsp;represents &lt;span class="math"&gt;\(i\)&lt;/span&gt; cardinality variables, i.e., intervals covered by&amp;nbsp;exactly &lt;span class="math"&gt;\(i\)&lt;/span&gt; tags.
For instance,&amp;nbsp;if &lt;span class="math"&gt;\(m=3\)&lt;/span&gt; then&amp;nbsp;the &lt;span class="math"&gt;\(q_1\)&lt;/span&gt; is the value of&amp;nbsp;variables &lt;span class="math"&gt;\(p_1, p_2, p_3\)&lt;/span&gt; and &lt;span class="math"&gt;\(q_2\)&lt;/span&gt; is the value&amp;nbsp;of &lt;span class="math"&gt;\(p_{12}, p_{13}, p_{23}\)&lt;/span&gt; and &lt;span class="math"&gt;\(q_3\)&lt;/span&gt; is the value&amp;nbsp;of &lt;span class="math"&gt;\(p_{123}\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;With respect to the new variable&amp;nbsp;vector &lt;span class="math"&gt;\(\boldsymbol{q}\)&lt;/span&gt;, the problem&amp;nbsp;becomes:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
f(\boldsymbol{q}) &amp;amp;=
\frac{1}{2} \sum_{i=1}^m \binom{m}{i} q_i \sum_{k=i}^m q_k \binom{m-i}{k-i} 
- \frac{1}{2} \sum_{i=1}^m \binom{m}{i} q_i + \frac{1}{2} \\
&amp;amp;=
\frac{1}{2} \sum_{i=1}^m \sum_{k=i}^m \binom{m}{k} \binom{k}{i} q_i q_k
- \frac{1}{2} \sum_{i=1}^m \binom{m}{i} q_i + \frac{1}{2} \\
&amp;amp;=
\frac{1}{2} \boldsymbol{q}^T C_m \boldsymbol{q}
- \frac{1}{2} \boldsymbol{c}_m^T \boldsymbol{q} + \frac{1}{2}
\end{align*}&lt;/div&gt;
&lt;p&gt;We minimize this function with the&amp;nbsp;constraint &lt;span class="math"&gt;\(\boldsymbol{c}_m^T \boldsymbol{q} \leq 1\)&lt;/span&gt; and &lt;span class="math"&gt;\(\boldsymbol{q} \geq \boldsymbol{0}\)&lt;/span&gt;.&lt;/p&gt;
&lt;h3 id="the-problem-is-not-convex"&gt;The problem is not&amp;nbsp;convex&lt;/h3&gt;
&lt;p&gt;With one and two tags the problem is strictly convex.
With three tags the problem is convex, but not strictly convex.
With four or more tags the problem is neither convex nor&amp;nbsp;concave.&lt;/p&gt;
&lt;p&gt;Here is an example&amp;nbsp;with &lt;span class="math"&gt;\(m=4\)&lt;/span&gt; showing the non-convexity&amp;nbsp;of &lt;span class="math"&gt;\(f(\boldsymbol{q})\)&lt;/span&gt;.
Along one search direction in four dimensional space the function is convex, but along another search direction it is&amp;nbsp;concave.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 95%; max-width:700px;"
src="https://tommyodland.com/images/articles/most_descriptive_tags/tag_structure_not_convex.png"&gt;&lt;/p&gt;
&lt;p&gt;For&amp;nbsp;each &lt;span class="math"&gt;\(m\)&lt;/span&gt; we will now search along a path that represents convex combinations of neighboring levels in the Hasse diagram.
In the figure&amp;nbsp;below &lt;span class="math"&gt;\(0\)&lt;/span&gt; maps&amp;nbsp;to &lt;span class="math"&gt;\((0, 0, 0, \ldots)\)&lt;/span&gt;, &lt;span class="math"&gt;\(1\)&lt;/span&gt; maps&amp;nbsp;to &lt;span class="math"&gt;\((1/\binom{m}{1}, 0, 0, \ldots)\)&lt;/span&gt;, &lt;span class="math"&gt;\(2\)&lt;/span&gt; maps&amp;nbsp;to &lt;span class="math"&gt;\((0, 1/\binom{m}{2}, 0, \ldots)\)&lt;/span&gt; and so forth.
A decimal value&amp;nbsp;like &lt;span class="math"&gt;\(1.3\)&lt;/span&gt; maps to a convex combination of the vectors&amp;nbsp;that &lt;span class="math"&gt;\(1\)&lt;/span&gt; and &lt;span class="math"&gt;\(2\)&lt;/span&gt; map&amp;nbsp;to.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 95%; max-width:550px;"
src="https://tommyodland.com/images/articles/most_descriptive_tags/tag_structure_along_boundary.png"&gt;&lt;/p&gt;
&lt;p&gt;We observe that&amp;nbsp;setting &lt;span class="math"&gt;\(q_i = 1/\binom{m}{i}\)&lt;/span&gt; at&amp;nbsp;level &lt;span class="math"&gt;\(i = \lfloor m/2 \rfloor\)&lt;/span&gt; minimizes the function along the path searched in the figure.
A formal proof that this is the best achievable solution escapes me.
However, in millions of computer simulations no better solution was found&amp;nbsp;for &lt;span class="math"&gt;\(m\geq 2\)&lt;/span&gt;, so I am quite confident that it&amp;rsquo;s the optimal&amp;nbsp;solution.&lt;/p&gt;
&lt;h3 id="two-bounds-on-the-optimal-solution"&gt;Two bounds on the optimal&amp;nbsp;solution&lt;/h3&gt;
&lt;p&gt;We present two upper bounds on the minimization problem&amp;mdash;the objective is at least as low as&amp;nbsp;these.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;One-coverage.&lt;/strong&gt;
A bound&amp;nbsp;for &lt;span class="math"&gt;\(m \geq 2\)&lt;/span&gt; can be established by covering each item with a single tag, where each tag covers an interval of&amp;nbsp;length &lt;span class="math"&gt;\(1/m\)&lt;/span&gt;.
This is represented&amp;nbsp;by &lt;span class="math"&gt;\(\boldsymbol{q} = (1/m, 0, 0, \ldots)\)&lt;/span&gt;, which is clearly a feasible solution.
The objective function value&amp;nbsp;is &lt;span class="math"&gt;\(f(\boldsymbol{q}) = 1/(2m)\)&lt;/span&gt;.
In the discrete case&amp;nbsp;of &lt;span class="math"&gt;\(n\)&lt;/span&gt; items, the objective&amp;nbsp;function &lt;span class="math"&gt;\(g\)&lt;/span&gt;, which penalizes the number of tags too,&amp;nbsp;becomes
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
g(m) = \frac{n}{2m} + \alpha m.
\end{align*}&lt;/div&gt;
&lt;p&gt;
Differentiating with respect&amp;nbsp;to &lt;span class="math"&gt;\(m\)&lt;/span&gt; and&amp;nbsp;solving &lt;span class="math"&gt;\(g'(m) = 0\)&lt;/span&gt; shows us that the optimal number of tags in this situation&amp;nbsp;is &lt;span class="math"&gt;\(m^\star = \sqrt{n/2 \alpha }\)&lt;/span&gt;.&amp;nbsp;Furthermore &lt;span class="math"&gt;\(g(m^\star) = \sqrt{2n\alpha}\)&lt;/span&gt;.
To summarize, if we construct a set&amp;nbsp;of &lt;span class="math"&gt;\(\sqrt{n/2 \alpha}\)&lt;/span&gt; mutually exclusive tags we have a reasonable starting point in a real-world&amp;nbsp;system.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The conjectured optimal coverage.&lt;/strong&gt;
We conjecture that the following is an optimal solution&amp;nbsp;for &lt;span class="math"&gt;\(m \geq 2\)&lt;/span&gt; tags.
Choose a full,&amp;nbsp;balanced &lt;span class="math"&gt;\(\lfloor m/2 \rfloor\)&lt;/span&gt; coverage.
In other words, choose the level in the Hasse diagram with the largest number of variables.
This is represented by&amp;nbsp;setting &lt;span class="math"&gt;\(q_i = 1/\binom{m}{i}\)&lt;/span&gt; at&amp;nbsp;level &lt;span class="math"&gt;\(i = \lfloor m/2 \rfloor\)&lt;/span&gt;, and the remaining entries&amp;nbsp;in &lt;span class="math"&gt;\(\boldsymbol{q}\)&lt;/span&gt; are set to zero.
The objective function value can be shown to&amp;nbsp;be &lt;span class="math"&gt;\(f(\boldsymbol{q}) = \frac{1}{2 \binom{m}{\lfloor m/2 \rfloor}}\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Solving
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
g(m) = \frac{n}{2 \binom{m}{\lfloor m/2 \rfloor}} + \alpha m
\end{align*}&lt;/div&gt;
&lt;p&gt;
for the&amp;nbsp;optimal &lt;span class="math"&gt;\(m\)&lt;/span&gt; must be done numerically, as no simple analytical expression is available.
For instance, if we&amp;nbsp;set &lt;span class="math"&gt;\(\alpha=1\)&lt;/span&gt; and&amp;nbsp;have &lt;span class="math"&gt;\(n=10^2\)&lt;/span&gt; items,&amp;nbsp;then &lt;span class="math"&gt;\(m=7\)&lt;/span&gt; tags is the optimal number, given that those tags obey the conjectured optimal structure.&amp;nbsp;With &lt;span class="math"&gt;\(n=10^3\)&lt;/span&gt; items &lt;span class="math"&gt;\(m=10\)&lt;/span&gt; tags is all we&amp;nbsp;need, &lt;span class="math"&gt;\(n=10^4\)&lt;/span&gt; items&amp;nbsp;require &lt;span class="math"&gt;\(m=14\)&lt;/span&gt; tags&amp;nbsp;and &lt;span class="math"&gt;\(n=10^6\)&lt;/span&gt; items require&amp;nbsp;only &lt;span class="math"&gt;\(m=21\)&lt;/span&gt; tags.&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 95%; max-width:550px;"
src="https://tommyodland.com/images/articles/most_descriptive_tags/tag_structure_bounds.png"&gt;&lt;/p&gt;
&lt;p&gt;Here are two examples showing optimal tag&amp;nbsp;structures:&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 95%; max-width:550px;"
src="https://tommyodland.com/images/articles/most_descriptive_tags/descriptive_tags_structure_6_tags.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img
style="display: block; margin-left: auto; margin-right: auto; width: 95%; max-width:600px;"
src="https://tommyodland.com/images/articles/most_descriptive_tags/descriptive_tags_structure_8_tags.png"&gt;&lt;/p&gt;
&lt;h2 id="summary-and-references"&gt;Summary and&amp;nbsp;references&lt;/h2&gt;
&lt;p&gt;In the &lt;a href="https://tommyodland.com/articles/2024/tags-for-optimal-information-retrieval-part-1-motivation"&gt;article leading up to this one&lt;/a&gt; we solved a combinatorial optimization problem using real-world data.
That led us to wonder more about the theoretically optimal tag structure, which we investigated in this&amp;nbsp;article.&lt;/p&gt;
&lt;p&gt;We began by solving small cases with one, two and three tags.
Only after understanding the objective function for these small cases did we generalize.
This is how I typically work with mathematical problems&amp;mdash;mastering the small concrete cases first, then attempting to&amp;nbsp;generalize.&lt;/p&gt;
&lt;p&gt;After writing down the objective function&amp;nbsp;for &lt;span class="math"&gt;\(m\)&lt;/span&gt; tags, we made observations regarding the symmetry of the function.
We reduced it from a function&amp;nbsp;taking &lt;span class="math"&gt;\(2^m\)&lt;/span&gt; arguments to one&amp;nbsp;taking &lt;span class="math"&gt;\(m\)&lt;/span&gt; arguments.
Then we determined that the function was not convex and established two upper bounds, one of which we conjecture to be the optimal solution.
I was unable to prove this, but ran millions of computer simulations trying out random tag structures, and not a single one beat the bound&amp;mdash;so I believe the conjecture is&amp;nbsp;true.&lt;/p&gt;
&lt;p&gt;For me this was an interesting problem to look at, since I found no literature on this subject.
Wikipedia has entries on &lt;a href="https://en.wikipedia.org/wiki/Subject_indexing"&gt;subject indexing&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/Tag_(metadata)"&gt;tagging&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/Taxonomy"&gt;taxonomies&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Information_retrieval"&gt;information retrieval&lt;/a&gt;&amp;mdash;but I found no research into how tags can be used for maximally efficient information&amp;nbsp;retrieval.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Fri, 31 May 2024 00:00:00 +0200</pubDate><guid>tag:tommyodland.com,2024-05-31:/articles/2024/tags-for-optimal-information-retrieval-part-2-theory</guid><category>articles</category><category>optimization</category></item></channel></rss>