<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Very Joe</title>
    <description>Much website. Wow.
</description>
    <link>https://veryjoe.com/</link>
    <atom:link href="https://veryjoe.com/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Sat, 31 Aug 2024 14:06:30 +0000</pubDate>
    <lastBuildDate>Sat, 31 Aug 2024 14:06:30 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      <item>
        <title>The value of information</title>
        <description>&lt;p&gt;How much should you pay for information to help you make a decision?&lt;/p&gt;

&lt;p&gt;Imagine that I run a business, and I’m thinking of launching a new product. I think it will sell well, but it’s probably worth running a survey or doing some user research to be more certain. How much should I spend on that research?  How much should I &lt;em&gt;value&lt;/em&gt; it?&lt;/p&gt;

&lt;p&gt;If I’m going to spend $100,000 developing and selling the product, that’s the max I stand to lose, but if I’m already fairly certain it will succeed, wouldn’t it be excessive to spend $100,000 on research?&lt;/p&gt;

&lt;p&gt;This post builds upon ideas in the book &lt;a href=&quot;https://www.howtomeasureanything.com/3rd-edition/&quot;&gt;How To Measure Anything&lt;/a&gt; by Douglas W. Hubbard, which I highly recommend. In the book, valuing information is a key part of a larger decision-making framework, but as I read it, I found myself wanting more mathematical justification for some of the statements and ideas.&lt;/p&gt;

&lt;p&gt;This post fills in the gaps, by examining the question of how much one should pay for information using a simple gambling game under the lens of maths and probability. We’ll start by figuring out how much you should pay for perfect information on the outcome of the game, then move on to imperfect information, which might be unreliable or even misleading. Finally, we’ll come away with some rules of thumb for thinking about the value of information.&lt;/p&gt;

&lt;p&gt;Jump to:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;#the-dice-game&quot;&gt;The dice game&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#perfect-information&quot;&gt;Perfect information&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#imperfect-misleading-information&quot;&gt;Imperfect (misleading) information&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#imperfect-useless-information&quot;&gt;Imperfect (useless) information&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#rules-of-thumb-for-thinking-about-the-value-of-information&quot;&gt;Rules of thumb&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;the-dice-game&quot;&gt;The dice game&lt;/h2&gt;

&lt;p&gt;I offer to play the following game as many times as you’d like. I’ll roll a dice, and if it lands on 1 or 2, you lose $900. If it lands on 3, 4, 5 or 6, you win $60. Even though you’re more likely to win than lose, the cost of losing is so great that your losses will far outstrip your winnings after a few games.&lt;/p&gt;

&lt;p&gt;We can calculate the &lt;strong&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Expected_value#Finite_case&quot;&gt;Expected Value&lt;/a&gt;&lt;/strong&gt; of this game - how much you make or lose on average with each play - by multiplying the value of each outcome with its probability, and adding the results.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Outcome&lt;/th&gt;
      &lt;th&gt;Probability&lt;/th&gt;
      &lt;th&gt;Value&lt;/th&gt;
      &lt;th&gt;Expected Value&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;1, 2&lt;/td&gt;
      &lt;td&gt;1/3&lt;/td&gt;
      &lt;td&gt;-$900&lt;/td&gt;
      &lt;td&gt;-$300&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;3, 4, 5, 6&lt;/td&gt;
      &lt;td&gt;2/3&lt;/td&gt;
      &lt;td&gt;$60&lt;/td&gt;
      &lt;td&gt;$40&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Overall&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;-$260&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Or as totally legit math notation: first defining the &lt;a href=&quot;https://en.wikipedia.org/wiki/Probability_mass_function#Finite&quot;&gt;probability mass function&lt;/a&gt; of a fair six-sided dice:&lt;/p&gt;

\[\Pr(x)_X = \begin{cases} 1/6 &amp;amp;  x \in \{1, 2, 3, 4, 5, 6\} \\ 0 &amp;amp; \textrm{otherwise} \\ \end{cases}\]

&lt;p&gt;Then defining the game and computing its expected value as described above:&lt;/p&gt;

\[\begin{align}
F(x) &amp;amp;= \begin{cases}-900 &amp;amp; x \in \{1, 2\}  \\ 60 &amp;amp; x \in \{3, 4, 5, 6\} \end{cases} \\
E[F(x)] &amp;amp;= \sum_{x \in \{1..6\}} \Pr(x)F(x) \\
&amp;amp;= \frac{1}{6} (60 + 60 + 60 + 60 - 900 - 900) \\
&amp;amp;= -260
\end{align}\]

&lt;p&gt;Given that you’ll lose an average of $260 per game, you really shouldn’t want to play it, so we’ll call “don’t play the game” your &lt;strong&gt;default decision&lt;/strong&gt;. If you decide not to play, but I roll the dice to see what would have happened and it lands on a 5, you miss out on $60. That’s &lt;strong&gt;Opportunity Loss&lt;/strong&gt;. Since the chance of that happening is 2/3, the expected value of your opportunity loss, i.e. your &lt;strong&gt;Expected Opportunity Loss&lt;/strong&gt;, is $40.&lt;/p&gt;

&lt;p&gt;Here are the same definitions in handy table format:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Term&lt;/th&gt;
      &lt;th&gt;Definition&lt;/th&gt;
      &lt;th&gt;Value in this game&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Default Decision&lt;/td&gt;
      &lt;td&gt;The decision you would make given the information you have.&lt;/td&gt;
      &lt;td&gt;Don’t play!&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Opportunity Loss&lt;/td&gt;
      &lt;td&gt;The amount that you expect to lose (or not make) if your decision was wrong&lt;/td&gt;
      &lt;td&gt;$60&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Expected Opportunity Loss (EOL)&lt;/td&gt;
      &lt;td&gt;The expected value of Opportunity Loss, i.e. the chance of each loss scenario multiplied by its value.&lt;/td&gt;
      &lt;td&gt;$60 * 2/3 = $40&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;perfect-information&quot;&gt;Perfect information&lt;/h2&gt;

&lt;p&gt;Now I’m going to give you the opportunity to learn the result of the game:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;I’ll roll the dice secretly,&lt;/li&gt;
  &lt;li&gt;For a price, I’ll offer to tell you the result,&lt;/li&gt;
  &lt;li&gt;After buying (or not buying) the result, you’ll decide whether to play.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Because I’m offering you &lt;em&gt;perfect information&lt;/em&gt; on the result of this game, you’re never at risk of losing $900. You either:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Don’t buy the result, and don’t play,&lt;/li&gt;
  &lt;li&gt;Learn that you’ll win and decide to play, or&lt;/li&gt;
  &lt;li&gt;Learn that you’ll lose, decide not to play.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this situation, what’s the absolute maximum you should pay to know the dice roll?&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Perhaps $60? No - if you pay $60, you’ll make that money back 2/3 of the time, but lose it 1/3 of the time. Overall, you expect to lose $20 per game.&lt;/li&gt;
  &lt;li&gt;Perhaps $0? That would be great for you, but I’m not gonna give you the information for free!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s solve this with maths. Defining \(k\) as the amount paid for perfect information, we want to find the greatest value of \(k\) such that the value of playing the game is at least $0. Adding in \(k\), the game is defined as:&lt;/p&gt;

\[G(x) = \begin{cases}- k + 0 &amp;amp; x \in \{1, 2\} \\ - k + 60 &amp;amp; x \in \{3, 4, 5, 6\} \end{cases}\]

&lt;p&gt;Solving \(E[G(x)] \geq 0\) for \(k\).&lt;/p&gt;

\[\begin{align}
0 &amp;amp;\leq E[G(x)] \\
&amp;amp;\leq \sum_{x \in \{1..6\}} \Pr(x)G(x) \\
&amp;amp;\leq \frac{2}{6}(-k) + \frac{4}{6}(- k + 60)\\
&amp;amp;\leq -k + \frac{4 * 60}{6} \\
\therefore k &amp;amp;\leq40
\end{align}\]

&lt;p&gt;The absolute maximum you should pay is $40, at which point you expect to break even. If you can pay less, you’ll make a profit. If you pay more, you’ll lose in the long run. To demonstrate that, this table shows the expected value of each outcome when you pay $40 for information.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Outcome&lt;/th&gt;
      &lt;th&gt;Probability&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;Value&lt;/th&gt;
      &lt;th&gt;Expected Value&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;1, 2&lt;/td&gt;
      &lt;td&gt;1/3&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;-$40 + $0 = -$40 (decide not to play)&lt;/td&gt;
      &lt;td&gt;-$13.33&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;3, 4, 5, 6&lt;/td&gt;
      &lt;td&gt;2/3&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;-$40 + $60 = $20 (decide to play)&lt;/td&gt;
      &lt;td&gt;$13.33&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Overall&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt; &lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;$0&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;What we have found is that the &lt;strong&gt;Expected Value of Perfect Information was equal to the Expected Opportunity Loss&lt;/strong&gt;, or: \(EVPI = EOL\)&lt;/p&gt;

&lt;p&gt;Although perfect information is not always unattainable, it’s clear that you’d  never want to pay more for &lt;em&gt;imperfect&lt;/em&gt; information than for perfect information. This has an interesting consequence: the &lt;em&gt;maximum&lt;/em&gt; you should be willing to pay for information is unrelated to the expected value of your default decision. If we changed the game so that you lose $9 million on a 1 or 2, it doesn’t matter, since with perfect information you’ll never risk that loss anyway!&lt;/p&gt;

&lt;p&gt;On the other hand, if I occasionally lie to you, you definitely &lt;em&gt;would&lt;/em&gt; care if you stood to lose $9 million versus $900. The question is, how much would you care?&lt;/p&gt;

&lt;h2 id=&quot;imperfect-misleading-information&quot;&gt;Imperfect (misleading) information&lt;/h2&gt;

&lt;p&gt;In a new and dangerous version of the game, I offer you information for \($k\), but there’s a chance \(p\) that I’ll lie to you. This will be represented by \(Y\) (for “wh&lt;strong&gt;y&lt;/strong&gt; would you lie to me?!”).&lt;/p&gt;

\[\Pr(y)_Y = \begin{cases} p &amp;amp;  lie \\ 1 - p &amp;amp; truth \\ \end{cases}\]

&lt;p&gt;The outcome of the game is determined by both the dice roll \(x\), and whether I lied \(y\). When I lie, you end up either losing $900, or missing out on $60.&lt;/p&gt;

\[H(x, y) = \begin{cases}
- k + 0 &amp;amp; x \in \{1, 2\} \cap y = truth \\
- k -900 &amp;amp; x \in \{1, 2\} \cap y = lie \\
- k + 60 &amp;amp; x \in \{3, 4, 5, 6\} \cap y = truth \\
- k + 0 &amp;amp; x \in \{3, 4, 5, 6\} \cap y = lie \\
\end{cases}\]

&lt;p&gt;What is the maximum you should pay \($k\), in terms of the unreliability of information \(p\)? Solving \(E[H(x, y)] \geq 0\) for \(k\) and \(p\):&lt;/p&gt;

\[\begin{align}
0 &amp;amp;\leq E[H(x, y)] \\
&amp;amp;\leq \sum_{x \in \{1..6\},\\y \in \{truth, lie\}} \Pr(x)\Pr(y)H(x, y) \\
&amp;amp;\leq \frac{1}{3}(1-p)(-k) + \frac{1}{3}p(-k - 900) + \frac{2}{3}(1-p)(-k + 60) + \frac{2}{3}p(-k + 0)\\
\end{align}\]

&lt;p&gt;Putting that through the &lt;a href=&quot;https://www.wolframalpha.com/input/?i=%5Cfrac%7B1%7D%7B3%7D%281-p%29%28-k%29+%2B+%5Cfrac%7B1%7D%7B3%7Dp%28-k+-+900%29+%2B+%5Cfrac%7B2%7D%7B3%7D%281-p%29%28-k+%2B+60%29+%2B+%5Cfrac%7B2%7D%7B3%7Dp%28-k+%2B+0%29+%3E%3D+0&quot;&gt;magic math unicorn&lt;/a&gt;:&lt;/p&gt;

\[k \leq 40 - 340p \\\]

&lt;p&gt;To understand this, consider the extremes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;If I never lie (\(p = 0\)), I’m giving you perfect information. We’ve already covered that case: \(k \leq \$40\), where $40 is the Expected Opportunity Loss of not playing the game.&lt;/li&gt;
  &lt;li&gt;If I always lie (\(p = 1\)), then I mislead you every time. I’d have to &lt;em&gt;give you&lt;/em&gt; $300 per game to make up for your losses. Note that $300 is the Expected Loss when playing the basic game!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Between \(p = 0\) and \(1\), the expected value of information varies linearly. If I lie to you less than \(p = 40/340 \approx 11.7\%\) of the time, then there exists a price at which it’s worth paying to play.&lt;/p&gt;

