<?xml version="1.0" encoding="UTF-8"?>
<rss  xmlns:atom="http://www.w3.org/2005/Atom" 
      xmlns:media="http://search.yahoo.com/mrss/" 
      xmlns:content="http://purl.org/rss/1.0/modules/content/" 
      xmlns:dc="http://purl.org/dc/elements/1.1/" 
      version="2.0">
<channel>
<title>BenDiagrams</title>
<link>https://bendiagrams.com/writing.html</link>
<atom:link href="https://bendiagrams.com/writing.xml" rel="self" type="application/rss+xml"/>
<description>Weekly data analysis, mostly to figure out how something actually works.</description>
<image>
<url>https://bendiagrams.com/images/logo.png</url>
<title>BenDiagrams</title>
<link>https://bendiagrams.com/writing.html</link>
<height>71</height>
<width>144</width>
</image>
<generator>quarto-1.6.41</generator>
<lastBuildDate>Thu, 20 Aug 2026 00:00:00 GMT</lastBuildDate>
<item>
  <title>What Kalshi Thinks Will Win the College Football Playoff</title>
  <dc:creator>Ben Ballard</dc:creator>
  <link>https://bendiagrams.com/posts/2026-08-20-what-kalshi-thinks-will-win-the-college-football-playoff/</link>
  <description><![CDATA[ 




<p>Kalshi runs a market for who wins the 2026 season’s College Football Playoff National Championship: fifty separate contracts, one per team, each paying $1 if that team wins and $0 if it doesn’t. The price of a contract is denominated in cents, and if you squint, a cent is a percentage point — a contract trading at 9¢ is the market saying “9%.”</p>
<p>That’s the pitch for prediction markets: instead of asking an analyst for their gut feeling, you get a number people were willing to bet real money on. First question worth checking, before trusting that number for anything: does it actually behave like a probability?</p>
<section id="pulling-the-board" class="level2">
<h2 class="anchored" data-anchor-id="pulling-the-board">Pulling the board</h2>
<div id="3d190145" class="cell" data-execution_count="1">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> pandas <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> pd</span>
<span id="cb1-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> sitelib.kalshi <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> KalshiClient</span>
<span id="cb1-3"></span>
<span id="cb1-4">client <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> KalshiClient()</span>
<span id="cb1-5">markets <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.get_markets(series_ticker<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"KXNCAAF"</span>, status<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"open"</span>)</span>
<span id="cb1-6"></span>
<span id="cb1-7">df <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pd.DataFrame(</span>
<span id="cb1-8">    {</span>
<span id="cb1-9">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"team"</span>: m[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"yes_sub_title"</span>],</span>
<span id="cb1-10">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bid"</span>: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(m[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"yes_bid_dollars"</span>]),</span>
<span id="cb1-11">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ask"</span>: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(m[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"yes_ask_dollars"</span>]),</span>
<span id="cb1-12">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"last"</span>: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(m[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"last_price_dollars"</span>]),</span>
<span id="cb1-13">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"volume"</span>: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(m[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"volume_fp"</span>]),</span>
<span id="cb1-14">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"open_interest"</span>: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(m[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"open_interest_fp"</span>]),</span>
<span id="cb1-15">    }</span>
<span id="cb1-16">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> m <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> markets</span>
<span id="cb1-17">).sort_values(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ask"</span>, ascending<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>).reset_index(drop<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb1-18"></span>
<span id="cb1-19"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(df)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> teams still have a contract on the board"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>50 teams still have a contract on the board</code></pre>
</div>
</div>
<p>Each row is a bid and an ask, same as any order book: the ask is what it costs to bet <em>yes</em> on that team right now, the bid is what someone will pay you to bet <em>yes</em> right now. The gap between them is the cost of trading immediately rather than waiting for a better price — call it the market’s estimate, plus a small tax for wanting an answer this second.</p>
</section>
<section id="the-favorites" class="level2">
<h2 class="anchored" data-anchor-id="the-favorites">The favorites</h2>
<div id="cell-fig-favorites" class="cell" data-execution_count="2">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> matplotlib.pyplot <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> plt</span>
<span id="cb3-2"></span>
<span id="cb3-3">top <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> df.head(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>).iloc[::<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb3-4"></span>
<span id="cb3-5">fig, ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>))</span>
<span id="cb3-6">ax.barh(top[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"team"</span>], top[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ask"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#1f4e79"</span>)</span>
<span id="cb3-7">ax.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Implied win probability (%)"</span>)</span>
<span id="cb3-8">ax.spines[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"top"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"right"</span>]].set_visible(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb3-9">plt.show()</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-favorites" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-favorites-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://bendiagrams.com/posts/2026-08-20-what-kalshi-thinks-will-win-the-college-football-playoff/index_files/figure-html/fig-favorites-output-1.png" class="img-fluid figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-favorites-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: Implied win probability (ask price) for the ten shortest-odds teams in the CFP championship market.
</figcaption>
</figure>
</div>
</div>
</div>
<p>Figure&nbsp;1 is the market’s answer to “who’s going to win,” priced in real dollars rather than a preseason poll. It’ll be a different bar chart by January — that’s the point of pulling it from a live order book instead of writing down an opinion.</p>
</section>
<section id="where-the-probability-story-breaks-down" class="level2">
<h2 class="anchored" data-anchor-id="where-the-probability-story-breaks-down">Where the probability story breaks down</h2>
<p>Two things in this board don’t behave the way “price equals probability” would suggest.</p>
<p><strong>The price floor.</strong> Kalshi won’t quote a contract below 1¢. Look at the bottom of the board:</p>
<div id="704083f1" class="cell" data-execution_count="3">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1">bottom <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> df.tail(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>)[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"team"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bid"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ask"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"volume"</span>]]</span>
<span id="cb4-2">bottom</span></code></pre></div>
</details>
<div class="cell-output cell-output-display" data-execution_count="3">
<div>


<table class="dataframe caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">team</th>
<th data-quarto-table-cell-role="th">bid</th>
<th data-quarto-table-cell-role="th">ask</th>
<th data-quarto-table-cell-role="th">volume</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">42</td>
<td>Clemson</td>
<td>0.0</td>
<td>0.01</td>
<td>211823.72</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">43</td>
<td>California</td>
<td>0.0</td>
<td>0.01</td>
<td>47647.64</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">44</td>
<td>Baylor</td>
<td>0.0</td>
<td>0.01</td>
<td>19676.03</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">45</td>
<td>BYU</td>
<td>0.0</td>
<td>0.01</td>
<td>117604.55</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">46</td>
<td>Auburn</td>
<td>0.0</td>
<td>0.01</td>
<td>145738.31</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">47</td>
<td>Arizona St.</td>
<td>0.0</td>
<td>0.01</td>
<td>9134.36</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">48</td>
<td>Arkansas</td>
<td>0.0</td>
<td>0.01</td>
<td>10684.40</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">49</td>
<td>Arizona</td>
<td>0.0</td>
<td>0.01</td>
<td>14638.93</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
<p>Every team here is asking 1¢ with a 0¢ bid and little or no volume — not because the market has priced each of them at exactly a 1-in-100 shot, but because a cent is the smallest unit Kalshi trades in and nobody has bothered to trade these at all. A true probability estimate would let a hundred different longshots be a hundred different sizes of unlikely. This board can’t tell you if a team is a 1% shot or a 0.01% shot; both round to the same 1¢ floor.</p>
<p><strong>The book doesn’t sum to 100%.</strong> Exactly one of these fifty teams will win, so a coherent set of probabilities should add up to 1.0.</p>
<div id="e6638e8f" class="cell" data-execution_count="4">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1">implied_total <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> df[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ask"</span>].<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>()</span>
<span id="cb5-2"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"sum of ask-price implied probabilities: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>implied_total<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>sum of ask-price implied probabilities: 1.43</code></pre>
</div>
</div>
<p>It doesn’t — it adds up to noticeably more than one. That gap is the same thing as the vig on a sportsbook line: paying the <em>ask</em> on every team simultaneously would cost more than $1 total, guaranteeing a loss no matter who wins. The overround is spread unevenly, too — thin, 1¢-floored longshots contribute more excess probability per team than liquid favorites do, because the floor stops their price from ever reading as low as their real chances.</p>
</section>
<section id="what-id-check-next" class="level2">
<h2 class="anchored" data-anchor-id="what-id-check-next">What I’d check next</h2>
<p>Whether the favorites list actually tracks something real — lining this up against a win-total or power-rating source partway through the season, to see whether the market or the model moves first when a team starts losing. And whether the overround shrinks as the season progresses and the field of fifty collapses to a handful of live contenders with real volume behind them.</p>


<!-- -->

</section>

 ]]></description>
  <category>kalshi</category>
  <category>prediction-markets</category>
  <category>college-football</category>
  <guid>https://bendiagrams.com/posts/2026-08-20-what-kalshi-thinks-will-win-the-college-football-playoff/</guid>
  <pubDate>Thu, 20 Aug 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>How This Site Works</title>
  <dc:creator>Ben Ballard</dc:creator>
  <link>https://bendiagrams.com/posts/2026-08-18-how-this-site-works/</link>
  <description><![CDATA[ 




<p>This is the first post, so it may as well document the machine.</p>
<p>The goal is a weekly analysis habit with as little friction as possible between “I wonder about X” and a published page. Two things usually kill that habit: the publishing step being annoying, and old work breaking when you rebuild the site. Both are solved below.</p>
<section id="a-post-is-a-directory" class="level2">
<h2 class="anchored" data-anchor-id="a-post-is-a-directory">A post is a directory</h2>
<p>Every post lives in its own folder under <code>posts/</code>:</p>
<pre><code>posts/
  2026-08-18-how-this-site-works/
    index.qmd
  2026-08-25-something-else/
    index.qmd
    data.csv</code></pre>
<p>The URL comes from the folder name, so <code>index.qmd</code> is always the file I edit. Data, images and scratch files for a post sit next to it instead of in a shared <code>data/</code> pile that nobody can untangle six months later. A post is self-contained enough to delete in one move.</p>
<p>The date prefix in the folder name is for my benefit when sorting files; the date the site actually uses is the <code>date:</code> field in the YAML header.</p>
</section>
<section id="old-posts-never-re-run" class="level2">
<h2 class="anchored" data-anchor-id="old-posts-never-re-run">Old posts never re-run</h2>
<p>The setting doing the most work on this site is one line in <code>_quarto.yml</code>:</p>
<div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode yaml code-with-copy"><code class="sourceCode yaml"><span id="cb2-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">execute</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb2-2"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">freeze</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> auto</span></span></code></pre></div>
<p>Without it, every rebuild re-executes every post. That means last year’s analysis re-hits an API that may have changed its schema, or needs a credential I’ve since rotated, or quietly produces different numbers because the upstream data was revised — and the published post silently changes underneath a conclusion I wrote about the old numbers.</p>
<p>With <code>freeze: auto</code>, a post executes once. Its outputs get written to <code>_freeze/</code> and are reused forever after, until I edit that post’s source. Rebuilding the site becomes a pure markdown-to-HTML operation for everything except the post I’m actually working on.</p>
<p>The consequence worth remembering: <strong><code>_freeze/</code> is committed to git.</strong> It is not a cache to gitignore, it is the record of what the code returned on the day it ran.</p>
</section>
<section id="code-is-present-but-out-of-the-way" class="level2">
<h2 class="anchored" data-anchor-id="code-is-present-but-out-of-the-way">Code is present but out of the way</h2>
<p>Code blocks are collapsed behind a toggle by default, set once in <code>posts/_metadata.yml</code>. The prose reads first; the method is one click away. Here is a real one — a check on whether a coin-flip streak of 7 is actually unusual over a season’s worth of flips, which is the kind of “that seems surprising” claim that is usually just sample size.</p>
<div id="38327ece" class="cell" data-execution_count="1">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> numpy <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> np</span>
<span id="cb3-2"></span>
<span id="cb3-3">rng <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.random.default_rng(seed<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">42</span>)</span>
<span id="cb3-4"></span>
<span id="cb3-5"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> longest_streak(flips):</span>
<span id="cb3-6">    best <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> current <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb3-7">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(flips)):</span>
<span id="cb3-8">        current <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> current <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> flips[i] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> flips[i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb3-9">        best <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(best, current)</span>
<span id="cb3-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> best</span>
<span id="cb3-11"></span>
<span id="cb3-12">trials <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10_000</span></span>
<span id="cb3-13">n_flips <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">162</span></span>
<span id="cb3-14">streaks <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.array([</span>
<span id="cb3-15">    longest_streak(rng.integers(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>n_flips))</span>
<span id="cb3-16">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(trials)</span>
<span id="cb3-17">])</span>
<span id="cb3-18"></span>
<span id="cb3-19"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"median longest streak: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>np<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>median(streaks)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.0f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb3-20"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"share of seasons with a streak of 7 or more: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>(streaks <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">7</span>)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>mean()<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1%}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>median longest streak: 7
share of seasons with a streak of 7 or more: 73.4%</code></pre>
</div>
</div>
<p>A run of seven is the <em>typical</em> outcome, not the surprising one. That is the whole genre of post I want to write here.</p>
<div id="cell-fig-streaks" class="cell" data-execution_count="2">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> matplotlib.pyplot <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> plt</span>
<span id="cb5-2"></span>
<span id="cb5-3">fig, ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.5</span>))</span>
<span id="cb5-4">values, counts <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.unique(streaks, return_counts<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb5-5">ax.bar(values, counts <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> trials, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#1f4e79"</span>, width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7</span>)</span>
<span id="cb5-6">ax.axvline(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">7</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#c0392b"</span>, linestyle<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"--"</span>, linewidth<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.5</span>)</span>
<span id="cb5-7">ax.text(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">7.15</span>, ax.get_ylim()[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.9</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"a streak of 7"</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#c0392b"</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>)</span>
<span id="cb5-8">ax.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Longest streak in a season"</span>)</span>
<span id="cb5-9">ax.set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Share of simulated seasons"</span>)</span>
<span id="cb5-10">ax.spines[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"top"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"right"</span>]].set_visible(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb5-11">plt.show()</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-streaks" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-streaks-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://bendiagrams.com/posts/2026-08-18-how-this-site-works/index_files/figure-html/fig-streaks-output-1.png" class="img-fluid figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-streaks-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: Longest run of identical outcomes in 162 fair coin flips, across 10,000 simulated seasons.
</figcaption>
</figure>
</div>
</div>
</div>
<p>Referring to it as Figure&nbsp;1 gives a numbered cross-reference that works in every output format, which matters for the next section.</p>
</section>
<section id="interactive-where-it-helps" class="level2">
<h2 class="anchored" data-anchor-id="interactive-where-it-helps">Interactive where it helps</h2>
<p>Plotly figures work on the web version of a post:</p>
<div class="cell" data-execution_count="3">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> plotly.express <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> px</span>
<span id="cb6-2"></span>
<span id="cb6-3">fig <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> px.bar(</span>
<span id="cb6-4">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>values,</span>
<span id="cb6-5">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>counts <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> trials,</span>
<span id="cb6-6">    labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>{<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"x"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Longest streak in a season"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"y"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Share of seasons"</span>},</span>
<span id="cb6-7">    template<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"plotly_white"</span>,</span>
<span id="cb6-8">)</span>
<span id="cb6-9">fig.update_traces(marker_color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#1f4e79"</span>)</span>
<span id="cb6-10">fig.update_layout(height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">400</span>, margin<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>(l<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">40</span>, r<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">20</span>, t<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">20</span>, b<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">40</span>))</span>
<span id="cb6-11">fig</span></code></pre></div>
</details>
<div id="fig-interactive" class="cell quarto-float quarto-figure quarto-figure-center anchored" data-execution_count="3">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-interactive-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div id="fig-interactive-1" class="cell-output cell-output-display quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-subfloat-fig figure">
<div aria-describedby="fig-interactive-1-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
        <script>
        window.PlotlyConfig = {MathJaxConfig: 'local'};
        if (window.MathJax && window.MathJax.Hub && window.MathJax.Hub.Config) {window.MathJax.Hub.Config({SVG: {font: "STIX-Web"}});}
        </script>
        <script type="module">import "https://cdn.plot.ly/plotly-3.7.0.min"</script>
        
</div>
<figcaption class="quarto-float-caption-bottom quarto-subfloat-caption quarto-subfloat-fig" id="fig-interactive-1-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
(a) Same distribution, hover for exact values.
</figcaption>
</figure>
</div>
<div id="fig-interactive-2" class="cell-output cell-output-display quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-subfloat-fig figure">
<div aria-describedby="fig-interactive-2-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div style="height:400px; width:100%;">            <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_SVG"></script><script>if (window.MathJax && window.MathJax.Hub && window.MathJax.Hub.Config) {window.MathJax.Hub.Config({SVG: {font: "STIX-Web"}});}</script>                <script>window.PlotlyConfig = {MathJaxConfig: 'local'};</script>
        <script charset="utf-8" src="https://cdn.plot.ly/plotly-3.7.0.min.js" integrity="sha256-jvTGqxNp8AGWEcvNLVuKr+8j5dGe9Yw51LQkmDH+IYA=" crossorigin="anonymous"></script>                <div id="382276bf-2297-46ae-b696-e6bf3a11e6d3" class="plotly-graph-div" style="height:100%; width:100%;"></div>            <script>                window.PLOTLYENV=window.PLOTLYENV || {};                                if (document.getElementById("382276bf-2297-46ae-b696-e6bf3a11e6d3")) {                    Plotly.newPlot(                        "382276bf-2297-46ae-b696-e6bf3a11e6d3",                        [{"hovertemplate":"Longest streak in a season=%{x}\u003cbr\u003eShare of seasons=%{y}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"","marker":{"color":"#1f4e79","pattern":{"shape":""}},"name":"","orientation":"v","showlegend":false,"textposition":"auto","x":{"dtype":"i1","bdata":"BAUGBwgJCgsMDQ4PEBESExQ="},"xaxis":"x","y":{"dtype":"f8","bdata":"YTJVMCqpYz+wcmiR7XyvP0Jg5dAi28k\u002fTmIQWDm00D9bQj7o2azKP6rx0k1iEMA\u002fIv32deCcsT8N4C2QoPihP9\u002fgC5OpgpE\u002f\u002fKnx0k1igD+hZ7Pqc7V1Py9uowG8BWI\u002flPYGX5hMVT9hMlUwKqlDPy1DHOviNjo\u002fYTJVMCqpMz8tQxzr4jYaPw=="},"yaxis":"y","type":"bar"}],                        {"template":{"data":{"barpolar":[{"marker":{"line":{"color":"white","width":0.5},"pattern":{"fillmode":"overlay","size":10,"solidity":0.2}},"type":"barpolar"}],"bar":[{"error_x":{"color":"#2a3f5f"},"error_y":{"color":"#2a3f5f"},"marker":{"line":{"color":"white","width":0.5},"pattern":{"fillmode":"overlay","size":10,"solidity":0.2}},"type":"bar"}],"carpet":[{"aaxis":{"endlinecolor":"#2a3f5f","gridcolor":"#C8D4E3","linecolor":"#C8D4E3","minorgridcolor":"#C8D4E3","startlinecolor":"#2a3f5f"},"baxis":{"endlinecolor":"#2a3f5f","gridcolor":"#C8D4E3","linecolor":"#C8D4E3","minorgridcolor":"#C8D4E3","startlinecolor":"#2a3f5f"},"type":"carpet"}],"choropleth":[{"colorbar":{"outlinewidth":0,"ticks":""},"type":"choropleth"}],"contourcarpet":[{"colorbar":{"outlinewidth":0,"ticks":""},"type":"contourcarpet"}],"contour":[{"colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"type":"contour"}],"heatmap":[{"colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"type":"heatmap"}],"histogram2dcontour":[{"colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"type":"histogram2dcontour"}],"histogram2d":[{"colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"type":"histogram2d"}],"histogram":[{"marker":{"pattern":{"fillmode":"overlay","size":10,"solidity":0.2}},"type":"histogram"}],"mesh3d":[{"colorbar":{"outlinewidth":0,"ticks":""},"type":"mesh3d"}],"parcoords":[{"line":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"parcoords"}],"pie":[{"automargin":true,"type":"pie"}],"scatter3d":[{"line":{"colorbar":{"outlinewidth":0,"ticks":""}},"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scatter3d"}],"scattercarpet":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scattercarpet"}],"scattergeo":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scattergeo"}],"scattergl":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scattergl"}],"scattermapbox":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scattermapbox"}],"scattermap":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scattermap"}],"scatterpolargl":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scatterpolargl"}],"scatterpolar":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scatterpolar"}],"scatter":[{"fillpattern":{"fillmode":"overlay","size":10,"solidity":0.2},"type":"scatter"}],"scatterternary":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scatterternary"}],"surface":[{"colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"type":"surface"}],"table":[{"cells":{"fill":{"color":"#EBF0F8"},"line":{"color":"white"}},"header":{"fill":{"color":"#C8D4E3"},"line":{"color":"white"}},"type":"table"}]},"layout":{"annotationdefaults":{"arrowcolor":"#2a3f5f","arrowhead":0,"arrowwidth":1},"autotypenumbers":"strict","coloraxis":{"colorbar":{"outlinewidth":0,"ticks":""}},"colorscale":{"diverging":[[0,"#8e0152"],[0.1,"#c51b7d"],[0.2,"#de77ae"],[0.3,"#f1b6da"],[0.4,"#fde0ef"],[0.5,"#f7f7f7"],[0.6,"#e6f5d0"],[0.7,"#b8e186"],[0.8,"#7fbc41"],[0.9,"#4d9221"],[1,"#276419"]],"sequential":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"sequentialminus":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]},"colorway":["#636efa","#EF553B","#00cc96","#ab63fa","#FFA15A","#19d3f3","#FF6692","#B6E880","#FF97FF","#FECB52"],"font":{"color":"#2a3f5f"},"geo":{"bgcolor":"white","lakecolor":"white","landcolor":"white","showlakes":true,"showland":true,"subunitcolor":"#C8D4E3"},"hoverlabel":{"align":"left"},"hovermode":"closest","mapbox":{"style":"light"},"margin":{"b":0,"l":0,"r":0,"t":30},"paper_bgcolor":"white","plot_bgcolor":"white","polar":{"angularaxis":{"gridcolor":"#EBF0F8","linecolor":"#EBF0F8","ticks":""},"bgcolor":"white","radialaxis":{"gridcolor":"#EBF0F8","linecolor":"#EBF0F8","ticks":""}},"scene":{"xaxis":{"backgroundcolor":"white","gridcolor":"#DFE8F3","gridwidth":2,"linecolor":"#EBF0F8","showbackground":true,"ticks":"","zerolinecolor":"#EBF0F8"},"yaxis":{"backgroundcolor":"white","gridcolor":"#DFE8F3","gridwidth":2,"linecolor":"#EBF0F8","showbackground":true,"ticks":"","zerolinecolor":"#EBF0F8"},"zaxis":{"backgroundcolor":"white","gridcolor":"#DFE8F3","gridwidth":2,"linecolor":"#EBF0F8","showbackground":true,"ticks":"","zerolinecolor":"#EBF0F8"}},"shapedefaults":{"line":{"color":"#2a3f5f"}},"ternary":{"aaxis":{"gridcolor":"#DFE8F3","linecolor":"#A2B1C6","ticks":""},"baxis":{"gridcolor":"#DFE8F3","linecolor":"#A2B1C6","ticks":""},"bgcolor":"white","caxis":{"gridcolor":"#DFE8F3","linecolor":"#A2B1C6","ticks":""}},"title":{"x":0.05},"xaxis":{"automargin":true,"gridcolor":"#EBF0F8","linecolor":"#EBF0F8","ticks":"","title":{"standoff":15},"zerolinecolor":"#EBF0F8","zerolinewidth":2},"yaxis":{"automargin":true,"gridcolor":"#EBF0F8","linecolor":"#EBF0F8","ticks":"","title":{"standoff":15},"zerolinecolor":"#EBF0F8","zerolinewidth":2}}},"xaxis":{"anchor":"y","domain":[0.0,1.0],"title":{"text":"Longest streak in a season"}},"yaxis":{"anchor":"x","domain":[0.0,1.0],"title":{"text":"Share of seasons"}},"legend":{"tracegroupgap":0},"barmode":"relative","margin":{"l":40,"r":20,"t":20,"b":40},"height":400},                        {"responsive": true}                    ).then(function(){
                            
var gd = document.getElementById('382276bf-2297-46ae-b696-e6bf3a11e6d3');
var x = new MutationObserver(function (mutations, observer) {{
        var display = window.getComputedStyle(gd).display;
        if (!display || display === 'none') {{
            console.log([gd, 'removed!']);
            Plotly.purge(gd);
            observer.disconnect();
        }}
}});

// Listen for the removal of the full notebook cells
var notebookContainer = gd.closest('#notebook-container');
if (notebookContainer) {{
    x.observe(notebookContainer, {childList: true});
}}

// Listen for the clearing of the current output cell
var outputEl = gd.closest('.output');
if (outputEl) {{
    x.observe(outputEl, {childList: true});
}}

                        })                };            </script>        </div>
</div>
<figcaption class="quarto-float-caption-bottom quarto-subfloat-caption quarto-subfloat-fig" id="fig-interactive-2-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
(b)
</figcaption>
</figure>
</div>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-interactive-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2
</figcaption>
</figure>
</div>
</div>
<p>The tradeoff: an interactive figure cannot render into a PDF or a PowerPoint slide — it is JavaScript, and there is nothing for a static page to show. So any post I also want as a document uses matplotlib. <a href="../2026-08-18-one-source-many-outputs/">The next post</a> is about exactly that.</p>
</section>
<section id="publishing" class="level2">
<h2 class="anchored" data-anchor-id="publishing">Publishing</h2>
<p><code>quarto preview</code> while writing, then commit and push. A GitHub Actions workflow renders the site and deploys it. Because <code>_freeze/</code> is in the repo, that build does not need my API keys or my local data — it only executes posts whose source actually changed.</p>
<p>That is the entire loop: make a folder, write, push.</p>


<!-- -->

</section>

 ]]></description>
  <category>meta</category>
  <category>quarto</category>
  <category>tooling</category>
  <guid>https://bendiagrams.com/posts/2026-08-18-how-this-site-works/</guid>
  <pubDate>Tue, 18 Aug 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>One Source, Many Outputs</title>
  <dc:creator>Ben Ballard</dc:creator>
  <link>https://bendiagrams.com/posts/2026-08-18-one-source-many-outputs/</link>
  <description><![CDATA[ 




<p>The reason to write analysis in Quarto rather than a notebook plus a separate deck is that the deck stops drifting from the analysis. This page and the PDF, reveal.js and PowerPoint versions linked beside the title all came from one <code>index.qmd</code>. Nothing was copied.</p>
<p>Getting that to work is mostly about three constraints, which are easier to accept up front than to retrofit.</p>
<section id="rule-1-headings-are-slide-boundaries" class="level2">
<h2 class="anchored" data-anchor-id="rule-1-headings-are-slide-boundaries">Rule 1: Headings are slide boundaries</h2>
<p>In the article formats a <code>##</code> heading starts a section. In reveal.js and PowerPoint the same heading starts a <em>slide</em>, and its content has to fit on one.</p>
<p>That is a real writing constraint, not a formatting detail. It means a few tight paragraphs and one figure per heading. The upside is that prose written this way is better prose — if a section does not fit on a slide, it was probably doing two things at once.</p>
</section>
<section id="rule-2-static-figures-only" class="level2">
<h2 class="anchored" data-anchor-id="rule-2-static-figures-only">Rule 2: Static figures only</h2>
<p>An interactive Plotly chart is JavaScript. There is no sensible way to put it on a PDF page or a PowerPoint slide, so a multi-format document uses matplotlib.</p>
<div id="cell-fig-growth" class="cell" data-execution_count="1">
<details class="code-fold">
<summary>Code</summary>
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> numpy <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> np</span>
<span id="cb1-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> matplotlib.pyplot <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> plt</span>
<span id="cb1-3"></span>
<span id="cb1-4">years <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.arange(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">31</span>)</span>
<span id="cb1-5">fig, ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.2</span>))</span>
<span id="cb1-6"></span>
<span id="cb1-7"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> rate, color <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> [(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.04</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#95a5a6"</span>), (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.07</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#1f4e79"</span>), (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.10</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#c0392b"</span>)]:</span>
<span id="cb1-8">    ax.plot(years, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10_000</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> rate) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span> years,</span>
<span id="cb1-9">            color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>color, linewidth<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>rate<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.0%}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> annual return"</span>)</span>
<span id="cb1-10"></span>
<span id="cb1-11">ax.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Years"</span>)</span>
<span id="cb1-12">ax.set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Value of $10,000"</span>)</span>
<span id="cb1-13">ax.yaxis.set_major_formatter(<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">lambda</span> x, _: <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"$</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1000</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:,.0f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">k"</span>)</span>
<span id="cb1-14">ax.legend(frameon<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>, loc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"upper left"</span>)</span>
<span id="cb1-15">ax.spines[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"top"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"right"</span>]].set_visible(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb1-16">plt.show()</span></code></pre></div>
</details>
<div class="cell-output cell-output-display">
<div id="fig-growth" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-growth-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://bendiagrams.com/posts/2026-08-18-one-source-many-outputs/index_files/figure-html/fig-growth-output-1.png" class="img-fluid figure-img">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-growth-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: Compounding is the least intuitive arithmetic in finance.
</figcaption>
</figure>
</div>
</div>
</div>
<p>Three percentage points of annual return is a factor of two and a half over thirty years, which is Figure&nbsp;1’s entire point.</p>
</section>
<section id="rule-3-content-that-only-belongs-in-one-place-gets-fenced" class="level2">
<h2 class="anchored" data-anchor-id="rule-3-content-that-only-belongs-in-one-place-gets-fenced">Rule 3: Content that only belongs in one place gets fenced</h2>
<p>Some material is web-only — a long appendix, a data dictionary, an interactive widget. Some is slide-only, like a punchline with no supporting text. Quarto’s conditional divs handle both.</p>
<p>This paragraph appears only in the HTML version. It is the natural home for the caveats and the methodology notes that would ruin a slide.</p>
<p>The mechanism is <code>::: {.content-visible when-format="html"}</code>. There is also <code>content-hidden</code>, and <code>when-format</code> accepts <code>pdf</code>, <code>revealjs</code> and <code>pptx</code>.</p>
</section>
<section id="what-the-formats-cost" class="level2">
<h2 class="anchored" data-anchor-id="what-the-formats-cost">What the formats cost</h2>
<p>Each extra format is a line of YAML and a slower render. The honest accounting:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Format</th>
<th>Good for</th>
<th>Watch out for</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>html</code></td>
<td>The canonical post</td>
<td>Nothing; this is the default</td>
</tr>
<tr class="even">
<td><code>pdf</code></td>
<td>Sending to someone, printing</td>
<td>Needs LaTeX; wide tables overflow</td>
</tr>
<tr class="odd">
<td><code>revealjs</code></td>
<td>Presenting from a browser</td>
<td>Content must fit a slide</td>
</tr>
<tr class="even">
<td><code>pptx</code></td>
<td>Someone who needs to edit it</td>
<td>No theme control worth relying on</td>
</tr>
</tbody>
</table>
<p>I default to HTML only, and add formats to a specific post when there is an actual reason. <code>format-links</code> in the HTML sidebar generates the download links automatically, so readers get them for free whenever a post declares extra formats.</p>
</section>
<section id="the-takeaway" class="level2">
<h2 class="anchored" data-anchor-id="the-takeaway">The takeaway</h2>
<p>Write the analysis once, in the format that keeps the code next to the claim. Decide later who needs it as a document.</p>


<!-- -->

</section>

 ]]></description>
  <category>quarto</category>
  <category>tooling</category>
  <guid>https://bendiagrams.com/posts/2026-08-18-one-source-many-outputs/</guid>
  <pubDate>Tue, 18 Aug 2026 00:00:00 GMT</pubDate>
</item>
</channel>
</rss>
