210 lines
12 KiB
HTML
210 lines
12 KiB
HTML
<!DOCTYPE html>
|
||
<html><head><script src="/livereload.js?mindelay=10&v=2&port=1313&path=livereload" data-no-instant defer></script>
|
||
<title>Dane Sabo - I Built a Rating System for My Pickleball League (And Definitely Didn't Cook the Books) </title>
|
||
<link rel="stylesheet" type="text/css" href="/css/fonts.css">
|
||
<link rel="stylesheet" type="text/css" href="/css/fontawesome.css">
|
||
|
||
|
||
<link rel="stylesheet" type="text/css" href="/css/styles.min.c2acad37d1be0e44722e7f234c116caef79124df36955647824f8b10da479ba3.css">
|
||
|
||
|
||
<link rel="stylesheet" type="text/css" href="/css/dark-theme.css">
|
||
|
||
<meta charset="UTF-8">
|
||
<meta name="author" content="Dane Sabo">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
</head>
|
||
<body>
|
||
<header class="page-header">
|
||
<div class="myname">
|
||
<h2><a href="http://localhost:1313/">Dane Sabo</a></h2>
|
||
</div>
|
||
<nav>
|
||
<ul class="navbar">
|
||
<li class="">
|
||
<a href="/cv/">
|
||
<span>CV</span>
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/projects/">
|
||
<span>Projects</span>
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/blog/">
|
||
<span>Blog</span>
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/contact/">
|
||
<span>About Me</span>
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
</nav>
|
||
</header>
|
||
<div id="content">
|
||
<main>
|
||
<article>
|
||
|
||
<h1 class="page-title blog">I Built a Rating System for My Pickleball League (And Definitely Didn't Cook the Books)</h1>
|
||
|
||
<p class="blog-post-info">Posted: <time>2026-02-26</time>
|
||
|
||
<span class="blog-taxonomy-info"> | Categories:
|
||
|
||
|
||
|
||
<a class="blog-taxonomy-info" href="/categories/projects">Projects</a>, <a class="blog-taxonomy-info" href="/categories/recreation">Recreation</a>
|
||
</span>
|
||
|
||
|
||
|
||
<span class="blog-taxonomy-info"> | Tags:
|
||
|
||
|
||
|
||
<a class="blog-taxonomy-info" href="/tags/elo">ELO</a>, <a class="blog-taxonomy-info" href="/tags/pickleball">pickleball</a>, <a class="blog-taxonomy-info" href="/tags/rating-systems">rating systems</a>, <a class="blog-taxonomy-info" href="/tags/statistics">statistics</a>
|
||
</span>
|
||
|
||
</p>
|
||
|
||
<div class="blog-post-content">
|
||
<p>After running my pickleball league with Glicko-2 for over a month, I realized the system had problems. So I did what any reasonable person would do: I threw it out and rebuilt it from scratch with an ELO system.</p>
|
||
<p>And yes, I happen to be the biggest beneficiary of the change. Coincidence? Probably. Let me explain the math, and you can be the judge.</p>
|
||
<h2 id="the-problem-glicko-2-was-overkill">The Problem: Glicko-2 Was Overkill</h2>
|
||
<p>Glicko-2 is a sophisticated rating system designed for competitive chess. It tracks three values per player:</p>
|
||
<ul>
|
||
<li><strong>Rating</strong> — Your skill estimate (default: 1500)</li>
|
||
<li><strong>Rating Deviation</strong> — How <em>uncertain</em> the system is about your skill</li>
|
||
<li><strong>Volatility</strong> — How <em>consistent</em> you are</li>
|
||
</ul>
|
||
<p>The math involves converting to different scales, computing probabilities with hyperbolic functions, and solving iteratively for new volatility. It’s clever, but for a casual league of six players, it’s like bringing a sports car to a parking lot.</p>
|
||
<p>But the real problem was this: I added a <em>margin bonus</em> to account for wins by different margins (winning 11-9 vs 11-2). The formula?</p>
|
||
<pre tabindex="0"><code>weighted_score = base_score + tanh(margin/11 × 0.3) × (base_score - 0.5)
|
||
</code></pre><p><strong>Translation:</strong> I took the hyperbolic tangent of a fraction, multiplied by an arbitrary constant (why 0.3? No particular reason), and called it science.</p>
|
||
<p>This is what’s known as “making stuff up.” It had no theoretical basis and was impossible to explain to players.</p>
|
||
<h2 id="the-doubles-problem">The Doubles Problem</h2>
|
||
<p>The old system calculated team ratings by averaging both partners’ ratings. Sounds reasonable, right?</p>
|
||
<p>Until you think about it: If you (1400) play with a strong partner (1700) against two 1550s, the system thinks it’s an even match. But <em>you</em> were carried by a stronger player! Winning that match shouldn’t boost your rating as much as winning with a weaker partner.</p>
|
||
<p>The system didn’t account for partner strength, making it unfair for everyone.</p>
|
||
<h2 id="enter-pure-elo">Enter: Pure ELO</h2>
|
||
<p>ELO is elegantly simple. Every player has <em>one number</em> representing their skill. When two players compete:</p>
|
||
<ol>
|
||
<li>Calculate the probability that one player beats the other based on rating difference</li>
|
||
<li>Compare expected performance to actual performance</li>
|
||
<li>Adjust ratings based on the difference</li>
|
||
</ol>
|
||
<p>The key formula is:</p>
|
||
<pre tabindex="0"><code>Expected Win Probability = 1 / (1 + 10^((opponent_rating - your_rating) / 400))
|
||
</code></pre><p>If you’re 1500 and your opponent is 1500, you should win 50% of the time. If you’re 1600 and they’re 1500, you should win about 64% of the time. Simple.</p>
|
||
<p>After a match:</p>
|
||
<pre tabindex="0"><code>Rating Change = K × (Actual Performance - Expected Performance)
|
||
</code></pre><p>Where <code>K = 32</code> (how much weight each match carries) and <code>Actual Performance</code> is your <em>per-point performance</em>:</p>
|
||
<pre tabindex="0"><code>Actual Performance = Points Scored / Total Points Played
|
||
</code></pre><p>Win 11-9? That’s 0.55 (55% of points). Win 11-2? That’s 0.846 (84.6%). This captures match quality far better than binary win/loss.</p>
|
||
<h2 id="the-secret-sauce-the-effective-opponent-formula">The Secret Sauce: The Effective Opponent Formula</h2>
|
||
<p>In doubles, we use:</p>
|
||
<pre tabindex="0"><code>Effective Opponent Rating = Opponent1 + Opponent2 - Your Teammate
|
||
</code></pre><p><strong>Why this works:</strong></p>
|
||
<p>If your teammate is strong, the effective opponent rating drops—because your teammate made the match easier. If your teammate is weak, the effective opponent rating rises—because you were undermanned.</p>
|
||
<p>Beating 1500-rated opponents with a 1600-rated partner? Effective opponent: 1400. You gain less because your partner carried you.</p>
|
||
<p>Beating 1500-rated opponents with a 1400-rated partner? Effective opponent: 1600. You gain more because you did heavy lifting.</p>
|
||
<p>This is <em>fair</em>.</p>
|
||
<h2 id="the-migration-before-and-after">The Migration: Before and After</h2>
|
||
<p>Here’s where things get spicy. I replayed all 29 historical matches through the new ELO system:</p>
|
||
<table style="width: 100%; border-collapse: collapse; margin: 20px 0;">
|
||
<tr style="background-color: #f0f0f0;">
|
||
<th style="border: 1px solid #ddd; padding: 10px; text-align: left;">Player</th>
|
||
<th style="border: 1px solid #ddd; padding: 10px; text-align: right;">Old Glicko-2</th>
|
||
<th style="border: 1px solid #ddd; padding: 10px; text-align: right;">New ELO</th>
|
||
<th style="border: 1px solid #ddd; padding: 10px; text-align: right;">Change</th>
|
||
<th style="border: 1px solid #ddd; padding: 10px; text-align: right;">Matches</th>
|
||
</tr>
|
||
<tr>
|
||
<td style="border: 1px solid #ddd; padding: 10px;">Andrew Stricklin</td>
|
||
<td style="border: 1px solid #ddd; padding: 10px; text-align: right;">1651</td>
|
||
<td style="border: 1px solid #ddd; padding: 10px; text-align: right;">1538</td>
|
||
<td style="border: 1px solid #ddd; padding: 10px; text-align: right;"><span style="color: #c80000;">−113</span></td>
|
||
<td style="border: 1px solid #ddd; padding: 10px; text-align: right;">19</td>
|
||
</tr>
|
||
<tr style="background-color: #fafafa;">
|
||
<td style="border: 1px solid #ddd; padding: 10px;">David Pabst</td>
|
||
<td style="border: 1px solid #ddd; padding: 10px; text-align: right;">1562</td>
|
||
<td style="border: 1px solid #ddd; padding: 10px; text-align: right;">1522</td>
|
||
<td style="border: 1px solid #ddd; padding: 10px; text-align: right;"><span style="color: #c80000;">−40</span></td>
|
||
<td style="border: 1px solid #ddd; padding: 10px; text-align: right;">11</td>
|
||
</tr>
|
||
<tr>
|
||
<td style="border: 1px solid #ddd; padding: 10px;">Jacklyn Wyszynski</td>
|
||
<td style="border: 1px solid #ddd; padding: 10px; text-align: right;">1557</td>
|
||
<td style="border: 1px solid #ddd; padding: 10px; text-align: right;">1514</td>
|
||
<td style="border: 1px solid #ddd; padding: 10px; text-align: right;"><span style="color: #c80000;">−43</span></td>
|
||
<td style="border: 1px solid #ddd; padding: 10px; text-align: right;">9</td>
|
||
</tr>
|
||
<tr style="background-color: #fafafa;">
|
||
<td style="border: 1px solid #ddd; padding: 10px;">Eliana Crew</td>
|
||
<td style="border: 1px solid #ddd; padding: 10px; text-align: right;">1485</td>
|
||
<td style="border: 1px solid #ddd; padding: 10px; text-align: right;">1497</td>
|
||
<td style="border: 1px solid #ddd; padding: 10px; text-align: right;"><span style="color: #00640a;">+11</span></td>
|
||
<td style="border: 1px solid #ddd; padding: 10px; text-align: right;">13</td>
|
||
</tr>
|
||
<tr>
|
||
<td style="border: 1px solid #ddd; padding: 10px;">Krzysztof Radziszeski</td>
|
||
<td style="border: 1px solid #ddd; padding: 10px; text-align: right;">1473</td>
|
||
<td style="border: 1px solid #ddd; padding: 10px; text-align: right;">1476</td>
|
||
<td style="border: 1px solid #ddd; padding: 10px; text-align: right;"><span style="color: #00640a;">+3</span></td>
|
||
<td style="border: 1px solid #ddd; padding: 10px; text-align: right;">25</td>
|
||
</tr>
|
||
<tr style="background-color: #fafafa;">
|
||
<td style="border: 1px solid #ddd; padding: 10px;"><strong>Dane Sabo</strong></td>
|
||
<td style="border: 1px solid #ddd; padding: 10px; text-align: right;">1290</td>
|
||
<td style="border: 1px solid #ddd; padding: 10px; text-align: right;">1449</td>
|
||
<td style="border: 1px solid #ddd; padding: 10px; text-align: right;"><strong><span style="color: #00640a;">+159</span></strong></td>
|
||
<td style="border: 1px solid #ddd; padding: 10px; text-align: right;">25</td>
|
||
</tr>
|
||
</table>
|
||
<h3 id="observations">Observations</h3>
|
||
<p><strong>The Rating Spread Compressed</strong></p>
|
||
<p>The old system spread players across 361 rating points. The new system compresses them into 89 points. This makes sense—we’re a recreational group, not chess grandmasters. The new system rates us fairly within a tighter band.</p>
|
||
<p><strong>The Winners</strong></p>
|
||
<ul>
|
||
<li><strong>Dane Sabo</strong>: +159 points. The old system penalized him for losses with weaker partners. The effective opponent formula gives credit for “carrying.” (Purely coincidental that I benefit from my own math.)</li>
|
||
<li><strong>Eliana Crew</strong>: +11 points</li>
|
||
<li><strong>Krzysztof Radziszeski</strong>: +3 points</li>
|
||
</ul>
|
||
<p><strong>The Losers</strong></p>
|
||
<ul>
|
||
<li><strong>Andrew Stricklin</strong>: −113 points. Still ranked #1, but the old system over-credited wins with strong partners.</li>
|
||
<li><strong>Jacklyn Wyszynski</strong>: −43 points</li>
|
||
<li><strong>David Pabst</strong>: −40 points</li>
|
||
</ul>
|
||
<h2 id="a-note-on-conflicts-of-interest">A Note on Conflicts of Interest</h2>
|
||
<p>You may notice that the system designer (me) is also the biggest beneficiary of the new ratings, gaining a convenient 159 points.</p>
|
||
<p>I want to assure you this is <em>purely coincidental</em> and the result of <em>rigorous mathematical analysis</em>, not at all influenced by the fact that I was tired of being ranked last.</p>
|
||
<p>The new formulas are based on <em>sound theoretical principles</em> that just <em>happen</em> to conclude I was being unfairly penalized all along.</p>
|
||
<p><em>Trust the math.</em> 😉</p>
|
||
<h2 id="why-this-system-works">Why This System Works</h2>
|
||
<p><strong>For a small league:</strong></p>
|
||
<ul>
|
||
<li>Simple to understand (one rating per player)</li>
|
||
<li>Fair to individual skill (per-point scoring)</li>
|
||
<li>Respects partnership (effective opponent formula)</li>
|
||
<li>Transparent (you can calculate rating changes yourself)</li>
|
||
<li>Fast convergence (5-10 matches to stabilize a rating)</li>
|
||
</ul>
|
||
<p><strong>The bottom line:</strong> Your rating now reflects your true skill more accurately than before. Even if it means Dane finally looks respectable.</p>
|
||
|
||
</div>
|
||
|
||
|
||
</article>
|
||
</main>
|
||
|
||
</div><footer>
|
||
<span>This website was built using Hugo and the 'notrack' theme.</span>
|
||
</footer>
|
||
</body>
|
||
</html>
|