&lt;p&gt;Let’s take \(p=5\%\) and \(k=0\) as an example - so I give you the information for free but lie to you 1 in 20 times - and then compute the Expected Value of the game:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Dice Roll&lt;/th&gt;
      &lt;th&gt;I tell you…&lt;/th&gt;
      &lt;th&gt;Probability&lt;/th&gt;
      &lt;th&gt;Value&lt;/th&gt;
      &lt;th&gt;Expected Value&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;1, 2&lt;/td&gt;
      &lt;td&gt;Lose (truth)&lt;/td&gt;
      &lt;td&gt;1/3 * 19/20 = 19/60&lt;/td&gt;
      &lt;td&gt;$0 (decided not to play)&lt;/td&gt;
      &lt;td&gt;$0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;1, 2&lt;/td&gt;
      &lt;td&gt;Win (lie)&lt;/td&gt;
      &lt;td&gt;1/3 * 1/20 = 1/60&lt;/td&gt;
      &lt;td&gt;-$900 (mislead into playing)&lt;/td&gt;
      &lt;td&gt;-$15&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;3, 4, 5, 6&lt;/td&gt;
      &lt;td&gt;Win (truth)&lt;/td&gt;
      &lt;td&gt;2/3 * 19/20 = 38/60&lt;/td&gt;
      &lt;td&gt;$60 (decided to play)&lt;/td&gt;
      &lt;td&gt;$38&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;3, 4, 5, 6&lt;/td&gt;
      &lt;td&gt;Lose (lie)&lt;/td&gt;
      &lt;td&gt;2/3 * 1/20 = 2/60&lt;/td&gt;
      &lt;td&gt;$0 (mislead into not playing)&lt;/td&gt;
      &lt;td&gt;$0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Overall&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;-&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;$23&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The expected value of the game - $23 - is exactly what the math above gave us for the maximum we’d want to pay for information: \(k = 40 - 340/20 = $23\).&lt;/p&gt;

&lt;p&gt;There is another interesting way of looking at this: the Expected Opportunity Loss of the game with 95% truthful information is:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;$15 - if you are mislead into playing on a 1 or 2&lt;/li&gt;
  &lt;li&gt;plus $2 - if you are mislead into not playing on a 3, 4, 5, 6&lt;/li&gt;
  &lt;li&gt;equals $17.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With the original strategy (don’t play), you stand to lose out on $40 if you’re wrong. With the new strategy (play if I tell you you’ll win), you only risk losing or missing out on $17. Thus, Expected Loss has been reduced by $40 - $17, which is $23!&lt;/p&gt;

&lt;p&gt;And &lt;em&gt;that&lt;/em&gt; is how How To Measure Anything frames the Expected Value of Information: \(EVI = EOL_{before} - EOL_{after}\). That’s why the \(EVPI = EOL\), because perfect information reduces the chance of a bad decision to zero.&lt;/p&gt;

&lt;h2 id=&quot;imperfect-useless-information&quot;&gt;Imperfect (useless) information&lt;/h2&gt;

&lt;p&gt;Finally, we can also imagine a version where instead of truth or lies, I give you truth or nothing. When you pay for information but receive nothing, you wasted your money and you just have to stick with your default decision to not play. If there’s even a small chance I might give you the truth then there should be &lt;em&gt;some price&lt;/em&gt; worth paying, but if I’ll never tell you anything, then the information is clearly worth nothing. Symbolically, \(k \leq 40 - 40p\).&lt;/p&gt;

&lt;p&gt;This suggests two categories of imperfect information: potentially useless information, and potentially misleading information.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Potentially useless information&lt;/strong&gt; might influence you to move from your default decision, but only if it was wrong. For example: an office manager of a small company is deciding whether to provide a vegetarian option for lunch. If the number of vegetarians exceeds a threshold they’ll include the option, but since they’ve not heard that anyone is vegetarian they might default to not doing so. In that situation, a survey of the office can only help, but it could be useless if too many vegetarians miss the survey.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Potentially misleading information&lt;/strong&gt; might influence you to move from your default decision, even if it was &lt;em&gt;right&lt;/em&gt;. Taking the example from the opening of product research, perhaps I’m so confident in the product that my default decision is to go ahead with it. If I run a survey of potential customers, and it’s answered by an unrepresentative slice of the population who all hate it, it could persuade me to abandon the project even if it was a good idea.&lt;/p&gt;

&lt;h1 id=&quot;rules-of-thumb-for-thinking-about-the-value-of-information&quot;&gt;Rules of thumb for thinking about the value of information&lt;/h1&gt;

&lt;p&gt;In summary, the situations examined in this post suggest two guiding principles for valuing information.&lt;/p&gt;

&lt;p&gt;Firstly, &lt;strong&gt;the most you should pay for information is the amount you expect to lose if your default decision is wrong&lt;/strong&gt;. This is because the most you’ll pay for information is whatever you would pay for perfect information, and perfect information cannot cause you to change to a bad decision.&lt;/p&gt;

&lt;p&gt;Secondly, &lt;strong&gt;strive to collect information which is not &lt;em&gt;potentially misleading&lt;/em&gt;&lt;/strong&gt;. When the worst case is that information is useless, you don’t have to consider the cost of switching to a bad decision, but when information can mislead you, then you must take into account the cost of being persuaded to make a bad decision.&lt;/p&gt;

&lt;p&gt;It’s worth noting that these principles rely on your initial estimates being vaguely correct, or at least honestly representative of how little you know. Producing &lt;a href=&quot;https://en.wikipedia.org/wiki/Calibrated_probability_assessment&quot;&gt;calibrated estimates&lt;/a&gt; is a skill you can learn (another topic covered by Hubbard in How To Measure Anything).&lt;/p&gt;

&lt;p&gt;Ultimately, these were just simple examples. In the real world, there will usually be many variables at play, and decisions are often on a continuum (not just yes/no), so I hope to cover more complex situations in a future post. I wrote this post so that I could feel that I truly understand and believe Hubbard’s statements on the value of information - with worked examples to reference - and in that sense it has been a success for me. I hope there was something interesting in here for you, too!&lt;/p&gt;

</description>
        <pubDate>Thu, 14 May 2020 15:15:00 +0000</pubDate>
        <link>https://veryjoe.com/maths/2020/05/14/The-value-of-information.html</link>
        <guid isPermaLink="true">https://veryjoe.com/maths/2020/05/14/The-value-of-information.html</guid>
        
        
        <category>maths</category>
        
      </item>
    
      <item>
        <title>Estimation of normal distribution parameters from percentiles, in Python</title>
        <description>&lt;p&gt;Let’s say you’re going to do some business modeling for a friend who’s thinking of opening a flower shop, producing something like &lt;a href=&quot;https://www.getguesstimate.com/models/3206&quot;&gt;this&lt;/a&gt; at the end.&lt;/p&gt;

&lt;p&gt;If you ask your friend the following question…&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Can you give me the scale and shape parameters of the lognormal distribution representing estimated customer spend?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You’re likely to be given a blank stare in response. You’re more likely to find success with this line of questioning:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;What do you think a top 95% customer is likely to spend? What about the bottom 5%? Do you think customers are most likely to spend in the middle of that, or closer to the bottom?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Estimation is difficult, but if you’re doing it at all, thinking about it in terms of percentiles tends to be an intuitive approach.&lt;/p&gt;

&lt;p&gt;Unfortunately, statistical software doesn’t work this way. Distributions are parametrized in terms of means, standard deviations, shape parameters, and so on. So, how do we go from “90% of customers spend between $10 and $90” to a probability distribution in software?&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Find a &lt;a href=&quot;https://www.johndcook.com/quantiles_parameters.pdf&quot;&gt;paper&lt;/a&gt; titled &lt;em&gt;Determining distribution parameters from quantiles&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;Implement it in Python!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This post simply demonstrates some Python code examples for deriving normal and log-normal distributions from percentile estimates.&lt;/p&gt;

&lt;p&gt;If you’re interested in understanding the maths behind this and learning about other distributions, I encourage you to check out the paper. You can also read &lt;a href=&quot;https://www.johndcook.com/blog/2010/01/31/parameters-from-percentiles/&quot;&gt;this blog post&lt;/a&gt;, which links to &lt;a href=&quot;https://www.codeproject.com/Articles/56371/Finding-Probability-Distribution-Parameters-from-P&quot;&gt;this&lt;/a&gt; Python code for Gamma and Beta distributions.&lt;/p&gt;

&lt;h2 id=&quot;notebook-setup&quot;&gt;Notebook setup&lt;/h2&gt;

&lt;p&gt;First we just import some libraries and set up some plotting functions.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;scipy.stats&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lognorm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;norm&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;numpy&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;matplotlib.pyplot&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;plt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;show_pdf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot; Plot the PDF of a distribution to an axis. &quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;minx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;maxx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ppf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.0001&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ppf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.9999&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;linspace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;minx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;maxx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;plot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pdf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;


&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;show_percentiles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;low&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;high&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;
    Fill the bottom &apos;low&apos;-th percentiles and top &apos;high&apos;-th percentiles
    of the distribution on an axis.
    &quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;minx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;maxx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ppf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.0001&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ppf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.9999&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;x1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ppf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;low&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;high&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;    
    &lt;span class=&quot;n&quot;&gt;xs1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;linspace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;minx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;xs2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;linspace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;maxx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fill_between&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;xs1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pdf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;xs1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fill_between&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;xs2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pdf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;xs2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If we estimate that 5% of customers spend under $10, and 95% spend under $90, then we can write that as&lt;/p&gt;

\[\begin{align}
P(X &amp;lt; \$10) = 0.05 \\
P(X &amp;lt; \$90) = 0.95
\end{align}\]

&lt;p&gt;Generalizing, we’re interesed in finding the scale and location of distributions parametrized as follows:&lt;/p&gt;

\[\begin{align}
P(X &amp;lt; x_1) = p_1 \\
P(X &amp;lt; x_2) = p_2
\end{align}\]

&lt;h2 id=&quot;normal-distribution&quot;&gt;Normal Distribution&lt;/h2&gt;

&lt;p&gt;Here’s the solution for a normal distribution. Here, \(\phi{-1}(p)\) is the inverse Cumulative Distribution Function of the standard normal distribution. Again, check out the paper to see the derivation!&lt;/p&gt;

\[\begin{align}
\textrm{scale} &amp;amp;= \frac{x_2 - x_1}{\phi^{-1}(p_2) - \phi^{-1}(p_1)} \\ \\
\textrm{location} &amp;amp;= \frac{x_1\phi^{-1}(p_2) - x_2\phi^{-1}(p_1)}{\phi^{-1}(p_2) - \phi^{-1}(p_1)}
\end{align}\]

&lt;p&gt;Here’s a python function which implements that:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;norm_from_percentiles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot; Return a normal distribuion X parametrized by:
    
            P(X &amp;lt; p1) = x1
            P(X &amp;lt; p2) = x2
    &quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;p1ppf&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;norm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ppf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;p2ppf&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;norm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ppf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;location&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p2ppf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p1ppf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p2ppf&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p1ppf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;scale&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p2ppf&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p1ppf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;norm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;loc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;location&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scale&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;scale&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the examples below, I’ve produced two normal distributions using percentile estimates.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;show_percentiles&lt;/code&gt; function - which fills in the top and bottom percentiles - demonstrates that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;norm_from_percentiles&lt;/code&gt; works as intended. Given only the percentiles, it fills in charts in at the expected &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt; locations.&lt;/p&gt;

&lt;p&gt;The second example demonstrates that the percentiles don’t need to be centered. You can parametrize using any two percentiles!&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;fig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ax1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ax2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;plt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;subplots&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;figsize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;dist&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;norm_from_percentiles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.05&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;90&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.95&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;show_pdf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ax1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;show_percentiles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ax1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.05&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.95&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ax1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;5% below 10, 5% above 90&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;


&lt;span class=&quot;n&quot;&gt;dist&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;norm_from_percentiles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.05&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;show_pdf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ax2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;show_percentiles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ax2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.05&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ax2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;5% below 5, 50% above 10&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;plt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;suptitle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Normal distributions&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/images/notebooks/2020-04-26-Parameter-Estimation_files/2020-04-26-Parameter-Estimation_11_0.png&quot; alt=&quot;png&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;log-normal-distribution&quot;&gt;Log-Normal Distribution&lt;/h2&gt;

&lt;p&gt;The paper stipulates that for a log-normal distribution, you can simply replace \(x_1\) and \(x_2\) with \(\log x_1\) and \(\log x_2\):&lt;/p&gt;

\[\begin{align}
\textrm{scale} &amp;amp;= \frac{\log x_2 - \log x_1}{\phi^{-1}(p_2) - \phi^{-1}(p_1)} \\ \\
\textrm{location} &amp;amp;= \frac{(\log x_1)\phi^{-1}(p_2) - (\log x_2)\phi^{-1}(p_1)}{\phi^{-1}(p_2) - \phi^{-1}(p_1)}
\end{align}\]

