<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>You&apos;ve Reached the Center of the Internet</title>
    <description>It&apos;s a blog
</description>
    <link>blog.benwiener.com/</link>
    <atom:link href="blog.benwiener.com/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Tue, 08 Apr 2025 01:13:50 +0000</pubDate>
    <lastBuildDate>Tue, 08 Apr 2025 01:13:50 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      <item>
        <title>Batting Order (Kind of) Doesn&apos;t Matter*</title>
        <description>&lt;p&gt;I live in Greater Boston, but I’ve been a Yankees fan my whole life.
The season is getting started and I have a new baby, so I’ve been listening to every episode of my favorite baseball podcast, &lt;a href=&quot;https://www.youtube.com/channel/UCzcaZfAmCJlCSoBOiNWx20Q&quot;&gt;Talkin’ Yanks&lt;/a&gt;.
Hosts Jomboy and Jake spend a lot of time discussing, critiquing, and guessing Yankees batting orders.
It’s a fun topic, but my intuition is that the batting order isn’t that important. 
This post is my attempt at estimating how important batting order is.&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet tw-align-center&quot; style=&quot;text-align: center;&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Jomboy predicts the Yankees Opening Day starting lineup &lt;a href=&quot;https://t.co/PeeX2YKzjT&quot;&gt;pic.twitter.com/PeeX2YKzjT&lt;/a&gt;&lt;/p&gt;&amp;mdash; Talkin&amp;#39; Yanks (@TalkinYanks) &lt;a href=&quot;https://twitter.com/TalkinYanks/status/1902075624048455761?ref_src=twsrc%5Etfw&quot;&gt;March 18, 2025&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;h1 id=&quot;the-method&quot;&gt;The method&lt;/h1&gt;

&lt;p&gt;I used a &lt;em&gt;Monte Carlo&lt;/em&gt; approach to generate distributions of runs scored given different batting orders.
This is a fancy way of saying that I simulated a lot of baseball games with different lineups, and used the outcomes of those simluations as distributions of expected run production. 
By comparing these distributions across different batting orders, I could evaluate which lineups are more effective.&lt;/p&gt;

&lt;p&gt;To implement the simulation, I modeled the game in the simplest way I could.
As you’ll see, I think it does a decent job of capturing reality.
I defined hitters as a set of static per-plate-appearance probabilities.
The plate appearance outcomes I modeled are walk, single, double, triple, and home run.
So each hitter is completely defined by their \(P_{BB}\), \(P_{1B}\), \(P_{2B}\), \(P_{3B}\), and \(P_{HR}\).
&lt;!-- Correspondingly, \\(P_{out} = 1 - P_{BB} + P_{1B} + P_{2B} + P_{3B} + P_{HR}\\). --&gt;&lt;/p&gt;

&lt;p&gt;To simulate a game, I step through the lineup and randomly select an outcome weighted by the current batter’s probabilities.
I track outs and runs scored and which bases are occupied.
When three outs are recorded, I clear the bases and start the next inning.
To simulate a game, I run this algorithm for 9 innings and track what happens.&lt;/p&gt;

&lt;p&gt;I use simple logic to advance baserunners and score runs.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Walks advance runners only when forced&lt;/li&gt;
  &lt;li&gt;Singles put the batter on first and score runners from second and third
    &lt;ul&gt;
      &lt;li&gt;Runners advance from first to third with 30% probability, else they go to second base&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Doubles clear the bases and put the batter on second&lt;/li&gt;
  &lt;li&gt;Triples clear the bases and put the batter on third&lt;/li&gt;
  &lt;li&gt;Home runs clear the bases and score the batter&lt;/li&gt;
  &lt;li&gt;Outs advance runners from second to third and score runners from third with 10% probability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By running hundreds of thousands of such simulations I can estimate distributions of team outcomes.&lt;/p&gt;

&lt;p&gt;There’s a lot here that’s not modeled.
The pitcher is of course an important factor, but my model essentially always assumes an average pitcher is on the mound.
Similarly, factors like the defense, runners, lefty/righty splits, extra innings, and emotion, are not modeled.&lt;/p&gt;

&lt;p&gt;With that caveat, here is the simulated distribution of the runs scored in each game by the 2024 Yankees.
I’ll focus on the Ya&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/2025/run_dist_comp.png&quot; alt=&quot;&quot; class=&quot;center&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I used 2024 data from Baseball Reference to define the plate appearance outcome probabilities for a lineup of Gleyber Torres, Juan Soto, Aaron Judge, Austin Wells, Giancarlo Stanton, Jazz Chisolm Jr., Anthony Rizzo, Anthony Volpe, and Alex Verdugo.&lt;/p&gt;

&lt;p&gt;Alongside the simulation results, I plotted the actual per-game run distribution from the same year.
The model lines well with the real data.&lt;/p&gt;

&lt;p&gt;Before messing with batting orders, a sanity check.
What happens if we remove Aaron Judge from this lineup?
He’s the best hitter here by a decent margin, so it should have a big effect.
Here’s what the run distribution looks like if you replace Judge with a second copy of leadoff hitter Gleyber Torres.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/2025/run_dist_no_judge.png&quot; alt=&quot;&quot; class=&quot;center&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Pretty big effect.
Replacing Judge with another Gleyber brings the 2024 Yankees from 5.4 to 4.7 runs per game.
Incidentally, if you multiply this per-game difference by the number of games Judge played, subtract the total difference from the Yankees’ 2024 total runs scored, and plug into a &lt;a href=&quot;https://en.wikipedia.org/wiki/Pythagorean_expectation&quot;&gt;Pythagorean win-loss&lt;/a&gt; formula, you get a value of about 10 Wins Above Gleyber or 11.7 Wins Above Replacement.
This is pretty close to his 2024 fWAR of 11.2. 
Nice to see different methods agree.&lt;/p&gt;

&lt;h1 id=&quot;the-results&quot;&gt;The results&lt;/h1&gt;

&lt;p&gt;Now let’s get to batting order. 
Here’s what happens if we do something pretty extreme and slide Judge from 3rd in the lineup, where he typically hit in 2024, all the way down to 9th.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/2025/run_dist_judge_9.png&quot; alt=&quot;&quot; class=&quot;center&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Much smaller change, but actually still significant.
This change decreases the average runs scored from about 5.4 to 5.3 runs per game.
This pencils out to about one Pythagorean win.
One win is significant, but this is a drastic lineup move that no one would ever consider.&lt;/p&gt;

&lt;p&gt;Here are the mean runs scored when placing Judge in each lineup position.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/2025/runs_vs_pos.png&quot; alt=&quot;&quot; class=&quot;center&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The first four positions are statistically indistinguishable.
Once Judge reaches the bottom of the order, there is a real difference.
Using this method, it’s easy to dig deeper into why the first four positions are meaningfully better.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/2025/judge_runs_rbis.png&quot; alt=&quot;&quot; class=&quot;center&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If Judge hits 1st or 2nd, he scores more runs. 
He hits more often, and gets driven in by Juan Soto a lot.
If Judge hits 3rd or 4th, he drives in more runs. 
He drives in on-base machine Juan Soto a lot.&lt;/p&gt;

&lt;h1 id=&quot;my-conclusion&quot;&gt;My conclusion&lt;/h1&gt;

&lt;p&gt;Overall, I think this means we shouldn’t worry too much about the lineup.
Put the best hitters at the top and the worst hitters at the bottom.
Don’t hit Aaron Judge ninth.&lt;/p&gt;

&lt;p&gt;As I said above, lefty/righty matchups are not factored in here.
I think it makes sense to try to alternate lefties and righties in the lineup to avoid giving relief pitchers runs of like-handed hitters to face.
I would love to factor this into my model, but the adversarial nature of relief pitcher strategy makes it much more complicated than what I did above.&lt;/p&gt;

&lt;p&gt;So alternate lefties and righties, put the best hitters at the top, and stop worrying.&lt;/p&gt;

&lt;p&gt;Jake sucks (not really).&lt;/p&gt;

&lt;h1 id=&quot;update&quot;&gt;Update&lt;/h1&gt;

&lt;p&gt;In response to &lt;a href=&quot;https://www.reddit.com/r/Sabermetrics/comments/1jtva4c/comment/mlxjtxd/?utm_source=share&amp;amp;utm_medium=web3x&amp;amp;utm_name=web3xcss&amp;amp;utm_term=1&amp;amp;utm_content=share_button&quot;&gt;Reddit commenters u/HyperactiveBaldMonk and u/BatJewOfficial&lt;/a&gt;, here are similar results for the Bryce Harper and the 2024 Phillies.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/2025/runs_vs_pos_harper.png&quot; alt=&quot;&quot; class=&quot;center&quot; /&gt;
&lt;img src=&quot;/assets/img/2025/harper_runs_rbis.png&quot; alt=&quot;&quot; class=&quot;center&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;*if you ignore a bunch of things including relief pitcher lefty/righty matchup strategy&lt;/strong&gt;&lt;/p&gt;
</description>
        <pubDate>Tue, 01 Apr 2025 00:00:00 +0000</pubDate>
        <link>blog.benwiener.com/baseball/2025/04/01/batting-order.html</link>
        <guid isPermaLink="true">blog.benwiener.com/baseball/2025/04/01/batting-order.html</guid>
        
        
        <category>Baseball</category>
        
      </item>
    
      <item>
        <title>Homemade Yagi-Uda Antenna</title>
        <description>&lt;p&gt;I’ve been getting into radio again. 
Phil and I pulled out our old handheld Baofeng radios and tried to reach each other from our houses.
He wrote up a &lt;a href=&quot;https://www.philipzucker.com/yagi_uda/&quot;&gt;post on this&lt;/a&gt; too.
The radios we have are dual-band, meaning they can transmit and recieve on the 70 cm UHF band and the 2m VHF band. 
By the way, we both have ARRL Technicians licenses, which are required to transmit in these bands.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/2025/yagi.png&quot; alt=&quot;Yagi-Uda antenna&quot; class=&quot;center&quot; width=&quot;80%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;We live about a half-mile apart and we found we could hear each other on 70 cm band but not the 2 m.
Both of these bands are said to require line of sight.
I can’t literally see Phil’s house from mine, but the 70 cm band worked anyway.
I guess this means my signal is bouncing around a bit and eventually finding its way to Phil.&lt;/p&gt;

&lt;p&gt;We did this experiment using the stock &lt;a href=&quot;https://en.wikipedia.org/wiki/Rubber_ducky_antenna&quot;&gt;rubber ducky antenna&lt;/a&gt;.
These are omnidirectional, so they transmit evenly in all directions within the plane normal to the antenna.
If you know where your target is, this is sort of wasteful. 
You’re spending some of your transmitter’s power in the wrong direction.
I thought it would be fun to build a directional antenna for 2 m and see if we could reach each other’s houses.
We built a simple antenna design called a &lt;a href=&quot;https://en.wikipedia.org/wiki/Yagi%E2%80%93Uda_antenna&quot;&gt;Yagi-Uda&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The Yagi-Uda design is probably familiar to you, classic roof-top TV antennas are Yagis.&lt;/p&gt;

