Monthly Archives: June, 2022

Expected wOBA Over the Zone


Like buying a house, a pitcher knows that the most important aspects of pitching are location, location, and location. Given the ready accessibility of Statcast data online and on baseball broadcasts, we are very familiar with seeing the location of pitches about the zone. The quality of a ball put in play depends strongly on the pitch location. Most batters have preferred regions in the zone and they wait for pitches in this desirable zone where they can make solid swing contact.

This post describes the construction and illustrates a Shiny app where one can visualize regions of solid contact for balls put into play for a particular batter or pitcher during the current 2022 season. We measure quality of contact by the Statcast estimated_woba_using_speedangle variable. This variable is the expected value of wOBA using the launch angle and exit velocity measurements. By controlling a slider one can easily see the region of points where the expected wOBA measure is unusually large or small. I’ll spend some time discussing the nuts and bolts of the Shiny app and provide some illustrations of the use of the app.

The Shiny User Interface

There are five inputs to the “ExpectedwOBA_Zone” Shiny app:

  • (Batter or Pitcher?) Select if we are interested in location of balls in play for a specific batter or a specific pitcher.
  • (Minimum IP) Select the minimum balls in play. This will allow us to focus on starters who have had a large number of balls in play this season.
  • (Player) Select a player from the pop-up menu where the names are found from the first two inputs.
  • (Bound) Select a bound B for the expected wOBA measurement.
  • (Large or Small Expected wOBA) Decide if we wish to look a points where E(wOBA) >= B or where E(wOBA) <= B.

The code for the Shiny app can be found as a single script file app.R in my ShinyBaseball package. The ui() function sets up the user interface (specification and location of input controls and output) and the server() function does the work on the server side. There are two functions in server() that do all of the work. The SelectPlayers() function selects the players defined by the batter/pitcher and minimum number of in-play inputs, and the ewoba_plot() function contains the ggplot2 code in constructing the graph. The data is current Statcast data for the 2022 season that is read from one of my Github repositories.

I should mention I am implementing some dynamic ui in this Shiny function. By the use of several observeEvent() functions in the server() function, I am able to modify the names in the pop-up input menu depending on the inputs on batter/pitcher and the minimum in-play events.

A Batter Example

Suppose we’re interested in learning about the region about the zone where Jose Altuve exhibits his power. In the following snapshot, we select “batter”, choose 153 as the minimum number of balls in-play and select Altuve from the drop-down menu. We see the zone locations for all balls put into play where the point color corresponds to the expected wOBA.

If we move the E(wOBA) bound slider to the right, we’ll see only the points where the expected wOBA exceeds different values. For example in the snapshot below, we see the points where E(wOBA) >= 0.9 — this shows that Altuve exhibits the most power for balls low in the zone. By changing the last option to “<=” and selecting a small value for the positive E(wOBA) bound, one can also find the locations of the balls in play where E(wOBA) is unusually small.

A PItcher Example

Aaron Nola has allowed 10 home runs this season, so I am curious about the pitch locations for these hits. I select “pitcher” and 153 minimum balls in-play and choose Nola from the drop-down menu. By selecting 1 as the bound value, the plot shows the locations of the pitches where the expected wOBA is at least as 1. It is interesting that practically all of these hard hits occur for pitch locations close to the middle line where plate_x = 0. I suspect that Nola would tell us that these particular pitches were not thrown to the desired location.


  • This particular Shiny app is currently live at so the interested reader can try out the app out for different 2022 batters or pitchers of interest.
  • This is a relatively easy app to code, so you can check out the Shiny script here. One can run this app on your computer by placing the app in a new folder, opening the app.R file, and using the Run App button in RStudio. (The data for the app is downloaded from one of my Github repositories.)
  • Once you get an idea of an interesting app, it doesn’t take that long to do the coding. One could enhance this particular Shiny app by focusing on locations on balls in-play for particular pitch types, counts, or batter side for a specific pitcher.