&lt;p&gt;and as we shall see, that seems to work! However, following Scipy’s &lt;a href=&quot;https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.lognorm.html&quot;&gt;documentation&lt;/a&gt; on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lognorm&lt;/code&gt;, we also need to make a final transformation from this parametrization to SciPy’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shape&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scale&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;A common parametrization for a lognormal random variable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Y&lt;/code&gt; is in terms of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mean&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mu&lt;/code&gt;, and standard deviation, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sigma&lt;/code&gt;, of the unique normally distributed random variable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;X&lt;/code&gt; such that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exp(X) = Y&lt;/code&gt;. This parametrization corresponds to setting &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;s = sigma&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scale = exp(mu)&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here’s the Python code:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;lognorm_from_percentiles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot; Return a log-normal distribuion X parametrized by:
    
            P(X &amp;lt; p1) = x1
            P(X &amp;lt; p2) = x2
    &quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;x1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;x2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;p1ppf&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;norm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ppf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;p2ppf&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;norm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ppf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    
    &lt;span class=&quot;n&quot;&gt;scale&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p2ppf&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p1ppf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;mean&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p2ppf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p1ppf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p2ppf&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p1ppf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lognorm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;scale&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scale&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mean&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The examples below use the same parameters as the normal distributions above, but look very different (they have a heavy right-skew). This highlights the importance of choosing the correct distribution.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;fig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ax1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ax2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;plt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;subplots&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;figsize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;dist&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lognorm_from_percentiles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.05&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;90&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.95&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;show_pdf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ax1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;show_percentiles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ax1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.05&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.95&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ax1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;5% below 10, 5% above 90&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;


&lt;span class=&quot;n&quot;&gt;dist&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lognorm_from_percentiles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.05&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;show_pdf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ax2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;show_percentiles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ax2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.05&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ax2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;5% below 5, 50% above 10&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;plt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;suptitle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Log-Normal distributions&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/images/notebooks/2020-04-26-Parameter-Estimation_files/2020-04-26-Parameter-Estimation_15_0.png&quot; alt=&quot;png&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;in-conclusion&quot;&gt;In conclusion&lt;/h2&gt;

&lt;p&gt;Sometimes, the best you have is your best guess, and it’s much easier for people to guess things like “10% of people would pay less than X” than it is for people to directly guess the location and scale parameters of a distribution.&lt;/p&gt;

&lt;p&gt;All of this has left me wondering: why isn’t percentile parametrization more commonly found in maths packages and statistics libraries? I suppose it’s just not a core functionality, and as we’ve seen it’s pretty easy to implement if you need, but I feel like wider adoption of percentile parametrization could go a long way to making simple statistical modeling more accessible.&lt;/p&gt;

&lt;p&gt;One exception is &lt;a href=&quot;https://www.getguesstimate.com/&quot;&gt;Guesstimate&lt;/a&gt; (of the Flower Shop example from earlier), a great tool which allows users to define and combine a wide variety of distributions using percentiles. That being said, you’re limited to 90% confidence intervals and in Guesstimate, whereas here we’ve demonstrated that you can use any percentiles you like.&lt;/p&gt;

&lt;p&gt;Ultimately, this all just scratches the surface of a very interesting topic. For example, &lt;a href=&quot;http://www.metalogdistributions.com/images/KeelinPowley_QuantileParameterizedDistributions_2011.pdf&quot;&gt;Quantile Parametrized-Distributions&lt;/a&gt; are parametrized with 1st, 10th, 50th and 90th percentiles. Unlike the normal and log-normal distributions which are forever symmetric and right-skewed, the 5 parameters of QPDs produce an appropriately skewed distribution. An implementation in Javascript which generalizes these ideas even further can be found &lt;a href=&quot;https://observablehq.com/@whilp/gqpd&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The math is clearly out there, some people are clearly taking interest in this, and now this implimentation is out there too. Next time you need to model something, consider estimatimating some percentiles!&lt;/p&gt;
</description>
        <pubDate>Mon, 27 Apr 2020 03:15:00 +0000</pubDate>
        <link>https://veryjoe.com/maths/2020/04/27/Parameter-Estimation.html</link>
        <guid isPermaLink="true">https://veryjoe.com/maths/2020/04/27/Parameter-Estimation.html</guid>
        
        
        <category>maths</category>
        
      </item>
    
      <item>
        <title>How To Measure Anything: The Information Values for Multiple Variables</title>
        <description>&lt;p&gt;The book &lt;a href=&quot;https://www.howtomeasureanything.com/3rd-edition/&quot;&gt;How To Measure Anything&lt;/a&gt; (by Douglas W. Hubbard) teaches a process for quantitative decision making. I highly recommend giving it a read, but here’s the overall approach it suggests:&lt;/p&gt;

&lt;hr /&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Define and model a decision. A decision must have a measurable impact, e.g. company profits, which is modeled with a collection of variables. For example, decide to sell a product if \(\textrm{Unit Profit} * \textrm{Buyers} - \textrm{Fixed cost} &amp;gt; 0\)&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Estimate distributions for each variable using existing knowledge. Ideally, this should be done by experts in the area who have undergone &lt;a href=&quot;https://en.wikipedia.org/wiki/Calibrated_probability_assessment&quot;&gt;calibration training&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Compute the value of obtaining more accurate estimates for each variable.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Reduce uncertainty by measuring the highest-value variables, or by decomposing them into finer grained estimates.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Make a decision once uncertainty has been sufficiently reduced.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;
&lt;p&gt;&lt;br /&gt;
I believe the cornerstone of this approach is step 3, but how exactly do you compute the value of measuring a variable?&lt;/p&gt;

&lt;p&gt;The book describes the theory in detail, with some nice examples… for a model with a single variable. It then acknowledges that most models will consist of many variables, and dedicates a mere &lt;em&gt;two short paragraphs&lt;/em&gt; to the multi-variate case!&lt;/p&gt;

&lt;p&gt;This blog post fills in the gap with a working example. I define a model, then compute the value of obtaining perfect information for each of its variables using the more accurate method suggested by the book.&lt;/p&gt;

&lt;h2 id=&quot;the-advertising-campaign&quot;&gt;The advertising campaign&lt;/h2&gt;

&lt;p&gt;The model we’ll use is similar to the simple model used in Chapter 6, but with a second variable included.&lt;/p&gt;

&lt;p&gt;There is a proposal for an ad campaign which will will cost $5m to run. If the campaign is rejected, no money will be made or spent. If the campaign is accepted, it will run in two market segments, and may succeed or fail in each segment independently.&lt;/p&gt;

&lt;p&gt;In this unrealistic example, we know exactly how much we’ll make if we succeed, but our experts have estimated the chance of success in each segment.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt; &lt;/th&gt;
      &lt;th&gt;Chance of success&lt;/th&gt;
      &lt;th&gt;Revenue if successful&lt;/th&gt;
      &lt;th&gt;Revenue if unsuccessful&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Segment A&lt;/td&gt;
      &lt;td&gt;60%&lt;/td&gt;
      &lt;td&gt;$45m&lt;/td&gt;
      &lt;td&gt;$0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Segment B&lt;/td&gt;
      &lt;td&gt;70%&lt;/td&gt;
      &lt;td&gt;$6m&lt;/td&gt;
      &lt;td&gt;$0&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;We could run surveys to improve our confidence in the outcomes, but which market segment should we do that for, and how much should we spend?&lt;/p&gt;

&lt;p&gt;Intuition should rightly tell you that A is worth more to measure than B, but the hope is that if we can demonstrate Hubbard’s technique on this simple example, you can generalize it to situations where the outcome is less clear.&lt;/p&gt;

&lt;h2 id=&quot;the-value-of-perfect-information&quot;&gt;The value of perfect information&lt;/h2&gt;

&lt;p&gt;The method starts by computing what it calls the “Overall Expected Opportunity Loss” for the model. Here’s the quote from Chapter 6, page 164 of the Third Edition:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Another more elaborate – but more accurate – method starts by running a simulation that shows our expected loss at our current level of uncertainty. I call this the Overall EOL or the Decision EOL. This is simply the average of all outcomes given our current (default) decision assuming we don’t measure any further. […] All of these results averaged together – zeros and non-zero values alike – is the Overall EOL.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Opportunity Loss (OL) is defined as the cost of a bad decision, which could be as much as $5m for this campaign if it completely fails. Expected Opportunity Loss (EOL) is the cost of a bad decision multiplied by its probability.&lt;/p&gt;

&lt;p&gt;If the campaign is run, we risk losing money if we don’t make as much as the $5m we paid for it.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;A result&lt;/th&gt;
      &lt;th&gt;B result&lt;/th&gt;
      &lt;th&gt;Profit&lt;/th&gt;
      &lt;th&gt;Probability&lt;/th&gt;
      &lt;th&gt;Opportunity Loss&lt;br /&gt;(loss if campaign is accepted)&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;A succeeds&lt;/td&gt;
      &lt;td&gt;B succeeds&lt;/td&gt;
      &lt;td&gt;$46m&lt;/td&gt;
      &lt;td&gt;42%&lt;/td&gt;
      &lt;td&gt;$0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;A succeeds&lt;/td&gt;
      &lt;td&gt;B fails&lt;/td&gt;
      &lt;td&gt;$40m&lt;/td&gt;
      &lt;td&gt;18%&lt;/td&gt;
      &lt;td&gt;$0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;A fails&lt;/td&gt;
      &lt;td&gt;B succeeds&lt;/td&gt;
      &lt;td&gt;$1m&lt;/td&gt;
      &lt;td&gt;28%&lt;/td&gt;
      &lt;td&gt;$0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;A fails&lt;/td&gt;
      &lt;td&gt;B fails&lt;/td&gt;
      &lt;td&gt;-$5m&lt;/td&gt;
      &lt;td&gt;12%&lt;/td&gt;
      &lt;td&gt;$5m&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

\[\begin{eqnarray}
\textrm{EOL if Accepted} &amp;amp;=&amp;amp; \sum_{\textrm{all scenarios}}{\textrm{Opportunity Loss} * \textrm{Probability}} \\
                         &amp;amp;=&amp;amp; $5m * 12\% \\
                         &amp;amp;=&amp;amp; $600k
\end{eqnarray}\]

&lt;p&gt;Ignoring how much we stand to &lt;em&gt;make&lt;/em&gt;, running this campaign involves a 12% risk of a $5m loss, so the expected loss is $600k.&lt;/p&gt;

&lt;p&gt;We can make a similar calculation for the case where the campaign is not run. Here, the risk is that we lose out out on everything we would have made.&lt;sup id=&quot;fnref:eolname&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:eolname&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;A result&lt;/th&gt;
      &lt;th&gt;B result&lt;/th&gt;
      &lt;th&gt;Profit&lt;/th&gt;
      &lt;th&gt;Probability&lt;/th&gt;
      &lt;th&gt;Opportunity Loss&lt;br /&gt;(loss if campaign is rejected)&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;A succeeds&lt;/td&gt;
      &lt;td&gt;B succeeds&lt;/td&gt;
      &lt;td&gt;$0&lt;/td&gt;
      &lt;td&gt;42%&lt;/td&gt;
      &lt;td&gt;$46m&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;A succeeds&lt;/td&gt;
      &lt;td&gt;B fails&lt;/td&gt;
      &lt;td&gt;$0&lt;/td&gt;
      &lt;td&gt;18%&lt;/td&gt;
      &lt;td&gt;$40m&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;A fails&lt;/td&gt;
      &lt;td&gt;B succeeds&lt;/td&gt;
      &lt;td&gt;$0&lt;/td&gt;
      &lt;td&gt;28%&lt;/td&gt;
      &lt;td&gt;$1m&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;A fails&lt;/td&gt;
      &lt;td&gt;B fails&lt;/td&gt;
      &lt;td&gt;$0&lt;/td&gt;
      &lt;td&gt;12%&lt;/td&gt;
      &lt;td&gt;$0&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

\[\begin{eqnarray}
\textrm{EOL if Rejected} &amp;amp;=&amp;amp; \sum_{\textrm{all scenarios}}{\textrm{Opportunity Loss} * \textrm{Probability}} \\
                         &amp;amp;=&amp;amp; $46m * 42\% \\ &amp;amp;+&amp;amp;  $40m * 18\% \\ &amp;amp;+&amp;amp;  $1m * 28\% \\
                         &amp;amp;=&amp;amp; $26.8m
\end{eqnarray}\]

&lt;p&gt;Based on what we know, should we decide to run the campaign? That depends on our risk tolerance - if we don’t have $5m to lose, we might be more sensitive to actual losses than missed profits.&lt;/p&gt;

&lt;p&gt;However, let’s keep things simple and say that we’ll take whichever option has the lowest risk. This policy is our &lt;strong&gt;decision rule&lt;/strong&gt;. It results in a “default” decision to run the campaign, and means that our Overall EOL is currently $600k.&lt;/p&gt;

&lt;p&gt;Just to be safe, should we spend a bit on a survey to refine our estimate of the chance of failure?&lt;/p&gt;

&lt;p&gt;Let’s imagine we can buy &lt;em&gt;perfect information&lt;/em&gt;. We’d definitely do it for $1, and definitely not for $100m, so what’s the maximum we should pay? The theory goes that given our current level of uncertainty, we would pay up to $600k to confirm that the campaign won’t fail. Despite the fact that we might lose up to $5m, any more than $600k and we’re paying more than our expected loss.&lt;/p&gt;

&lt;p&gt;Conversely, if our default option was to reject the campaign, we’re throwing away an ‘expected’ $26.8m, so it would probably be worth spending a lot more to make sure we’re not missing out. However, if we pay more than $26.8m, we’re paying more than we expect to gain.&lt;/p&gt;

&lt;p&gt;This is what the book calls the Expected Value of Perfect Information (EVPI), and as you can see, the value of information partly depends on the action you’d take without it. We’ve calculated it for the entire model, but what if we can get perfect information for just one variable?&lt;/p&gt;