&lt;p&gt;It consists of a &lt;em&gt;feeder element&lt;/em&gt;, which is a pair of conductors that are driven by the radio transmitter, a &lt;em&gt;reflector&lt;/em&gt;, and one or more &lt;em&gt;directors&lt;/em&gt;.
&lt;img src=&quot;/assets/img/2025/yagi_diagram.png&quot; alt=&quot;Yagi-Uda diagram&quot; class=&quot;center&quot; width=&quot;60%&quot; /&gt;
The reflector and directors are just passive conductors but they react to the driven element and contribute to the overall output.&lt;/p&gt;

&lt;p&gt;The Wikipedia article has a nice animation that might give you a sense of how the elements work together. 
&lt;img src=&quot;/assets/img/2025/yagi_animation.gif&quot; alt=&quot;Yagi-Uda animation&quot; class=&quot;center&quot; width=&quot;60%&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;the-build&quot;&gt;The Build&lt;/h1&gt;

&lt;p&gt;We built a &lt;em&gt;tape measure Yagi&lt;/em&gt;. 
This is a common method for hobbyists and I think it’s really clever.
You cut up a tape measure to form the conductors. 
This has a few benefits.
It’s easy to cut.
The measurement device is built in.
The antenna ends up being flexible and collapsable.
You might even have a spare tape measure lying around.&lt;/p&gt;

&lt;p&gt;We used the dimensions from &lt;a href=&quot;https://www.instructables.com/Diy-Tape-Measure-Yagi-Antenna-for-Ham-Radio/&quot;&gt;an Instructables page&lt;/a&gt; which seems to be based on someone else’s design. 
There are tons of designs out there.&lt;/p&gt;

&lt;p&gt;Here’s what we ended up with:
&lt;img src=&quot;/assets/img/2025/yagi.png&quot; alt=&quot;Yagi-Uda antenna&quot; class=&quot;center&quot; width=&quot;80%&quot; /&gt;
We used pieces of PVC pipe and zip ties to mount the tape measure segements to a wooden boom and soldered an SMA connector across the driven elements.
There are pieces of double sided tape between the tape and the PVC to keep things from sliding.
&lt;img src=&quot;/assets/img/2025/sma.png&quot; alt=&quot;Yagi-Uda SMA connector&quot; class=&quot;center&quot; width=&quot;80%&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;tuning&quot;&gt;Tuning&lt;/h1&gt;

&lt;p&gt;The antenna is designed to work at 145 MHz, which is within the 2 m band.
That doesn’t mean that the antenna is ready to use at 145 MHz, however.
If the antenna’s impedance doesn’t match the transmitter’s impedance, energy will be reflected back into the transmitter.
This could damage the transmitter.&lt;/p&gt;

&lt;p&gt;There are different ways of measuring the reflected power, but the most common seems to be &lt;a href=&quot;https://en.wikipedia.org/wiki/Standing_wave_ratio&quot;&gt;&lt;em&gt;Standing Wave Ratio&lt;/em&gt; or &lt;em&gt;SWR&lt;/em&gt;&lt;/a&gt;.
SWR is the ratio between the minimum and maximum amplitude of a standing wave in a transmission line.
A SWR of 1 means perfect transmission.
An SWR of infinity meanse perfect reflection.
Online sources suggest that an SWR of less than 2 is a good goal for a radio antenna.&lt;/p&gt;

&lt;p&gt;You can buy a standalone SWR meter, but Phil got us a more general instrument called a &lt;em&gt;Vector Network Analyzer&lt;/em&gt; or &lt;em&gt;VNA&lt;/em&gt;. 
Specifically, he got a &lt;a href=&quot;https://nanovna.com/&quot;&gt;NanoVNA&lt;/a&gt;.
It’s really cool, I highly recommend getting one if you’re into radio at all.
Essentially, it measures the complex impedance of the circuit you connect it to over a range of frequencies.
&lt;img src=&quot;/assets/img/2025/vna.png&quot; alt=&quot;VNA Screen&quot; class=&quot;center&quot; width=&quot;80%&quot; /&gt;
In the image above, the VNA shows two traces.
In yellow is the SWR measured directly.
It shows that at 147 MHz, the SWR is 1.614.
Not bad.&lt;/p&gt;

&lt;p&gt;The teal line shows what’s called a &lt;a href=&quot;https://en.wikipedia.org/wiki/Smith_chart&quot;&gt;Smith chart&lt;/a&gt;.
This is a way to plot complex impedance transformed such that a purely resistive 50 Ω load is the center, a short circuit is on the left, an open circuit is on the right, and purely reactive loads are at the top and bottom.
&lt;img src=&quot;/assets/img/2025/smith.png&quot; alt=&quot;Smith Chart&quot; class=&quot;center&quot; width=&quot;80%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;That 50 Ω load is important because that’s the typical output impedance of a radio transmitter.
If the antenna’s impedance falls in the center of a Smith chart, the impedances are pefectly matched.
In the VNA image above, you can see that the indicated point on the Smith chart trace is close to the center.
That means our antenna was well matched to the transmitter.
That’s lucky, because we didn’t have a great sense of how we’d tune the impedance if we needed to.
These measurements gave us the confidence to plug the antenna into the Baofeng.
I turned the transmit power to low just to be safe.&lt;/p&gt;

&lt;h1 id=&quot;radiation-pattern&quot;&gt;Radiation Pattern&lt;/h1&gt;

&lt;p&gt;With the antenna built, we decided to try to measure the radiation pattern.
That is, measure how much power is emitted at each angle.
We mounted the antenna on a tripod and used an RTL-SDR USB radio reciever to measure recieved power.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/2025/tripod.png&quot; alt=&quot;Yagi-Uda antenna on a tripod&quot; width=&quot;49%&quot; /&gt;
&lt;img src=&quot;/assets/img/2025/sdr.png&quot; alt=&quot;RTL-SDR connected to a laptop&quot; width=&quot;49%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;We rotated the antenna in 10° increments and measured the power at each angle.
Here’s the radiation pattern we found.
&lt;img src=&quot;/assets/img/2025/radiation.png&quot; alt=&quot;Smith Chart&quot; class=&quot;center&quot; width=&quot;80%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Looks reasonable when compared to other references I’ve seen for Yagi-Uda radiation patterns.
One interesteing feature is the asymmetry.
We think this may have been caused by my house.
In these coordinates, it is located between about 20° and 90°.
I believe it reflected a bit of the signal back toward the reciever.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/2025/yagi_reflect.png&quot; alt=&quot;Reflection diagram&quot; class=&quot;center&quot; width=&quot;80%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;We’ll have to try this out in an empty field.&lt;/p&gt;
</description>
        <pubDate>Sun, 02 Mar 2025 00:00:00 +0000</pubDate>
        <link>blog.benwiener.com/radio/2025/03/02/yagi.html</link>
        <guid isPermaLink="true">blog.benwiener.com/radio/2025/03/02/yagi.html</guid>
        
        
        <category>Radio</category>
        
      </item>
    
      <item>
        <title>Expected Strike Difference -- A simple catcher framing metric</title>
        <description>&lt;p&gt;In this post I’ll build a simple catcher framing metric I’m calling Expected Strike Difference.