&lt;h2 id=&quot;the-actual-value-of-information-for-a-single-variable&quot;&gt;The actual value of information for a single variable&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;We then run a series of Monte Carlo Simulations where we pretend we knew one selected variable in the model exactly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let’s imagine that we get some new information, and now we’re 100% confident the campaign will fail in segment A.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;A result&lt;/th&gt;
      &lt;th&gt;B result&lt;/th&gt;
      &lt;th&gt;Profit&lt;/th&gt;
      &lt;th&gt;Probability&lt;/th&gt;
      &lt;th&gt;Opportunity Loss (Accepted)&lt;/th&gt;
      &lt;th&gt;Opportunity Loss (Rejected)&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;A succeeds&lt;/td&gt;
      &lt;td&gt;B succeeds&lt;/td&gt;
      &lt;td&gt;$46m&lt;/td&gt;
      &lt;td&gt;0%&lt;/td&gt;
      &lt;td&gt;$0&lt;/td&gt;
      &lt;td&gt;$46m&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;A succeeds&lt;/td&gt;
      &lt;td&gt;B fails&lt;/td&gt;
      &lt;td&gt;$40m&lt;/td&gt;
      &lt;td&gt;0%&lt;/td&gt;
      &lt;td&gt;$0&lt;/td&gt;
      &lt;td&gt;$40m&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;A fails&lt;/td&gt;
      &lt;td&gt;B succeeds&lt;/td&gt;
      &lt;td&gt;$1m&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;70%&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;$0&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;$1m&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;A fails&lt;/td&gt;
      &lt;td&gt;B fails&lt;/td&gt;
      &lt;td&gt;-$5m&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;30%&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;$5m&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;$0&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

\[\begin{eqnarray}
\textrm{EOL if Accepted} &amp;amp;=&amp;amp; $5m &amp;amp;*&amp;amp; 30\% &amp;amp;=&amp;amp; $1.5m \\
\textrm{EOL if Rejected} &amp;amp;=&amp;amp; $1m &amp;amp;*&amp;amp; 70\% &amp;amp;=&amp;amp; $700k
\end{eqnarray}\]

&lt;p&gt;Note that we computed these numbers analytically, but 100 random simulations (i.e. Monte Carlo) would have produced roughly the same results.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;We apply a decision rule that tells the model what I would have done differently if I knew only that variable exactly. If knowing this variable was informative, my opportunity losses from making a bad decision (the Overall EOL) should go down.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;This is the part of the book I’ve been having trouble interpreting.&lt;/strong&gt; The new information was clearly informative, since my decision rule now tells me to reject the proposal. However, the expected opportunity loss of my decision has gone &lt;em&gt;up&lt;/em&gt; from $600k to $700k! So what was the value of this information?&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Was it $0, since my expected losses didn’t decrease?&lt;/li&gt;
  &lt;li&gt;Was it $100k, the absolute difference between Overall EOL before and after?&lt;/li&gt;
  &lt;li&gt;Was it $900k, since the expected loss of my previous decision turned out to be $900k larger than I thought?&lt;/li&gt;
  &lt;li&gt;Or was it $800k, since the new information updates the EOL for my previous decision to $1.5m and then updates my decision to reduce my EOL to $700k?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I argue the the answer is (4), and I’m going to back that up with some extreme examples.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;If I had somehow learned that running the campaign risked a billion dollars, with a $50m potential upside, that information would clearly have immense value. I was going to run that campaign before! If I had paid a $100m dollars to avoid an expected billion dollar loss, that would have been very much worth it.&lt;/li&gt;
  &lt;li&gt;However, if I’d have learned that the campaign stands to make OR lose one one billion dollars – and I value actual loss as much as I value lost potential gains – I would regret having spent any money on research at all. If I have 1000 opportunities to run this campaign, it won’t matter whether I do or not - I’ll make an average of $0. Spending money to learn that would have just cost me extra.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With that in mind, my interpretation if the&lt;/p&gt;

&lt;p&gt;Putting that aside, lets try the same thing for the case that we learn that segment A is 100% likely to succeed. Noting that this reduces the situation to a single variable, we can simplify the table a bit.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;B result&lt;/th&gt;
      &lt;th&gt;Profit&lt;/th&gt;
      &lt;th&gt;Probability&lt;/th&gt;
      &lt;th&gt;Opportunity Loss (Accepted)&lt;/th&gt;
      &lt;th&gt;Opportunity Loss (Rejected)&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;B succeeds&lt;/td&gt;
      &lt;td&gt;$46m&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;70%&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;$0&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;$46m&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;B fails&lt;/td&gt;
      &lt;td&gt;$40m&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;30%&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;$0&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;$40m&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

\[\begin{eqnarray}
\textrm{EOL if Accepted} &amp;amp;=&amp;amp; $0 \\
\textrm{EOL if Rejected} &amp;amp;=&amp;amp; $46m * 90\% + $40m * 10\% = $44.2m
\end{eqnarray}\]

&lt;p&gt;Again, perfect information reduced our risk to $0, and our decision rule tells us to definitely run the campaign, because segment A’s success will always cover the potential loss of segment B. Overall, it looks like the expected value of perfect information on segment A is $600k.&lt;/p&gt;

&lt;p&gt;Now let’s consider perfect information about segment B. Suppose we learn that it will fail.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;A result&lt;/th&gt;
      &lt;th&gt;Profit&lt;/th&gt;
      &lt;th&gt;Probability&lt;/th&gt;
      &lt;th&gt;Opportunity Loss (Accepted)&lt;/th&gt;
      &lt;th&gt;Opportunity Loss (Rejected)&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;A succeeds&lt;/td&gt;
      &lt;td&gt;$40m&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;60%&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;$0&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;$40m&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;A fails&lt;/td&gt;
      &lt;td&gt;-$5m&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;40%&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;$5m&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;$0&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

\[\begin{eqnarray}
\textrm{EOL if Accepted} &amp;amp;=&amp;amp; $5m &amp;amp;*&amp;amp; 40\% &amp;amp;=&amp;amp; $2m \\
\textrm{EOL if Rejected} &amp;amp;=&amp;amp; $40m &amp;amp;*&amp;amp; 60\% &amp;amp;=&amp;amp; $24m
\end{eqnarray}\]

&lt;p&gt;The campaign looks $720,000 more risky, but that doesn’t change our decision to run it.&lt;/p&gt;

&lt;p&gt;What if we learn that it succeeds?&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;A result&lt;/th&gt;
      &lt;th&gt;Profit&lt;/th&gt;
      &lt;th&gt;Probability&lt;/th&gt;
      &lt;th&gt;Opportunity Loss (Accepted)&lt;/th&gt;
      &lt;th&gt;Opportunity Loss (Rejected)&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;A succeeds&lt;/td&gt;
      &lt;td&gt;$46m&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;60%&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;$0&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;$46m&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;A fails&lt;/td&gt;
      &lt;td&gt;$1m&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;40%&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;$0&lt;/td&gt;
      &lt;td&gt;$0&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

\[\begin{eqnarray}
\textrm{EOL if Accepted} &amp;amp;=&amp;amp; $0 \\
\textrm{EOL if Rejected} &amp;amp;=&amp;amp; $46m &amp;amp;*&amp;amp; 60\% &amp;amp;=&amp;amp; $27.6m
\end{eqnarray}\]

&lt;p&gt;Now the Expected Opportunity Loss of our decision to run the campaign is reduced by $80,000.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The change in Overall EOL by eliminating uncertainty for a single variable is the “Individual EVPI” for that variable.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is where my interpretation of the book is fuzzy. It seems clear that we should pay up to $600k for perfect information on Segment A, but since information on Segment B can never change our decision, would we pay even one dollar to learn more about it?&lt;/p&gt;

&lt;h2 id=&quot;knowing-what-to-measure&quot;&gt;Knowing what to measure&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;WIP&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;this was a simple example&lt;/li&gt;
  &lt;li&gt;it still demonstrates that P(success of B) is the most valuable estimate to refine&lt;/li&gt;
  &lt;li&gt;when a model consists of a sum of variables, the variable with the widest range is always going to be the most informative&lt;/li&gt;
  &lt;li&gt;but in more complex models:
    &lt;ul&gt;
      &lt;li&gt;it won’t always be a sum of variables&lt;/li&gt;
      &lt;li&gt;it may be that no single variable is informative&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;decision-rules-loss-functions&quot;&gt;Decision rules, loss functions&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;WIP&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;“Decision rule” selects from decisions based on their EOL&lt;/li&gt;
  &lt;li&gt;Could just take the decision with the lowest EOL, but not always&lt;/li&gt;
  &lt;li&gt;You might be more sensitive to actual loss than to lost potential gain.&lt;/li&gt;
  &lt;li&gt;e.g. might have a cap on maximum actual loss (a budget), but not a cap on lost potential gain.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;realistic-decisions&quot;&gt;Realistic decisions&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;WIP&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Application to ranges of variables and decisions would follow the same principle
    &lt;ul&gt;
      &lt;li&gt;Hold a variable (or group of variables) at a single value&lt;/li&gt;
      &lt;li&gt;Simulate the others &amp;amp; compute EOLs of resulting decisions.&lt;/li&gt;
      &lt;li&gt;Compute EOL for&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;interlude-does-any-of-this-actually-make-sense&quot;&gt;Interlude: does any of this actually make sense?&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;WIP - worth including?&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;linear-loss-versus-maximum-loss&quot;&gt;Linear loss versus maximum loss&lt;/h3&gt;

&lt;p&gt;The concept of paying &lt;strong&gt;[WIP finish this sentence]&lt;/strong&gt;, and ultimately it’s a pretty simple model of risk. Perhaps we think that we should spend up to $5m because there’s &lt;em&gt;some chance&lt;/em&gt; we could lose that much. That would be the &lt;strong&gt;Maximum&lt;/strong&gt; Opportunity Loss, and perhaps it sounds like a good idea, but it falls apart for slimmer probabilities. For example, if we introduce a 0.001% chance of a massive PR disaster which costs $500m in lost sales, should we spend $500m to test the advertisement for PR issues? Probably not! The linear model (for this single variable) suggests $5,000 which seems much more reasonable.&lt;/p&gt;

&lt;h3 id=&quot;uncalibrated-estimates&quot;&gt;Uncalibrated estimates&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;TODO&lt;/strong&gt; something about how the model falls apart if you begin with uncalibrated estimates.&lt;/p&gt;

&lt;p&gt;######################## TODO ####################&lt;/p&gt;

&lt;p&gt;Simulate two agents making a decision&lt;/p&gt;

&lt;p&gt;D = p1 * a + p2 * b &amp;gt; c&lt;/p&gt;

&lt;p&gt;Agent 1 always chooses the same thing.&lt;/p&gt;

&lt;p&gt;Agent 2 learns the true value of p1 or p2, then chooses appropriately&lt;/p&gt;
&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:eolname&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;When you realize that loss can including &lt;em&gt;losing out&lt;/em&gt;, I think that it makes the name Expected &lt;strong&gt;Opportunity&lt;/strong&gt; Loss more clear. &lt;a href=&quot;#fnref:eolname&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Sat, 14 Mar 2020 19:23:37 +0000</pubDate>
        <link>https://veryjoe.com/tech/2020/03/14/Information-Value-Multiple-Variables.html</link>
        <guid isPermaLink="true">https://veryjoe.com/tech/2020/03/14/Information-Value-Multiple-Variables.html</guid>
        
        <category>hide</category>
        
        
        <category>tech</category>
        
      </item>
    
      <item>
        <title>Simple Custom Resources for the AWS CDK</title>
        <description>&lt;p&gt;If you are using the &lt;a href=&quot;https://docs.aws.amazon.com/cdk/latest/guide/home.html&quot;&gt;AWS CDK&lt;/a&gt; you will quickly discover that because it’s just a wrapper around CloudFormation, it shares
with it the issue that not all of AWS’s features have been implemented.&lt;/p&gt;

&lt;p&gt;Perhaps you need to enable Aurora’s &lt;a href=&quot;https://aws.amazon.com/blogs/aws/new-data-api-for-amazon-aurora-serverless/&quot;&gt;Data API&lt;/a&gt;, which was the case for me. Whatever it is, you know you just need
to make a single AWS API call. What’s the simplest thing for you to do?&lt;/p&gt;

&lt;h2 id=&quot;the-not-simple-approach&quot;&gt;The not simple approach&lt;/h2&gt;

&lt;p&gt;Your story might go something like this:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;You do some googling and learn from &lt;a href=&quot;https://stackoverflow.com/questions/54931548/enable-aurora-data-api-from-cloudformation&quot;&gt;StackOverflow&lt;/a&gt; that you can do this with “Custom Resources”, which the CDK supports
with &lt;a href=&quot;https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudformation.CustomResource.html&quot;&gt;CustomResource&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;You find the &lt;a href=&quot;https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-lambda-function-code-cfnresponsemodule.html&quot;&gt;cfn-response&lt;/a&gt; module which is supposed to abstract away the details of sending a response to CloudFormation
to let it know the status of your resource after CloudFormation requests that is is created, updated or deleted.&lt;/li&gt;
  &lt;li&gt;You try numerous times to actually import it correctly in your Lambda function, which is difficult because:
    &lt;ul&gt;
      &lt;li&gt;The docs ask you use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ZipFile&lt;/code&gt; but in the CDK you do that using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lambda.Code.inline&lt;/code&gt;, &lt;em&gt;not&lt;/em&gt; by specifying a directory which
will get zipped, which is not terribly obvious,&lt;/li&gt;
      &lt;li&gt;Every time you make a mistake, CloudFormation gets stuck rolling back because your custom resource is unable
to respond to say that it has been successfully deleted,&lt;/li&gt;
      &lt;li&gt;You eventually discover that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cfn-response&lt;/code&gt; library doesn’t work with a lambda function which uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async&lt;/code&gt; anyway,
and you don’t want to give up on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async&lt;/code&gt; because it’s ~the future~.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;So you switch to using &lt;a href=&quot;https://github.com/ispyinternet/cfn-response-promise&quot;&gt;cfn-response-promise&lt;/a&gt;. Now you have to go back to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lambda.Code.asset&lt;/code&gt; because you’re installing a 3rd
party library. Also, remember to return the result of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;send&lt;/code&gt; now or you’ll still get stuck!&lt;/li&gt;
  &lt;li&gt;You learn the hard way that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CustomResource&lt;/code&gt; parameter names are title-cased when they’re passed to your lambda function.&lt;/li&gt;
  &lt;li&gt;You discover that you can’t pass a reference to a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CfnDBSubnetGroup&lt;/code&gt; via Custom REsource parameters using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.dbClusterIdentifier&lt;/code&gt;, but &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.ref&lt;/code&gt; seems to work.&lt;/li&gt;
  &lt;li&gt;When you get through all of this it dawns on you that you still have to give your Lambda function permissions to do whatever it needs to do.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At this point you start to wonder whether there was a simpler way, poke around in the CDK docs and notice something sitting at the bottom… &lt;a href=&quot;https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_custom-resources.AwsCustomResource.html&quot;&gt;custom-resources&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This library provides a Construct called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AwsCustomResource&lt;/code&gt; which lets you specify a single SDK call which will happen when my resource is created, updated or deleted. Great! You throw something together:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;AwsCustomResource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;ToggleDataAPI&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;onCreate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;RDS&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;ModifyDBCluster&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;parameters&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;DBClusterIdentifier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dbClusterIdentifier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;EnableHttpEndpoint&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;physicalResourceId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`DbDataApiToggle`&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It looks like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cdk synth&lt;/code&gt; is happy so you run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cdk deploy&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Failed to create resource. awsService[call.action] is not a function
    new CustomResource (.../node_modules/@aws-cdk/aws-cloudformation/lib/custom-resource.ts:92:21)
    \_ new AwsCustomResource (.../node_modules/@aws-cdk/custom-resources/lib/aws-custom-resource.ts:159:27)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Why is this happening? ModifyDBCluster perfectly matches the action name given in the &lt;a href=&quot;https://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_ModifyDBCluster.html&quot;&gt;AWS Docs&lt;/a&gt;. Perhaps the source
code will be enlightening. You find the reference to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;awsService&lt;/code&gt; in the source code for the &lt;a href=&quot;https://github.com/aws/aws-cdk/blob/6c0bf4ac1b21116d94e26d740a0302f92207b3b1/packages/%40aws-cdk/custom-resources/lib/aws-custom-resource-provider/index.ts#L63&quot;&gt;Lambda function&lt;/a&gt; itself.&lt;/p&gt;