The approach is similar to some other recent posts of mine like &lt;a href=&quot;/baseball/2024/03/03/team-fielding.html&quot;&gt;this one&lt;/a&gt;.
Using &lt;a href=&quot;(https://github.com/jldbc/pybaseball)&quot;&gt;pybaseball&lt;/a&gt; Statcast data I’ll train a classifier to predict whether a pitch will be called a ball or a strike based on the location where it crosses the plate, the count, handedness of the hitter.
Then I’ll go through every pitch caught by each catcher and count up the differences between the predicted strikes and called strikes.
This difference theoretically represents the number of extra strikes stolen (or lost) compared to an average catcher.&lt;/p&gt;

&lt;h1 id=&quot;the-data&quot;&gt;The Data&lt;/h1&gt;
&lt;p&gt;First the basics.&lt;/p&gt;

&lt;p&gt;In the Statcast coordinate system, x is inside-outside in the strikezone with the first-base side positive, y is toward-away from the catcher with the field side positive, z is up-down with up positive.
&lt;img src=&quot;/assets/img/2024/08/coordinates.png&quot; alt=&quot;Statcast coordinate system&quot; class=&quot;center&quot; width=&quot;40%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Here’s what the observed strike zone looks like:
&lt;img src=&quot;/assets/img/2024/08/x_z.png&quot; alt=&quot;Strikezone heatmap&quot; class=&quot;center&quot; width=&quot;60%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I normalized the pitch location such that the plate spans from -1 to 1 in each dimension.
That’s what the “norm” means in the axis labels. 
The unit here is half plate-widths since -1 to 1 is a full width.&lt;/p&gt;

&lt;p&gt;The color represents the probability of a pitch being called a strike based on location.
The strikezone is a bit wider than the nominal 17 inches of the plate.
Most of this is due to the width of the ball itself (about 3 inches or or 0.17 plate widths).&lt;/p&gt;

&lt;p&gt;Next, here a few fun details I found and tried to account for.&lt;/p&gt;

&lt;h2 id=&quot;batter-handedness&quot;&gt;Batter handedness&lt;/h2&gt;
&lt;p&gt;I noticed that the strikezone looks a bit different for left handed and right handed hitters.
Specifically, it seems to be offset a little bit away from the hitter. 
I decided to correct for this by reversing the x direction of the strike zone for lefties.&lt;/p&gt;

&lt;p class=&quot;center&amp;quot;&quot;&gt;&lt;img src=&quot;/assets/img/2024/08/handedness.png&quot; alt=&quot;Strikezone x coordinate uncorrected for handedness&quot; width=&quot;45%&quot; /&gt;
&lt;img src=&quot;/assets/img/2024/08/handedness_norm.png&quot; alt=&quot;Strikezone x coordinate corrected for handedness&quot; width=&quot;45%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;These plots show the just the x coordinate of the strike zone for lefties and righties.
In the left plot, the yellow band is offset to the negative-x side for lefties compared to righties.
After correcting for handedness by reversing x for lefties, this offset is mostly gone in the right plot.&lt;/p&gt;

&lt;h2 id=&quot;two-strikes-three-balls&quot;&gt;Two strikes, three balls&lt;/h2&gt;
&lt;p&gt;I also noticed that umpires are less likely to call a marginal pitch a strike when there are two strikes.
I’ve heard this called “omission bias” and it is a well documented effect in strike calling.
Basically, umps are a bit adverse to calling strike three or ball four and directly ending the at-bat.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/2024/08/two_strikes.png&quot; alt=&quot;Strikezone x coordinate with and without two strikes&quot; class=&quot;center&quot; width=&quot;60%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This plot shows a skinnier yellow band when there are two strikes and less than three balls.
This indicates that the effective strike zone is smaller in that situation.&lt;/p&gt;

&lt;p&gt;Similarly, they are less likely to call a ball with three balls and less than two strikes.&lt;/p&gt;

&lt;h2 id=&quot;pitch-movement&quot;&gt;Pitch movement&lt;/h2&gt;

&lt;p&gt;My intuition was that the transverse velocity (the left-right and up-down velocity) of the pitch will have an influence on the umpire’s call.
For example, I expected a pitch at the bottom of the zone to have a better chance of being called a ball if it is breaking sharply downward.
I was not able to observe a clear effect like this in the data so I left it out of this model.&lt;/p&gt;

&lt;h2 id=&quot;the-features&quot;&gt;The features&lt;/h2&gt;
&lt;p&gt;Ultimately, the features I used to estimate strike probability were:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Normalized and handedness-corrected x position when crossing the plate
    &lt;ul&gt;
      &lt;li&gt;Runs from -1.0 on the outside edge of the zone to 1.0 on the inside edge&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Normalized z position when crossing the plate
    &lt;ul&gt;
      &lt;li&gt;Runs from -1.0 on the low edge of the zone to 1.0 on the high edge&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Whether there are two strikes and less than three balls
    &lt;ul&gt;
      &lt;li&gt;Set to 1.0 if there are two strikes and less than three balls, 0.0 otherwise&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Whether there are tree balls and less than two strikes
    &lt;ul&gt;
      &lt;li&gt;Set to 1.0 if there are three balls and less than two strikes, 0.0 otherwise&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s what the data processing code looks like if you’re curious.
It uses PyBaseball and Polars.&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;PLATE_WIDTH_IN&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;17&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;PLATE_HALF_WIDTH_FT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PLATE_WIDTH_IN&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;24&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;balls_strikes&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;pl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;from_pandas&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;statcast&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start_dt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;2023-04-01&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end_dt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;2024-04-01&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;nb&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;pl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;col&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;description&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;is_in&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;called_strike&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;ball&apos;&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;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;pl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;col&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;fielder_2&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;alias&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;catcher_id&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;n&quot;&gt;pl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;col&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;description&quot;&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;s&quot;&gt;&quot;called_strike&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cast&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;alias&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;strike&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;plate_x&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# x location at plate in feet from center
&lt;/span&gt;        &lt;span class=&quot;s&quot;&gt;&quot;plate_z&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# z location at plate in feet from ground
&lt;/span&gt;        &lt;span class=&quot;s&quot;&gt;&quot;sz_bot&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# bottom of strike zone for batter in feet from ground
&lt;/span&gt;        &lt;span class=&quot;s&quot;&gt;&quot;sz_top&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# top of strike zone for batter in feet from ground
&lt;/span&gt;        &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;col&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;sz_top&quot;&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;pl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;col&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;sz_bot&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;alias&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;sz_height&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;n&quot;&gt;pl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;col&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;strikes&quot;&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;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;alias&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;two_strikes&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;n&quot;&gt;pl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;col&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;balls&quot;&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;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;alias&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;three_balls&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;mi&quot;&gt;1&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;2&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;pl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;col&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;stand&quot;&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;s&quot;&gt;&quot;L&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;alias&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;handedness_factor&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# -1 for lefties, 1 for righties
&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;with_columns&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;pl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;col&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;plate_z&quot;&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;pl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;col&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;sz_bot&quot;&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;pl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;col&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;sz_height&quot;&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;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;o&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;n&quot;&gt;alias&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;plate_z_norm&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# plate_z from -1 to 1       
&lt;/span&gt;        &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;col&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;plate_x&quot;&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;PLATE_HALF_WIDTH_FT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;alias&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;plate_x_norm&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# plate_x from -1 to 1
&lt;/span&gt;        &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;col&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;two_strikes&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;col&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;three_balls&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;alias&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;two_strikes_and_not_three_balls&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;o&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;col&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;two_strikes&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;col&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;three_balls&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;alias&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;three_balls_and_not_two_strikes&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;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;drop_nulls&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;h1 id=&quot;k-nearest-neighbors&quot;&gt;K Nearest Neighbors&lt;/h1&gt;
&lt;p&gt;I used a method called &lt;a href=&quot;https://en.wikipedia.org/wiki/K-nearest_neighbors_algorithm&quot;&gt;K Nearest Neighbors&lt;/a&gt;(KNN) to establish the expected strike probability for each pitch.
For each pitch, I look up the 25 most similar pitches.
The set of similar pitches is called the “neighborhood” and (in this case) it’s determined based on distance when plotted on a graph.
For example, the neighborhood might look like this for a pitch near the inside corner for a right handed hitter.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/2024/08/neighborhood.png&quot; alt=&quot;Inside corner neighborhood&quot; class=&quot;center&quot; width=&quot;60%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The expected strike probability is defined as the the fraction of pitches in the neighborhood that were called strikes.
In the data I used, the neighborhoods are much smaller than the one depicted.
There are so many pitches that the 25 nearest ones are close together.&lt;/p&gt;

&lt;p&gt;Two of the features I used (two strikes less than three balls, three balls less than two strikes) have values that are either 0 or 1.
These have an interesting effect on the neighborhood calculation.
Because of the relative scales involved, neighborhoods will almost always include only pitches with the same value for these discrete features.
This is okay though.&lt;/p&gt;

&lt;p&gt;Essentially I have three separate classifiers here: one for 0-2, 1-2, and 2-2 counts when umpires are more inclined to call a ball, one for 3-0 and 3-1 counts when umpires are more likely to call a strike, and one for all other counts.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/2024/08/neighborhood_cat.png&quot; alt=&quot;Inside corner neighborhood&quot; class=&quot;center&quot; width=&quot;60%&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;results&quot;&gt;Results&lt;/h1&gt;
&lt;p&gt;With this KNN method it’s easy to evaluate a catcher.
For each catcher, look at the pitches they received.
For each pitch find the neighborhood pitches and calculate the fraction that were called strikes. 
Add up these fractions to determine the number of expected strikes.
Compare that sum to the total number of strike calls they got.&lt;/p&gt;

&lt;p&gt;The resulting stat correlates well with Fangraphs and Baseball Savant catcher framing statistics.
&lt;img src=&quot;/assets/img/2024/08/corr.png&quot; alt=&quot;Correlation with other statistics&quot; class=&quot;center&quot; width=&quot;95%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Finally, here’s how the numbers come out for 2023:&lt;/p&gt;
&lt;table border=&quot;0&quot; class=&quot;dataframe&quot;&gt;
  &lt;thead&gt;
    &lt;tr style=&quot;text-align: right;&quot;&gt;
      &lt;th&gt;Name&lt;/th&gt;
      &lt;th&gt;Avg expected strike difference&lt;br /&gt;(per 100 pitches)&lt;/th&gt;
      &lt;th&gt;Total expected strike difference&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Francisco Álvarez&lt;/td&gt;
      &lt;td&gt;1.52&lt;/td&gt;
      &lt;td&gt;125.72&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Patrick Bailey&lt;/td&gt;
      &lt;td&gt;1.94&lt;/td&gt;
      &lt;td&gt;124.24&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Austin Hedges&lt;/td&gt;
      &lt;td&gt;2.33&lt;/td&gt;
      &lt;td&gt;118.20&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Jonah Heim&lt;/td&gt;
      &lt;td&gt;0.83&lt;/td&gt;
      &lt;td&gt;77.64&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Jose Trevino&lt;/td&gt;
      &lt;td&gt;1.87&lt;/td&gt;
      &lt;td&gt;72.00&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Víctor Caratini&lt;/td&gt;
      &lt;td&gt;1.61&lt;/td&gt;
      &lt;td&gt;68.20&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Cal Raleigh&lt;/td&gt;
      &lt;td&gt;0.77&lt;/td&gt;
      &lt;td&gt;64.00&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Jake Rogers&lt;/td&gt;
      &lt;td&gt;0.87&lt;/td&gt;
      &lt;td&gt;60.76&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Alejandro Kirk&lt;/td&gt;
      &lt;td&gt;0.83&lt;/td&gt;
      &lt;td&gt;58.56&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;William Contreras&lt;/td&gt;
      &lt;td&gt;0.70&lt;/td&gt;
      &lt;td&gt;58.24&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Adley Rutschman&lt;/td&gt;
      &lt;td&gt;0.70&lt;/td&gt;
      &lt;td&gt;56.96&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Seby Zavala&lt;/td&gt;
      &lt;td&gt;1.22&lt;/td&gt;
      &lt;td&gt;54.20&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Kyle Higashioka&lt;/td&gt;
      &lt;td&gt;0.99&lt;/td&gt;
      &lt;td&gt;53.88&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Jason Delay&lt;/td&gt;
      &lt;td&gt;1.11&lt;/td&gt;
      &lt;td&gt;47.48&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Cam Gallagher&lt;/td&gt;
      &lt;td&gt;1.36&lt;/td&gt;
      &lt;td&gt;46.28&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Christian Vázquez&lt;/td&gt;
      &lt;td&gt;0.69&lt;/td&gt;
      &lt;td&gt;43.68&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Tucker Barnhart&lt;/td&gt;
      &lt;td&gt;1.54&lt;/td&gt;
      &lt;td&gt;40.64&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Yasmani Grandal&lt;/td&gt;
      &lt;td&gt;0.66&lt;/td&gt;
      &lt;td&gt;39.64&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Austin Wynns&lt;/td&gt;
      &lt;td&gt;0.95&lt;/td&gt;
      &lt;td&gt;31.72&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Joey Bart&lt;/td&gt;
      &lt;td&gt;1.25&lt;/td&gt;
      &lt;td&gt;25.00&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Freddy Fermin&lt;/td&gt;
      &lt;td&gt;0.58&lt;/td&gt;
      &lt;td&gt;24.64&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Miguel Amaya&lt;/td&gt;
      &lt;td&gt;0.97&lt;/td&gt;
      &lt;td&gt;24.28&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Ben Rortvedt&lt;/td&gt;
      &lt;td&gt;1.15&lt;/td&gt;
      &lt;td&gt;23.64&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Nick Fortes&lt;/td&gt;
      &lt;td&gt;0.34&lt;/td&gt;
      &lt;td&gt;23.24&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Gary Sánchez&lt;/td&gt;
      &lt;td&gt;0.50&lt;/td&gt;
      &lt;td&gt;23.00&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Travis D&apos;arnaud&lt;/td&gt;
      &lt;td&gt;0.47&lt;/td&gt;
      &lt;td&gt;22.24&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Sean Murphy&lt;/td&gt;
      &lt;td&gt;0.30&lt;/td&gt;
      &lt;td&gt;21.56&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Brian Serven&lt;/td&gt;
      &lt;td&gt;2.54&lt;/td&gt;
      &lt;td&gt;19.44&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Blake Sabol&lt;/td&gt;
      &lt;td&gt;0.59&lt;/td&gt;
      &lt;td&gt;19.16&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;René Pinto&lt;/td&gt;
      &lt;td&gt;0.73&lt;/td&gt;
      &lt;td&gt;17.80&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Austin Barnes&lt;/td&gt;
      &lt;td&gt;0.43&lt;/td&gt;
      &lt;td&gt;15.68&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Tomás Nido&lt;/td&gt;
      &lt;td&gt;0.93&lt;/td&gt;
      &lt;td&gt;13.16&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Tyler Heineman&lt;/td&gt;
      &lt;td&gt;1.18&lt;/td&gt;
      &lt;td&gt;11.92&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Mike Zunino&lt;/td&gt;
      &lt;td&gt;0.38&lt;/td&gt;
      &lt;td&gt;9.92&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Ali Sánchez&lt;/td&gt;
      &lt;td&gt;6.04&lt;/td&gt;
      &lt;td&gt;9.48&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Austin Wells&lt;/td&gt;
      &lt;td&gt;0.51&lt;/td&gt;
      &lt;td&gt;9.24&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Reese Mcguire&lt;/td&gt;
      &lt;td&gt;0.17&lt;/td&gt;
      &lt;td&gt;6.56&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Danny Jansen&lt;/td&gt;
      &lt;td&gt;0.12&lt;/td&gt;
      &lt;td&gt;5.68&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Roberto Pérez&lt;/td&gt;
      &lt;td&gt;2.07&lt;/td&gt;
      &lt;td&gt;5.28&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Omar Narváez&lt;/td&gt;
      &lt;td&gt;0.16&lt;/td&gt;
      &lt;td&gt;5.24&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Alex Jackson&lt;/td&gt;
      &lt;td&gt;2.65&lt;/td&gt;
      &lt;td&gt;5.04&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Dillon Dingler&lt;/td&gt;
      &lt;td&gt;3.70&lt;/td&gt;
      &lt;td&gt;4.88&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Logan Porter&lt;/td&gt;
      &lt;td&gt;0.71&lt;/td&gt;
      &lt;td&gt;4.84&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Will Smith&lt;/td&gt;
      &lt;td&gt;0.05&lt;/td&gt;
      &lt;td&gt;4.16&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Bo Naylor&lt;/td&gt;
      &lt;td&gt;0.08&lt;/td&gt;
      &lt;td&gt;3.80&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Sandy León&lt;/td&gt;
      &lt;td&gt;0.42&lt;/td&gt;
      &lt;td&gt;3.32&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Anthony Bemboom&lt;/td&gt;
      &lt;td&gt;0.76&lt;/td&gt;
      &lt;td&gt;3.28&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Mj Melendez&lt;/td&gt;
      &lt;td&gt;0.55&lt;/td&gt;
      &lt;td&gt;3.20&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Caleb Hamilton&lt;/td&gt;
      &lt;td&gt;2.08&lt;/td&gt;
      &lt;td&gt;3.00&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Andrew Knapp&lt;/td&gt;
      &lt;td&gt;8.67&lt;/td&gt;
      &lt;td&gt;2.60&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Grant Koch&lt;/td&gt;
      &lt;td&gt;3.45&lt;/td&gt;
      &lt;td&gt;2.52&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Payton Henry&lt;/td&gt;
      &lt;td&gt;1.72&lt;/td&gt;
      &lt;td&gt;2.20&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Israel Pineda&lt;/td&gt;
      &lt;td&gt;8.17&lt;/td&gt;
      &lt;td&gt;1.88&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Carlos Narvaez&lt;/td&gt;
      &lt;td&gt;7.27&lt;/td&gt;
      &lt;td&gt;1.60&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Tres Barrera&lt;/td&gt;
      &lt;td&gt;2.47&lt;/td&gt;
      &lt;td&gt;1.48&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Henry Davis&lt;/td&gt;
      &lt;td&gt;0.09&lt;/td&gt;
      &lt;td&gt;0.48&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Austin Allen&lt;/td&gt;
      &lt;td&gt;4.36&lt;/td&gt;
      &lt;td&gt;0.48&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Chris Okey&lt;/td&gt;
      &lt;td&gt;0.69&lt;/td&gt;
      &lt;td&gt;0.40&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Zack Collins&lt;/td&gt;
      &lt;td&gt;0.28&lt;/td&gt;
      &lt;td&gt;0.36&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Rob Brantly&lt;/td&gt;
      &lt;td&gt;1.27&lt;/td&gt;
      &lt;td&gt;0.28&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Mickey Gasper&lt;/td&gt;
      &lt;td&gt;1.41&lt;/td&gt;
      &lt;td&gt;0.24&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Dom Nuñez&lt;/td&gt;
      &lt;td&gt;2.67&lt;/td&gt;
      &lt;td&gt;0.16&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;David Bañuelos&lt;/td&gt;
      &lt;td&gt;-0.00&lt;/td&gt;
      &lt;td&gt;-0.00&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Hunter Goodman&lt;/td&gt;
      &lt;td&gt;-0.52&lt;/td&gt;
      &lt;td&gt;-0.12&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Joe Hudson&lt;/td&gt;
      &lt;td&gt;-0.61&lt;/td&gt;
      &lt;td&gt;-0.20&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Chuckie Robinson&lt;/td&gt;
      &lt;td&gt;-0.93&lt;/td&gt;
      &lt;td&gt;-0.28&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Mark Kolozsvary&lt;/td&gt;
      &lt;td&gt;-0.34&lt;/td&gt;
      &lt;td&gt;-0.48&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Aramis Garcia&lt;/td&gt;
      &lt;td&gt;-0.60&lt;/td&gt;
      &lt;td&gt;-0.52&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Jorge Alfaro&lt;/td&gt;
      &lt;td&gt;-0.16&lt;/td&gt;
      &lt;td&gt;-0.64&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Tyler Cropley&lt;/td&gt;
      &lt;td&gt;-0.47&lt;/td&gt;
      &lt;td&gt;-0.88&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Pedro Pagés&lt;/td&gt;
      &lt;td&gt;-1.40&lt;/td&gt;
      &lt;td&gt;-1.12&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Manny Piña&lt;/td&gt;
      &lt;td&gt;-1.06&lt;/td&gt;
      &lt;td&gt;-1.32&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Jhonny Pereda&lt;/td&gt;
      &lt;td&gt;-9.20&lt;/td&gt;
      &lt;td&gt;-1.84&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;César Salazar&lt;/td&gt;
      &lt;td&gt;-0.46&lt;/td&gt;
      &lt;td&gt;-2.16&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Mitch Garver&lt;/td&gt;
      &lt;td&gt;-0.12&lt;/td&gt;
      &lt;td&gt;-2.20&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Kyle Mccann&lt;/td&gt;
      &lt;td&gt;-1.28&lt;/td&gt;
      &lt;td&gt;-2.36&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Drew Romo&lt;/td&gt;
      &lt;td&gt;-10.21&lt;/td&gt;
      &lt;td&gt;-2.96&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Korey Lee&lt;/td&gt;
      &lt;td&gt;-0.17&lt;/td&gt;
      &lt;td&gt;-3.28&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;James Mccann&lt;/td&gt;
      &lt;td&gt;-0.10&lt;/td&gt;
      &lt;td&gt;-4.08&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Brian O&apos;keefe&lt;/td&gt;
      &lt;td&gt;-1.11&lt;/td&gt;
      &lt;td&gt;-4.84&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Michael Pérez&lt;/td&gt;
      &lt;td&gt;-3.89&lt;/td&gt;
      &lt;td&gt;-5.64&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Chad Wallach&lt;/td&gt;
      &lt;td&gt;-0.19&lt;/td&gt;
      &lt;td&gt;-6.48&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Drew Millas&lt;/td&gt;
      &lt;td&gt;-0.94&lt;/td&gt;
      &lt;td&gt;-6.68&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Chadwick Tromp&lt;/td&gt;
      &lt;td&gt;-1.52&lt;/td&gt;
      &lt;td&gt;-6.80&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Eric Haase&lt;/td&gt;
      &lt;td&gt;-0.20&lt;/td&gt;
      &lt;td&gt;-7.96&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Luis Torrens&lt;/td&gt;
      &lt;td&gt;-5.09&lt;/td&gt;
      &lt;td&gt;-8.24&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Meibrys Viloria&lt;/td&gt;
      &lt;td&gt;-4.40&lt;/td&gt;
      &lt;td&gt;-8.44&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Iván Herrera&lt;/td&gt;
      &lt;td&gt;-0.77&lt;/td&gt;
      &lt;td&gt;-8.52&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Sam Huff&lt;/td&gt;
      &lt;td&gt;-3.14&lt;/td&gt;
      &lt;td&gt;-9.68&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Endy Rodríguez&lt;/td&gt;
      &lt;td&gt;-0.28&lt;/td&gt;
      &lt;td&gt;-9.76&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Carlos Pérez&lt;/td&gt;
      &lt;td&gt;-1.34&lt;/td&gt;
      &lt;td&gt;-13.24&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Curt Casali&lt;/td&gt;
      &lt;td&gt;-0.66&lt;/td&gt;
      &lt;td&gt;-13.88&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Gabriel Moreno&lt;/td&gt;
      &lt;td&gt;-0.18&lt;/td&gt;
      &lt;td&gt;-15.28&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Luis Campusano&lt;/td&gt;
      &lt;td&gt;-0.50&lt;/td&gt;
      &lt;td&gt;-16.40&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Matt Thaiss&lt;/td&gt;
      &lt;td&gt;-0.33&lt;/td&gt;
      &lt;td&gt;-16.84&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Tyler Soderstrom&lt;/td&gt;
      &lt;td&gt;-1.57&lt;/td&gt;
      &lt;td&gt;-17.32&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Carson Kelly&lt;/td&gt;
      &lt;td&gt;-0.51&lt;/td&gt;
      &lt;td&gt;-18.32&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Christian Bethancourt&lt;/td&gt;
      &lt;td&gt;-0.34&lt;/td&gt;
      &lt;td&gt;-22.68&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Brett Sullivan&lt;/td&gt;
      &lt;td&gt;-1.48&lt;/td&gt;
      &lt;td&gt;-27.72&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Yainer Diaz&lt;/td&gt;
      &lt;td&gt;-0.70&lt;/td&gt;
      &lt;td&gt;-28.00&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Jacob Stallings&lt;/td&gt;
      &lt;td&gt;-0.56&lt;/td&gt;
      &lt;td&gt;-30.04&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;David Fry&lt;/td&gt;
      &lt;td&gt;-3.01&lt;/td&gt;
      &lt;td&gt;-30.60&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Austin Nola&lt;/td&gt;
      &lt;td&gt;-1.00&lt;/td&gt;
      &lt;td&gt;-31.08&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Tom Murphy&lt;/td&gt;
      &lt;td&gt;-1.17&lt;/td&gt;
      &lt;td&gt;-32.20&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Garrett Stubbs&lt;/td&gt;
      &lt;td&gt;-1.31&lt;/td&gt;
      &lt;td&gt;-33.56&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Ryan Jeffers&lt;/td&gt;
      &lt;td&gt;-0.63&lt;/td&gt;
      &lt;td&gt;-35.12&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Salvador Pérez&lt;/td&gt;
      &lt;td&gt;-0.55&lt;/td&gt;
      &lt;td&gt;-35.52&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Yan Gomes&lt;/td&gt;
      &lt;td&gt;-0.52&lt;/td&gt;
      &lt;td&gt;-36.60&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;José Herrera&lt;/td&gt;
      &lt;td&gt;-1.35&lt;/td&gt;
      &lt;td&gt;-38.56&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Carlos Pérez&lt;/td&gt;
      &lt;td&gt;-1.82&lt;/td&gt;
      &lt;td&gt;-45.64&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Francisco Mejía&lt;/td&gt;
      &lt;td&gt;-1.47&lt;/td&gt;
      &lt;td&gt;-45.76&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Luke Maile&lt;/td&gt;
      &lt;td&gt;-1.05&lt;/td&gt;
      &lt;td&gt;-47.04&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Andrew Knizner&lt;/td&gt;
      &lt;td&gt;-1.01&lt;/td&gt;
      &lt;td&gt;-50.92&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Willson Contreras&lt;/td&gt;
      &lt;td&gt;-0.75&lt;/td&gt;
      &lt;td&gt;-52.12&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Riley Adams&lt;/td&gt;
      &lt;td&gt;-1.84&lt;/td&gt;
      &lt;td&gt;-57.80&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Connor Wong&lt;/td&gt;
      &lt;td&gt;-0.75&lt;/td&gt;
      &lt;td&gt;-60.68&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Logan O&apos;hoppe&lt;/td&gt;
      &lt;td&gt;-1.57&lt;/td&gt;
      &lt;td&gt;-65.24&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Tyler Stephenson&lt;/td&gt;
      &lt;td&gt;-1.13&lt;/td&gt;
      &lt;td&gt;-69.72&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Elías Díaz&lt;/td&gt;
      &lt;td&gt;-1.07&lt;/td&gt;
      &lt;td&gt;-96.52&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Keibert Ruiz&lt;/td&gt;
      &lt;td&gt;-1.06&lt;/td&gt;
      &lt;td&gt;-97.56&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Shea Langeliers&lt;/td&gt;
      &lt;td&gt;-1.03&lt;/td&gt;
      &lt;td&gt;-99.08&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Martín Maldonado&lt;/td&gt;
      &lt;td&gt;-1.41&lt;/td&gt;
      &lt;td&gt;-126.52&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;J. T. Realmuto&lt;/td&gt;
      &lt;td&gt;-1.18&lt;/td&gt;
      &lt;td&gt;-126.96&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

</description>
        <pubDate>Sat, 31 Aug 2024 00:00:00 +0000</pubDate>
        <link>blog.benwiener.com/baseball/2024/08/31/expected-strike-diffence.html</link>
        <guid isPermaLink="true">blog.benwiener.com/baseball/2024/08/31/expected-strike-diffence.html</guid>
        
        
        <category>Baseball</category>
        
      </item>
    
      <item>
        <title>White Mountains 4000 Footers</title>
        <description>&lt;p&gt;I’m trying to summit all of the 4000 foot peaks in the White Mountains. Here’s how far I’ve gotten:&lt;/p&gt;

&lt;iframe src=&quot;https://www.google.com/maps/d/u/0/embed?mid=19FFHRPoBlBWLwVj3KUI1SwIgMYrtWtM&amp;amp;ehbc=2E312F&quot; width=&quot;100%&quot; height=&quot;480&quot;&gt;&lt;/iframe&gt;

&lt;iframe src=&quot;https://docs.google.com/spreadsheets/d/e/2PACX-1vRIgoEhqFOMdZSdl2mYy9gumkTpMIVw46NUFAiTzcSrfuUaM9a_q-OT_69WrRhevcKcHmEkp7GjDU9y/pubhtml?gid=0&amp;amp;single=true&amp;amp;widget=true&amp;amp;headers=false&quot; width=&quot;100%&quot; height=&quot;480&quot;&gt;&lt;/iframe&gt;
</description>
        <pubDate>Sun, 09 Jun 2024 00:00:00 +0000</pubDate>
        <link>blog.benwiener.com/hiking/2024/06/09/4kers.html</link>
        <guid isPermaLink="true">blog.benwiener.com/hiking/2024/06/09/4kers.html</guid>
        
        
        <category>Hiking</category>
        
      </item>
    
      <item>
        <title>Expected Outs Difference -- A simple team fielding metric</title>
        <description>&lt;p&gt;In this post, I’m going to outline a simple baseball team fielding metric I thought of.
I’m calling it Expected Outs Difference or EOD.&lt;/p&gt;

&lt;p&gt;Here’s the plan:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Obtain a data set of balls put in play&lt;/li&gt;
  &lt;li&gt;Train a supervised model to predict whether a given ball will become and out based on exit velocity, launch angle, and spray angle&lt;/li&gt;
  &lt;li&gt;Evaluate a team’s defense by running the model against each ball they faced and comparing the predicted outs with the actual ones&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This method allows me to compare how well a team’s defense does at compared to what we’d expect based on how other teams do.&lt;/p&gt;

&lt;h2 id=&quot;the-data&quot;&gt;The data&lt;/h2&gt;
&lt;p&gt;I started by downloading a 2023 Statcast dataset using &lt;a href=&quot;(https://github.com/jldbc/pybaseball)&quot;&gt;pybaseball&lt;/a&gt;.
This gave me a table containing every pitch from the season.&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;pybaseball&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;statcast&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;pybaseball.datahelpers.statcast_utils&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;add_spray_angle&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;OUTS_EVENTS&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;s&quot;&gt;&quot;field_out&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;fielders_choice_out&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;force_out&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;grounded_into_double_play&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;sac_fly_double_play&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;triple_play&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;double_play&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;START_DATE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;2023-03-01&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;END_DATE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;2023-10-01&quot;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;add_spray_anglestatcast&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start_dt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;START_DATE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end_dt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;END_DATE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# filter out strikes and balls
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;balls_in_play&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;type&quot;&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;s&quot;&gt;&quot;X&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# filter out foul outs
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;balls_in_play&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;balls_in_play&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;balls_in_play&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;spray_angle&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;between&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;mi&quot;&gt;45&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;45&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# label outs
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;balls_in_play&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;out&quot;&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;balls_in_play&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;events&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;isin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;OUTS_EVENTS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# drop rows with missing data
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;balls_in_play&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;balls_in_play&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dropna&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;subset&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;s&quot;&gt;&quot;launch_angle&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;launch_speed&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;spray_angle&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;Statcast provides a lot of information, but what I most cared about was exit velocity, launch angle, spray angle, and play outcome.
&lt;img src=&quot;/assets/img/2024/04/angles.png&quot; alt=&quot;Launch angle and spray angle diagram&quot; class=&quot;center&quot; width=&quot;60%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Here’s a 2D histogram showing the fraction of the hits within bins in exit velocity and launch angle that became outs.
Balls in the dark areas usually fall for hits. 
Balls in the yellow areas are usually outs.
&lt;img src=&quot;/assets/img/2024/04/actual_la_ls.png&quot; alt=&quot;Exit velocity launch angle out histogram&quot; class=&quot;center&quot; width=&quot;100%&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;the-model&quot;&gt;The model&lt;/h2&gt;
&lt;p&gt;I trained an SKLearn KNeighbors classifier.
To make a prediction, this model looks up the eight most similar hits.
If most of those hits became outs, the model classifies it as an out.
If not, the model classifies it as a non-out.&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;sklearn.neighbors&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;KNeighborsClassifier&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;balls_in_play&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;launch_speed&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;launch_angle&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;spray_angle&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;balls_in_play&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;out&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;clf&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;KNeighborsClassifier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n_neighbors&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fit&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;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;balls_in_play&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;predicted_out&quot;&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;clf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;predict&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This isn’t perfect.
When the model is classifying a point, it will always find the point itself as one of the nearest neighbors.
I don’t think this is a big issue though.&lt;/p&gt;

&lt;h2 id=&quot;calculating-eod&quot;&gt;Calculating EOD&lt;/h2&gt;
&lt;p&gt;Finally, I used the model to evaluate Expected Outs Difference.&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;c1&quot;&gt;# create a column giving the id of the fielding team
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;balls_in_play&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;fielding_team&quot;&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;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;balls_in_play&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;inning_topbot&quot;&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;s&quot;&gt;&quot;Top&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;balls_in_play&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;home_team&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;balls_in_play&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;away_team&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# group by fielding team and sum outs and predicted outs
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;teams&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;balls_in_play&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;groupby&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;fielding_team&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;agg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;out&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;sum&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;predicted_out&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;sum&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;events&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;count&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# subtracting the expected outs from the predicted outs gives Expected Outs Difference
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;teams&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;out_diff&quot;&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;teams&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;out&quot;&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;teams&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;predicted_out&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;teams&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sort_values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;out_diff&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ascending&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;False&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;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;              out	   predicted_out    events      out_diff
fielding_team				
MIL           2634          2561          3947          73
CHC           2622          2578          4057          44
BAL           2719          2679          4183          40
AZ            2787          2754          4309          33
TEX           2658          2626          4072          32
TOR           2702          2672          4204          30
SEA           2609          2580          3989          29
LAD           2729          2704          4144          25
CLE           2751          2730          4231          21
ATL           2581          2566          4035          15
KC            2688          2677          4272          11
SD            2565          2558          3919          7
DET           2783          2781          4310          2
CWS           2541          2539          4002          2
PIT           2766          2764          4350          2
MIN           2673          2677          4121          -4
NYM           2610          2619          4088          -9
NYY           2771          2780          4237          -9
TB            2719          2735          4146          -16
SF            2661          2681          4178          -20
WSH           2731          2754          4367          -23
MIA           2645          2669          4139          -24
HOU           2623          2649          4061          -26
OAK           2635          2667          4234          -32
STL           2922          2962          4660          -40
PHI           2772          2821          4301          -49
LAA           2620          2685          4153          -65
BOS           2633          2698          4179          -65
CIN           2636          2717          4179          -81
COL           2864          2957          4662          -93
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;evaluating-eod-against-other-fielding-metrics&quot;&gt;Evaluating EOD against other fielding metrics&lt;/h2&gt;
&lt;p&gt;This approach seems to roughly match other popular fielding metrics.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/2024/04/eod_oaa.png&quot; alt=&quot;EOD vs OAA&quot; width=&quot;32%&quot; /&gt;
&lt;img src=&quot;/assets/img/2024/04/eod_drs.png&quot; alt=&quot;EOD vs DRS&quot; width=&quot;32%&quot; /&gt;
&lt;img src=&quot;/assets/img/2024/04/eod_frv.png&quot; alt=&quot;EOD vs FRV&quot; width=&quot;32%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I think my method here is most conceptually similar to OAA.
Oddly, that’s the metric that EOD correlates most poorly with.&lt;/p&gt;
</description>
        <pubDate>Sun, 03 Mar 2024 00:00:00 +0000</pubDate>
        <link>blog.benwiener.com/baseball/2024/03/03/team-fielding.html</link>
        <guid isPermaLink="true">blog.benwiener.com/baseball/2024/03/03/team-fielding.html</guid>
        
        
        <category>Baseball</category>
        
      </item>
    
      <item>
        <title>Calculating Run Expectancy Tables</title>
        <description>&lt;p&gt;Below is some simple code for building a run expectancy table based on Statcast data.
A run expectancy table gives the average number of runs scored after each base/out state.
For example, with runners on 1st and 2nd and one out, the table gives the average number of runs that scored.&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;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;pandas&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pd&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;pybaseball&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;statcast&lt;/span&gt;


&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;run_expectancy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Series&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;
    Returns a run expectancy table based on Statcast data from `start_date` to `end_date`
    &quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;pitch_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DataFrame&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;statcast&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start_dt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end_dt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;end_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# create columns for whether a runner is on each base
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;1b&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;2b&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;3b&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;pitch_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;base&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;pitch_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;on_&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;notnull&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;pitch_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;inning_final_bat_score&quot;&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;pitch_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;groupby&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;s&quot;&gt;&quot;game_pk&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;inning&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;inning_topbot&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;s&quot;&gt;&quot;post_bat_score&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;max&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# filter down to one row per at-bat
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;ab_data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pitch_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pitch_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;pitch_number&quot;&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;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;ab_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;runs_after_ab&quot;&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;ab_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;inning_final_bat_score&quot;&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;ab_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;bat_score&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;c1&quot;&gt;# group by base/out state and calculate mean runs scored after that state
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ab_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;groupby&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;outs_when_up&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;1b&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;2b&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;3b&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;runs_after_ab&quot;&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;Here’s what it looks like for 2021:&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;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;run_expectancy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;2021-04-01&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;2021-12-01&quot;&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;outs_when_up&lt;/span&gt;  &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;     &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;     &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;   
&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;             &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;  &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;  &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;    &lt;span class=&quot;mf&quot;&gt;0.507303&lt;/span&gt;
                            &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;     &lt;span class=&quot;mf&quot;&gt;1.393333&lt;/span&gt;
                     &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;   &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;    &lt;span class=&quot;mf&quot;&gt;1.135049&lt;/span&gt;
                            &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;     &lt;span class=&quot;mf&quot;&gt;2.107407&lt;/span&gt;
              &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;   &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;  &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;    &lt;span class=&quot;mf&quot;&gt;0.916202&lt;/span&gt;
                            &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;     &lt;span class=&quot;mf&quot;&gt;1.745745&lt;/span&gt;
                     &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;   &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;    &lt;span class=&quot;mf&quot;&gt;1.523861&lt;/span&gt;
                            &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;     &lt;span class=&quot;mf&quot;&gt;2.446313&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;             &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;  &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;  &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;    &lt;span class=&quot;mf&quot;&gt;0.264921&lt;/span&gt;
                            &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;     &lt;span class=&quot;mf&quot;&gt;0.958691&lt;/span&gt;
                     &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;   &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;    &lt;span class=&quot;mf&quot;&gt;0.684807&lt;/span&gt;
                            &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;     &lt;span class=&quot;mf&quot;&gt;1.409165&lt;/span&gt;
              &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;   &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;  &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;    &lt;span class=&quot;mf&quot;&gt;0.534543&lt;/span&gt;
                            &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;     &lt;span class=&quot;mf&quot;&gt;1.126154&lt;/span&gt;
                     &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;   &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;    &lt;span class=&quot;mf&quot;&gt;0.923244&lt;/span&gt;
                            &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;      &lt;span class=&quot;mf&quot;&gt;1.68007&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;             &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;  &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;  &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;    &lt;span class=&quot;mf&quot;&gt;0.101856&lt;/span&gt;
                            &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;     &lt;span class=&quot;mf&quot;&gt;0.385488&lt;/span&gt;
                     &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;   &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;    &lt;span class=&quot;mf&quot;&gt;0.324888&lt;/span&gt;
                            &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;     &lt;span class=&quot;mf&quot;&gt;0.600758&lt;/span&gt;
              &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;   &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;  &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;    &lt;span class=&quot;mf&quot;&gt;0.228621&lt;/span&gt;
                            &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;     &lt;span class=&quot;mf&quot;&gt;0.493186&lt;/span&gt;
                     &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;   &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;    &lt;span class=&quot;mf&quot;&gt;0.451022&lt;/span&gt;
                            &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;     &lt;span class=&quot;mf&quot;&gt;0.825928&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Sun, 03 Mar 2024 00:00:00 +0000</pubDate>
        <link>blog.benwiener.com/baseball/2024/03/03/run-expectancy.html</link>
        <guid isPermaLink="true">blog.benwiener.com/baseball/2024/03/03/run-expectancy.html</guid>
        
        
        <category>Baseball</category>
        
      </item>
    
      <item>
        <title>Deriving FIP -- Fielding Independent Pitching</title>
        <description>&lt;p&gt;If you’re a baseball fan, you’ll probably have come across a stat called &lt;strong&gt;FIP&lt;/strong&gt; or &lt;a href=&quot;https://library.fangraphs.com/pitching/fip/&quot;&gt;Fielding Independent Pitching&lt;/a&gt;. 
As I understand it, &lt;strong&gt;FIP&lt;/strong&gt; was designed by &lt;a href=&quot;https://twitter.com/tangotiger&quot;&gt;Tom Tango&lt;/a&gt; to track a pitcher’s peformance in a way that doesn’t depend on fielders or on which balls happen to fall between them or get caught.
Because it removes these factors that are outside of a pitcher’s control, &lt;strong&gt;FIP&lt;/strong&gt; is also said to me more stable year-to-year and to be a better measure of a pitcher’s skill.&lt;/p&gt;

&lt;p&gt;I believe the theory behind this comes from &lt;a href=&quot;https://www.baseballprospectus.com/news/article/878/pitching-and-defense-how-much-control-do-hurlers-have/&quot;&gt;work by Voros McCracken&lt;/a&gt; showing that pitchers don’t have much control over the fraction of balls put in play that fall for hits.
This conflicts with some baseball common sense ideas like “pitching to contact”, but let’s leave that aside for now.
The fraction of balls in play that become hits is sometimes called &lt;strong&gt;BABIP&lt;/strong&gt; or Batting Average on Balls In Play.
By diminishing the luck-based effects of &lt;strong&gt;BABIP&lt;/strong&gt;, one can build a pitching statistic that’s more indicative of a pitcher’s true ability.
That’s the idea.&lt;/p&gt;

&lt;p&gt;To accomplish this goal, &lt;strong&gt;FIP&lt;/strong&gt; is based on results that pitchers have the most control over: home runs, strikeouts, walks, and hit by pitch.
Also, it’s constructed to have a similar scale to &lt;strong&gt;ERA&lt;/strong&gt; to make it familiar to fans.
Until recently, that was about all I knew.&lt;/p&gt;

&lt;p&gt;It’s easy to find a formula online:&lt;/p&gt;

\[\mathrm{FIP} = \frac{13{\mathrm{HR}} + 3(\mathrm{BB} + \mathrm{HBP}) - 2\mathrm{SO}}{\mathrm{IP}} + C_{\mathrm{FIP}}\]

&lt;p&gt;where \(\mathrm{HR}\), \(\mathrm{BB}\), \(\mathrm{HBP}\), \(\mathrm{SO}\), and \(\mathrm{IP}\) are the number of home runs, walks, hit by pitch, strike outs, and innings pitched collected by the pitcher, respectively.
\(C_{\mathrm{FIP}}\) is a constant which varies year by year and is apparently usually around three.
The constant brings &lt;strong&gt;FIP&lt;/strong&gt; into the same range as &lt;strong&gt;ERA&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;But where do the 13, 3, -2, and constant come from though?
This is a bit harder to find.&lt;/p&gt;

&lt;p&gt;In this post, I’ll derive a version of &lt;strong&gt;FIP&lt;/strong&gt; in a way that makes sense to me.
I’ll touch on this at the end, but I don’t think this is the way it’s actually done.&lt;/p&gt;

&lt;p&gt;My method is simple: I trained a linear regression model to predict a pitcher’s &lt;strong&gt;ERA&lt;/strong&gt; based on \(\mathrm{HR}\), \(\mathrm{BB} + \mathrm{HBP}\) and \(\mathrm{SO}\).
&lt;strong&gt;FIP&lt;/strong&gt; is just a linear combination of those parameters, so it will be easy to compare the trained parameters of the linear model with the real formula.
&lt;!-- To distinguish it from the normal version, I&apos;ll call it FIFIP or FIP Indpenendent Fielding Independent Pitching. --&gt;&lt;/p&gt;

&lt;h2 id=&quot;a-look-at-the-data&quot;&gt;A look at the data&lt;/h2&gt;

&lt;p&gt;I used &lt;a href=&quot;(https://github.com/jldbc/pybaseball)&quot;&gt;pybaseball&lt;/a&gt; to download full season pitching data from 2015 to 2023.&lt;/p&gt;

&lt;p&gt;The relationships between the raw statistics are as you’d expect.
For example, here’s ERA plotted against HR and SO:
&lt;img src=&quot;/assets/img/2024/02/hr_era.png&quot; alt=&quot;Home Run Fly Ball Relationship&quot; class=&quot;center&quot; width=&quot;80%&quot; /&gt;
Home runs allowed per inning pitched has a positive relationship with ERA.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/2024/02/so_era.png&quot; alt=&quot;Strikeout Fly Ball Relationship&quot; class=&quot;center&quot; width=&quot;80%&quot; /&gt;
Strikeouts per inning has a negative relationship with ERA.
Let’s move on.&lt;/p&gt;

&lt;h2 id=&quot;training-a-linear-model&quot;&gt;Training a linear model&lt;/h2&gt;

&lt;p&gt;Training a linear model is pretty easy:&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;pybaseball&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pitching_stats&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;sklearn.linear_model&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LinearRegression&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;logs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pitching_stats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2021&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2023&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qual&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;25&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;logs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;UBB&quot;&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;logs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;BB&quot;&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;logs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;IBB&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# unintentional walks is total walks minus intentional walks
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;UBB_HBP&quot;&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;logs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;UBB&quot;&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;logs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;HBP&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# sum walks and hit by pitch
&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;logs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;HR&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;UBB_HBP&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;SO&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;div&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;IP&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;axis&lt;/span&gt;&lt;span class=&quot;o&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;c1&quot;&gt;# normalize by innings pitched
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ERA&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;weight&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;IP&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;fip_reg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LinearRegression&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fit&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;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sample_weight&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;weight&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;Two things to point out here:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;I excluded pitchers with less than 25 innings pitched using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qual&lt;/code&gt; kwarg in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pitching_stats()&lt;/code&gt; call.&lt;/li&gt;
  &lt;li&gt;I weighted the samples based on innings pitched using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sample_weight&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The relationship between &lt;strong&gt;ERA&lt;/strong&gt; and &lt;strong&gt;FIP&lt;/strong&gt; looks reasonable:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/2024/02/era_fip.png&quot; alt=&quot;ERA FIP relationship&quot; class=&quot;center&quot; width=&quot;80%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Here are the fitted parameters:&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;fip_reg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;coef_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fip_reg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;intercept_&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;13.28482645&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;mf&quot;&gt;3.2804091&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;mf&quot;&gt;1.78295967&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]),&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;2.8140810888761045&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;So my version of &lt;strong&gt;FIP&lt;/strong&gt; is&lt;/p&gt;

\[\mathrm{FIP} = \frac{13.3{\mathrm{HR}} + 3.3(\mathrm{BB} + \mathrm{HBP}) - 1.8\mathrm{SO}}{\mathrm{IP}} + 2.8\]

&lt;p&gt;vs the regular version&lt;/p&gt;

\[\mathrm{FIP} = \frac{13{\mathrm{HR}} + 3(\mathrm{BB} + \mathrm{HBP}) - 2\mathrm{SO}}{\mathrm{IP}} + ~3\]

&lt;p&gt;Maybe this is silly, but I was a little shocked at how closely they align.
If you round my coefficients to the nearest whole number, the equations are identical.&lt;/p&gt;

&lt;h2 id=&quot;does-fip-work&quot;&gt;Does FIP work?&lt;/h2&gt;
&lt;p&gt;If &lt;strong&gt;FIP&lt;/strong&gt; works as intended, it should have more year-to-year predictive power than ERA.
I calculated the correlation between ERA in one year and ERA in the next year with the correlation between FIP in one year and ERA in the next year.
I’m comparing&lt;/p&gt;

\[\mathrm{corr}\left(\mathrm{ERA}_i, \mathrm{ERA}_{i+1}\right)\]

&lt;p&gt;with&lt;/p&gt;

\[\mathrm{corr}\left(\mathrm{FIP}_i, \mathrm{ERA}_{i+1}\right)\]

&lt;p&gt;Based on year to year correlations from 2015 to 2023, &lt;strong&gt;FIP&lt;/strong&gt; does seem to have better year-to-year predictive power.
The plot below shows a 1D scatter plot of the year-to-next-year correlations from 2015 to 2023.
&lt;img src=&quot;/assets/img/2024/02/correlations.png&quot; alt=&quot;Correlations&quot; class=&quot;center&quot; width=&quot;80%&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;xfip&quot;&gt;xFIP&lt;/h2&gt;
&lt;p&gt;If you’ve seen &lt;strong&gt;FIP&lt;/strong&gt;, you may also have come across a related stat called &lt;strong&gt;xFIP&lt;/strong&gt; (the x stands for “expected”).
It’s based on a familiar observation.
Just like pitchers seem to not have much control over BABIP, they also don’t seem to have much control over their home run to fly ball ratio.
&lt;strong&gt;xFIP&lt;/strong&gt; is just like &lt;strong&gt;FIP&lt;/strong&gt; except that it replaces the player’s actual home run total with an expected home run total by replacing their home run to fly ball ratio with the league average ratio:&lt;/p&gt;

\[\mathrm{HR} = \mathrm{FB} \left(\mathrm{HR}/\mathrm{FB}\right)\]

&lt;p&gt;with&lt;/p&gt;

\[\mathrm{xHR} = \mathrm{FB} \left(\overline{\mathrm{HR}/\mathrm{FB}}_{\mathrm{league}} \right)\]

&lt;p&gt;This has always felt a little strange to me.
Now &lt;strong&gt;xFIP&lt;/strong&gt; is back to being dependent on fielders and the luck of precisely where balls land.&lt;/p&gt;

&lt;p&gt;It’s simple to implement:&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;league_avg_hr_fb&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;HR&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;div&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;FB&quot;&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;span class=&quot;n&quot;&gt;logs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;xHR&quot;&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;logs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;FB&quot;&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;league_avg_hr_fb&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;logs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;xHR&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;UBB_HBP&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;SO&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;div&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;IP&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;axis&lt;/span&gt;&lt;span class=&quot;o&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;c1&quot;&gt;# normalize by innings pitched
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ERA&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;weight&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;IP&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;xfip_reg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LinearRegression&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fit&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;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sample_weight&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;weight&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 relationship between (same year) &lt;strong&gt;xFIP&lt;/strong&gt; and &lt;strong&gt;ERA&lt;/strong&gt; seems worse than that between &lt;strong&gt;FIP&lt;/strong&gt; and &lt;strong&gt;ERA&lt;/strong&gt;:
&lt;img src=&quot;/assets/img/2024/02/era_xfip.png&quot; alt=&quot;ERA FIP relationship&quot; class=&quot;center&quot; width=&quot;80%&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;does-xfip-work&quot;&gt;Does xFIP work?&lt;/h2&gt;
&lt;p&gt;I repeated the above experiments to look at &lt;strong&gt;xFIP&lt;/strong&gt;’s predictive power.
&lt;img src=&quot;/assets/img/2024/02/correlations_2.png&quot; alt=&quot;Correlations&quot; class=&quot;center&quot; width=&quot;80%&quot; /&gt;
&lt;strong&gt;xFIP&lt;/strong&gt; seems to have somewhat more predictive power than &lt;strong&gt;FIP&lt;/strong&gt; or &lt;strong&gt;ERA&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;how-similar-is-this-to-real-fip&quot;&gt;How similar is this to real FIP?&lt;/h2&gt;

&lt;p&gt;At time of writing, I haven’t found a satisfying derivation of the &lt;strong&gt;FIP&lt;/strong&gt; equation.
As best I can tell, the coefficients are derived from a type of run value estimate of each possible plate appearance outcome called &lt;em&gt;linear weights&lt;/em&gt;.
That topic probably deserves a post of its own.
However, my method found an equation that’s very similar to the real &lt;strong&gt;FIP&lt;/strong&gt; equation, so maybe it doesn’t matter how it was originally calculated.
If two different methods arrive at the same answer, that ought to give us extra confidence in the result.&lt;/p&gt;
</description>
        <pubDate>Sun, 25 Feb 2024 00:00:00 +0000</pubDate>
        <link>blog.benwiener.com/baseball/2024/02/25/fip.html</link>
        <guid isPermaLink="true">blog.benwiener.com/baseball/2024/02/25/fip.html</guid>
        
        
        <category>Baseball</category>
        
      </item>
    
      <item>
        <title>Marble Mirror Update</title>
        <description>&lt;p&gt;We’ve made a lot of progress since &lt;a href=&quot;/electronics/projects/programming/2021/10/28/marble-mirror.html&quot;&gt;my first marble machine post&lt;/a&gt;!&lt;/p&gt;
&lt;center&gt;
&lt;video width=&quot;80%&quot; autoplay=&quot;&quot; muted=&quot;&quot; loop=&quot;&quot;&gt;
    &lt;source src=&quot;/assets/img/2022/02/marble_machine_hambone_2_small.mp4&quot; type=&quot;video/mp4&quot; /&gt;
    Your browser does not support the video tag.
&lt;/video&gt;
&lt;/center&gt;

&lt;h2 id=&quot;major-changes&quot;&gt;Major Changes&lt;/h2&gt;
&lt;p&gt;Here’s an overview of the main changes we made since my last post.&lt;/p&gt;

&lt;h3 id=&quot;smaller-better-marbles&quot;&gt;Smaller, Better Marbles&lt;/h3&gt;
&lt;p&gt;Most obviously, we’ve switched from 3/8” stainless steel balls to 6mm ABS beads. I was skeptical at first, but I now see that this is better for a number of reasons.&lt;/p&gt;

&lt;p&gt;Why was I skeptical? I thought that small plastic beads would be too light and would get stuck in the machine, either by static charge or by just not having enough inertia to roll when necessary. Steel ball bearings are sold as machine parts and can be bought with tight tolerances. We were able to get two colors by oxidizing the steel with chemicals. The only plastic balls we could find that came in multiple colors were sold as art supplies or decoration. I thought this would mean they were inconsistent or not perfectly round. This has not been a problem at all. The balls are actually easier to move around by virtue of being lighter. The black and white beads have much better contrast than the oxidized and unoxidized ball bearings.&lt;/p&gt;

&lt;p&gt;We’re using a product sold as “undrilled beads”.&lt;/p&gt;

&lt;h3 id=&quot;fiber-optic-color-sensing&quot;&gt;Fiber Optic Color Sensing&lt;/h3&gt;
&lt;p&gt;The machine needs to know what color marbles are to be able to place them in the correct column. We do this with an RGB pixel sensor and an LED for illumination. Fitting the LED/sensor module onto the carriage was awkward and an annoying constraint. We solved this by moving the sensor to the side of the carriage and moving light around using PMMA fiber optic cables. One cable illuminates the marble and the other collects light for the sensor. I’m sort of surprised by how well this works.&lt;/p&gt;

&lt;p&gt;It looks cool, too.&lt;/p&gt;

&lt;h3 id=&quot;bucket-style-reservoir&quot;&gt;Bucket-Style Reservoir&lt;/h3&gt;
&lt;p&gt;One issue we’d been grappling with is the size of the ball reservoir. If we want to be able to display all possible images, we need at least enough black marbles to fill the whole screen and an equal number of white marbles. Imagine actually trying to draw a full black image with that configuration, though. The stream of marbles is drawn semi-randomly from the reservoir. Hunting for those last few black marble could take a while. If the reservoir can hold three times the number of marbles in the screen, it will still contain one third black marbles even when the screen is all black. That’s a big reservoir, though.&lt;/p&gt;

&lt;p&gt;In previous versions, marbles were completely confined to a single plane. To increase the reservoir size without having it dominate the visible surface, we added a big out-of-plane bucket behind the machine.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/2022/02/bucket.png&quot; alt=&quot;Photo front&quot; class=&quot;center&quot; width=&quot;50%&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;smoothly-curved-elevator-channel&quot;&gt;Smoothly-Curved Elevator Channel&lt;/h3&gt;
&lt;p&gt;We ran into issues with balls binding in the lower-left corner, where a stepper motor lifts marbles to the top of the machine. Previous versions featured a corner with a polygonal wall.
&lt;img src=&quot;/assets/img/2022/02/not_smooth.png&quot; alt=&quot;Model front&quot; class=&quot;center&quot; width=&quot;40%&quot; /&gt;
This was a simple design achievable with the laser cutter.&lt;/p&gt;

&lt;p&gt;The new version has a smooth corner. Also produced using a laser cutter, but a little less obvious.
&lt;img src=&quot;/assets/img/2022/02/smooth.png&quot; alt=&quot;Model front&quot; width=&quot;33%&quot; /&gt;
&lt;img src=&quot;/assets/img/2022/02/corner.png&quot; alt=&quot;Model front&quot; width=&quot;65%&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;improved-stepper-driver&quot;&gt;Improved Stepper Driver&lt;/h3&gt;
&lt;p&gt;In earlier versions of the marble machine, we used simple H-bridge style stepper drivers. They essentially just amplify the output of a digital pin. If, like us, you’re using the GPIO pins of a Raspberry Pi to command the driver, these are terrible. The speed that the pins can be controlled at and the consistency of the timing makes the drive speed low and the motor very noisy.&lt;/p&gt;

&lt;p&gt;We changed to a proper CNC control board that reads GRBL G-code. Now, instead of directly controlling the state of every pin, the Pi just sends serial commands saying “go to position 10 mm”.&lt;/p&gt;

&lt;p&gt;Getting this working was an ordeal, but was totally worth it. We had to fight to get this working, but now that it is the motion of the carriage and elevator is smooth and fast.&lt;/p&gt;
</description>
        <pubDate>Fri, 04 Feb 2022 00:00:00 +0000</pubDate>
        <link>blog.benwiener.com/electronics/projects/programming/2022/02/04/marble-mirror-update.html</link>
        <guid isPermaLink="true">blog.benwiener.com/electronics/projects/programming/2022/02/04/marble-mirror-update.html</guid>
        
        <category>WIP</category>
        
        
        <category>Electronics</category>
        
        <category>Projects</category>
        
        <category>Programming</category>
        
      </item>
    
      <item>
        <title>Cherry Rocking Chair for Dad or: A Botch-Job in Three Acts</title>
        <description>&lt;p&gt;My dad turned 70 this year, so Matt and I decided to make him an old man rocking chair.
This project was an adventure. 
We wasted a lot of perfectly good cherry by repeatedly messing up and/or changing our design.
Still, I’m proud of how it came out:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/2022/01/rocking_chair/final.jpg&quot; alt=&quot;Photo front&quot; class=&quot;center&quot; width=&quot;70%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Our inspiration was &lt;a href=&quot;https://www.perigold.com/furniture/pdp/masaya-co-amador-rocking-chair-ndac1039.html&quot;&gt;a beautiful rocking chair&lt;/a&gt; we found online:
&lt;img src=&quot;/assets/img/2022/01/rocking_chair/inspiration.png&quot; alt=&quot;Inspiration&quot; class=&quot;center&quot; width=&quot;80%&quot; /&gt;
Our starting design closely followed the Masaya chair. 
We planned to copy most of the details including the bridle jointed rockers, cantilevered armrests, and stretcher-supported seat. 
As we built, we made some changes. 
Usually, we plan things out in advance and more or less stick to the plan. 
This time, we often deferred choices until we were forced to make them.
For example, we never had a coherent plan to connect the seat to the stretchers. 
Ultimately, we avoided the stretchers all together and fastened the seat to the fame with four 3/4” pins.
This strategy, driven mostly by laziness and indecisiveness, was both flexible and stressful. 
I wasn’t truly confident in the project until we clamped up for the last time.&lt;/p&gt;

&lt;p&gt;Eventually, we landed on this:
&lt;img src=&quot;/assets/img/2022/01/rocking_chair/design.png&quot; alt=&quot;Photo front&quot; class=&quot;center&quot; width=&quot;70%&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;a-botch-job-in-three-acts&quot;&gt;A Botch-Job in Three Acts&lt;/h1&gt;
&lt;p&gt;We worked on this project over three periods: one in around Halloween, one over Thanksgiving, and one just after Christmas.&lt;/p&gt;

&lt;h2 id=&quot;act-i-halloween&quot;&gt;Act I: Halloween&lt;/h2&gt;
&lt;p&gt;This was definitely our &lt;strong&gt;spookiest&lt;/strong&gt; (and least successful) session. Matt and I got together and made &lt;strong&gt;doomed&lt;/strong&gt; pieces for the frame. We cut two rockers, four vertical supports, and three stretchers. Out of the nine pieces we built that weekend, only three made it into the final piece.&lt;/p&gt;

&lt;p&gt;The rockers were the most &lt;strong&gt;bone chilling&lt;/strong&gt; part to replace because they were cut from a ridiculously expensive piece of 9” wide 8/4 cherry. 
I bought a band saw on Craigslist specifically for this project. It’s a cheap 8” Delta benchtop band saw. I drove about an hour to pick it up. The seller turned out to be a pawn shop.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/2022/01/rocking_chair/first_frame.jpg&quot; alt=&quot;Photo front&quot; class=&quot;center&quot; width=&quot;70%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;We tried some test cuts and were immediately disappointed. This saw clearly wasn’t powerful enough for 8/4 hardwood and the surface finish was absolutely terrible. We opted instead to cut the rockers using a jigsaw. This was a big mistake. Rocker one looked okay, but while cutting rocker two the blade deflected almost half an inch. We switched back to the band saw to correct the undercut, but the damage was done. We convinced ourselves at the time that thinner rockers would be okay, but I think we both knew the truth.&lt;/p&gt;

&lt;h2 id=&quot;act-ii-thanksgiving&quot;&gt;Act II: Thanksgiving&lt;/h2&gt;
&lt;p&gt;Over Thanksgiving, we made the seat. We bravely defied typical three act structure, so this session went much better. One contributing factor was a new band saw. Will joined me to drive up toward the New Hampshire border to buy a much bigger and much better 12” Craftsman band saw for only $75. The seller was a nice man who had just finished reffing a soccer game and was not a pawn shop. Here’s Matt using it to cut a part of the seat back:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/2022/01/rocking_chair/back.jpg&quot; alt=&quot;Photo front&quot; class=&quot;center&quot; width=&quot;70%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;One aspect of the design that we deferred until the last minute was the woven seat. We bought a thousand feet of black polyester rope and ignored the question until it was time to start winding. The rope came on a massive and intimidating spool. We tested out a few options and dove in. Clamping the seat to a vertical column allowed us to pass the spool back and forth easily.
&lt;img src=&quot;/assets/img/2022/01/rocking_chair/weave.jpg&quot; alt=&quot;Photo front&quot; class=&quot;center&quot; width=&quot;70%&quot; /&gt;
There was so much rope.
&lt;img src=&quot;/assets/img/2022/01/rocking_chair/rope.jpg&quot; alt=&quot;Photo front&quot; class=&quot;center&quot; width=&quot;70%&quot; /&gt;
Eventually, it came together.
&lt;img src=&quot;/assets/img/2022/01/rocking_chair/seat.jpg&quot; alt=&quot;Photo front&quot; class=&quot;center&quot; width=&quot;70%&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;act-iii-christmas&quot;&gt;Act III: Christmas&lt;/h2&gt;
&lt;p&gt;The climactic final act. We got together for almost a week after Christmas and finished things up. First order of business: bite the bullet and buy more wood. The new rockers were a huge improvement. It was definitely the right decision to replace them. We also changed the design, replacing the cantilevered armrests with a more symmetrical design and adding overhangs in a few places to visually match the seat. 
&lt;img src=&quot;/assets/img/2022/01/rocking_chair/final_frame.jpg&quot; alt=&quot;Photo front&quot; class=&quot;center&quot; width=&quot;70%&quot; /&gt;
As we clamped the seat to the frame to decide how it would fit, we realized that the stretchers supporting the seat weren’t necessary. Instead, we connected the parts with pins.
&lt;img src=&quot;/assets/img/2022/01/rocking_chair/pins_frame.jpg&quot; alt=&quot;Photo front&quot; width=&quot;36%&quot; /&gt;
&lt;img src=&quot;/assets/img/2022/01/rocking_chair/pins_seat.jpg&quot; alt=&quot;Photo front&quot; width=&quot;61%&quot; /&gt;
We finally glued things up at 3 AM on New Year’s Day.
&lt;img src=&quot;/assets/img/2022/01/rocking_chair/final_glue.jpg&quot; alt=&quot;Photo front&quot; class=&quot;center&quot; width=&quot;70%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Very happy with how this project turned out. Happy birthday, Dad!&lt;/p&gt;
</description>
        <pubDate>Mon, 03 Jan 2022 00:00:00 +0000</pubDate>
        <link>blog.benwiener.com/woodworking/2022/01/03/rocking-chair.html</link>
        <guid isPermaLink="true">blog.benwiener.com/woodworking/2022/01/03/rocking-chair.html</guid>
        
        
        <category>Woodworking</category>
        
      </item>
    
      <item>
        <title>Marble Mirror (work in progress)</title>
        <description>&lt;p&gt;&lt;img src=&quot;/assets/img/2021/10/marble_mirror_front.png&quot; alt=&quot;Photo front&quot; class=&quot;center&quot; width=&quot;95%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I’ve always sort of wanted to make a marble-based machine. I’ve been following the progress of Wintergatan/Martin Molin’s &lt;a href=&quot;https://www.youtube.com/watch?v=C8qyVURtSZc&quot;&gt;Marble Machine X&lt;/a&gt; for some time. It’s a follow-up to the original and more well-known &lt;a href=&quot;https://www.youtube.com/watch?v=IvUU8joBb1Q&quot;&gt;Marble Machine&lt;/a&gt; of YouTube.&lt;/p&gt;

&lt;center&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/IvUU8joBb1Q&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;/center&gt;

&lt;p&gt;You should probably just leave that video running and let it be the background music while you read this post.&lt;/p&gt;

&lt;p&gt;This time around, Martin’s documented his progress with a compelling series of videos showing the ups and downs of his adventure to build a giant music playing machine based around marbles. 
A marble machine almost seems simple. It’s just tracks and belts and xylophones and stuff. 
His videos put the lie to this idea.
The Marble Machine X project has stalled and as of late 2021, he’s pursuing a ground-up redesign of his incredible machine after &lt;a href=&quot;https://www.youtube.com/watch?v=WN90HYiFpAw&quot;&gt;coming to terms with some fundamental design flaws&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Fortunately, we picked a much easier project.
The name is ridiculous at this point, but it’s stuck.
The original idea was to produce some kind display where the “pixels” are made of a grid of macro-scale physical objects and connect this screen to a webcam to create a “mirror”.
The direct inspiration was &lt;a href=&quot;http://www.smoothware.com/danny/&quot;&gt;Daniel Rozin’s mechanical mirrors&lt;/a&gt;.&lt;/p&gt;
&lt;center&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/1ZPJ0U_kpNg&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;/center&gt;

&lt;p&gt;We considered a few designs but settled on one that’s nowhere near real time. We finally have it working:&lt;/p&gt;
&lt;center&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;fr&quot; dir=&quot;ltr&quot;&gt;A momentous occasion! &lt;a href=&quot;https://t.co/fWjtwTHqRN&quot;&gt;pic.twitter.com/fWjtwTHqRN&lt;/a&gt;&lt;/p&gt;&amp;mdash; Philip Zucker (@SandMouth) &lt;a href=&quot;https://twitter.com/SandMouth/status/1453563957696999430?ref_src=twsrc%5Etfw&quot;&gt;October 28, 2021&lt;/a&gt;&lt;/blockquote&gt; &lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;/center&gt;

&lt;p&gt;It forms an image in the style of a Connect 4 board. A stepper motor lifts a column of marbles to the top of the machine by rotating a notched disk (blue) and drops one into the carriage (green). A RGB sensor in the carriage (red) measures the color of the cargo area and decides if it contains a reflective steel marble, a black marble, or no marble. If the marble can be used in one of the columns of the image, it is delivered to that column and dropped in. If the marble can’t be used because it doesn’t match the next pixel in any column, it is rejected and dropped back into the reservoir at the bottom.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/2021/10/model_front.png&quot; alt=&quot;Model front&quot; width=&quot;48%&quot; /&gt;
&lt;img src=&quot;/assets/img/2021/10/model_rear.png&quot; alt=&quot;Model front&quot; width=&quot;48%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The carriage rides on linear bearings on an 8mm steel rod. It’s driven by a stepper motor and lead screw. A servo with a plastic horn acts as a gate.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/2021/10/carriage.png&quot; alt=&quot;Model front&quot; width=&quot;38%&quot; /&gt;
&lt;img src=&quot;/assets/img/2021/10/carriage_2.png&quot; alt=&quot;Model front&quot; width=&quot;58%&quot; /&gt;&lt;/p&gt;
</description>
        <pubDate>Thu, 28 Oct 2021 00:00:00 +0000</pubDate>
        <link>blog.benwiener.com/electronics/projects/programming/2021/10/28/marble-mirror.html</link>
        <guid isPermaLink="true">blog.benwiener.com/electronics/projects/programming/2021/10/28/marble-mirror.html</guid>
        
        <category>WIP</category>
        
        
        <category>Electronics</category>
        
        <category>Projects</category>
        
        <category>Programming</category>
        
      </item>
    
  </channel>
</rss>