&lt;div class=&quot;language-ts highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;awsService&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;AWS&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;](&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;apiVersion&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;apiVersion&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;awsService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;](&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;parameters&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fixBooleans&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;parameters&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This code is trying to look up the function you specified in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aws-sdk&lt;/code&gt; library but &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ModifyDBCluster&lt;/code&gt; doesn’t
exist in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AWS.RDS&lt;/code&gt;. So what does exist in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RDS&lt;/code&gt;? Yep, &lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;modifyDBCluster&lt;/code&gt;&lt;/strong&gt; with a small &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;m&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Finally you &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cdk deploy&lt;/code&gt; and it works. You took 5 hours to successfully automate &lt;em&gt;a single button press&lt;/em&gt;.&lt;/p&gt;

&lt;div class=&quot;thumbnailed&quot;&gt;
  &lt;a href=&quot;/images/data_api.png&quot;&gt;
    &lt;img src=&quot;/images/data_api.png&quot; alt=&quot;Data API: checked&quot; /&gt;
  &lt;/a&gt;
  Was it worth it?
&lt;/div&gt;

&lt;h2 id=&quot;a-better-way&quot;&gt;A better way&lt;/h2&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AwsCustomResource&lt;/code&gt; class seems like it’s almost a winner when all you need to do is make AWS API calls, but I did not go
through all of the above described effort just to live in a world where I discover configuration typos at &lt;em&gt;deploy time&lt;/em&gt;, so I
made a library called &lt;a href=&quot;https://github.com/Spacerat/checked-aws-custom-resource&quot;&gt;checked-aws-custom-resource&lt;/a&gt; which consists of a single resource which just wraps &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AwsCustomResource&lt;/code&gt;
and checks at compile time whether all specified actions exist. It’s really pretty simple, but now I feel like I’ll be able
to use the CDK to poke AWS APIs without fear.&lt;/p&gt;

&lt;p&gt;It’s basically a drop-in replacement:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;CheckedAwsCustomResource&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;checked-aws-custom-resource&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;//...&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;CheckedAwsCustomResource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;ToggleDataAPI&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;onCreate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;RDS&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;ModifyDBCluster&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;parameters&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;DBClusterIdentifier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dbClusterIdentifier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;EnableHttpEndpoint&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;physicalResourceId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`DbDataApiToggle`&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;its implementation is little more than to subclass &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AwsCustomResource&lt;/code&gt; and add this to the constructor:&lt;/p&gt;

&lt;div class=&quot;language-ts highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typeof&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;awsService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; is not a function in AWS.&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;and you can install it yourself with:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm install checked-aws-custom-resource --save
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;thoughts&quot;&gt;Thoughts&lt;/h2&gt;

&lt;p&gt;Overall, I have come away from this experience with a few thoughts:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;CloudFormation (and AWS as a whole, really) is a minefield of gotchas,&lt;/li&gt;
  &lt;li&gt;If there are some simply documented basic best practices for CDK/CloudFormation/Infra-As-Code development out there, I would love to know more,&lt;/li&gt;
  &lt;li&gt;The CDK smooths over many of CloudFormation’s edges, but also introduces a few.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To be fair to the CDK, it’s mostly been a joy to use, and it’s still experimental, but ultimately, &lt;strong&gt;every
new layer of complexity creates an opportunity for bugs to hide and abstractions to leak.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For example, take this &lt;a href=&quot;https://github.com/aws/aws-cdk/blob/6c0bf4ac1b21116d94e26d740a0302f92207b3b1/packages/%40aws-cdk/custom-resources/lib/aws-custom-resource.ts&quot;&gt;snippet&lt;/a&gt; from the source code of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;custom-resources&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ts highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cm&quot;&gt;/**
 * Transform SDK service/action to IAM action using metadata from aws-sdk module.
 * Example: CloudWatchLogs with putRetentionPolicy =&amp;gt; logs:PutRetentionPolicy
 *
 * TODO: is this mapping correct for all services?
 */&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;awsSdkToIamAction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;srv&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toLowerCase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;iamService&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;awsSdkMetadata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;srv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;prefix&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;srv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;iamAction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;charAt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toUpperCase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;iamService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;iamAction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I don’t know &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TODO&lt;/code&gt;, you tell me!&lt;/p&gt;

</description>
        <pubDate>Sun, 28 Jul 2019 19:23:37 +0000</pubDate>
        <link>https://veryjoe.com/tech/2019/07/28/Simple-Custom-Resources-AWS-CDK.html</link>
        <guid isPermaLink="true">https://veryjoe.com/tech/2019/07/28/Simple-Custom-Resources-AWS-CDK.html</guid>
        
        
        <category>tech</category>
        
      </item>
    
      <item>
        <title>Debugging HTTPS configuration for Dokku</title>
        <description>&lt;p&gt;I recently decided to set up my website to use HTTPS. Configuring Github Pages - which &lt;a href=&quot;/tech/2016/09/26/first-post.html&quot;&gt;hosts&lt;/a&gt; this blog - was easy  . I just had to follow the Github Pages documentation on &lt;a href=&quot;https://help.github.com/en/articles/troubleshooting-custom-domains#dns-configuration-errors&quot;&gt;troubleshooting custom domains&lt;/a&gt; which directed me to update the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A&lt;/code&gt; records in veryjoe.com’s domain configuration, then &lt;a href=&quot;https://help.github.com/en/articles/securing-your-github-pages-site-with-https#enforcing-https-for-your-github-pages-site&quot;&gt;check a checkbox&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Dokku - which &lt;a href=&quot;/tech/2014/02/08/Setting-up-shop.html&quot;&gt;hosts&lt;/a&gt; my side project web apps - should have been just as easy. There is Dokku plugin called &lt;a href=&quot;https://github.com/dokku/dokku-letsencrypt&quot;&gt;dokku-letsencrypt&lt;/a&gt; which lets you automatically register and configure &lt;a href=&quot;https://letsencrypt.org/&quot;&gt;Let’s Encrypt&lt;/a&gt; SSL certificates. It promises to get you set up with just 3 commands, but I ran into two issues.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;It fails if you’ve misconfigured your app’s domain in Dokku.&lt;/li&gt;
  &lt;li&gt;It fails if your apps have no network listeners.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I’m not going to dig into the precise causes of these errors in this post, since I didn’t do that in real life. But in the unlikely event that you are currently dealing with the same issues, hopefully this helps you debug.&lt;/p&gt;

&lt;h2 id=&quot;1-dokku-domain-misconfiguration&quot;&gt;1. Dokku domain misconfiguration&lt;/h2&gt;

&lt;p&gt;The first site I tried to configure was &lt;a href=&quot;https://js.apps.veryjoe.com&quot;&gt;js.apps.veryjoe.com&lt;/a&gt;. Running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dokku letsencrypt js&lt;/code&gt; produced following error:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;ACME server returned an error: urn:acme:error:malformed :: The request message was malformed :: Error creating new authz :: Name does not end in a public suffix&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This suggested something was up with Dokku’s Domain name configuration. I investigated with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dokku domains:report&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;=====&amp;gt; js domains information
       Domains app enabled:           true
       Domains app vhosts:            js.apps
       Domains global enabled:        true
       Domains global vhosts:         apps

root@apps:~# dokku domains:add-global
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Apparently I had misconfigured Dokku, supplying it with just &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apps&lt;/code&gt; as the domain name instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apps.veryjoe.com&lt;/code&gt;, so the plugin was trying to request a certificate for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;js.apps&lt;/code&gt;, and Let’s Encrypt wasn’t having it.&lt;/p&gt;

&lt;p&gt;I tried to reset the global domain configuration, but that didn’t actually change the configuration for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;js&lt;/code&gt; app.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;root@apps:~# dokku domains:remove-global apps.veryjoe.com
-----&amp;gt; Removed apps
root@apps:~# dokku domains:add-global apps.veryjoe.com
-----&amp;gt; Added apps.veryjoe.com
root@apps:~# dokku domains:report
=====&amp;gt; js domains information
       Domains app enabled:           true
       Domains app vhosts:            js.apps
       Domains global enabled:        true
       Domains global vhosts:         apps.veryjoe.com
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I figured that perhaps I just had to clear the app-specific configuration too, and I was right.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;root@apps:~# dokku domains:clear js
root@apps:~# dokku domains:report
=====&amp;gt; js domains information
    Domains app enabled:           true
    Domains app vhosts:            js.apps.veryjoe.com
    Domains global enabled:        true
    Domains global vhosts:         apps.veryjoe.com

root@apps:~# dokku letsencrypt js
=====&amp;gt; Let&apos;s Encrypt js
...
-----&amp;gt; Certificate retrieved successfully.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In conclusion, &lt;strong&gt;Name does not end in a public suffix&lt;/strong&gt; is pretty self explanatory. You just need to get your domain names in order!&lt;/p&gt;

&lt;h2 id=&quot;2-missing-network-listeners&quot;&gt;2. missing network listeners&lt;/h2&gt;

&lt;p&gt;Next I tried &lt;a href=&quot;https://diff.apps.veryjoe.com&quot;&gt;diff.apps.veryjoe.com&lt;/a&gt;. The error follows (emphasis added):&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Unable to reach http://diff.apps.veryjoe.com/.well-known/acme-challenge/zWbBBKkpaQD-6O0NXO8FWktijAIKSpDMWI-2K9MlrVw&lt;/strong&gt;: HTTPSConnectionPool(host=’diff.apps.veryjoe.com’, port=443): Max retries exceeded with url: /.well-known/acme-challenge/zWbBBKkpaQD-6O0NXO8FWktijAIKSpDMWI-2K9MlrVw (Caused by SSLError(CertificateError(&lt;strong&gt;“hostname ‘diff.apps.veryjoe.com’ doesn’t match ‘js.apps.veryjoe.com’“&lt;/strong&gt;,),))&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;diff.apps.veryjoe.com was not successfully self-verified. CA is likely to fail as well!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Generating new certificate private key
CA marked some of the authorizations as invalid, which likely means &lt;strong&gt;it could not access http://example.com/.well-known/acme-challenge/X&lt;/strong&gt;. Did you set correct path in -d example.com:path or –default_root? Is there a warning log entry about unsuccessful self-verification? Are all your domains accessible from the internet? Failing authorizations: https://acme-staging.api.letsencrypt.org/acme/authz/rx8o_z-L66gQgOVHjWWUoLDlIb9vOEPzTBqPvxnYkEM&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let’s Encrypt tries to access a file which the dokku-letsencrypt plugin hosts in order to prove that I own the domain, but it can’t find it.&lt;/p&gt;

&lt;h3 id=&quot;debugging-the-network-error&quot;&gt;Debugging the network error&lt;/h3&gt;

&lt;p&gt;First I tried looking up &lt;strong&gt;“dokku letsencrypt hostname doesn’t match”&lt;/strong&gt; and found this &lt;a href=&quot;https://github.com/dokku/dokku-letsencrypt/issues/152&quot;&gt;issue&lt;/a&gt; which recommended following some instructions in the plugin’s readme. They didn’t work but I noticed something suspicious while I was following them:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;root@apps:~# dokku proxy:ports-add diff http:80:5555
!     No web listeners specified for diff
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;No web listeners specified&lt;/strong&gt; suggests some kind of network configuration error. I googled around and found a &lt;a href=&quot;https://github.com/dokku/dokku-letsencrypt/issues/145&quot;&gt;github issue&lt;/a&gt; and &lt;a href=&quot;https://jonathanmh.com/dokku-with-multiple-domains-and-letsencrypt/&quot;&gt;a tutorial&lt;/a&gt; which connected nginx to issues with dokku-letsencrypt. So why was &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;diff&lt;/code&gt; broken in this way but not &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;js&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;Eventually I ran &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dokku network:report&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;root@apps:~# dokku network:report
=====&amp;gt; alcoholculator network information
    Network bind all interfaces:   false
    Network listeners:
=====&amp;gt; diff network information
    Network bind all interfaces:   false
    Network listeners:             
=====&amp;gt; js network information
    Network listeners:             172.17.0.4:5000
    Network bind all interfaces:   false
=====&amp;gt; paint network information
    Network listeners:             172.17.0.2:5000
    Network bind all interfaces:   false
=====&amp;gt; thumbnailer network information
    Network listeners:             172.17.0.3:5000
    Network bind all interfaces:   false
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now I had a hypothesis: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;alcoholculator&lt;/code&gt; should also break, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;paint&lt;/code&gt; should succeed.&lt;/p&gt;

&lt;p&gt;Unfortunately, I hit a snag at this point.&lt;/p&gt;

&lt;h3 id=&quot;avoiding-lets-encrypt-rate-limits&quot;&gt;Avoiding Let’s Encrypt rate limits&lt;/h3&gt;

&lt;p&gt;Let’s Encrypt only lets you try to register certificates a few times every three hours. Running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dokku letsencrypt paint&lt;/code&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;There were too many requests of a given type :: Error creating new registration :: too many registrations for this IP: see &lt;a href=&quot;https://letsencrypt.org/docs/rate-limits/&quot;&gt;https://letsencrypt.org/docs/rate-limits/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The solution suggested by the link is to use the staging environment while you experiment.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;dokku config:set --no-restart paint DOKKU_LETSENCRYPT_SERVER=staging
dokku config:set --no-restart alcoholculator DOKKU_LETSENCRYPT_SERVER=staging
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;solving-the-issue&quot;&gt;Solving the issue&lt;/h3&gt;

&lt;p&gt;At this point I was able to prove my hypothesis. Running against their staging environment, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;paint&lt;/code&gt; successfully registered for a certificate, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;alcoholculator&lt;/code&gt; encountered the same error as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;diff&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;diff&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;alcoholculator&lt;/code&gt; apps are both static Javascript sites, so I figured that the root cause could lie in their buildpack. I inspected their repositories and in both I found a file called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; containing:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;export BUILDPACK_URL=https://github.com/florianheinemann/buildpack-nginx.git
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As I began to search for issues related to Dokku’s static site buildpack and letsencrypt, I realized that that the latest version of the buildpack actually lives at &lt;a href=&quot;https://github.com/dokku/buildpack-nginx&quot;&gt;https://github.com/dokku/buildpack-nginx&lt;/a&gt; now. The instructions there tell you to just put a file called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.static&lt;/code&gt; in the root of your repository. I did that for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;diff&lt;/code&gt; and pushed the changes. Now it had a network listener:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;root@apps:~# dokku network:report diff
=====&amp;gt; diff network information
    Network bind all interfaces:   false
    Network listeners:             172.17.0.6:5000
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dokku-letsencrypt diff&lt;/code&gt; ran successfully against the staging environment:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;root@apps:~# dokku letsencrypt diff
-----&amp;gt; Certificate retrieved successfully.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I decided that it wasn’t worth digging into why exactly the old version was breaking things. Sometimes things break, you update them, and then they’re fixed 🤷‍♂️.&lt;/p&gt;

&lt;p&gt;After fixing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;alcoholculator&lt;/code&gt; in the same way, I just had to wait 3 hours for Let’s Encrypt’s rate limits to reset and then I was able to set everything up.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Ultimately both of the problems here were of my own making: misconfiguration and outdated dependencies. The &lt;a href=&quot;https://github.com/dokku/dokku-letsencrypt&quot;&gt;dokku-letsencrypt&lt;/a&gt; plugin is pretty great (&lt;a href=&quot;https://medium.com/@pimterry/effortlessly-add-https-to-dokku-with-lets-encrypt-900696366890&quot;&gt;this tutorial&lt;/a&gt; suggests that a Let’s Encrypt certificate would be significantly more painful to set up without it!) but no software can completely prevent user error. With decent error messages and warnings, I was able to web-search my way to solutions. I hope you enjoy your secure connection to my &lt;a href=&quot;/projects&quot;&gt;side projects&lt;/a&gt;!&lt;/p&gt;

</description>
        <pubDate>Sat, 06 Jul 2019 19:11:00 +0000</pubDate>
        <link>https://veryjoe.com/tech/2019/07/06/HTTPS-dokku.html</link>
        <guid isPermaLink="true">https://veryjoe.com/tech/2019/07/06/HTTPS-dokku.html</guid>
        
        
        <category>tech</category>
        
      </item>
    
      <item>
        <title>I used the formula for the volume of a cylinder in real life for the first time ever and it went OK</title>
        <description>&lt;p&gt;For me, the phrase “pie dee over two squared aitch” (𝜋 (d½)² h) - also known as “pie are squared aitch” (𝜋r²h) - evokes images of university lectures; magnetic cylindrical rods, electric charge in a piece of wire. If you need to understand those things, you need to know how much space they take up in the world. Until the situation described here, I don’t think I’ve ever actually needed to calculate the volume of a cylinder outside of an exam.&lt;/p&gt;

&lt;p&gt;Recently I found myself making a pretty large quantity of apple butter. I can’t say I’m apple butter’s greatest fan, but I’ve started cooking in bulk according to precise recipes and storing it all in glass jars at room temperature a.k.a. “canning”, and apple butter is a good introductory recipe.&lt;/p&gt;

&lt;p&gt;The recipe looked simple enough, it’s basically apple sauce cooked for longer. The recipe asks you to boil 6 lbs of apples in 2 cups of apple cider for half an hour, blend it, then add 3 cups of sugar, some cinnamon and keep cooking for a few hours.&lt;/p&gt;

&lt;p&gt;However, I hit a snag at the “blend it” stage. The recipe book suggested I transfer boiled apples to a blender for puréeing, then transfer them back to the pot. Such unnecessary effort! I’m pretty sure that immersion blenders had been invented by 2006 when this book was written.&lt;/p&gt;

&lt;p&gt;I patted myself on the back as I transformed the contents of my pot of apples to mush and saved a significant quantity of cooking and washing labor. This was a figurative patting on the back. I recommend a two-handed grip for stick-blending large quantities of scalding hot apples.&lt;/p&gt;

&lt;p&gt;Then I read on. The next step was “Measure 12 cups (3 L) of apple purée”, then the step after was to add the sugar, but if I had too much apple purée the ratio of sugar to purée might be off and so there might not be enough sugar to preserve the final product! I needed to be sure in that moment that I had the correct volume of apples. I resigned myself to having to wash a measuring jug after all&lt;/p&gt;

&lt;p&gt;Then inspiration came to me. I ran to grab a tape measure. First I measured the height of the pot, starting from the top of the base.&lt;/p&gt;

&lt;div class=&quot;thumbnailed&quot;&gt;
  &lt;a href=&quot;/images/cylinder/height.jpg&quot;&gt;
    &lt;img src=&quot;/thumbnails/cylinder/height.jpg&quot; /&gt;
  &lt;/a&gt;
  The height of the pot.
&lt;/div&gt;

&lt;p&gt;then I measured the distance between the top of the purée and the top of the pot,&lt;/p&gt;

&lt;div class=&quot;thumbnailed&quot;&gt;
  &lt;a href=&quot;/images/cylinder/depth.jpg&quot;&gt;
    &lt;img src=&quot;/thumbnails/cylinder/depth.jpg&quot; /&gt;
  &lt;/a&gt;
  The distance between the top of the purée and the top of the pot.
&lt;/div&gt;

&lt;p&gt;and finally I measured the diameter of the pot.&lt;/p&gt;

&lt;div class=&quot;thumbnailed&quot;&gt;
  &lt;a href=&quot;/images/cylinder/diameter.jpg&quot;&gt;
    &lt;img src=&quot;/thumbnails/cylinder/diameter.jpg&quot; /&gt;
  &lt;/a&gt;
  The pot&apos;s diameter.
&lt;/div&gt;

&lt;p&gt;The two vertical measurements gave me 23 - 17 = 6cm for the depth of the purée, so I plugged in all the numbers. (25cm/2)² ⨉ 𝜋 ⨉ (23 - 17) cm = 2945cm³! Apparently I had managed to produce an almost perfectly correct volume of purée. Happy with this outcome, I followed the recipe to the end.&lt;/p&gt;

&lt;div class=&quot;thumbnailed&quot;&gt;
  &lt;a href=&quot;/images/cylinder/apple butter.jpg&quot;&gt;
    &lt;img src=&quot;/thumbnails/cylinder/apple butter.jpg&quot; /&gt;
  &lt;/a&gt;
  A batch of apple butter.
&lt;/div&gt;

&lt;p&gt;However, in retrospect I think I was being a bit cavalier with my measurements, rounding to the nearest 0.5 cm. If I were 0.25cm off the real measurements, how wrong could I be?&lt;/p&gt;

&lt;p&gt;If my measurements overestimated the external height and diameter and underestimated the internal height all by 0.25cm, the real volume would have been (24.75 cm / 2)² ⨉ 𝜋 ⨉ (22.75 - 17.25) cm = 2646cm³, and I would have been 12% off the recipe. In the other direction, if the real volume were (26cm/2)² ⨉ 𝜋 ⨉ (23.25 - 16.75) cm = 3505cm³ I would have been 16% over!&lt;/p&gt;

&lt;p&gt;That doesn’t seem so bad, but there was another mistake I could have made: if the tape measure was not perfectly vertical then I may actually have measured the longest side of a triangle formed by the side of the pan, the 1.5cm metal tip of the tape measure, and the tape itself, which would have resulted in an overestimate.&lt;/p&gt;

&lt;p&gt;Thankfully, the &lt;a href=&quot;https://en.wikipedia.org/wiki/Zhoubi_Suanjing&quot;&gt;Zhoubi Suanjing&lt;/a&gt; taught us that a² + b² = c². In the worst case, if my 23cm outside pan measurement had been angled and overestimated by 0.25cm, the real height would actually be a = sqrt(22.75² - 1.5²) = 22.70 cm, and the loss of 0.05cm would bring the real volume from 2646 cm³ to 2622 cm³. Think about that 40cm³ difference as 20 little-finger-tip sized cubes, or roughly 1/6 of a 8z jar of apple butter.&lt;/p&gt;

&lt;p&gt;In any case, later on I messed up by undercooking the apple butter by about 3 hours, noticing my mistake after canning about half of the mixture into four 8oz jars, then cooking the rest for longer and canning it in five more 8oz jars, bringing me to 9 jars instead of the recipe-suggested 8, so in the end it was hard to know how accurate I really was.&lt;/p&gt;

&lt;p&gt;But hey, at least I had fun measuring things. If you actually managed to get this far: thanks for your time!&lt;/p&gt;
</description>
        <pubDate>Sun, 26 May 2019 19:23:37 +0000</pubDate>
        <link>https://veryjoe.com/maths/2019/05/26/Volume-of-a-cylinder.html</link>
        <guid isPermaLink="true">https://veryjoe.com/maths/2019/05/26/Volume-of-a-cylinder.html</guid>
        
        
        <category>maths</category>
        
      </item>
    
      <item>
        <title>Hello Jekyll</title>
        <description>&lt;p&gt;DigitalOcean’s lovely WordPress instance decided it would constantly throw me &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Database Errors&lt;/code&gt; which I couldn’t be bothered to fix because, in the end, why do I need a database at all?&lt;/p&gt;

&lt;p&gt;So I’m trying out Jekyll!&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# Also:
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;apparently_it_has&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;code highlighting %s&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Built in!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;So far I’m pretty happy with it, although it took a little effort to set up. My primary stumbling block was themes not working, but it turns out that if you have&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;url: http://mysiteblahblah.com
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;set in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_config.yml&lt;/code&gt; file, Jekyll uses that as the absolute URL from which to try to include all static assets (like themes) - not so useful while you’re developing locally!&lt;/p&gt;

&lt;p&gt;In any case, it’s nice to be able to just write in markdown and HTML and instantly preview in my browser, and it’s nice to be able to easily and fully manage my own content. I’m hosting this on &lt;a href=&quot;https://pages.github.com/&quot;&gt;github pages&lt;/a&gt;, so all I have to do is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git push origin master&lt;/code&gt; and lo, my blog is updated.&lt;/p&gt;
</description>
        <pubDate>Tue, 27 Sep 2016 04:23:37 +0000</pubDate>
        <link>https://veryjoe.com/tech/2016/09/27/first-post.html</link>
        <guid isPermaLink="true">https://veryjoe.com/tech/2016/09/27/first-post.html</guid>
        
        
        <category>tech</category>
        
      </item>
    
      <item>
        <title>California: impressions, words, photos, food</title>
        <description>&lt;p&gt;People keep asking: what’s California like? Everyone has funny accents and rain doesn’t exist.&lt;/p&gt;

&lt;p&gt;I guess you want more than that. OK, I’ll give you my &lt;em&gt;very&lt;/em&gt; first impression: everything is made of grids.&lt;/p&gt;

&lt;div class=&quot;thumbnailed&quot;&gt;
  &lt;a href=&quot;/images/food/1-plane.jpg&quot;&gt;
    &lt;img src=&quot;/thumbnails/food/1-plane.jpg&quot; /&gt;
  &lt;/a&gt;
  A plane&apos;s eye view of American grid layouts
&lt;/div&gt;

&lt;p&gt;This is a fairly well known fact amongst us Brits. U.S. cities were founded when we knew how big cities would get, so they just laid everything out in grids. Want to give someone an exact location in a city? Just say “On &amp;lt;x street&amp;gt; &amp;amp; &amp;lt;y street&amp;gt;” and they’ll turn up at the intersection of x and y. Neat!&lt;/p&gt;

&lt;p&gt;Another funny thing about grids is that I apparently say it like “grits”, which resulted in an Uber driver who was completely baffled that I would say “Everything is made of grits here!”. Why is that baffling?&lt;/p&gt;

&lt;div class=&quot;thumbnailed&quot;&gt;
  &lt;a href=&quot;/images/food/2-grits.jpg&quot;&gt;
    &lt;img src=&quot;/thumbnails/food/2-grits.jpg&quot; /&gt;
  &lt;/a&gt;
  Grits.
&lt;/div&gt;

&lt;p&gt;Because this is &lt;a href=&quot;https://en.wikipedia.org/wiki/Grits&quot;&gt;grits&lt;/a&gt;, “a food made by boiling ground maize (also known as corn), and usually served with other flavourings as a breakfast dish, usually savoury.” Basically corn porridge, except they don’t really have the word “porridge” here.&lt;/p&gt;

&lt;p&gt;Going back to the bit where people misunderstand me, why does everything think I’m saying “Jeff” when I say “Joe”? It’s happened so much, I think I might rename myself to Jeff.&lt;/p&gt;

&lt;p&gt;But anyway, things get weirder.￼&lt;/p&gt;

&lt;div class=&quot;thumbnailed&quot;&gt;
  &lt;a href=&quot;/images/food/3-waffles.jpg&quot;&gt;
    &lt;img src=&quot;/thumbnails/food/3-waffles.jpg&quot; /&gt;
  &lt;/a&gt;
  I have no words.
&lt;/div&gt;

&lt;p&gt;This, my English friends, is the Chicken and Waffles which the grits came with. Yes, chicken and waffles. Us British folks have a great appetite for American food culture; we have McDonalds and burgers and american style pizza. And there are some things we know that they have but we just reject, such as deep fried butter. And then there’s chicken and waffles.&lt;/p&gt;

&lt;p&gt;Why haven’t we heard of chicken and waffles before? Here’s it’s just a Thing. You can just go to a chicken and waffles place. It’s not as common as pizza or burgers, but every decent sized town will have some chicken and waffles. Every English person I have spoken to is baffled by the concept. I asked a Belgian friend if she had heard of chicken and waffles and she said “yes”, then I said, “no, chicken &lt;em&gt;and&lt;/em&gt; waffles”. She was disgusted. When my friend Katarina took me to this chicken and waffles joint in Oakland - fairly intoxicated, I will add - I spent the entire time laughing at chicken and waffles, grits/grids, and my own sorry state of intoxication. I feel like I’ve said “chicken and waffles” more times here than any other phrase ever.&lt;/p&gt;

&lt;p&gt;But actually, as you’d expect, if you like waffles and also chicken, you’ll like chicken and waffles. Americans took two unrelated food items and slapped them together. Well done! I suppose I could do the same thing. Hmm. Chocolate… blueberry… bagels…
￼&lt;/p&gt;

&lt;div class=&quot;thumbnailed&quot;&gt;
  &lt;a href=&quot;/images/food/4-crazybagel.jpg&quot;&gt;
    &lt;img src=&quot;/thumbnails/food/4-crazybagel.jpg&quot; /&gt;
  &lt;/a&gt;
  There is no one to blame for this abomination but me.
&lt;/div&gt;

&lt;p&gt;Chocolate chip bagel with blueberry cream-cheese? Nope OK I’ll leave the food invention to other people. Bagel places here are unsurprisingly Better Than Ours. The best ones have a large varieties of bagels, baked in store, a large selection of cream cheeses, and they let you combine them however you like. Honestly, &lt;a href=&quot;http://izzysbrooklynbagels.com/&quot;&gt;Izzy’s Brooklyn Bagels&lt;/a&gt;&amp;lt;/a&amp;gt; is probably the best place to get breakfast on University Avenue, and that’s not just my Jewish heritage talking. But when you go, you should get something more like this:&lt;/p&gt;

&lt;div class=&quot;thumbnailed&quot;&gt;
  &lt;a href=&quot;/images/food/5-bagel.jpg&quot;&gt;
    &lt;img src=&quot;/thumbnails/food/5-bagel.jpg&quot; /&gt;
  &lt;/a&gt;
  A sensible, tasty bagel.
&lt;/div&gt;

&lt;p&gt;I think that was chive cream cheese and sun-dried tomato bagel, or something along those lines. Whatever you get, it’ll probably be better bang for your buck than this place:
￼&lt;/p&gt;
&lt;div class=&quot;thumbnailed&quot;&gt;
  &lt;a href=&quot;/images/food/6-benedict.jpg&quot;&gt;
    &lt;img src=&quot;/thumbnails/food/6-benedict.jpg&quot; /&gt;
  &lt;/a&gt;
  The sauce kind of looks like melted plastic.
&lt;/div&gt;

&lt;p&gt;Why do they serve… boiled/roast potatoes(?) with Eggs Benedict? Anyway, this place on California Avenue made it clear that the U.S. is just as capable as us at doing slightly overpriced breakfast, except they add strange sides that don’t really fit.&lt;/p&gt;

&lt;p&gt;On the topic of sides: you know the whole “America has huge portions” thing? Well, it’s often very true, specifically at sit-down restaurants. This was a relatively &lt;a href=&quot;http://www.urbandictionary.com/define.php?term=bourgey&quot;&gt;bourgey&lt;/a&gt; (rhymes with Bruges-eee) Mexican joint also on California Avenue: Palo Alto Del Sol, where I had my first ever shot of genuinely tasty Tequila. Apologies for the terrible quality, the iPhone 5S camera isn’t spectacular in low light.&lt;/p&gt;

&lt;div class=&quot;thumbnailed&quot;&gt;
  &lt;a href=&quot;/images/food/7-enchilada.jpg&quot;&gt;
    &lt;img src=&quot;/thumbnails/food/7-enchilada.jpg&quot; /&gt;
  &lt;/a&gt;
  It&apos;s like a flag in enchilada form
&lt;/div&gt;

&lt;p&gt;To give you an idea of the scale of this: they took a normal good-sized plate and then stretched it, and filled it with very dense beans, steak-filled wraps, and rice, and smothered it all in sauce and cheese. I’ve been thinking carefully about this, and I’ve come to the conclusion that finishing all of your food seems more optional in this country than in the UK. They give you too many chips, too much rice, too many potatoes. Sure you can finish it all if you’re hungry, but if you sit there trying to force it all down, people look at you funny. “Why are you trying to finish that? It’s just potatoes!”. Unbridled prosperity can have some interesting consequences.&lt;/p&gt;

&lt;p&gt;Anyway, that’s the ‘upper-tier’ Mexican food which the British are mostly used to, but I’m in California, a place which the U.S. &lt;a href=&quot;https://en.wikipedia.org/wiki/Mexican_Cession&quot;&gt;bought from Mexico&lt;/a&gt;. They have actual Mexican food here. So here’s a question: what is a taco?&lt;/p&gt;

&lt;div class=&quot;thumbnailed&quot;&gt;
  &lt;a href=&quot;/images/food/8-tacos.jpg&quot;&gt;
    &lt;img src=&quot;/thumbnails/food/8-tacos.jpg&quot; /&gt;
  &lt;/a&gt;
  Flat things with stuff on them
&lt;/div&gt;

&lt;p&gt;Many things are tacos, but these are one of the most common kinds! I think I might once have seen tacos like this in the UK, but still they were at some upmarket place in London. But no, my friends, this is also tacos, a blob of meat spooned on top of floppy tortilla bread. You can get one of these babies for $1 from a taco truck - that’s $3-4 dollars for a full meal. Or you can pay three times as much if you want tacos that aren’t as good. Or in this case, you can go to a Hackathon where Pebble rents out a taco truck and gives everyone free tacos. Yay tech! So, what else is Mexican?￼&lt;/p&gt;

&lt;div class=&quot;thumbnailed&quot;&gt;
  &lt;a href=&quot;/images/food/9-menudo.jpg&quot;&gt;
    &lt;img src=&quot;/thumbnails/food/9-menudo.jpg&quot; /&gt;
  &lt;/a&gt;
  It feels odd to put stomach inside your stomach, but #yolo
&lt;/div&gt;

&lt;p&gt;This is &lt;a href=&quot;https://en.wikipedia.org/wiki/Menudo_(soup)&quot;&gt;Menudo&lt;/a&gt;, basically a bowl of beef stomach, which you would have for breakfast. I was directed by Katarina to rip a corn tortilla up into little pieces and drop them in to “thicken” the soup, which felt kind of exotically unfamiliar. As a whole, I feel like this is the kind of food which many English folk might turn their noses at, but I can tell you that it was delicious. Other tortilla related products: blue tortilla chips!
￼&lt;/p&gt;

&lt;div class=&quot;thumbnailed&quot;&gt;
  &lt;a href=&quot;/images/food/10-bluechip.jpg&quot;&gt;
    &lt;img src=&quot;/thumbnails/food/10-bluechip.jpg&quot; /&gt;
  &lt;/a&gt;
  I try to always qualify &quot;chips&quot; with &quot;tortilla&quot;, since if I don&apos;t I&apos;ll start referring to &quot;crisps&quot; as &quot;chips&quot;. That way lies madness.
&lt;/div&gt;

&lt;p&gt;Yep, blue corn is a real thing, and an unfortunate victim of bad lighting.&lt;/p&gt;

&lt;p&gt;In general, food from The Americas in general is more plentiful here. Here’s a messy photo from a mom-and-pop Salvadorian café we went to in San Francisco’s Mission District:&lt;/p&gt;

&lt;div class=&quot;thumbnailed&quot;&gt;
  &lt;a href=&quot;/images/food/11-papusa.jpg&quot;&gt;
    &lt;img src=&quot;/thumbnails/food/11-papusa.jpg&quot; /&gt;
  &lt;/a&gt;
  A jumble of half-eaten Stuff on a plate. A note on portion sizes: this was large enough for both of us.
&lt;/div&gt;

&lt;p&gt;This is &lt;a href=&quot;https://en.wikipedia.org/wiki/Pupusa&quot;&gt;papusa&lt;/a&gt;, a kind of soft tortilla filled with delicious things like cheese and beans and maybe pork too, served with rice and beans. It’s reminiscent of omelettes or pancakes, in that flat carby/proteiny way. In terms of price:satisfaction ratio, papusa is the best food I’ve had here. It’s fatty and salty and umami but not too oily, every component is delicious, each part complements the other perfectly, there’s nothing pretentious about it. It’s just good, simple food. And it’s from El Salvador, a country which seems to have zero restaurant representation in the United Kingdom. Don’t believe me? I googled “Salvadorian restaurant UK” and all I found was &lt;a href=&quot;http://salvadorandamanda.com/bloomsbury/menus/tapas-menu&quot;&gt;Salvadoran Armada&lt;/a&gt;, a “Spanish” restaurant, which has no papusa. A crying shame, if you ask me.&lt;/p&gt;

&lt;p&gt;So you’ve seen one of the three food-specialisms in the bay area I’ve identified: food from south of the border. Another one is avocados. Subway has a “local special” with avocados in it. Pebble’s work catering regularly puts avocados in the salad. This is an avocado sandwich with turkey and bacon, the Nifty from the lovely &lt;a href=&quot;http://www.peninsulacreamery.com/&quot;&gt;Peninsula Creamery&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;thumbnailed&quot;&gt;
  &lt;a href=&quot;/images/food/12-sandwich.jpg&quot;&gt;
    &lt;img src=&quot;/thumbnails/food/12-sandwich.jpg&quot; /&gt;
  &lt;/a&gt;
  The second best thing on the menu, next to Jefe.
&lt;/div&gt;

&lt;p&gt;Besides its extensive and excellent selection of sandwiches and breakfast items, their menu exhibits something else which seems to be more prevalent in the bay area than elsewhere: a larger selection of cheeses. When you order a sandwich, you’ll then have to decide between one of five different kinds of cheese.&lt;/p&gt;

&lt;div class=&quot;thumbnailed&quot;&gt;
  &lt;a href=&quot;/images/food/13-creamery.jpg&quot;&gt;
    &lt;img src=&quot;/thumbnails/food/13-creamery.jpg&quot; /&gt;
  &lt;/a&gt;
  5 different kinds of cheese. Sometimes they make you pick your bread too. It&apos;s all too much for me. I just choose randomly these days. Also: &quot;hot links&quot; are sausages in buns.
&lt;/div&gt;

&lt;p&gt;This experience threw me off at first, since it’s quite unlike the UK where “cheese” basically means “cheddar” unless you qualify it with “cream”. The same place also sells a product called Orange Whip. My English friends, I am about to bring to you a new and amazing recipe that even you at home can make all by yourself. Here are the steps&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Blend ice-cream with an orange fizzy-drink (or “soda”)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s it. It is beautiful. It tastes like a fizzy orange sweet except that you’re drinking it. Today, however, it occurred to me that I could ask for Orange Whip with an ice-cream that’s not vanilla. Today, I invented the Raspberry Orange Whip.&lt;/p&gt;

&lt;div class=&quot;thumbnailed&quot;&gt;
  &lt;a href=&quot;/images/food/14-whip.jpg&quot;&gt;
    &lt;img src=&quot;/thumbnails/food/14-whip.jpg&quot; /&gt;
  &lt;/a&gt;
  It just looks like a milkshake, but it tastes like a divine elixir of love
&lt;/div&gt;

&lt;p&gt;A new-hire was at the counter at the time and I think he wasn’t actually sure how to make it, so he asked his manager. She then called me up to the counter to let me know that “that’s not how we make it here. Are you sure that’s what you want? It might not be good, I just want you to understand that”. She pulled a friendly yet disgusted face which said “you’re not allowed to complain when this is inevitably terrible”. I’m happy to say that she was very wrong. The Raspberry Orange Whip is amazing.&lt;/p&gt;

&lt;p&gt;The J1 visa that I’m on is technically a “cultural exchange” visa, with which I am supposed to bring part of the U.S. back home, and also contribute to the U.S. This is my contribution.&lt;/p&gt;

&lt;p&gt;Another California Classic is clam chowder, since seafood in general is very popular. So far all I’ve had is a small cup of clam chowder in a fancy hipster bar in downtown Palo Alto (Local Union 271). It tasted like cream of mushroom soup with clams in it, which is good because I happen to like cream of mushroom soup. I will include this heavily adjusted photograph for the sake of completeness.&lt;/p&gt;

&lt;div class=&quot;thumbnailed&quot;&gt;
  &lt;a href=&quot;/images/food/15-chowder.jpg&quot;&gt;
    &lt;img src=&quot;/thumbnails/food/15-chowder.jpg&quot; /&gt;
  &lt;/a&gt;
  What is this, a clam chowder for ants? (in fact, the small portion was a welcome change)
&lt;/div&gt;

&lt;p&gt;I found out about this place from the bartender at the &lt;a href=&quot;http://www.gravitywinebar.com&quot;&gt;Gravity Wine Bar&lt;/a&gt;. I was told that America has excellent service, and I wasn’t sure what to expect. Well, one could dedicate a whole blog post to American service, but instead, here’s a single example.&lt;/p&gt;

&lt;div class=&quot;thumbnailed&quot;&gt;
  &lt;a href=&quot;/images/food/16-tips.jpg&quot;&gt;
    &lt;img src=&quot;/thumbnails/food/16-tips.jpg&quot; /&gt;
  &lt;/a&gt;
  Tips for tips!
&lt;/div&gt;

&lt;p&gt;When I idly asked the bartender “do you know any other good spots in the area?” he proceeded to dump his entire knowledge of bars, restaurants and nightlife in this and all neighbouring cities until I heavily hinted that I was very grateful and had more than enough. For service like this, I can give a 25% tip.&lt;/p&gt;

&lt;p&gt;But sometimes, America just gets things wrong.&lt;/p&gt;

&lt;div class=&quot;thumbnailed&quot;&gt;
  &lt;a href=&quot;/images/food/17-pizza.jpg&quot;&gt;
    &lt;img src=&quot;/thumbnails/food/17-pizza.jpg&quot; /&gt;
  &lt;/a&gt;
  Did they just put stuff on in the wrong order by accident and call it a day?
&lt;/div&gt;

&lt;p&gt;Why is the tomato &lt;em&gt;on top of the cheese and fillings&lt;/em&gt;? This is incorrect. This is not how you pizza. Sure, its delicious and allows you to include more cheese and lets you really taste the tomato sauce, but it’s wrong. And then, biscuits.&lt;/p&gt;

&lt;div class=&quot;thumbnailed&quot;&gt;
  &lt;a href=&quot;/images/food/18-nope.jpg&quot;&gt;
    &lt;img src=&quot;/thumbnails/food/18-nope.jpg&quot; /&gt;
  &lt;/a&gt;
  Nope.
&lt;/div&gt;

&lt;p&gt;Anything which we call “cookies”, americans call “biscuits”. The word “biscuit” in America refers to this abomination. It’s a vaguely scone-like ball of bread, which doesn’t really need to exist. Apparently you eat it with gravy, but I declined to put myself through that ordeal out of sheer indignation.&lt;/p&gt;

&lt;p&gt;OK, they weren’t actually that bad. But they’re not biscuits. Digestives are biscuits. I argued with Katarina for half a day to convince her that &lt;a href=&quot;http://i2.manchestereveningnews.co.uk/incoming/article6463444.ece/alternates/s2197/mcvities-biscuit.jpg&quot;&gt;digestives&lt;/a&gt; are biscuits. Eventually she agreed on the grounds that digestives are a thing that you dip in tea, and tea is particularly British, so we reserve the right to call them biscuits. However she won’t agree that &lt;a href=&quot;https://en.wikipedia.org/wiki/Bourbon_biscuit&quot;&gt;bourbons&lt;/a&gt; are biscuits and not cookies, despite the accompanying cup of tea in Wikipedia’s photo. Americans…&lt;/p&gt;

&lt;p&gt;So, surprise! You might have thought you were going to read a varied and interesting analysis of the differences between the Bay Area and the United Kingdom, but in fact I just tricked you into reading a food-blog post with practically every single self-indulgent food snap I’ve taken since landing. Luckily for you, I don’t intend to continue this way. Future planned topics include “socioeconomic observations: urban contrast” and “doorknobs”.&lt;/p&gt;
</description>
        <pubDate>Wed, 19 Aug 2015 04:23:37 +0000</pubDate>
        <link>https://veryjoe.com/life/2015/08/19/california.html</link>
        <guid isPermaLink="true">https://veryjoe.com/life/2015/08/19/california.html</guid>
        
        
        <category>life</category>
        
      </item>
    
      <item>
        <title>Hummus Pasta Salad</title>
        <description>&lt;p&gt;I made this recently by searching for “hummus pasta salad” on google, taking inspiration for a recipe or two and then improvising with what I had.&lt;/p&gt;

&lt;p&gt;It’s surprisingly tasty! The flavours of each component stay relatively separated, so the better your olives/feta/tomatoes/hummus, the better your dish will be.&lt;/p&gt;

&lt;div class=&quot;thumbnailed&quot;&gt;
  &lt;a href=&quot;/images/pastasalad.jpg&quot;&gt;
    &lt;img src=&quot;/thumbnails/pastasalad.jpg&quot; alt=&quot;Hummus Pasta Salad&quot; /&gt;
  &lt;/a&gt;
  appreciate my blurry tupperware food-photographpy
&lt;/div&gt;

&lt;h2 id=&quot;ingredients&quot;&gt;Ingredients&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;About 250g of pasta (e.g. Fusilli. Wholewheat is nice/optional)&lt;/li&gt;
  &lt;li&gt;Three/four tablespoons of hummus. I make my own with &lt;a href=&quot;http://www.inspiredtaste.net/15938/easy-and-smooth-hummus-recipe/&quot; title=&quot;Simple hummus recipe&quot;&gt;this recipe&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;A bowl of cherry tomatoes&lt;/li&gt;
  &lt;li&gt;Half a jar of olives (about 90g?)&lt;/li&gt;
  &lt;li&gt;A single red pepper&lt;/li&gt;
  &lt;li&gt;About 200g of feta, chopped (vegan option: omit!)&lt;/li&gt;
  &lt;li&gt;A glove of garlic, chopped&lt;/li&gt;
  &lt;li&gt;Spices (e.g. black pepper, paprika, basil)&lt;/li&gt;
  &lt;li&gt;A bit of oil for frying&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;instructions&quot;&gt;Instructions&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;Add red pepper, half of the tomatoes and garlic to a frying pan, fry on a medium heat for a few minutes, then turn down to low and add the olives and spices.&lt;/li&gt;
  &lt;li&gt;Meanwhile, mix the hummus with water until it takes on the consistency of cheese sauce. If you didn’t add enough lemon juice before, now’s your chance!&lt;/li&gt;
  &lt;li&gt;When the tomatoes have just started to fall apart, add the pasta, the hummus and the rest of the tomatoes. &lt;/li&gt;
  &lt;li&gt;Simmer for a few minutes (more if you made your sauce too thin), adding the feta halfway through. Serve warm or cold!&lt;/li&gt;
&lt;/ol&gt;
</description>
        <pubDate>Mon, 08 Dec 2014 04:00:00 +0000</pubDate>
        <link>https://veryjoe.com/food/2014/12/08/Hummus-pasta-salad.html</link>
        <guid isPermaLink="true">https://veryjoe.com/food/2014/12/08/Hummus-pasta-salad.html</guid>
        
        
        <category>food</category>
        
      </item>
    
      <item>
        <title>I hosted CollabPaint! And got annoyed at 4-years-ago-Joe.</title>
        <description>&lt;p&gt;I put my old collaborative whiteboard project &lt;a href=&quot;http://paint.veryjoe.com&quot;&gt;CollabPaint&lt;/a&gt; back up on the internet! Multiple people can log in to a room and paint at the same time, without having to register for an account or sign up.&lt;/p&gt;

&lt;div class=&quot;thumbnailed&quot;&gt;
  &lt;a href=&quot;/images/collabpaint.png&quot;&gt;
    &lt;img src=&quot;/thumbnails/collabpaint.png&quot; alt=&quot;Screenshot of Collabpaint&quot; /&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;p&gt;You can set the size/colour/opacity for your brush and also draw lines, rectangles and ovals. There’s even a chat box and the option to save download the thing you draw.&lt;/p&gt;

&lt;p&gt;I wrote CollabPaint about four years ago, and it was one of my first forays in to node.js and socket.io after CollabBox. Like all of my other projects, I never really polished it. It’s still missing a few things such as an undo button (and some overdue bugfixes…), but otherwise it gets the job done!&lt;/p&gt;

&lt;h3 id=&quot;on-future-proofing-code-and-following-best-practice&quot;&gt;On future-proofing Code and following best practice&lt;/h3&gt;

&lt;p&gt;Sadly I had trouble getting CollabBox to run, because I used old crusty versions of all the libraries and never made a package.json file to record which versions of what things I used, so everything is broken. But, &lt;a href=&quot;https://github.com/Spacerat/Collab-Box&quot;&gt;here&lt;/a&gt; it is on github anyway.&lt;/p&gt;

&lt;p&gt;If I could go back four years, I’d tell myself the following set of related things:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Keep a record of which versions of everything you use, if you can&lt;/li&gt;
  &lt;li&gt;When you start using a new language or ecosystem, make sure you actually look up the best practices for the things you’re using.&lt;/li&gt;
  &lt;li&gt;For example, with node.js and npm, actually bother to write package.json files to track all your dependancies!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This kind of stuff should really go without saying, but it strikes me as interesting that - as a hobbyist coder four+ years ago - it didn’t really occur to me. So if you ever see your hobby-programmer kids writing code and they forget to make a package.json file, set them straight so they don’t make the same mistakes I did!&lt;/p&gt;
</description>
        <pubDate>Thu, 04 Dec 2014 03:00:00 +0000</pubDate>
        <link>https://veryjoe.com/tech/2014/12/04/Collabpaint.html</link>
        <guid isPermaLink="true">https://veryjoe.com/tech/2014/12/04/Collabpaint.html</guid>
        
        
        <category>tech</category>
        
      </item>
    
  </channel>
</rss>
