<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
      <channel>
        <title>Analytical Endeavors</title>
        <link>https://erbo.rbind.io/</link>
        <description>Recent content on Analytical Endeavors</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>en-us</language>
        <lastBuildDate>Thu, 03 May 2018 00:00:00 +0000</lastBuildDate>
        
            <atom:link href="https://erbo.rbind.io/index.xml" rel="self" type="application/rss+xml" />
        
        
        <item>
          <title>Create a Twitter bot for your local animal shelter using rtweet</title>
          <link>https://erbo.rbind.io/blog/2018-05-03-create-a-twitter-bot-for-your-local-animal-shelter-using-rtweet/</link>
          <pubDate>Thu, 03 May 2018 00:00:00 +0000</pubDate>
          
          <guid>https://erbo.rbind.io/blog/2018-05-03-create-a-twitter-bot-for-your-local-animal-shelter-using-rtweet/</guid>
          <description>&lt;div id=&#34;TOC&#34;&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#create-an-app-and-a-token&#34;&gt;Create an app and a token&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#get-some-pet-data&#34;&gt;GET some pet data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#formulate-the-tweet&#34;&gt;Formulate the tweet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#tweet-and-automate&#34;&gt;Tweet and automate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#next-steps&#34;&gt;Next Steps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#acknowledgements&#34;&gt;Acknowledgements&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#references&#34;&gt;References&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;

&lt;p&gt;A month or so ago, I came across a cool &lt;a href=&#34;http://www.wbiw.com/local/archive/2018/03/animal-shelter-twitter-bot-is-collaboration-between-city-and-civic-code-group.php&#34;&gt;article&lt;/a&gt; about a civic group in Bloomington, IN called &lt;a href=&#34;https://github.com/BMGhack&#34;&gt;BMGhack&lt;/a&gt; that coded a Twitter bot in Python for their local animal services department. I’d been reading articles about the earlier &lt;code&gt;twitteR&lt;/code&gt; and newer &lt;code&gt;rtweet&lt;/code&gt; packages for awhile and thought this was something I could tackle in R. The one I created is for Louisville Metro Animal Services (LMAS), but petfinder.com hosts pet profiles from many organizations. So you should have no problem finding one in your area. To start, let’s go ahead a create the app and get a token.&lt;/p&gt;
&lt;div id=&#34;create-an-app-and-a-token&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Create an app and a token&lt;/h2&gt;
&lt;p&gt;For the most part, everything is explained in Kearney’s &lt;a href=&#34;http://rtweet.info/articles/auth.html&#34;&gt;tutorial&lt;/a&gt;, so I won’t rehash it here. Although, currently there’s a problem with the token that &lt;code&gt;create_token&lt;/code&gt; makes. It’s a read-only token, and we need a read and write token in order to tweet using &lt;code&gt;post_tweet&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The good news is that I have a temporary fix until the issue is resolved. After making the token with &lt;code&gt;create_token&lt;/code&gt;, you’ll need to regenerate your token at the apps.twitter.com site. Now, you’ll have a read and write token. Then, you replace the old token and secret values with the new token and secret values. Write your new token object to a .rds file in your home directory and Bob’s your uncle.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;library(tidyverse)
library(rtweet)



appname &amp;lt;- &amp;quot;&amp;lt;app name&amp;gt;&amp;quot;

key &amp;lt;- &amp;quot;&amp;lt;key&amp;gt;&amp;quot;

secret &amp;lt;- &amp;quot;&amp;lt;secret&amp;gt;&amp;quot;

# create (read-only) token
twitter_token &amp;lt;- create_token(
      app = appname,
      consumer_key = key,
      consumer_secret = secret
)

# Shows Home Directory path
home_directory &amp;lt;- path.expand(&amp;quot;~/&amp;quot;)

# replace with read and write token and secret values
twitter_token$credentials$oauth_token &amp;lt;- &amp;quot;&amp;lt;new token&amp;gt;&amp;quot;
twitter_token$credentials$oauth_token_secret &amp;lt;- &amp;quot;&amp;lt;new token secret&amp;gt;&amp;quot;

write_rds(twitter_token, &amp;quot;&amp;lt;home directory path&amp;gt;twitter_token.rds&amp;quot;)


file_name &amp;lt;- file.path(home_directory, &amp;quot;twitter_token.rds&amp;quot;)

# create environment variable so rtweet doesnt need token loaded explicitly in order to work.
cat(paste0(&amp;quot;TWITTER_PAT=&amp;quot;, file_name),
    file = file.path(home_directory, &amp;quot;.Renviron&amp;quot;),
    append = TRUE,
    fill = TRUE)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div id=&#34;get-some-pet-data&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;GET some pet data&lt;/h2&gt;
&lt;p&gt;There are two options for getting the information we need: scrape the animal services’ site or use the petfinder.com &lt;a href=&#34;https://www.petfinder.com/developers/api-docs&#34;&gt;API&lt;/a&gt;. There are pros and cons for each but I’m going to use the API. To access the API, you’ll need a key and to get that key you’ll need to follow the link to the documentation above and create an account.&lt;/p&gt;
&lt;p&gt;Once we have our key we need to figure out the shelter ID. The location argument can take a ZIP code or city/state input, and there’s also a XML or JSON option for the response format. On the LMAS &lt;a href=&#34;https://louisvilleky.gov/government/animals-services&#34;&gt;website&lt;/a&gt;, the phone number and addresses are listed, so I’m going to use the zip codes to narrow the field. We’re left with three entries and from here, we see our shelter ID is “KY102”. So, now we can use the shelter.getPets method to download our pet data. I’ll go into a little more detail about the code later in the post.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;library(jsonlite)
library(httr)

URL &amp;lt;- &amp;quot;http://api.petfinder.com/shelter.find&amp;quot;
args &amp;lt;- list(key = &amp;quot;&amp;lt;key&amp;gt;&amp;quot;, location = &amp;quot;Louisville, KY&amp;quot;, format = &amp;quot;json&amp;quot;, count = &amp;quot;200&amp;quot;)
api_json &amp;lt;- GET(url = URL, query = args)

# lets you know if any errors occurred in the GET request
stop_for_status(api_json)

# creates character vector that&amp;#39;s needed for fromJSON
content_json &amp;lt;- content(api_json, as = &amp;quot;text&amp;quot;, encoding = &amp;quot;UTF-8&amp;quot;)

# creates list of nested data.frames
obj_json &amp;lt;- fromJSON(content_json)

# Find shelter, 
flatten(obj_json$petfinder$shelters$shelter) %&amp;gt;% 
      rename_at(vars(ends_with(&amp;quot;.$t&amp;quot;)), ~str_replace(., &amp;quot;\\.\\$t&amp;quot;, &amp;quot;&amp;quot;)) %&amp;gt;% 
      filter(zip == &amp;quot;40205&amp;quot; | zip == &amp;quot;40218&amp;quot;) %&amp;gt;% 
      select(name, email, id)

                              name                                   email    id
1         Pit Bulls of St. Francis           adopt@pitbullsofstfrancis.org KY507
2 Louisville Metro Animal Services AnimalServicesAdoption@louisvilleky.gov KY102
3       Kentucky Great Dane Rescue       KentuckyGreatDaneRescue@Yahoo.com KY404&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, we use our shelter id to pull some pet data, but you’ll notice I’m only pulling 2 records. This is for EDA purposes. In the final script, I recommend you go to your shelter’s website to get an estimate of how many pets they have for adoption. After you have an estimate, you can set the &lt;code&gt;count&lt;/code&gt; argument to a number greater than the records available for your shelter and it won’t be an issue. LMAS has profiles for around 120 pets, so I ended up setting my &lt;code&gt;count&lt;/code&gt; equal to 200.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;URL &amp;lt;- &amp;quot;http://api.petfinder.com/shelter.getPets&amp;quot;
args &amp;lt;- list(key = &amp;quot;&amp;lt;key&amp;gt;&amp;quot;, id = &amp;quot;KY102&amp;quot;, format = &amp;quot;json&amp;quot;, output = &amp;quot;full&amp;quot;, count = &amp;quot;2&amp;quot;)
api_json &amp;lt;- GET(url = URL, query = args)

# lets you know if any errors occurred in the GET request
stop_for_status(api_json)

# creates character vector that&amp;#39;s needed for fromJSON
content_json &amp;lt;- content(api_json, as = &amp;quot;text&amp;quot;, encoding = &amp;quot;UTF-8&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The data I’ll be using for this post are available at my github: &lt;a href=&#34;https://github.com/nllspc/LMAS-Twitterbot/blob/master/data/vector_json.rds&#34;&gt;content_json_eda&lt;/a&gt; and &lt;a href=&#34;https://github.com/nllspc/LMAS-Twitterbot/blob/master/data/vector_json2.rds&#34;&gt;content_json&lt;/a&gt;. &lt;code&gt;prettify&lt;/code&gt; lets us investigate the nested structure of the JSON we pulled from the API. I’m only showing the top part as these structures can be quite large and complex. Here we can see the meat of the data, with which we want to work, starts three levels down, petfinder –&amp;gt; pets –&amp;gt; pet.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# example data; character vector
content_json_eda &amp;lt;- read_rds(&amp;quot;vector_json.rds&amp;quot;) # 2 records
content_json &amp;lt;- read_rds(&amp;quot;vector_json2.rds&amp;quot;) # 200 records


# Get a sense of the nested structure.
content_json_eda %&amp;gt;% prettify

{
    &amp;quot;@encoding&amp;quot;: &amp;quot;iso-8859-1&amp;quot;,
    &amp;quot;@version&amp;quot;: &amp;quot;1.0&amp;quot;,
    &amp;quot;petfinder&amp;quot;: {
        &amp;quot;@xmlns:xsi&amp;quot;: &amp;quot;http://www.w3.org/2001/XMLSchema-instance&amp;quot;,
        &amp;quot;lastOffset&amp;quot;: {
            &amp;quot;$t&amp;quot;: &amp;quot;2&amp;quot;
        },
        &amp;quot;pets&amp;quot;: {
            &amp;quot;pet&amp;quot;: [
                {
                    &amp;quot;options&amp;quot;: {
                        &amp;quot;option&amp;quot;: {
                            &amp;quot;$t&amp;quot;: &amp;quot;altered&amp;quot;
                        }
                    },
                    &amp;quot;status&amp;quot;: {
                        &amp;quot;$t&amp;quot;: &amp;quot;A&amp;quot;
                    },
                    &amp;quot;contact&amp;quot;: {
                          ...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div id=&#34;formulate-the-tweet&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Formulate the tweet&lt;/h2&gt;
&lt;p&gt;I haven’t experienced any problems with the Petfinder.com API but if errors do occur, they should be captured by the &lt;code&gt;stop_for_status&lt;/code&gt; function earlier. If not, you can check &lt;code&gt;obj_json$header$status$message&lt;/code&gt; for answers. Definitions for the status messages can be found in the API documentation.&lt;/p&gt;
&lt;p&gt;After applying &lt;code&gt;fromJSON&lt;/code&gt; and &lt;code&gt;flatten&lt;/code&gt;, we now have a dataframe that we can manipulate by familiar data cleaning methods. Most of the cleaning isn’t noteworthy, but I will mention the &lt;code&gt;weights&lt;/code&gt; variable that I created. It’s an attempt to give pets that have had longer stays in the shelter a greater opportunity to be noticed by potential adopters. The greater the weight the more likely that pet is to be selected. It’s based on the &lt;code&gt;lastUpdate&lt;/code&gt; variable which isn’t defined in the API documentation. I’m assuming it’s the last time the shelter updated the pet’s information, but it could be the last time Petfinder updated their information (or something else entirely). If it’s the former and judging by the lack of variation in the &lt;code&gt;status&lt;/code&gt; column, it might provide some measure of length of stay. If it’s the latter, the effort could be a complete whiff.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# creates list of nested data.frames
obj_json &amp;lt;- fromJSON(content_json)


# api_message &amp;lt;- obj_json$header$status$message


# flatten creates a df; some cols have &amp;quot;.$t&amp;quot; in their names; more heavily weights pets that have been in shelter longer so those pets get more greater opportunity to be seen
pet_df &amp;lt;- flatten(obj_json$petfinder$pets$pet) %&amp;gt;%
      rename_at(vars(ends_with(&amp;quot;.$t&amp;quot;)), ~str_replace(., &amp;quot;\\.\\$t&amp;quot;, &amp;quot;&amp;quot;)) %&amp;gt;%
      mutate(lastUpdate = as.POSIXct(lastUpdate),
             link = paste0(&amp;quot;https://www.petfinder.com/petdetail/&amp;quot;, id),
             sex = recode(sex, &amp;quot;F&amp;quot; = &amp;quot;Female&amp;quot;, &amp;quot;M&amp;quot; = &amp;quot;Male&amp;quot;),
             size = recode(size, &amp;quot;L&amp;quot; = &amp;quot;Large&amp;quot;, &amp;quot;S&amp;quot; = &amp;quot;Small&amp;quot;,
                           &amp;quot;M&amp;quot; = &amp;quot;Medium&amp;quot;, &amp;quot;XL&amp;quot; = &amp;quot;Extra Large&amp;quot;),
             status = recode(status, &amp;quot;A&amp;quot; = &amp;quot;Adoptable&amp;quot;, &amp;quot;H&amp;quot; = &amp;quot;Hold&amp;quot;,
                             &amp;quot;P&amp;quot; = &amp;quot;Pending&amp;quot;, &amp;quot;X&amp;quot; = &amp;quot;Adopted/Removed&amp;quot;),
             name = lettercase::str_title_case(tolower(name)),
             elapsedTime = round(Sys.time() - lastUpdate, 0),
             rank = rank(elapsedTime)
      ) %&amp;gt;%  
      mutate(weights = portfolio::weight(., in.var = &amp;quot;rank&amp;quot;, type = &amp;quot;linear&amp;quot;, sides = &amp;quot;long&amp;quot;, size = &amp;quot;all&amp;quot;)) %&amp;gt;% 
      sample_n(size = 1, weight = weights)


glimpse(pet_df)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## Observations: 1
## Variables: 26
## $ options.option     &amp;lt;chr&amp;gt; &amp;quot;altered&amp;quot;
## $ status             &amp;lt;chr&amp;gt; &amp;quot;Adoptable&amp;quot;
## $ contact.phone      &amp;lt;chr&amp;gt; &amp;quot;(502) 473-7387&amp;quot;
## $ contact.state      &amp;lt;chr&amp;gt; &amp;quot;KY&amp;quot;
## $ contact.address2   &amp;lt;chr&amp;gt; &amp;quot;3705 Manslick Road Intake Facility&amp;quot;
## $ contact.email      &amp;lt;chr&amp;gt; &amp;quot;AnimalServicesAdoption@louisvilleky.gov&amp;quot;
## $ contact.city       &amp;lt;chr&amp;gt; &amp;quot;Louisville&amp;quot;
## $ contact.zip        &amp;lt;chr&amp;gt; &amp;quot;40218&amp;quot;
## $ contact.address1   &amp;lt;chr&amp;gt; &amp;quot;3516 Newburg Road Adoption Center&amp;quot;
## $ age                &amp;lt;chr&amp;gt; &amp;quot;Senior&amp;quot;
## $ size               &amp;lt;chr&amp;gt; &amp;quot;Medium&amp;quot;
## $ media.photos.photo &amp;lt;list&amp;gt; [&amp;lt;c(&amp;quot;pnt&amp;quot;, &amp;quot;fpm&amp;quot;, &amp;quot;x&amp;quot;, &amp;quot;pn&amp;quot;, &amp;quot;t&amp;quot;), c(&amp;quot;http...
## $ id                 &amp;lt;chr&amp;gt; &amp;quot;41246124&amp;quot;
## $ shelterPetId       &amp;lt;chr&amp;gt; &amp;quot;A637255&amp;quot;
## $ breeds.breed       &amp;lt;list&amp;gt; [[&amp;quot;Beagle&amp;quot;]]
## $ name               &amp;lt;chr&amp;gt; &amp;quot;Willy&amp;quot;
## $ sex                &amp;lt;chr&amp;gt; &amp;quot;Male&amp;quot;
## $ description        &amp;lt;chr&amp;gt; NA
## $ mix                &amp;lt;chr&amp;gt; &amp;quot;no&amp;quot;
## $ shelterId          &amp;lt;chr&amp;gt; &amp;quot;KY102&amp;quot;
## $ lastUpdate         &amp;lt;dttm&amp;gt; 2018-03-23
## $ animal             &amp;lt;chr&amp;gt; &amp;quot;Dog&amp;quot;
## $ link               &amp;lt;chr&amp;gt; &amp;quot;https://www.petfinder.com/petdetail/41246124&amp;quot;
## $ elapsedTime        &amp;lt;time&amp;gt; 45 days
## $ rank               &amp;lt;dbl&amp;gt; 99.5
## $ weights            &amp;lt;dbl&amp;gt; 0.01230349&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Looking at the columns in &lt;code&gt;pet_df&lt;/code&gt;, you can see some of the classes are lists and potentially need unnesting. &lt;code&gt;options.option&lt;/code&gt; can have values that give information about whether the pet has been neutered (“altered”), housebroken, good with other animals, etc.. &lt;code&gt;breeds.breed&lt;/code&gt; can have multiple values if the animal is mixed breed.&lt;/p&gt;
&lt;p&gt;Since unnesting these columns can result in different numbers of rows, the unnesting needs to occur in different code chunks. Also, the column names depend upon whether the column is a nested list or not. For example, if &lt;code&gt;breeds.breed&lt;/code&gt; is nested, there will be a &lt;code&gt;$t&lt;/code&gt; appended to column name. As long as these columns are non-NA, they’re added to &lt;code&gt;bot_df&lt;/code&gt; which will be the primary dataframe we’ll use to create the tweet.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# Dataframe to add columns to
bot_df &amp;lt;- pet_df %&amp;gt;% 
      select(`pet type` = animal, age, sex, size, link)

# Different colnames depending on nrows unnested

if(!is.na(pet_df$options.option)) {
      pet_options &amp;lt;- pet_df %&amp;gt;% 
            select(options.option) %&amp;gt;% 
            unnest %&amp;gt;% 
            rename_at(vars(matches(&amp;quot;\\$t&amp;quot;)), ~str_replace(., &amp;quot;\\$t&amp;quot;, &amp;quot;options&amp;quot;)) %&amp;gt;%
            rename_at(vars(matches(&amp;quot;options.option&amp;quot;)), ~str_replace(., &amp;quot;options.option&amp;quot;, &amp;quot;options&amp;quot;)) %&amp;gt;% 
            summarize(misc = glue::collapse(options, sep = &amp;quot;, &amp;quot;))
      
      bot_df &amp;lt;- bot_df %&amp;gt;% 
            bind_cols(pet_options) %&amp;gt;% 
            select(`pet type`, misc, everything())
}

if(!is.na(pet_df$breeds.breed)) {
      pet_breeds &amp;lt;- pet_df %&amp;gt;% 
            select(breeds.breed) %&amp;gt;% 
            unnest %&amp;gt;% 
            rename_at(vars(matches(&amp;quot;\\$t&amp;quot;)), ~str_replace(., &amp;quot;\\$t&amp;quot;, &amp;quot;breeds&amp;quot;)) %&amp;gt;%
            rename_at(vars(matches(&amp;quot;breeds.breed&amp;quot;)), ~str_replace(., &amp;quot;breeds.breed&amp;quot;, &amp;quot;breeds&amp;quot;)) %&amp;gt;% 
            summarize(`breed(s)` = glue::collapse(breeds, sep = &amp;quot;, &amp;quot;))
      
      bot_df &amp;lt;- bot_df %&amp;gt;% 
            bind_cols(pet_breeds) %&amp;gt;% 
            select(`pet type`, `breed(s)`, everything())
}

glimpse(bot_df)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## Observations: 1
## Variables: 7
## $ `pet type` &amp;lt;chr&amp;gt; &amp;quot;Dog&amp;quot;
## $ `breed(s)` &amp;lt;chr&amp;gt; &amp;quot;Beagle&amp;quot;
## $ misc       &amp;lt;chr&amp;gt; &amp;quot;altered&amp;quot;
## $ age        &amp;lt;chr&amp;gt; &amp;quot;Senior&amp;quot;
## $ sex        &amp;lt;chr&amp;gt; &amp;quot;Male&amp;quot;
## $ size       &amp;lt;chr&amp;gt; &amp;quot;Medium&amp;quot;
## $ link       &amp;lt;chr&amp;gt; &amp;quot;https://www.petfinder.com/petdetail/41246124&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;select_if&lt;/code&gt; keeps columns without values out of our message template, so the tweet doesn’t display a “NA” when a column is vacant. On a side note, I have really started to love these special-case &lt;code&gt;dplyr&lt;/code&gt; functions (-if, -at, -all). They’ve become such mental energy savers for me. I recommend you have Suzan Baert’s indispensable &lt;code&gt;dplyr&lt;/code&gt; tutorial &lt;a href=&#34;https://suzan.rbind.io/2018/01/dplyr-tutorial-1&#34;&gt;series&lt;/a&gt; bookmarked because there are some syntax quirks when using these functions that I don’t find completely intuitive.&lt;/p&gt;
&lt;p&gt;I had never worked with &lt;code&gt;glue&lt;/code&gt; before this project, but wow, what a cool package. Here, I’ve layered two different templates by collapsing the first template, adding another column, and using &lt;code&gt;glue_data&lt;/code&gt; again to create my final message. If you’re unfamiliar with the package, there are two similar functions, &lt;code&gt;glue&lt;/code&gt; and &lt;code&gt;glue_data&lt;/code&gt;. The only difference being that &lt;code&gt;glue_data&lt;/code&gt; looks for variables in its first argument instead of the calling environment. So for example, &lt;code&gt;glue_data&lt;/code&gt; won’t pay attention to any &lt;code&gt;name&lt;/code&gt; variable defined outside of my pipeline, because &lt;code&gt;bot_df&lt;/code&gt; is its first argument.&lt;/p&gt;
&lt;p&gt;Also, you should be aware that Petfinder.com requires including “Powered by Petfinder.com” in your app. It’s a small price to pay in order to help some pets find good homes.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;message &amp;lt;- bot_df %&amp;gt;% 
      select_if(~!is.na(.)) %&amp;gt;% 
      gather %&amp;gt;% 
      mutate(spec = glue::glue_data(., &amp;quot;
                                    {key}: {value}
                                    &amp;quot;)) %&amp;gt;% 
      summarize(spec = glue::collapse(spec, sep = &amp;quot;\n&amp;quot;)) %&amp;gt;% 
      add_column(name = pet_df$name) %&amp;gt;% 
      mutate(message = glue::glue_data(., &amp;quot;
                                  {name} is:
                                  {spec}
                                  #adoptdontshop #rescue #adoptme #shelterpets
                                  Powered by Petfinder.com
                                  &amp;quot;)) %&amp;gt;% 
      select(message)

head(message)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;##                                                                                                                                                                                                                       message
## 1 Willy is:\npet type: Dog\nbreed(s): Beagle\nmisc: altered\nage: Senior\nsex: Male\nsize: Medium\nlink: https://www.petfinder.com/petdetail/41246124\n#adoptdontshop #rescue #adoptme #shelterpets\nPowered by Petfinder.com&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can’t have a pet app and not have an oversized image that unleashes all the cuteness. Gotta tug on those heart strings. Unfortunately, Twitter doesn’t display images from an image link, so we’re going to have to jump through some hoops. Twitter does permit image files to be uploaded, and one of our columns has links to pictures with varying resolutions. We pull the image into our environment from the URL and write it to a temporary file on our disk that will be fed to &lt;code&gt;post_tweet&lt;/code&gt; later.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# Some images are blurry on Twitter, but row 3 seems to work alright

if(is.null(pet_df$media.photos.photo[[1]])) {
      # Default photo if no photo is provided
      img_url &amp;lt;- &amp;quot;http://www.dogsinpictures.com/images/dog-cat-bunny-bird-love.jpg&amp;quot;
      image_obj &amp;lt;- magick::image_read(img_url)
      tmp &amp;lt;- tempfile(fileext=&amp;quot;.jpg&amp;quot;)
      magick::image_write(image_obj, path = tmp, format = &amp;quot;jpg&amp;quot;)
}else{
      img_df &amp;lt;- pet_df %&amp;gt;%
            select(media.photos.photo) %&amp;gt;%
            unnest %&amp;gt;%
            slice(3)
      img_url &amp;lt;- img_df$`$t`[[1]]
      
      image_obj &amp;lt;- magick::image_read(img_url)
      tmp &amp;lt;- tempfile(fileext=&amp;quot;.jpg&amp;quot;)
      magick::image_write(image_obj, path = tmp, format = &amp;quot;jpg&amp;quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div id=&#34;tweet-and-automate&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Tweet and automate&lt;/h2&gt;
&lt;p&gt;We’re set to tweet!&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;post_tweet(message[[1]], media = tmp)&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote class=&#34;twitter-tweet&#34;&gt;&lt;p lang=&#34;en&#34; dir=&#34;ltr&#34;&gt;Louise is:&lt;br&gt;pet type: Dog&lt;br&gt;breed(s): Pit Bull Terrier&lt;br&gt;misc: altered&lt;br&gt;age: Adult&lt;br&gt;sex: Female&lt;br&gt;size: Medium&lt;br&gt;link: &lt;a href=&#34;https://t.co/4h99GrVaMJ&#34;&gt;https://t.co/4h99GrVaMJ&lt;/a&gt;&lt;a href=&#34;https://twitter.com/hashtag/adoptdontshop?src=hash&amp;amp;ref_src=twsrc%5Etfw&#34;&gt;#adoptdontshop&lt;/a&gt; &lt;a href=&#34;https://twitter.com/hashtag/rescue?src=hash&amp;amp;ref_src=twsrc%5Etfw&#34;&gt;#rescue&lt;/a&gt; &lt;a href=&#34;https://twitter.com/hashtag/adoptme?src=hash&amp;amp;ref_src=twsrc%5Etfw&#34;&gt;#adoptme&lt;/a&gt; &lt;a href=&#34;https://twitter.com/hashtag/shelterpets?src=hash&amp;amp;ref_src=twsrc%5Etfw&#34;&gt;#shelterpets&lt;/a&gt;&lt;br&gt;Powered by &lt;a href=&#34;https://t.co/T0HSpQ8toe&#34;&gt;https://t.co/T0HSpQ8toe&lt;/a&gt; &lt;a href=&#34;https://t.co/ojgz1JwK42&#34;&gt;pic.twitter.com/ojgz1JwK42&lt;/a&gt;&lt;/p&gt;&amp;mdash; Eric (@erbo_exp_bot) &lt;a href=&#34;https://twitter.com/erbo_exp_bot/status/990798251577237504?ref_src=twsrc%5Etfw&#34;&gt;April 30, 2018&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&#34;https://platform.twitter.com/widgets.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;

&lt;p&gt;To be a bot though, it needs to be automated. One way to accomplish this is to set up Windows Task Scheduler. You can create the task through the program itself. In that case, I recommend you check out the bottom of McCann’s &lt;a href=&#34;https://rpubs.com/mccannecology/54352&#34;&gt;post&lt;/a&gt; on RPubs. I used the &lt;code&gt;taskscheduleR&lt;/code&gt; package which I preferred because of the log file it creates. Task Scheduler has a history tab and a log but it doesn’t show you any useful information if an error is thrown. If you do use &lt;a href=&#34;https://github.com/nosac/taskscheduleR&#34;&gt;taskscheduleR&lt;/a&gt; and its add-in, make sure the date format matches your computer’s.&lt;/p&gt;
&lt;p&gt;Once your task is initiated. I recommend setting a few options in Task Scheduler. In your task’s general tab, you want to make sure the “run with highest privileges” box is ticked. Otherwise, the UAC box pops-up everytime the task runs. Also, tick “Run whether user is logged on or not” and (if necessary) “do not store password…”. Ticking those boxes will make it so the task runs in the background and doesn’t open a window. There are some other settings worth considering, so you should do some investigating on your own. Also, make sure Rscript.exe has high enough permissions. I gave mine “full control”, but it may not need to be that high.&lt;/p&gt;
&lt;p&gt;Lastly, you have to read-in the token and point to it explicitly in &lt;code&gt;post_tweet&lt;/code&gt;. Otherwise, for some reason, when the script is executed, lines get written to your .Renviron and bogus copies of your token file are created.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;twitter_token &amp;lt;- read_rds(&amp;quot;&amp;lt;home directory&amp;gt;twitter_token.rds&amp;quot;)

post_tweet(message[[1]], media = tmp, token = twitter_token)

file.remove(tmp)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;details&gt;&lt;summary&gt;Session info&lt;/summary&gt;&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;Session info --------------------------------------------------------------------------------------------------------
 setting  value                       
 version  R version 3.5.0 (2018-04-23)
 system   i386, mingw32               
 ui       RStudio (1.1.423)           
 language (EN)                        
 collate  English_United States.1252  
 tz       America/New_York            
 date     2018-05-03                  

Packages ------------------------------------------------------------------------------------------------------------
 package    * version date       source        
 assertthat   0.2.0   2017-04-11 CRAN (R 3.5.0)
 base       * 3.5.0   2018-04-23 local         
 bindr        0.1.1   2018-03-13 CRAN (R 3.5.0)
 bindrcpp   * 0.2.2   2018-03-29 CRAN (R 3.5.0)
 broom        0.4.4   2018-03-29 CRAN (R 3.5.0)
 cellranger   1.1.0   2016-07-27 CRAN (R 3.5.0)
 cli          1.0.0   2017-11-05 CRAN (R 3.5.0)
 colorspace   1.3-2   2016-12-14 CRAN (R 3.5.0)
 compiler     3.5.0   2018-04-23 local         
 crayon       1.3.4   2017-09-16 CRAN (R 3.5.0)
 curl         3.2     2018-03-28 CRAN (R 3.5.0)
 datasets   * 3.5.0   2018-04-23 local         
 devtools     1.13.5  2018-02-18 CRAN (R 3.5.0)
 digest       0.6.15  2018-01-28 CRAN (R 3.5.0)
 dplyr      * 0.7.4   2017-09-28 CRAN (R 3.5.0)
 forcats    * 0.3.0   2018-02-19 CRAN (R 3.5.0)
 foreign      0.8-70  2017-11-28 CRAN (R 3.5.0)
 ggplot2    * 2.2.1   2016-12-30 CRAN (R 3.5.0)
 glue         1.2.0   2017-10-29 CRAN (R 3.5.0)
 graphics   * 3.5.0   2018-04-23 local         
 grDevices  * 3.5.0   2018-04-23 local         
 grid         3.5.0   2018-04-23 local         
 gtable       0.2.0   2016-02-26 CRAN (R 3.5.0)
 haven        1.1.1   2018-01-18 CRAN (R 3.5.0)
 hms          0.4.2   2018-03-10 CRAN (R 3.5.0)
 httr       * 1.3.1   2017-08-20 CRAN (R 3.5.0)
 jsonlite   * 1.5     2017-06-01 CRAN (R 3.5.0)
 knitr        1.20    2018-02-20 CRAN (R 3.5.0)
 lattice      0.20-35 2017-03-25 CRAN (R 3.5.0)
 lazyeval     0.2.1   2017-10-29 CRAN (R 3.5.0)
 lettercase   0.13.1  2016-03-03 CRAN (R 3.5.0)
 lubridate    1.7.4   2018-04-11 CRAN (R 3.5.0)
 magick       1.8     2018-03-19 CRAN (R 3.5.0)
 magrittr     1.5     2014-11-22 CRAN (R 3.5.0)
 memoise      1.1.0   2017-04-21 CRAN (R 3.5.0)
 methods    * 3.5.0   2018-04-23 local         
 mime         0.5     2016-07-07 CRAN (R 3.5.0)
 mnormt       1.5-5   2016-10-15 CRAN (R 3.5.0)
 modelr       0.1.1   2017-07-24 CRAN (R 3.5.0)
 munsell      0.4.3   2016-02-13 CRAN (R 3.5.0)
 nlme         3.1-137 2018-04-07 CRAN (R 3.5.0)
 openssl      1.0.1   2018-03-03 CRAN (R 3.5.0)
 parallel     3.5.0   2018-04-23 local         
 pillar       1.2.1   2018-02-27 CRAN (R 3.5.0)
 pkgconfig    2.0.1   2017-03-21 CRAN (R 3.5.0)
 plyr         1.8.4   2016-06-08 CRAN (R 3.5.0)
 portfolio    0.4-7   2015-01-29 CRAN (R 3.5.0)
 psych        1.8.3.3 2018-03-30 CRAN (R 3.5.0)
 purrr      * 0.2.4   2017-10-18 CRAN (R 3.5.0)
 R6           2.2.2   2017-06-17 CRAN (R 3.5.0)
 Rcpp         0.12.16 2018-03-13 CRAN (R 3.5.0)
 readr      * 1.1.1   2017-05-16 CRAN (R 3.5.0)
 readxl       1.1.0   2018-04-20 CRAN (R 3.5.0)
 reshape2     1.4.3   2017-12-11 CRAN (R 3.5.0)
 rlang        0.2.0   2018-02-20 CRAN (R 3.5.0)
 rstudioapi   0.7     2017-09-07 CRAN (R 3.5.0)
 rtweet     * 0.6.0   2017-11-16 CRAN (R 3.5.0)
 rvest        0.3.2   2016-06-17 CRAN (R 3.5.0)
 scales       0.5.0   2017-08-24 CRAN (R 3.5.0)
 stats      * 3.5.0   2018-04-23 local         
 stringi      1.1.7   2018-03-12 CRAN (R 3.5.0)
 stringr    * 1.3.0   2018-02-19 CRAN (R 3.5.0)
 tibble     * 1.4.2   2018-01-22 CRAN (R 3.5.0)
 tidyr      * 0.8.0   2018-01-29 CRAN (R 3.5.0)
 tidyselect   0.2.4   2018-02-26 CRAN (R 3.5.0)
 tidyverse  * 1.2.1   2017-11-14 CRAN (R 3.5.0)
 tools        3.5.0   2018-04-23 local         
 utils      * 3.5.0   2018-04-23 local         
 withr        2.1.2   2018-03-15 CRAN (R 3.5.0)
 xml2         1.2.0   2018-01-24 CRAN (R 3.5.0)
 yaml         2.1.18  2018-03-08 CRAN (R 3.5.0)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/details&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&#34;next-steps&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Next Steps&lt;/h2&gt;
&lt;p&gt;This method should work fine if you’re hosting on a local machine, but for cloud-based hosting, the parts where we write/read the image and token to/from disk may have to be changed so these things are stored in memory. With the current &lt;code&gt;rtweet&lt;/code&gt; package, I don’t think the solution is straight-forward. I haven’t delved into it too deeply, but one problem is the image input for &lt;code&gt;post_tweet&lt;/code&gt; is limited to a “path”. I did do a cursory examination of the &lt;code&gt;post_tweet&lt;/code&gt; function and I think it can modified without too much trouble to accept an image object that’s stored in memory.&lt;/p&gt;
&lt;p&gt;As for the token portion of issue, I haven’t looked into it at all. The &lt;code&gt;twitteR&lt;/code&gt; package is scheduled for a “leisurely deprecation”, and from my memory of a few articles that used that package, I think there might be some code in there could help. It’s just a hunch though. If anyone finds a solution, I’d be very interested.&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&#34;acknowledgements&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Acknowledgements&lt;/h2&gt;
&lt;p&gt;Thanks to &lt;a href=&#34;https://github.com/sdcharle&#34;&gt;Steve Charlesworth&lt;/a&gt; for taking the time to answer some questions regarding his bot.&lt;/p&gt;
&lt;p&gt;Thanks to &lt;a href=&#34;https://www.linkedin.com/in/-mgo-&#34;&gt;Matthew Gotth-Olsen&lt;/a&gt; for liaising with Louisville Metro Animal Services&lt;/p&gt;
&lt;p&gt;Special Thanks to&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://erbo.rbind.io/charts/louisville-rstats-hex.png&#34; width=&#34;100px&#34; height=&#34;100px&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/louisville-rstats&#34;&gt;Louisville RStats&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&#34;references&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;p&gt;
&lt;a id=&#39;bib-Boettiger_2017&#39;&gt;&lt;/a&gt;&lt;a href=&#34;#cite-Boettiger_2017&#34;&gt;[1]&lt;/a&gt;&lt;cite&gt; C. Boettiger. &lt;em&gt;knitcitations: Citations for ‘Knitr’ Markdown Files&lt;/em&gt;. R package version 1.0.8. 2017. URL: &lt;a href=&#34;https://CRAN.R-project.org/package=knitcitations&#34;&gt;https://CRAN.R-project.org/package=knitcitations&lt;/a&gt;.&lt;/cite&gt;
&lt;/p&gt;
&lt;p&gt;
&lt;a id=&#39;bib-Brown_2016&#39;&gt;&lt;/a&gt;&lt;a href=&#34;#cite-Brown_2016&#34;&gt;[2]&lt;/a&gt;&lt;cite&gt; C. Brown. &lt;em&gt;lettercase: Utilities for Formatting Strings with Consistent Capitalization, Word Breaks and White Space&lt;/em&gt;. R package version 0.13.1. 2016. URL: &lt;a href=&#34;https://CRAN.R-project.org/package=lettercase&#34;&gt;https://CRAN.R-project.org/package=lettercase&lt;/a&gt;.&lt;/cite&gt;
&lt;/p&gt;
&lt;p&gt;
&lt;a id=&#39;bib-c_2017&#39;&gt;&lt;/a&gt;&lt;a href=&#34;#cite-c_2017&#34;&gt;[3]&lt;/a&gt;&lt;cite&gt; c, e. =. “&lt;a href=&#34;mailto:jwijffels@bnosac.be%22&#34;&gt;jwijffels@bnosac.be&amp;quot;&lt;/a&gt;;), person and person)). &lt;em&gt;taskscheduleR: Schedule R Scripts and Processes with the Windows Task Scheduler&lt;/em&gt;. R package version 1.1. 2017. URL: &lt;a href=&#34;https://CRAN.R-project.org/package=taskscheduleR&#34;&gt;https://CRAN.R-project.org/package=taskscheduleR&lt;/a&gt;.&lt;/cite&gt;
&lt;/p&gt;
&lt;p&gt;
&lt;a id=&#39;bib-Hester_2017&#39;&gt;&lt;/a&gt;&lt;a href=&#34;#cite-Hester_2017&#34;&gt;[4]&lt;/a&gt;&lt;cite&gt; J. Hester. &lt;em&gt;glue: Interpreted String Literals&lt;/em&gt;. R package version 1.2.0. 2017. URL: &lt;a href=&#34;https://CRAN.R-project.org/package=glue&#34;&gt;https://CRAN.R-project.org/package=glue&lt;/a&gt;.&lt;/cite&gt;
&lt;/p&gt;
&lt;p&gt;
&lt;a id=&#39;bib-rtweet-package&#39;&gt;&lt;/a&gt;&lt;a href=&#34;#cite-rtweet-package&#34;&gt;[5]&lt;/a&gt;&lt;cite&gt; M. W. Kearney. &lt;em&gt;rtweet: Collecting Twitter Data&lt;/em&gt;. R package version 0.6.0. 2017. URL: &lt;a href=&#34;https://cran.r-project.org/package=rtweet&#34;&gt;https://cran.r-project.org/package=rtweet&lt;/a&gt;.&lt;/cite&gt;
&lt;/p&gt;
&lt;p&gt;
&lt;a id=&#39;bib-Ooms_2018&#39;&gt;&lt;/a&gt;&lt;a href=&#34;#cite-Ooms_2018&#34;&gt;[6]&lt;/a&gt;&lt;cite&gt; J. Ooms. &lt;em&gt;magick: Advanced Graphics and Image-Processing in R&lt;/em&gt;. R package version 1.8. 2018. URL: &lt;a href=&#34;https://CRAN.R-project.org/package=magick&#34;&gt;https://CRAN.R-project.org/package=magick&lt;/a&gt;.&lt;/cite&gt;
&lt;/p&gt;
&lt;p&gt;
&lt;a id=&#39;bib-Ooms_2014&#39;&gt;&lt;/a&gt;&lt;a href=&#34;#cite-Ooms_2014&#34;&gt;[7]&lt;/a&gt;&lt;cite&gt; J. Ooms. “The jsonlite Package: A Practical and Consistent Mapping Between JSON Data and R Objects”. In: &lt;em&gt;arXiv:1403.2805 [stat.CO]&lt;/em&gt; (2014). URL: &lt;a href=&#34;https://arxiv.org/abs/1403.2805&#34;&gt;https://arxiv.org/abs/1403.2805&lt;/a&gt;.&lt;/cite&gt;
&lt;/p&gt;
&lt;p&gt;
&lt;a id=&#39;bib-Wickham_2017&#39;&gt;&lt;/a&gt;&lt;a href=&#34;#cite-Wickham_2017&#34;&gt;[8]&lt;/a&gt;&lt;cite&gt; H. Wickham. &lt;em&gt;httr: Tools for Working with URLs and HTTP&lt;/em&gt;. R package version 1.3.1. 2017. URL: &lt;a href=&#34;https://CRAN.R-project.org/package=httr&#34;&gt;https://CRAN.R-project.org/package=httr&lt;/a&gt;.&lt;/cite&gt;
&lt;/p&gt;
&lt;p&gt;
&lt;a id=&#39;bib-Wickham_2017a&#39;&gt;&lt;/a&gt;&lt;a href=&#34;#cite-Wickham_2017a&#34;&gt;[9]&lt;/a&gt;&lt;cite&gt; H. Wickham. &lt;em&gt;tidyverse: Easily Install and Load the ‘Tidyverse’&lt;/em&gt;. R package version 1.2.1. 2017. URL: &lt;a href=&#34;https://CRAN.R-project.org/package=tidyverse&#34;&gt;https://CRAN.R-project.org/package=tidyverse&lt;/a&gt;.&lt;/cite&gt;
&lt;/p&gt;
&lt;/div&gt;
</description>
        </item>
        
        <item>
          <title>A Baseball Dashboard in Time for Opening Weekend (part three)</title>
          <link>https://erbo.rbind.io/blog/2018-03-23-a-baseball-dashboard-in-time-for-opening-weekend-part-three/</link>
          <pubDate>Fri, 23 Mar 2018 00:00:00 +0000</pubDate>
          
          <guid>https://erbo.rbind.io/blog/2018-03-23-a-baseball-dashboard-in-time-for-opening-weekend-part-three/</guid>
          <description>&lt;script src=&#34;https://erbo.rbind.io/rmarkdown-libs/htmlwidgets/htmlwidgets.js&#34;&gt;&lt;/script&gt;
&lt;script src=&#34;https://erbo.rbind.io/rmarkdown-libs/pymjs/pym.v1.js&#34;&gt;&lt;/script&gt;
&lt;script src=&#34;https://erbo.rbind.io/rmarkdown-libs/widgetframe-binding/widgetframe.js&#34;&gt;&lt;/script&gt;
&lt;script src=&#34;https://erbo.rbind.io/rmarkdown-libs/d3/d3.min.js&#34;&gt;&lt;/script&gt;
&lt;script src=&#34;https://erbo.rbind.io/rmarkdown-libs/d3-lasso/d3-lasso.min.js&#34;&gt;&lt;/script&gt;
&lt;link href=&#34;https://erbo.rbind.io/rmarkdown-libs/ggiraph/styles.css&#34; rel=&#34;stylesheet&#34; /&gt;
&lt;script src=&#34;https://erbo.rbind.io/rmarkdown-libs/ggiraph/ggiraph_utils.js&#34;&gt;&lt;/script&gt;
&lt;script src=&#34;https://erbo.rbind.io/rmarkdown-libs/ggiraph/ggiraph_over_effect.js&#34;&gt;&lt;/script&gt;
&lt;script src=&#34;https://erbo.rbind.io/rmarkdown-libs/ggiraph/ggiraph_zoom.js&#34;&gt;&lt;/script&gt;
&lt;script src=&#34;https://erbo.rbind.io/rmarkdown-libs/ggiraph/ggiraph_tooltip.js&#34;&gt;&lt;/script&gt;
&lt;script src=&#34;https://erbo.rbind.io/rmarkdown-libs/ggiraph/ggiraph_selector.js&#34;&gt;&lt;/script&gt;
&lt;script src=&#34;https://erbo.rbind.io/rmarkdown-libs/ggiraph-binding/ggiraph.js&#34;&gt;&lt;/script&gt;
&lt;script src=&#34;https://erbo.rbind.io/rmarkdown-libs/svg_1/scripts_svg_1.js&#34;&gt;&lt;/script&gt;

&lt;div id=&#34;TOC&#34;&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#datatable&#34;&gt;DataTable&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#cleveland-dot-plots&#34;&gt;Cleveland Dot Plots&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#interactive-line-chart&#34;&gt;Interactive Line Chart&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#dashboard&#34;&gt;Dashboard&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#conclusion&#34;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#references&#34;&gt;References&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;

&lt;blockquote class=&#34;twitter-tweet&#34;&gt;&lt;p lang=&#34;en&#34; dir=&#34;ltr&#34;&gt;&lt;a href=&#34;https://twitter.com/ctrent?ref_src=twsrc%5Etfw&#34;&gt;@ctrent&lt;/a&gt; I’ve always thought that it’d be fun to debate the &lt;a href=&#34;https://twitter.com/Reds?ref_src=twsrc%5Etfw&#34;&gt;@Reds&lt;/a&gt; HOF like the real one. Set a high , but debatable, bar. Bring on the JAWS&lt;/p&gt;&amp;mdash; Craig Wales (@C_Dubs1) &lt;a href=&#34;https://twitter.com/C_Dubs1/status/900017684590579713?ref_src=twsrc%5Etfw&#34;&gt;August 22, 2017&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&#34;https://platform.twitter.com/widgets.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;

&lt;blockquote class=&#34;twitter-tweet&#34;&gt;&lt;p lang=&#34;en&#34; dir=&#34;ltr&#34;&gt;it would be interesting, I don&amp;#39;t have the math or computer skills to do so &lt;a href=&#34;https://t.co/p5n9O5NjGT&#34;&gt;https://t.co/p5n9O5NjGT&lt;/a&gt;&lt;/p&gt;&amp;mdash; C. Trent Rosecrans (@ctrent) &lt;a href=&#34;https://twitter.com/ctrent/status/900033162771910656?ref_src=twsrc%5Etfw&#34;&gt;August 22, 2017&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&#34;https://platform.twitter.com/widgets.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;

&lt;p&gt;In &lt;a href=&#34;https://erbo.rbind.io/blog/2018-03-09-a-baseball-dashboard-in-time-for-opening-weekend-part-one/&#34;&gt;part one&lt;/a&gt;, we collected the data and performed a little EDA. In &lt;a href=&#34;https://erbo.rbind.io/blog/2018-03-21-a-baseball-dashboard-in-time-for-opening-weekend-part-two/&#34;&gt;part two&lt;/a&gt;, we did some calculations and prepared the data to be visualized. Now, we get to &lt;strong&gt;&lt;em&gt;see&lt;/em&gt;&lt;/strong&gt; the fruits of our labor. You’ll need these objects from part two: &lt;code&gt;display_table&lt;/code&gt;, &lt;code&gt;jaws_group&lt;/code&gt;, &lt;code&gt;war_group&lt;/code&gt;, and &lt;code&gt;war_combo_avg&lt;/code&gt;.&lt;/p&gt;
&lt;div id=&#34;datatable&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;DataTable&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://rstudio.github.io/DT/options.html&#34;&gt;RStudio&lt;/a&gt; and &lt;a href=&#34;https://datatables.net/reference/option/&#34;&gt;DataTable&lt;/a&gt; have excellent documentation on the various features, so I won’t explain every option I’ve enabled here. I will bring to your attention to a few things though.&lt;/p&gt;
&lt;p&gt;If you decide to include &lt;code&gt;CSV&lt;/code&gt; and &lt;code&gt;PDF&lt;/code&gt; buttons, I recommend you also include the “Show &lt;number&gt; entries” feature. It’s activated by default but I’ve turned it off here with &lt;code&gt;lengthChange = FALSE&lt;/code&gt;. When a user downloads data using either of these buttons, only the data that is displayed is downloaded, so allowing the user to be able to choose how many rows are displayed is a good option. The problem is the default position (“lfrtip”) is in the header which now is crowded with buttons and the search window. The &lt;a href=&#34;https://datatables.net/reference/option/dom&#34;&gt;&lt;code&gt;dom&lt;/code&gt;&lt;/a&gt; option allows you to configure the location of control elements by specifying the element order. “l” represents the “entries” feature. This order places it in the bottom left corner of the table. In my opinion this is the most display-friendly location.&lt;/p&gt;
&lt;p&gt;I also consider the horizontal scrolling option a necessity. There’s an extension &lt;code&gt;Responsive&lt;/code&gt; that’s supposed to make your table mobile-friendly but I’ve found it to be buggy. It sometimes creates edge effects that make your columns unreadable. The stable option is to enable horizontal scrolling by setting &lt;code&gt;scrollX = TRUE&lt;/code&gt; (capital X).&lt;/p&gt;
&lt;p&gt;You’ll also notice the javascript at the bottom. This allows you to change the background and text color of the header. If you want to match the colors of the visuals to a particular team, &lt;a href=&#34;https://teamcolorcodes.com&#34;&gt;teamcolorcodes.com&lt;/a&gt; or &lt;a href=&#34;https://www.codeofcolors.com&#34;&gt;codeofcolors.com&lt;/a&gt; provides color codes to all MLB teams and other sports teams as well.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;library(DT)

dt &amp;lt;- datatable(data = display_table,
          rownames = FALSE,
          extensions = c(&amp;quot;FixedColumns&amp;quot;,&amp;quot;Buttons&amp;quot;),
          options = list(language = list(sSearch = &amp;quot;Filter:&amp;quot;),
                         search = list(regex = TRUE),
                         buttons = c(&amp;quot;colvis&amp;quot;, &amp;quot;csv&amp;quot;, &amp;quot;pdf&amp;quot;),
                         scrollX = TRUE,
                         pageLength = 3,
                         lengthChange = FALSE,
                         fixedColumns = list(leftColumns = 1),
                         dom = &amp;quot;Bfrtlip&amp;quot;,
                         initComplete = JS(
                               &amp;quot;function(settings, json) {&amp;quot;,
                               &amp;quot;$(this.api().table().header()).css({&amp;#39;background-color&amp;#39;: &amp;#39;#C6011F&amp;#39;, &amp;#39;color&amp;#39;: &amp;#39;#FFF&amp;#39;});&amp;quot;,
                               &amp;quot;}&amp;quot;
                         ))
)&lt;/code&gt;&lt;/pre&gt;
&lt;div id=&#34;htmlwidget-1&#34; style=&#34;width:100%;height:450px;&#34; class=&#34;widgetframe html-widget&#34;&gt;&lt;/div&gt;
&lt;script type=&#34;application/json&#34; data-for=&#34;htmlwidget-1&#34;&gt;{&#34;x&#34;:{&#34;url&#34;:&#34;/post/2018-03-23-a-baseball-dashboard-in-time-for-opening-day-part-three_files/figure-html//widgets/widget_iframe-dt.html&#34;,&#34;options&#34;:{&#34;xdomain&#34;:&#34;*&#34;,&#34;allowfullscreen&#34;:false,&#34;lazyload&#34;:false}},&#34;evals&#34;:[],&#34;jsHooks&#34;:[]}&lt;/script&gt;
&lt;/div&gt;
&lt;div id=&#34;cleveland-dot-plots&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Cleveland Dot Plots&lt;/h2&gt;
&lt;p&gt;Dot plots are the old-new hotness as an alternative to bar charts according to my twitter feed, so I thought I’d give them a look. I used this &lt;a href=&#34;uc-r.github.io/cleveland-dot-plots&#34;&gt;article&lt;/a&gt; from University of Cincinnati Stats (coincidence or destiny?) site to guide me.&lt;/p&gt;
&lt;p&gt;There will be two dot plots for the dashboard, but I’m only showing one here. The main issues I had were the legend placement and the value labels. If you have (or don’t have) large spreads between some players and the “typical HOF’er”, you’ll want to play with the limits in &lt;code&gt;scale_x_continuous&lt;/code&gt;. If the legend gives you problems, I’d look at &lt;code&gt;legend.position&lt;/code&gt; and maybe &lt;code&gt;legend.direction&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;library(tidyverse)

head(war_group, 3)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## # A tibble: 3 x 4
##   Name         Group Stat  Value
##   &amp;lt;chr&amp;gt;        &amp;lt;chr&amp;gt; &amp;lt;chr&amp;gt; &amp;lt;dbl&amp;gt;
## 1 Aaron Boone  3B    WAR    11.6
## 2 Adam Dunn    LF    WAR    16.4
## 3 Barry Larkin SS    WAR    70.2&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;war_dot &amp;lt;- war_group %&amp;gt;% 
      filter(Name == &amp;quot;Adam Dunn&amp;quot;)


war_right_label &amp;lt;- war_dot %&amp;gt;% 
      group_by(Group) %&amp;gt;% 
      arrange(desc(Value)) %&amp;gt;% 
      top_n(1)

war_left_label &amp;lt;- war_dot %&amp;gt;% 
      group_by(Group) %&amp;gt;% 
      arrange(desc(Value)) %&amp;gt;% 
      slice(2)

ggplot(war_dot, aes(x = Value, y = Group)) +
      geom_line(aes(group = Group)) +
      geom_point(aes(color = Stat), size = 3) +
      geom_text(data = war_right_label, aes(color = Stat, label = round(Value, 1)), size = 5, hjust = -0.5) +
      geom_text(data = war_left_label, aes(color = Stat, label = round(Value, 1)), size = 5, hjust = 1.5) +
      scale_x_continuous(limits = c(min(war_dot$Value)-30, max(war_dot$Value)+28)) + 
      scale_color_manual(labels = c(&amp;quot;Typical HOFer (weighted)&amp;quot;, &amp;quot;Player&amp;quot;), values = c(&amp;quot;#000000&amp;quot;, &amp;quot;#C6011F&amp;quot;)) +
      labs(title = &amp;quot;WARtenure&amp;quot;) +
      theme_minimal() +
      theme(axis.title = element_blank(),
            panel.grid.major.x = element_blank(),
            panel.grid.minor = element_blank(),
            legend.title = element_blank(),
            legend.justification = c(0,1),
            legend.position = c(.1, 1),
            legend.background = element_blank(),
            legend.direction = &amp;quot;vertical&amp;quot;,
            plot.title = element_text(size = 20, margin = margin(b = 10))
      )&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&#34;https://erbo.rbind.io/post/2018-03-23-a-baseball-dashboard-in-time-for-opening-day-part-three_files/figure-html/dot-1.png&#34; width=&#34;288&#34; /&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&#34;interactive-line-chart&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Interactive Line Chart&lt;/h2&gt;
&lt;p&gt;The line chart shows the player’s WAR for every season he was a Red. Hovering over each point displays its value. The horizontal dashed line is one of two values: the median WAR per season of pitchers or the median WAR per season of position players.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;library(ggiraph)

head(war_combo_avg, 3)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## # A tibble: 3 x 7
##   bbref_id  Name       yearId    WAR POS   type  `Median WAR`
##   &amp;lt;chr&amp;gt;     &amp;lt;chr&amp;gt;       &amp;lt;int&amp;gt;  &amp;lt;dbl&amp;gt; &amp;lt;chr&amp;gt; &amp;lt;chr&amp;gt;        &amp;lt;dbl&amp;gt;
## 1 luquedo01 Dolf Luque   1918 -0.140 P     WAR           2.38
## 2 luquedo01 Dolf Luque   1919  1.14  P     WAR           2.38
## 3 luquedo01 Dolf Luque   1922  3.10  P     WAR           2.38&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;war_line &amp;lt;- war_combo_avg %&amp;gt;% 
      filter(Name == &amp;quot;Johnny Bench&amp;quot;)

line_filtered &amp;lt;- war_line %&amp;gt;% 
      filter(type == &amp;quot;WAR4&amp;quot;)

p &amp;lt;- ggplot(data = war_line) + 
      geom_point_interactive(aes(x = yearId, y = WAR, group = type, tooltip = WAR), color = alpha(&amp;quot;#000000&amp;quot;, 0.5)) +
      geom_point_interactive(data = line_filtered, aes(x = yearId, y = WAR, color = type, tooltip = WAR), size = 2.5, shape = 17) +
      geom_line(aes(x = yearId, y = WAR)) +
      # all the Median WAR values the same, taking mean is just me hacking to get a value instead of a vector for the y-intercept
      geom_hline(aes(yintercept = mean(`Median WAR`), linetype = &amp;quot;Typical HOFer&amp;quot;), color = alpha(&amp;quot;#C6011F&amp;quot;, 0.5), size = 1.25) +
      scale_linetype_manual(values = 2, guide = guide_legend(override.aes = list(color = &amp;quot;#C6011F&amp;quot;))) +
      scale_y_continuous(limits = c(min(war_line$WAR)-5, max(war_line$WAR)+5)) +
      labs(title = &amp;quot;WAR&amp;quot;) +
      theme_minimal() +
      theme(axis.title = element_blank(),
            panel.grid.major.x = element_blank(),
            panel.grid.minor = element_blank(),
            legend.title = element_blank(),
            legend.justification = c(0,1),
            legend.position = c(.1, 1),
            legend.box = &amp;quot;horizontal&amp;quot;,
            legend.background = element_blank(),
            legend.direction = &amp;quot;horizontal&amp;quot;,
            plot.title = element_text(size = 20, margin = margin(b = 10))
      )

ggiraph(ggobj = p)&lt;/code&gt;&lt;/pre&gt;
&lt;div id=&#34;htmlwidget-2&#34; style=&#34;width:672px;height:480px;&#34; class=&#34;ggiraph html-widget&#34;&gt;&lt;/div&gt;
&lt;script type=&#34;application/json&#34; data-for=&#34;htmlwidget-2&#34;&gt;{&#34;x&#34;:{&#34;html&#34;:&#34;&lt;?xml version=\&#34;1.0\&#34; encoding=\&#34;UTF-8\&#34;?&gt;\n&lt;svg xmlns=\&#34;http://www.w3.org/2000/svg\&#34; xmlns:xlink=\&#34;http://www.w3.org/1999/xlink\&#34; id=\&#34;svg_1\&#34; viewBox=\&#34;0 0 432.00 360.00\&#34;&gt;\n  &lt;g&gt;\n    &lt;defs&gt;\n      &lt;clipPath id=\&#34;cl1_0\&#34;&gt;\n        &lt;rect x=\&#34;0.00\&#34; y=\&#34;360.00\&#34; width=\&#34;0.00\&#34; height=\&#34;72.00\&#34;/&gt;\n      &lt;\/clipPath&gt;\n    &lt;\/defs&gt;\n    &lt;rect x=\&#34;0.00\&#34; y=\&#34;0.00\&#34; width=\&#34;432.00\&#34; height=\&#34;360.00\&#34; id=\&#34;1\&#34; clip-path=\&#34;url(#cl1_0)\&#34; fill=\&#34;#FFFFFF\&#34; fill-opacity=\&#34;1\&#34; stroke-width=\&#34;0.75\&#34; stroke=\&#34;#FFFFFF\&#34; stroke-opacity=\&#34;1\&#34; stroke-linejoin=\&#34;round\&#34; stroke-linecap=\&#34;round\&#34;/&gt;\n    &lt;defs&gt;\n      &lt;clipPath id=\&#34;cl1_1\&#34;&gt;\n        &lt;rect x=\&#34;0.00\&#34; y=\&#34;0.00\&#34; width=\&#34;432.00\&#34; height=\&#34;360.00\&#34;/&gt;\n      &lt;\/clipPath&gt;\n    &lt;\/defs&gt;\n    &lt;defs&gt;\n      &lt;clipPath id=\&#34;cl1_2\&#34;&gt;\n        &lt;rect x=\&#34;20.42\&#34; y=\&#34;29.75\&#34; width=\&#34;406.10\&#34; height=\&#34;313.40\&#34;/&gt;\n      &lt;\/clipPath&gt;\n    &lt;\/defs&gt;\n    &lt;polyline points=\&#34;20.42,321.87 426.52,321.87\&#34; id=\&#34;2\&#34; clip-path=\&#34;url(#cl1_2)\&#34; fill=\&#34;none\&#34; stroke-width=\&#34;1.06698\&#34; stroke=\&#34;#EBEBEB\&#34; stroke-opacity=\&#34;1\&#34; stroke-linejoin=\&#34;round\&#34; stroke-linecap=\&#34;butt\&#34;/&gt;\n    &lt;polyline points=\&#34;20.42,247.06 426.52,247.06\&#34; id=\&#34;3\&#34; clip-path=\&#34;url(#cl1_2)\&#34; fill=\&#34;none\&#34; stroke-width=\&#34;1.06698\&#34; stroke=\&#34;#EBEBEB\&#34; stroke-opacity=\&#34;1\&#34; stroke-linejoin=\&#34;round\&#34; stroke-linecap=\&#34;butt\&#34;/&gt;\n    &lt;polyline points=\&#34;20.42,172.24 426.52,172.24\&#34; id=\&#34;4\&#34; clip-path=\&#34;url(#cl1_2)\&#34; fill=\&#34;none\&#34; stroke-width=\&#34;1.06698\&#34; stroke=\&#34;#EBEBEB\&#34; stroke-opacity=\&#34;1\&#34; stroke-linejoin=\&#34;round\&#34; stroke-linecap=\&#34;butt\&#34;/&gt;\n    &lt;polyline points=\&#34;20.42,97.42 426.52,97.42\&#34; id=\&#34;5\&#34; clip-path=\&#34;url(#cl1_2)\&#34; fill=\&#34;none\&#34; stroke-width=\&#34;1.06698\&#34; stroke=\&#34;#EBEBEB\&#34; stroke-opacity=\&#34;1\&#34; stroke-linejoin=\&#34;round\&#34; stroke-linecap=\&#34;butt\&#34;/&gt;\n    &lt;circle cx=\&#34;38.88\&#34; cy=\&#34;254.09\&#34; r=\&#34;1.47pt\&#34; id=\&#34;6\&#34; clip-path=\&#34;url(#cl1_2)\&#34; fill=\&#34;#000000\&#34; fill-opacity=\&#34;0.5\&#34; stroke=\&#34;#000000\&#34; stroke-opacity=\&#34;0.5\&#34;/&gt;\n    &lt;circle cx=\&#34;61.95\&#34; cy=\&#34;172.39\&#34; r=\&#34;1.47pt\&#34; id=\&#34;7\&#34; clip-path=\&#34;url(#cl1_2)\&#34; fill=\&#34;#000000\&#34; fill-opacity=\&#34;0.5\&#34; stroke=\&#34;#000000\&#34; stroke-opacity=\&#34;0.5\&#34;/&gt;\n    &lt;circle cx=\&#34;85.03\&#34; cy=\&#34;155.48\&#34; r=\&#34;1.47pt\&#34; id=\&#34;8\&#34; clip-path=\&#34;url(#cl1_2)\&#34; fill=\&#34;#000000\&#34; fill-opacity=\&#34;0.5\&#34; stroke=\&#34;#000000\&#34; stroke-opacity=\&#34;0.5\&#34;/&gt;\n    &lt;circle cx=\&#34;131.18\&#34; cy=\&#34;186.45\&#34; r=\&#34;1.47pt\&#34; id=\&#34;9\&#34; clip-path=\&#34;url(#cl1_2)\&#34; fill=\&#34;#000000\&#34; fill-opacity=\&#34;0.5\&#34; stroke=\&#34;#000000\&#34; stroke-opacity=\&#34;0.5\&#34;/&gt;\n    &lt;circle cx=\&#34;177.32\&#34; cy=\&#34;176.28\&#34; r=\&#34;1.47pt\&#34; id=\&#34;10\&#34; clip-path=\&#34;url(#cl1_2)\&#34; fill=\&#34;#000000\&#34; fill-opacity=\&#34;0.5\&#34; stroke=\&#34;#000000\&#34; stroke-opacity=\&#34;0.5\&#34;/&gt;\n    &lt;circle cx=\&#34;246.54\&#34; cy=\&#34;177.92\&#34; r=\&#34;1.47pt\&#34; id=\&#34;11\&#34; clip-path=\&#34;url(#cl1_2)\&#34; fill=\&#34;#000000\&#34; fill-opacity=\&#34;0.5\&#34; stroke=\&#34;#000000\&#34; stroke-opacity=\&#34;0.5\&#34;/&gt;\n    &lt;circle cx=\&#34;269.62\&#34; cy=\&#34;172.69\&#34; r=\&#34;1.47pt\&#34; id=\&#34;12\&#34; clip-path=\&#34;url(#cl1_2)\&#34; fill=\&#34;#000000\&#34; fill-opacity=\&#34;0.5\&#34; stroke=\&#34;#000000\&#34; stroke-opacity=\&#34;0.5\&#34;/&gt;\n    &lt;circle cx=\&#34;292.69\&#34; cy=\&#34;180.62\&#34; r=\&#34;1.47pt\&#34; id=\&#34;13\&#34; clip-path=\&#34;url(#cl1_2)\&#34; fill=\&#34;#000000\&#34; fill-opacity=\&#34;0.5\&#34; stroke=\&#34;#000000\&#34; stroke-opacity=\&#34;0.5\&#34;/&gt;\n    &lt;circle cx=\&#34;315.77\&#34; cy=\&#34;163.86\&#34; r=\&#34;1.47pt\&#34; id=\&#34;14\&#34; clip-path=\&#34;url(#cl1_2)\&#34; fill=\&#34;#000000\&#34; fill-opacity=\&#34;0.5\&#34; stroke=\&#34;#000000\&#34; stroke-opacity=\&#34;0.5\&#34;/&gt;\n    &lt;circle cx=\&#34;338.84\&#34; cy=\&#34;197.08\&#34; r=\&#34;1.47pt\&#34; id=\&#34;15\&#34; clip-path=\&#34;url(#cl1_2)\&#34; fill=\&#34;#000000\&#34; fill-opacity=\&#34;0.5\&#34; stroke=\&#34;#000000\&#34; stroke-opacity=\&#34;0.5\&#34;/&gt;\n    &lt;circle cx=\&#34;361.91\&#34; cy=\&#34;229.85\&#34; r=\&#34;1.47pt\&#34; id=\&#34;16\&#34; clip-path=\&#34;url(#cl1_2)\&#34; fill=\&#34;#000000\&#34; fill-opacity=\&#34;0.5\&#34; stroke=\&#34;#000000\&#34; stroke-opacity=\&#34;0.5\&#34;/&gt;\n    &lt;circle cx=\&#34;384.99\&#34; cy=\&#34;247.35\&#34; r=\&#34;1.47pt\&#34; id=\&#34;17\&#34; clip-path=\&#34;url(#cl1_2)\&#34; fill=\&#34;#000000\&#34; fill-opacity=\&#34;0.5\&#34; stroke=\&#34;#000000\&#34; stroke-opacity=\&#34;0.5\&#34;/&gt;\n    &lt;circle cx=\&#34;408.06\&#34; cy=\&#34;230.60\&#34; r=\&#34;1.47pt\&#34; id=\&#34;18\&#34; clip-path=\&#34;url(#cl1_2)\&#34; fill=\&#34;#000000\&#34; fill-opacity=\&#34;0.5\&#34; stroke=\&#34;#000000\&#34; stroke-opacity=\&#34;0.5\&#34;/&gt;\n    &lt;circle cx=\&#34;108.10\&#34; cy=\&#34;135.88\&#34; r=\&#34;1.47pt\&#34; id=\&#34;19\&#34; clip-path=\&#34;url(#cl1_2)\&#34; fill=\&#34;#000000\&#34; fill-opacity=\&#34;0.5\&#34; stroke=\&#34;#000000\&#34; stroke-opacity=\&#34;0.5\&#34;/&gt;\n    &lt;circle cx=\&#34;154.25\&#34; cy=\&#34;118.82\&#34; r=\&#34;1.47pt\&#34; id=\&#34;20\&#34; clip-path=\&#34;url(#cl1_2)\&#34; fill=\&#34;#000000\&#34; fill-opacity=\&#34;0.5\&#34; stroke=\&#34;#000000\&#34; stroke-opacity=\&#34;0.5\&#34;/&gt;\n    &lt;circle cx=\&#34;200.40\&#34; cy=\&#34;130.04\&#34; r=\&#34;1.47pt\&#34; id=\&#34;21\&#34; clip-path=\&#34;url(#cl1_2)\&#34; fill=\&#34;#000000\&#34; fill-opacity=\&#34;0.5\&#34; stroke=\&#34;#000000\&#34; stroke-opacity=\&#34;0.5\&#34;/&gt;\n    &lt;circle cx=\&#34;223.47\&#34; cy=\&#34;148.45\&#34; r=\&#34;1.47pt\&#34; id=\&#34;22\&#34; clip-path=\&#34;url(#cl1_2)\&#34; fill=\&#34;#000000\&#34; fill-opacity=\&#34;0.5\&#34; stroke=\&#34;#000000\&#34; stroke-opacity=\&#34;0.5\&#34;/&gt;\n    &lt;polygon points=\&#34;108.10,131.18 112.17,138.23 104.03,138.23\&#34; id=\&#34;23\&#34; clip-path=\&#34;url(#cl1_2)\&#34; fill=\&#34;#F8766D\&#34; fill-opacity=\&#34;1\&#34; stroke=\&#34;none\&#34;/&gt;\n    &lt;polygon points=\&#34;154.25,114.12 158.32,121.17 150.18,121.17\&#34; id=\&#34;24\&#34; clip-path=\&#34;url(#cl1_2)\&#34; fill=\&#34;#F8766D\&#34; fill-opacity=\&#34;1\&#34; stroke=\&#34;none\&#34;/&gt;\n    &lt;polygon points=\&#34;200.40,125.34 204.47,132.39 196.33,132.39\&#34; id=\&#34;25\&#34; clip-path=\&#34;url(#cl1_2)\&#34; fill=\&#34;#F8766D\&#34; fill-opacity=\&#34;1\&#34; stroke=\&#34;none\&#34;/&gt;\n    &lt;polygon points=\&#34;223.47,143.75 227.54,150.80 219.40,150.80\&#34; id=\&#34;26\&#34; clip-path=\&#34;url(#cl1_2)\&#34; fill=\&#34;#F8766D\&#34; fill-opacity=\&#34;1\&#34; stroke=\&#34;none\&#34;/&gt;\n    &lt;polyline points=\&#34;38.88,254.09 61.95,172.39 85.03,155.48 108.10,135.88 131.18,186.45 154.25,118.82 177.32,176.28 200.40,130.04 223.47,148.45 246.54,177.92 269.62,172.69 292.69,180.62 315.77,163.86 338.84,197.08 361.91,229.85 384.99,247.35 408.06,230.60\&#34; id=\&#34;27\&#34; clip-path=\&#34;url(#cl1_2)\&#34; fill=\&#34;none\&#34; stroke-width=\&#34;1.06698\&#34; stroke=\&#34;#000000\&#34; stroke-opacity=\&#34;1\&#34; stroke-linejoin=\&#34;round\&#34; stroke-linecap=\&#34;butt\&#34;/&gt;\n    &lt;line x1=\&#34;20.42\&#34; y1=\&#34;212.79\&#34; x2=\&#34;426.52\&#34; y2=\&#34;212.79\&#34; id=\&#34;28\&#34; clip-path=\&#34;url(#cl1_2)\&#34; stroke-width=\&#34;2.66745\&#34; stroke=\&#34;#C6011F\&#34; stroke-opacity=\&#34;0.5\&#34; stroke-dasharray=\&#34;12,12\&#34; stroke-linejoin=\&#34;round\&#34; stroke-linecap=\&#34;butt\&#34; fill=\&#34;#C6011F\&#34; fill-opacity=\&#34;0.5\&#34;/&gt;\n    &lt;defs&gt;\n      &lt;clipPath id=\&#34;cl1_3\&#34;&gt;\n        &lt;rect x=\&#34;0.00\&#34; y=\&#34;0.00\&#34; width=\&#34;432.00\&#34; height=\&#34;360.00\&#34;/&gt;\n      &lt;\/clipPath&gt;\n    &lt;\/defs&gt;\n    &lt;g clip-path=\&#34;url(#cl1_3)\&#34;&gt;\n      &lt;text x=\&#34;7.49\&#34; y=\&#34;325.09\&#34; id=\&#34;29\&#34; font-size=\&#34;6.60pt\&#34; fill=\&#34;#4D4D4D\&#34; fill-opacity=\&#34;1\&#34; font-family=\&#34;Arial\&#34;&gt;-5&lt;\/text&gt;\n    &lt;\/g&gt;\n    &lt;g clip-path=\&#34;url(#cl1_3)\&#34;&gt;\n      &lt;text x=\&#34;10.48\&#34; y=\&#34;250.27\&#34; id=\&#34;30\&#34; font-size=\&#34;6.60pt\&#34; fill=\&#34;#4D4D4D\&#34; fill-opacity=\&#34;1\&#34; font-family=\&#34;Arial\&#34;&gt;0&lt;\/text&gt;\n    &lt;\/g&gt;\n    &lt;g clip-path=\&#34;url(#cl1_3)\&#34;&gt;\n      &lt;text x=\&#34;10.48\&#34; y=\&#34;175.46\&#34; id=\&#34;31\&#34; font-size=\&#34;6.60pt\&#34; fill=\&#34;#4D4D4D\&#34; fill-opacity=\&#34;1\&#34; font-family=\&#34;Arial\&#34;&gt;5&lt;\/text&gt;\n    &lt;\/g&gt;\n    &lt;g clip-path=\&#34;url(#cl1_3)\&#34;&gt;\n      &lt;text x=\&#34;5.48\&#34; y=\&#34;100.64\&#34; id=\&#34;32\&#34; font-size=\&#34;6.60pt\&#34; fill=\&#34;#4D4D4D\&#34; fill-opacity=\&#34;1\&#34; font-family=\&#34;Arial\&#34;&gt;10&lt;\/text&gt;\n    &lt;\/g&gt;\n    &lt;g clip-path=\&#34;url(#cl1_3)\&#34;&gt;\n      &lt;text x=\&#34;98.09\&#34; y=\&#34;354.52\&#34; id=\&#34;33\&#34; font-size=\&#34;6.60pt\&#34; fill=\&#34;#4D4D4D\&#34; fill-opacity=\&#34;1\&#34; font-family=\&#34;Arial\&#34;&gt;1970&lt;\/text&gt;\n    &lt;\/g&gt;\n    &lt;g clip-path=\&#34;url(#cl1_3)\&#34;&gt;\n      &lt;text x=\&#34;213.46\&#34; y=\&#34;354.52\&#34; id=\&#34;34\&#34; font-size=\&#34;6.60pt\&#34; fill=\&#34;#4D4D4D\&#34; fill-opacity=\&#34;1\&#34; font-family=\&#34;Arial\&#34;&gt;1975&lt;\/text&gt;\n    &lt;\/g&gt;\n    &lt;g clip-path=\&#34;url(#cl1_3)\&#34;&gt;\n      &lt;text x=\&#34;328.83\&#34; y=\&#34;354.52\&#34; id=\&#34;35\&#34; font-size=\&#34;6.60pt\&#34; fill=\&#34;#4D4D4D\&#34; fill-opacity=\&#34;1\&#34; font-family=\&#34;Arial\&#34;&gt;1980&lt;\/text&gt;\n    &lt;\/g&gt;\n    &lt;polygon points=\&#34;79.66,39.36 83.73,46.41 75.59,46.41\&#34; id=\&#34;36\&#34; clip-path=\&#34;url(#cl1_3)\&#34; fill=\&#34;#F8766D\&#34; fill-opacity=\&#34;1\&#34; stroke=\&#34;none\&#34;/&gt;\n    &lt;g clip-path=\&#34;url(#cl1_3)\&#34;&gt;\n      &lt;text x=\&#34;90.46\&#34; y=\&#34;47.28\&#34; id=\&#34;37\&#34; font-size=\&#34;6.60pt\&#34; font-family=\&#34;Arial\&#34;&gt;WAR4&lt;\/text&gt;\n    &lt;\/g&gt;\n    &lt;line x1=\&#34;145.19\&#34; y1=\&#34;44.06\&#34; x2=\&#34;159.01\&#34; y2=\&#34;44.06\&#34; id=\&#34;38\&#34; clip-path=\&#34;url(#cl1_3)\&#34; stroke-width=\&#34;2.66745\&#34; stroke=\&#34;#C6011F\&#34; stroke-opacity=\&#34;1\&#34; stroke-dasharray=\&#34;12,12\&#34; stroke-linejoin=\&#34;round\&#34; stroke-linecap=\&#34;butt\&#34; fill=\&#34;#FFFFFF\&#34; fill-opacity=\&#34;1\&#34;/&gt;\n    &lt;g clip-path=\&#34;url(#cl1_3)\&#34;&gt;\n      &lt;text x=\&#34;162.90\&#34; y=\&#34;47.28\&#34; id=\&#34;39\&#34; font-size=\&#34;6.60pt\&#34; font-family=\&#34;Arial\&#34;&gt;Typical HOFer&lt;\/text&gt;\n    &lt;\/g&gt;\n    &lt;g clip-path=\&#34;url(#cl1_3)\&#34;&gt;\n      &lt;text x=\&#34;20.42\&#34; y=\&#34;19.79\&#34; id=\&#34;40\&#34; font-size=\&#34;15.00pt\&#34; font-family=\&#34;Arial\&#34;&gt;WAR&lt;\/text&gt;\n    &lt;\/g&gt;\n  &lt;\/g&gt;\n&lt;\/svg&gt;\n&#34;,&#34;css&#34;:&#34;.tooltip_svg_1 {position:absolute;pointer-events:none;z-index:999;padding:5px;background:black;color:white;border-radius:2px 2px 2px 2px;}\n.cl_data_id_svg_1:{}.cl_data_id_svg_1:hover{fill:orange;stroke:gray;}\n.clicked_svg_1{fill:orange;stroke:gray;}&#34;,&#34;ui_html&#34;:&#34;&lt;div class=&#39;ggiraph-toolbar&#39;&gt;&lt;div class=&#39;ggiraph-toolbar-block shinyonly&#39;&gt;&lt;a class=&#39;ggiraph-toolbar-icon neutral&#39; title=&#39;lasso selection&#39; href=&#39;javascript:lasso_on(\&#34;svg_1\&#34;, true, \&#34;array_selected_svg_1\&#34;, \&#34;clicked_svg_1\&#34;);&#39;&gt;&lt;svg width=&#39;15pt&#39; height=&#39;15pt&#39; viewBox=&#39;0 0 230 230&#39;&gt;&lt;g&gt;&lt;ellipse ry=&#39;65.5&#39; rx=&#39;86.5&#39; cy=&#39;94&#39; cx=&#39;115.5&#39; stroke-width=&#39;20&#39; fill=&#39;transparent&#39;/&gt;&lt;ellipse ry=&#39;11.500001&#39; rx=&#39;10.5&#39; cy=&#39;153&#39; cx=&#39;91.5&#39; stroke-width=&#39;20&#39; fill=&#39;transparent&#39;/&gt;&lt;line y2=&#39;210.5&#39; x2=&#39;105&#39; y1=&#39;164.5&#39; x1=&#39;96&#39; stroke-width=&#39;20&#39;/&gt;&lt;\/g&gt;&lt;\/svg&gt;&lt;\/a&gt;&lt;a class=&#39;ggiraph-toolbar-icon drop&#39; title=&#39;lasso anti-selection&#39; href=&#39;javascript:lasso_on(\&#34;svg_1\&#34;, false, \&#34;array_selected_svg_1\&#34;, \&#34;clicked_svg_1\&#34;);&#39;&gt;&lt;svg width=&#39;15pt&#39; height=&#39;15pt&#39; viewBox=&#39;0 0 230 230&#39;&gt;&lt;g&gt;&lt;ellipse ry=&#39;65.5&#39; rx=&#39;86.5&#39; cy=&#39;94&#39; cx=&#39;115.5&#39; stroke-width=&#39;20&#39; fill=&#39;transparent&#39;/&gt;&lt;ellipse ry=&#39;11.500001&#39; rx=&#39;10.5&#39; cy=&#39;153&#39; cx=&#39;91.5&#39; stroke-width=&#39;20&#39; fill=&#39;transparent&#39;/&gt;&lt;line y2=&#39;210.5&#39; x2=&#39;105&#39; y1=&#39;164.5&#39; x1=&#39;96&#39; stroke-width=&#39;20&#39;/&gt;&lt;\/g&gt;&lt;\/svg&gt;&lt;\/a&gt;&lt;\/div&gt;&lt;\/div&gt;&#34;,&#34;uid&#34;:&#34;svg_1&#34;,&#34;width&#34;:&#34;75%&#34;,&#34;funname&#34;:&#34;init_prop_svg_1&#34;,&#34;sel_array_name&#34;:&#34;array_selected_svg_1&#34;,&#34;selected_class&#34;:&#34;clicked_svg_1&#34;,&#34;tooltip_opacity&#34;:0.9,&#34;tooltip_offx&#34;:10,&#34;tooltip_offy&#34;:0,&#34;zoom_max&#34;:1,&#34;selection_type&#34;:&#34;multiple&#34;},&#34;evals&#34;:[],&#34;jsHooks&#34;:[]}&lt;/script&gt;
&lt;/div&gt;
&lt;div id=&#34;dashboard&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Dashboard&lt;/h2&gt;
&lt;p&gt;I won’t be going over the &lt;code&gt;shinydashboard&lt;/code&gt; code in detail but you can check it out on your own, &lt;a href=&#34;https://github.com/nllspc/Sports-Analysis/tree/master/RedsJAWS/post/shiny&#34;&gt;here&lt;/a&gt;. There is some html I’d like to highlight though.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;dashBody &amp;lt;- dashboardBody(
      # Changes color of header
      tags$head(
            tags$style(HTML(&amp;#39;
                            /* Changes color of title portion of header */
                            .skin-blue .main-header .logo {
                            background-color: #C6011F;
                            }
                            
                            .skin-blue .main-header .logo:hover {
                            background-color: #C6011F;
                            }
                            
                            /* Changes color of rest of header */
                            .skin-blue .main-header .navbar {
                            background-color: #C6011F;
                            }
                            
                            /* Changes color of sidebar toggle when hovered */
                            .skin-blue .main-header .navbar .sidebar-toggle:hover{
                            background-color: #000000;
                            }
                            
                            &amp;#39;))
            
            ),&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Placing this HTML at the beginning of &lt;code&gt;dashboardBody&lt;/code&gt; will allow you to choose the colors of various elements of the header.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# Stops errors being displayed in plot windows
      tags$style(type=&amp;quot;text/css&amp;quot;,
                 &amp;quot;.shiny-output-error { visibility: hidden; }&amp;quot;,
                 &amp;quot;.shiny-output-error:before { visibility: hidden; }&amp;quot;
      ),&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Normally, while the dashboard is waiting for user input, an error will be displayed in place of the charts. Placing this code at the beginning of &lt;code&gt;dashboardBody&lt;/code&gt; will hide this error. The code should be on the same level as &lt;code&gt;tags$head(&lt;/code&gt; above.&lt;/p&gt;
&lt;p&gt;Without further ado, here’s the final product…&lt;/p&gt;
&lt;p&gt;(Due to my website’s sidebar, the dashboard will probably appear stacked. Here’s a &lt;a href=&#34;https://erbo.shinyapps.io/jaws4-post/&#34;&gt;link&lt;/a&gt; to the dashboard that should display full-screen.)&lt;/p&gt;
&lt;script type=&#34;text/javascript&#34; src=&#34;https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/3.5.16/iframeResizer.min.js&#34;&gt;&lt;/script&gt;
&lt;style&gt;
      iframe {
            min-width: 100%;
      }
&lt;/style&gt;
&lt;iframe id=&#34;myIframe&#34; src=&#34;https://erbo.shinyapps.io/jaws4-post/&#34; scrolling=&#34;no&#34; frameborder=&#34;no&#34;&gt;
&lt;/iframe&gt;
&lt;script&gt;
      iFrameResize({
            heightCalculationMethod: &#39;taggedElement&#39;
      });
&lt;/script&gt;
&lt;/div&gt;
&lt;div id=&#34;conclusion&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This dashboard is actually only one item in a more tricked-out version that I finished a few weeks ago. If you’d like to take this even further, you can see my other dashboard &lt;a href=&#34;https://erbo.shinyapps.io/jaws4/&#34;&gt;here&lt;/a&gt; and the code is on my &lt;a href=&#34;https://github.com/nllspc/Sports-Analysis/tree/master/RedsJAWS/&#34;&gt;github&lt;/a&gt;. The processing scripts are a little rough, so be prepared. It was a learning experience 😊. Hopefully, I’ll get around to making them more presentable this fall.&lt;/p&gt;
&lt;p&gt;This project combined two loves of mine: baseball and r – which I really enjoyed. There wasn’t any complex modeling but it really challenged me in ways I didn’t think it would. The visual design decisions were more difficult than I anticipated, and it also allowed me to level-up in &lt;code&gt;purrr&lt;/code&gt; and &lt;code&gt;tidyeval&lt;/code&gt;. Hope you enjoyed it.&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&#34;references&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;p&gt;
&lt;a id=&#39;bib-Boettiger_2017&#39;&gt;&lt;/a&gt;&lt;a href=&#34;#cite-Boettiger_2017&#34;&gt;[1]&lt;/a&gt;&lt;cite&gt; C. Boettiger. &lt;em&gt;knitcitations: Citations for ‘Knitr’ Markdown Files&lt;/em&gt;. R package version 1.0.8. 2017. URL: &lt;a href=&#34;https://CRAN.R-project.org/package=knitcitations&#34;&gt;https://CRAN.R-project.org/package=knitcitations&lt;/a&gt;.&lt;/cite&gt;
&lt;/p&gt;
&lt;p&gt;
&lt;a id=&#39;bib-Chang_2017&#39;&gt;&lt;/a&gt;&lt;a href=&#34;#cite-Chang_2017&#34;&gt;[2]&lt;/a&gt;&lt;cite&gt; W. Chang and B. Borges Ribeiro. &lt;em&gt;shinydashboard: Create Dashboards with ‘Shiny’&lt;/em&gt;. R package version 0.6.1. 2017. URL: &lt;a href=&#34;https://CRAN.R-project.org/package=shinydashboard&#34;&gt;https://CRAN.R-project.org/package=shinydashboard&lt;/a&gt;.&lt;/cite&gt;
&lt;/p&gt;
&lt;p&gt;
&lt;a id=&#39;bib-Gohel_2017&#39;&gt;&lt;/a&gt;&lt;a href=&#34;#cite-Gohel_2017&#34;&gt;[3]&lt;/a&gt;&lt;cite&gt; D. Gohel. &lt;em&gt;ggiraph: Make ‘ggplot2’ Graphics Interactive&lt;/em&gt;. R package version 0.4.2. 2017. URL: &lt;a href=&#34;https://CRAN.R-project.org/package=ggiraph&#34;&gt;https://CRAN.R-project.org/package=ggiraph&lt;/a&gt;.&lt;/cite&gt;
&lt;/p&gt;
&lt;p&gt;
&lt;a id=&#39;bib-Karambelkar_2018&#39;&gt;&lt;/a&gt;&lt;a href=&#34;#cite-Karambelkar_2018&#34;&gt;[4]&lt;/a&gt;&lt;cite&gt; B. Karambelkar. &lt;em&gt;widgetframe: ‘Htmlwidgets’ in Responsive ‘iframes’&lt;/em&gt;. &lt;a href=&#34;https://github.com/bhaskarvk/widgetframe&#34; class=&#34;uri&#34;&gt;https://github.com/bhaskarvk/widgetframe&lt;/a&gt;, &lt;a href=&#34;https://bhaskarvk.github.io/widgetframe/&#34; class=&#34;uri&#34;&gt;https://bhaskarvk.github.io/widgetframe/&lt;/a&gt;. 2018.&lt;/cite&gt;
&lt;/p&gt;
&lt;p&gt;
&lt;a id=&#39;bib-Wickham_2017&#39;&gt;&lt;/a&gt;&lt;a href=&#34;#cite-Wickham_2017&#34;&gt;[5]&lt;/a&gt;&lt;cite&gt; H. Wickham. &lt;em&gt;tidyverse: Easily Install and Load the ‘Tidyverse’&lt;/em&gt;. R package version 1.2.1. 2017. URL: &lt;a href=&#34;https://CRAN.R-project.org/package=tidyverse&#34;&gt;https://CRAN.R-project.org/package=tidyverse&lt;/a&gt;.&lt;/cite&gt;
&lt;/p&gt;
&lt;p&gt;
&lt;a id=&#39;bib-Xie_2018&#39;&gt;&lt;/a&gt;&lt;a href=&#34;#cite-Xie_2018&#34;&gt;[6]&lt;/a&gt;&lt;cite&gt; Y. Xie. &lt;em&gt;DT: A Wrapper of the JavaScript Library ‘DataTables’&lt;/em&gt;. R package version 0.4. 2018. URL: &lt;a href=&#34;https://rstudio.github.io/DT&#34;&gt;https://rstudio.github.io/DT&lt;/a&gt;.&lt;/cite&gt;
&lt;/p&gt;
&lt;/div&gt;
</description>
        </item>
        
        <item>
          <title>A Baseball Dashboard in Time for Opening Weekend (part two)</title>
          <link>https://erbo.rbind.io/blog/2018-03-21-a-baseball-dashboard-in-time-for-opening-weekend-part-two/</link>
          <pubDate>Wed, 21 Mar 2018 00:00:00 +0000</pubDate>
          
          <guid>https://erbo.rbind.io/blog/2018-03-21-a-baseball-dashboard-in-time-for-opening-weekend-part-two/</guid>
          <description>&lt;div id=&#34;TOC&#34;&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#jaws-calculation&#34;&gt;JAWS Calculation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#weighting-positions-and-averages&#34;&gt;Weighting Positions and Averages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#create-dataframes-for-visuals&#34;&gt;Create Dataframes for Visuals&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#save-objects&#34;&gt;Save Objects&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#conclusion&#34;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#references&#34;&gt;References&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;

&lt;blockquote class=&#34;twitter-tweet&#34;&gt;&lt;p lang=&#34;en&#34; dir=&#34;ltr&#34;&gt;&lt;a href=&#34;https://twitter.com/ctrent?ref_src=twsrc%5Etfw&#34;&gt;@ctrent&lt;/a&gt; I’ve always thought that it’d be fun to debate the &lt;a href=&#34;https://twitter.com/Reds?ref_src=twsrc%5Etfw&#34;&gt;@Reds&lt;/a&gt; HOF like the real one. Set a high , but debatable, bar. Bring on the JAWS&lt;/p&gt;&amp;mdash; Craig Wales (@C_Dubs1) &lt;a href=&#34;https://twitter.com/C_Dubs1/status/900017684590579713?ref_src=twsrc%5Etfw&#34;&gt;August 22, 2017&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&#34;https://platform.twitter.com/widgets.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;

&lt;blockquote class=&#34;twitter-tweet&#34;&gt;&lt;p lang=&#34;en&#34; dir=&#34;ltr&#34;&gt;it would be interesting, I don&amp;#39;t have the math or computer skills to do so &lt;a href=&#34;https://t.co/p5n9O5NjGT&#34;&gt;https://t.co/p5n9O5NjGT&lt;/a&gt;&lt;/p&gt;&amp;mdash; C. Trent Rosecrans (@ctrent) &lt;a href=&#34;https://twitter.com/ctrent/status/900033162771910656?ref_src=twsrc%5Etfw&#34;&gt;August 22, 2017&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&#34;https://platform.twitter.com/widgets.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;

&lt;p&gt;In &lt;a href=&#34;https://erbo.rbind.io/blog/2018-03-09-a-baseball-dashboard-in-time-for-opening-weekend-part-one/&#34;&gt;part one&lt;/a&gt;, we got the player WAR values, primary positions, and determined the tenure qualification. In part two, we’ll perform the JAWS calculations and create dataframes that will be needed for our dashboard. Recall that for these calculations, you’ll need &lt;code&gt;indWar&lt;/code&gt;, &lt;code&gt;nomWar&lt;/code&gt;, &lt;code&gt;posDat&lt;/code&gt;, and &lt;code&gt;warDat&lt;/code&gt; from part one.&lt;/p&gt;
&lt;div id=&#34;jaws-calculation&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;JAWS Calculation&lt;/h2&gt;
&lt;p&gt;To compute the JAWS values, we’ll take the average of the sum of the top four WAR values and the sum of the WAR accrued while playing for the Reds&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;library(tidyverse)

# total WAR during Reds tenure
warSum &amp;lt;- warDat %&amp;gt;%
      group_by(playerId) %&amp;gt;%
      summarize(WARtenure = sum(rWAR)) %&amp;gt;% 
      ungroup()

# Sum of top 4 WAR years
war4Dat &amp;lt;- warDat %&amp;gt;%
      group_by(playerId) %&amp;gt;%
      top_n(4, rWAR) %&amp;gt;%
      tally(rWAR) %&amp;gt;%
      rename(WAR4 = n)

# Calculating JAWS
warJaws &amp;lt;- warSum %&amp;gt;% 
      inner_join(war4Dat, by = &amp;#39;playerId&amp;#39;) %&amp;gt;% 
      mutate(JAWS4 = round((WARtenure + WAR4)/2, 2)) %&amp;gt;% 
      select(playerId, WARtenure, WAR4, JAWS4)

# Add Names and Positions to dataframe
names &amp;lt;- warDat %&amp;gt;% 
      select(playerId, Name) %&amp;gt;% 
      distinct()

warJaws &amp;lt;- warJaws %&amp;gt;%
      inner_join(posDat, by = &amp;#39;playerId&amp;#39;) %&amp;gt;% 
      inner_join(names, by = &amp;#39;playerId&amp;#39;) %&amp;gt;% 
      select(playerId, Name, POS, everything())


head(warJaws, 3)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## # A tibble: 3 x 6
##   playerId  Name         POS   WARtenure  WAR4 JAWS4
##   &amp;lt;chr&amp;gt;     &amp;lt;chr&amp;gt;        &amp;lt;chr&amp;gt;     &amp;lt;dbl&amp;gt; &amp;lt;dbl&amp;gt; &amp;lt;dbl&amp;gt;
## 1 becklja01 Jake Beckley 1B         23.5  16.5  20.0
## 2 bellgu01  Gus Bell     CF         13.0  12.4  12.7
## 3 benchjo01 Johnny Bench C          75.0  30.4  52.7&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div id=&#34;weighting-positions-and-averages&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Weighting Positions and Averages&lt;/h2&gt;
&lt;p&gt;The number of players at each non-pitcher position differs quite a bit and will skew our averages, so we’ll add “average” Hall of Fame players to each position pool to reduce the bias. Pitchers aren’t compared to positional players statistically so there will be two sets of average calculations. They also aren’t subdivided into Starting and Relief so there will be no need to add “average” players to their pool.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# Only want inductees in our average calculation
indJaws &amp;lt;- warJaws %&amp;gt;% 
      anti_join(nomWar, by = &amp;#39;playerId&amp;#39;)

batJaws &amp;lt;- indJaws %&amp;gt;%
      select(-playerId) %&amp;gt;% 
      filter(POS != &amp;quot;P&amp;quot;)

# 1B and CF are highest with 10 members a piece so they won&amp;#39;t need filler players
table(batJaws$POS)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## 
## 1B 2B 3B  C CF LF RF SS 
## 10  7  2  4 10  5  5  6&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First base and Center Field have the most players so we’ll add “average” players to the other position pools until the amounts are equal.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# Number of filler players needed at each position
neededPOS &amp;lt;- batJaws %&amp;gt;%
      group_by(POS) %&amp;gt;%
      summarize(n = n()) %&amp;gt;% 
      mutate(remPOS = max(n) - n) %&amp;gt;%
      filter(POS != &amp;quot;1B&amp;quot;, POS != &amp;quot;CF&amp;quot;) %&amp;gt;%
      select(-n)

# List of lists with filler position amounts
posLL &amp;lt;- map2(neededPOS$POS, neededPOS$remPOS, function(POS, n) {
      POS &amp;lt;- rep(POS, n)
})

# Create tibble with all the filler players for each position

# Empty tibble
posFillTib &amp;lt;- tibble(
      Name = character(),
      POS = character(),
      WARtenure = numeric(),
      WAR4 = numeric(),
      JAWS4 = numeric()
      
)

# input: Position; function creates one filler player with avgHOF stats
fillPOS &amp;lt;- function(POS) {
      posFillTib &amp;lt;- posFillTib %&amp;gt;%
            add_row(Name = &amp;quot;avgHOFplayer&amp;quot;,
                    POS = POS,
                    WARtenure = median(batJaws$WARtenure),
                    WAR4 = median(batJaws$WAR4),
                    JAWS4 = median(batJaws$JAWS4)
                    
            )
}
# List of lists fed to function; outputs tibble of filler players
fillerPlayers &amp;lt;- map_dfr(posLL, fillPOS)

# Creating weighted distribution of position players
wtBatDistr &amp;lt;- batJaws %&amp;gt;%
      bind_rows(fillerPlayers)

head(wtBatDistr, 3)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## # A tibble: 3 x 5
##   Name         POS   WARtenure  WAR4 JAWS4
##   &amp;lt;chr&amp;gt;        &amp;lt;chr&amp;gt;     &amp;lt;dbl&amp;gt; &amp;lt;dbl&amp;gt; &amp;lt;dbl&amp;gt;
## 1 Jake Beckley 1B         23.5  16.5  20.0
## 2 Gus Bell     CF         13.0  12.4  12.7
## 3 Johnny Bench C          75.0  30.4  52.7&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can now calculate the averages using some cool, nested &lt;code&gt;purrr::map&lt;/code&gt; action.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# Calculate weighted averages at each position
wbd_nested &amp;lt;- wtBatDistr %&amp;gt;% 
      group_by(POS) %&amp;gt;% 
      nest()

wt_avg_FUN &amp;lt;- function(df) {
      mutate(df, `Wt Avg WAR` = round(mean(WARtenure), 1),
             `Wt Avg WAR4` = round(mean(WAR4), 1),
             `Wt Avg JAWS4` = round(mean(JAWS4), 1))
}

wbd_avgs &amp;lt;- wbd_nested %&amp;gt;% 
      mutate(stats = map(data, wt_avg_FUN)) %&amp;gt;% 
      select(POS, stats) %&amp;gt;% 
      unnest() %&amp;gt;% 
      select(Name, POS, everything()) %&amp;gt;% 
      filter(Name != &amp;quot;avgHOFplayer&amp;quot;)

head(wbd_avgs, 3)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## # A tibble: 3 x 8
##   Name          POS   WARtenure  WAR4 JAWS4 `Wt Avg WAR` `Wt Avg WAR4`
##   &amp;lt;chr&amp;gt;         &amp;lt;chr&amp;gt;     &amp;lt;dbl&amp;gt; &amp;lt;dbl&amp;gt; &amp;lt;dbl&amp;gt;        &amp;lt;dbl&amp;gt;         &amp;lt;dbl&amp;gt;
## 1 Jake Beckley  1B        23.5  16.5  20.0          22.3          15.4
## 2 Sean Casey    1B        16.6  13.3  14.9          22.3          15.4
## 3 Gordy Coleman 1B         7.27  7.18  7.23         22.3          15.4
## # ... with 1 more variable: `Wt Avg JAWS4` &amp;lt;dbl&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div id=&#34;create-dataframes-for-visuals&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Create Dataframes for Visuals&lt;/h2&gt;
&lt;div id=&#34;datatable&#34; class=&#34;section level3&#34;&gt;
&lt;h3&gt;DataTable&lt;/h3&gt;
&lt;p&gt;Our first visual will be a &lt;code&gt;DT&lt;/code&gt; datatable with the WAR and JAWS calculations for each player. The positional JAWS and WAR averages we calculated above using only the inductees will be added to the nominee stat lines according to their primary position. Then pitcher averages are figured, and everything is combined into one dataframe.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# Get positional player nominees
nomBatJaws &amp;lt;- warJaws %&amp;gt;% 
      anti_join(indWar, by = &amp;#39;playerId&amp;#39;) %&amp;gt;% 
      filter(POS != &amp;quot;P&amp;quot;) %&amp;gt;% 
      select(-playerId)

# Sync averages to nominee positions and combine with inductee averages dataframe
wtBatJaws &amp;lt;- nomBatJaws %&amp;gt;% 
      mutate(`Wt Avg WAR` = plyr::mapvalues(POS, from = wbd_avgs$POS,
                                            to = wbd_avgs$`Wt Avg WAR`) %&amp;gt;% as.numeric(),
             `Wt Avg WAR4` = plyr::mapvalues(POS, from = wbd_avgs$POS,
                                             to = wbd_avgs$`Wt Avg WAR4`) %&amp;gt;% as.numeric(),
             `Wt Avg JAWS4` = plyr::mapvalues(POS, from = wbd_avgs$POS,
                                              to = wbd_avgs$`Wt Avg JAWS4`) %&amp;gt;% as.numeric()) %&amp;gt;% 
      bind_rows(wbd_avgs)


# Pitcher averages
pitJaws &amp;lt;- warJaws %&amp;gt;% 
      anti_join(nomWar, by = &amp;#39;playerId&amp;#39;) %&amp;gt;% 
      select(-playerId) %&amp;gt;% 
      filter(POS == &amp;quot;P&amp;quot;) %&amp;gt;%
      mutate(`Wt Avg WAR` = round(mean(WARtenure), 1),
             `Wt Avg WAR4` = round(mean(WAR4), 1),
             `Wt Avg JAWS4` = round(mean(JAWS4), 1))

# Get pitcher Nominees
nomPitJaws &amp;lt;- warJaws %&amp;gt;% 
      anti_join(indWar, by = &amp;#39;playerId&amp;#39;) %&amp;gt;% 
      filter(POS == &amp;quot;P&amp;quot;) %&amp;gt;% 
      select(-playerId)

# Sync (pitcher pool not actually weighted)
wtPitJaws &amp;lt;- nomPitJaws %&amp;gt;% 
      mutate(`Wt Avg WAR` = plyr::mapvalues(POS, from = pitJaws$POS,
                                            to = pitJaws$`Wt Avg WAR`) %&amp;gt;% as.numeric(),
             `Wt Avg WAR4` = plyr::mapvalues(POS, from = pitJaws$POS,
                                             to = pitJaws$`Wt Avg WAR4`) %&amp;gt;% as.numeric(),
             `Wt Avg JAWS4` = plyr::mapvalues(POS, from = pitJaws$POS, to = pitJaws$`Wt Avg JAWS4`) %&amp;gt;% as.numeric()) %&amp;gt;% 
      bind_rows(pitJaws)


display_table &amp;lt;- wtBatJaws %&amp;gt;% 
      bind_rows(wtPitJaws) %&amp;gt;% 
      arrange(Name)

head(display_table, 3)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## # A tibble: 3 x 8
##   Name         POS   WARtenure  WAR4 JAWS4 `Wt Avg WAR` `Wt Avg WAR4`
##   &amp;lt;chr&amp;gt;        &amp;lt;chr&amp;gt;     &amp;lt;dbl&amp;gt; &amp;lt;dbl&amp;gt; &amp;lt;dbl&amp;gt;        &amp;lt;dbl&amp;gt;         &amp;lt;dbl&amp;gt;
## 1 Aaron Boone  3B         11.6  10.0  10.8         20.4          14.9
## 2 Adam Dunn    LF         16.4  11.9  14.2         29.0          16.6
## 3 Barry Larkin SS         70.2  26.2  48.2         23.5          14.2
## # ... with 1 more variable: `Wt Avg JAWS4` &amp;lt;dbl&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div id=&#34;cleveland-dot-plots&#34; class=&#34;section level3&#34;&gt;
&lt;h3&gt;Cleveland Dot Plots&lt;/h3&gt;
&lt;p&gt;When comparing position players, position to position isn’t the only comparison that can be made. In some situations, it’s more fair to look at wider, positional group statistics. There are five groups that we’ll use: corner infielders, middle infielders, outfielders, corners, and middle. These groups along with the positional JAWS and WAR calculations will be visualized with Cleveland Dot Plots.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# Build df with group positions
cornerIF &amp;lt;- warJaws %&amp;gt;% 
      filter(POS == &amp;quot;1B&amp;quot; | POS == &amp;quot;3B&amp;quot;) %&amp;gt;%
      mutate(POS = plyr::mapvalues(POS, from = c(&amp;quot;1B&amp;quot;, &amp;quot;3B&amp;quot;),
                                   to = c(&amp;quot;CI&amp;quot;, &amp;quot;CI&amp;quot;)))

middleIF &amp;lt;- warJaws %&amp;gt;% 
      filter(POS == &amp;quot;2B&amp;quot; | POS == &amp;quot;SS&amp;quot;) %&amp;gt;%
      mutate(POS = plyr::mapvalues(POS, from = c(&amp;quot;2B&amp;quot;, &amp;quot;SS&amp;quot;),
                                   to = c(&amp;quot;MI&amp;quot;, &amp;quot;MI&amp;quot;)))

outField &amp;lt;- warJaws %&amp;gt;% 
      filter(POS == &amp;quot;LF&amp;quot; | POS == &amp;quot;CF&amp;quot; | POS == &amp;quot;RF&amp;quot;) %&amp;gt;%
      mutate(POS = plyr::mapvalues(POS, from = c(&amp;quot;LF&amp;quot;, &amp;quot;CF&amp;quot;, &amp;quot;RF&amp;quot;),
                                   to = c(&amp;quot;OF&amp;quot;, &amp;quot;OF&amp;quot;, &amp;quot;OF&amp;quot;)))

corners &amp;lt;- warJaws %&amp;gt;% 
      filter(POS == &amp;quot;1B&amp;quot; | POS == &amp;quot;3B&amp;quot; | POS == &amp;quot;LF&amp;quot; | POS == &amp;quot;RF&amp;quot;) %&amp;gt;% 
      mutate(POS = plyr::mapvalues(POS, from = c(&amp;quot;1B&amp;quot;, &amp;quot;LF&amp;quot;, &amp;quot;RF&amp;quot;, &amp;quot;3B&amp;quot;),
                                   to = c(&amp;quot;CO&amp;quot;, &amp;quot;CO&amp;quot;, &amp;quot;CO&amp;quot;, &amp;quot;CO&amp;quot;)))

middle &amp;lt;- warJaws %&amp;gt;% 
      filter(POS == &amp;quot;2B&amp;quot; | POS == &amp;quot;SS&amp;quot; | POS == &amp;quot;C&amp;quot; | POS == &amp;quot;CF&amp;quot;) %&amp;gt;% 
      mutate(POS = plyr::mapvalues(POS, from = c(&amp;quot;2B&amp;quot;, &amp;quot;SS&amp;quot;, &amp;quot;C&amp;quot;, &amp;quot;CF&amp;quot;),
                                   to = c(&amp;quot;Md&amp;quot;, &amp;quot;Md&amp;quot;, &amp;quot;Md&amp;quot;, &amp;quot;Md&amp;quot;)))

other_groups &amp;lt;- cornerIF %&amp;gt;% 
      bind_rows(middleIF, outField, corners, middle)


# Calculate averages of each group

other_groups_i &amp;lt;- other_groups %&amp;gt;% 
      anti_join(nomWar, by = &amp;#39;playerId&amp;#39;)

og_nested &amp;lt;- other_groups_i %&amp;gt;% 
      group_by(POS) %&amp;gt;% 
      nest()

avg_FUN &amp;lt;- function(df) {
      mutate(df, WAR_avg = round(mean(WARtenure), 1),
             WAR4_avg = round(mean(WAR4), 1),
             JAWS_avg = round(mean(JAWS4), 1))
}

group_avgs_i &amp;lt;- og_nested %&amp;gt;% 
      mutate(stats = map(data, avg_FUN)) %&amp;gt;% 
      select(POS, stats) %&amp;gt;% 
      unnest() %&amp;gt;% 
      select(playerId, Name, POS, everything())

# Add Nominees

other_groups_n &amp;lt;- other_groups %&amp;gt;% 
      anti_join(indWar, by = &amp;#39;playerId&amp;#39;)

group_avgs &amp;lt;- other_groups_n %&amp;gt;% 
      mutate(WAR_avg = plyr::mapvalues(POS, from = group_avgs_i$POS,
                                       to = group_avgs_i$WAR_avg) %&amp;gt;% as.numeric(),
             WAR4_avg = plyr::mapvalues(POS, from = group_avgs_i$POS,
                                        to = group_avgs_i$WAR4_avg) %&amp;gt;% as.numeric(),
             JAWS_avg = plyr::mapvalues(POS, from = group_avgs_i$POS,
                                        to = group_avgs_i$JAWS_avg) %&amp;gt;% as.numeric()) %&amp;gt;% 
      bind_rows(group_avgs_i)

# Prepare dataframe for JAWS dot chart
dot_table &amp;lt;- display_table %&amp;gt;% 
      rename(JAWS_avg = `Wt Avg JAWS4`, WAR_avg = `Wt Avg WAR`) %&amp;gt;% 
      bind_rows(group_avgs)

jaws_group &amp;lt;- dot_table %&amp;gt;% 
      select(Name, POS, JAWS4, JAWS_avg) %&amp;gt;% 
      rename(Group = POS, `Avg HOF` = JAWS_avg) %&amp;gt;% 
      gather(key = &amp;quot;Stat&amp;quot;, value = &amp;quot;Value&amp;quot;, -c(Name, Group))

# Prepare dataframe for WAR dot chart
war_group &amp;lt;- dot_table %&amp;gt;% 
      select(Name, POS, WARtenure, WAR_avg) %&amp;gt;% 
      rename(Group = POS, `Avg HOF` = WAR_avg, WAR = WARtenure) %&amp;gt;% 
      gather(key = &amp;quot;Stat&amp;quot;, value = &amp;quot;Value&amp;quot;, -c(Name, Group))

glimpse(war_group)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## Observations: 368
## Variables: 4
## $ Name  &amp;lt;chr&amp;gt; &amp;quot;Aaron Boone&amp;quot;, &amp;quot;Adam Dunn&amp;quot;, &amp;quot;Barry Larkin&amp;quot;, &amp;quot;Bid McPhee&amp;quot;...
## $ Group &amp;lt;chr&amp;gt; &amp;quot;3B&amp;quot;, &amp;quot;LF&amp;quot;, &amp;quot;SS&amp;quot;, &amp;quot;2B&amp;quot;, &amp;quot;SS&amp;quot;, &amp;quot;P&amp;quot;, &amp;quot;P&amp;quot;, &amp;quot;P&amp;quot;, &amp;quot;C&amp;quot;, &amp;quot;P&amp;quot;, &amp;quot;...
## $ Stat  &amp;lt;chr&amp;gt; &amp;quot;WAR&amp;quot;, &amp;quot;WAR&amp;quot;, &amp;quot;WAR&amp;quot;, &amp;quot;WAR&amp;quot;, &amp;quot;WAR&amp;quot;, &amp;quot;WAR&amp;quot;, &amp;quot;WAR&amp;quot;, &amp;quot;WAR&amp;quot;, ...
## $ Value &amp;lt;dbl&amp;gt; 11.61, 16.44, 70.17, 52.39, 13.55, 29.07, 22.26, 7.43, 1...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div id=&#34;interactive-line-chart&#34; class=&#34;section level3&#34;&gt;
&lt;h3&gt;Interactive Line Chart&lt;/h3&gt;
&lt;p&gt;The final visual will be a line graph of player WAR values for each season played with the Reds. We’ll add some emphasis to the four largest WAR values and a horizontal line to indicate a typical Hall of Famer.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# WAR4 + years; add type column
war4Dat &amp;lt;- warDat %&amp;gt;%
      group_by(playerId) %&amp;gt;%
      top_n(4, rWAR) %&amp;gt;% 
      ungroup() %&amp;gt;% 
      select(-teamId) %&amp;gt;% 
      add_column(type = rep(&amp;quot;WAR4&amp;quot;, 328))

# Not WAR4 + years; add type column
notWar4 &amp;lt;- warDat %&amp;gt;% 
      anti_join(war4Dat, by = c(&amp;quot;playerId&amp;quot;, &amp;quot;yearId&amp;quot;)) %&amp;gt;% 
      select(-teamId) %&amp;gt;% 
      add_column(type = rep(&amp;quot;WAR&amp;quot;, 427))

war_combined &amp;lt;- notWar4 %&amp;gt;% 
      bind_rows(war4Dat)


#  Positional and Pitcher seasonal average WAR values

pitMedWar &amp;lt;- war_combined %&amp;gt;% 
      filter(POS == &amp;quot;P&amp;quot;) %&amp;gt;% 
      summarize(`Median Pitcher WAR` = median(rWAR))

posMedWAR &amp;lt;- war_combined %&amp;gt;% 
      filter(POS != &amp;quot;P&amp;quot;) %&amp;gt;% 
      summarize(`Median Position WAR` = median(rWAR))

war_combo_avg &amp;lt;- war_combined %&amp;gt;% 
      mutate(`Median WAR` = if_else(POS == &amp;quot;P&amp;quot;,
                                    pitMedWar$`Median Pitcher WAR`[[1]],
                                    posMedWAR$`Median Position WAR`[[1]])) %&amp;gt;% 
      rename(bbref_id = playerId, WAR = rWAR) %&amp;gt;% 
      select(bbref_id, Name, everything())&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&#34;save-objects&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Save Objects&lt;/h2&gt;
&lt;p&gt;Only four objects will be required for the final post in this series: &lt;code&gt;display_table&lt;/code&gt;, &lt;code&gt;jaws_group&lt;/code&gt;, &lt;code&gt;war_group&lt;/code&gt;, and &lt;code&gt;war_combo_avg&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&#34;conclusion&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this post, we calculated the JAWS statistic and positional averages that can be used to evaluate nominees and compare members of the Reds Hall of Fame. We also created positional group averages which could come in handy in certain situations such as with players that were more versatile and played multiple positions throughout their career. Lastly, we produced the data sets that will be used in our visuals for &lt;a href=&#34;https://erbo.rbind.io/blog/2018-03-23-a-baseball-dashboard-in-time-for-opening-weekend-part-three/&#34;&gt;part three&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&#34;references&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;p&gt;
&lt;a id=&#39;bib-Boettiger_2017&#39;&gt;&lt;/a&gt;&lt;a href=&#34;#cite-Boettiger_2017&#34;&gt;[1]&lt;/a&gt;&lt;cite&gt; C. Boettiger. &lt;em&gt;knitcitations: Citations for ‘Knitr’ Markdown Files&lt;/em&gt;. R package version 1.0.8. 2017. URL: &lt;a href=&#34;https://CRAN.R-project.org/package=knitcitations&#34;&gt;https://CRAN.R-project.org/package=knitcitations&lt;/a&gt;.&lt;/cite&gt;
&lt;/p&gt;
&lt;p&gt;
&lt;a id=&#39;bib-Wickham_2011&#39;&gt;&lt;/a&gt;&lt;a href=&#34;#cite-Wickham_2011&#34;&gt;[2]&lt;/a&gt;&lt;cite&gt; H. Wickham. “The Split-Apply-Combine Strategy for Data Analysis”. In: &lt;em&gt;Journal of Statistical Software&lt;/em&gt; 40.1 (2011), pp. 1–29. URL: &lt;a href=&#34;http://www.jstatsoft.org/v40/i01/&#34;&gt;http://www.jstatsoft.org/v40/i01/&lt;/a&gt;.&lt;/cite&gt;
&lt;/p&gt;
&lt;p&gt;
&lt;a id=&#39;bib-Wickham_2017&#39;&gt;&lt;/a&gt;&lt;a href=&#34;#cite-Wickham_2017&#34;&gt;[3]&lt;/a&gt;&lt;cite&gt; H. Wickham. &lt;em&gt;tidyverse: Easily Install and Load the ‘Tidyverse’&lt;/em&gt;. R package version 1.2.1. 2017. URL: &lt;a href=&#34;https://CRAN.R-project.org/package=tidyverse&#34;&gt;https://CRAN.R-project.org/package=tidyverse&lt;/a&gt;.&lt;/cite&gt;
&lt;/p&gt;
&lt;/div&gt;
</description>
        </item>
        
        <item>
          <title>A Baseball Dashboard in Time for Opening Weekend (part one)</title>
          <link>https://erbo.rbind.io/blog/2018-03-09-a-baseball-dashboard-in-time-for-opening-weekend-part-one/</link>
          <pubDate>Fri, 09 Mar 2018 00:00:00 +0000</pubDate>
          
          <guid>https://erbo.rbind.io/blog/2018-03-09-a-baseball-dashboard-in-time-for-opening-weekend-part-one/</guid>
          <description>&lt;div id=&#34;TOC&#34;&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#jaws&#34;&gt;JAWS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#scrape-hall-of-fame-inductee-names&#34;&gt;Scrape Hall of Fame Inductee Names&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#war-values&#34;&gt;WAR Values&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#eda&#34;&gt;EDA&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#save-objects&#34;&gt;Save Objects&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#conclusion&#34;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#references&#34;&gt;References&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;

&lt;blockquote class=&#34;twitter-tweet&#34;&gt;&lt;p lang=&#34;en&#34; dir=&#34;ltr&#34;&gt;&lt;a href=&#34;https://twitter.com/ctrent?ref_src=twsrc%5Etfw&#34;&gt;@ctrent&lt;/a&gt; I’ve always thought that it’d be fun to debate the &lt;a href=&#34;https://twitter.com/Reds?ref_src=twsrc%5Etfw&#34;&gt;@Reds&lt;/a&gt; HOF like the real one. Set a high , but debatable, bar. Bring on the JAWS&lt;/p&gt;&amp;mdash; Craig Wales (@C_Dubs1) &lt;a href=&#34;https://twitter.com/C_Dubs1/status/900017684590579713?ref_src=twsrc%5Etfw&#34;&gt;August 22, 2017&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&#34;https://platform.twitter.com/widgets.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;

&lt;blockquote class=&#34;twitter-tweet&#34;&gt;&lt;p lang=&#34;en&#34; dir=&#34;ltr&#34;&gt;it would be interesting, I don&amp;#39;t have the math or computer skills to do so &lt;a href=&#34;https://t.co/p5n9O5NjGT&#34;&gt;https://t.co/p5n9O5NjGT&lt;/a&gt;&lt;/p&gt;&amp;mdash; C. Trent Rosecrans (@ctrent) &lt;a href=&#34;https://twitter.com/ctrent/status/900033162771910656?ref_src=twsrc%5Etfw&#34;&gt;August 22, 2017&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&#34;https://platform.twitter.com/widgets.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;

&lt;p&gt;Baseball fan? ☑️ Math? ☑️ Computers skills? ☑️&lt;/p&gt;
&lt;p&gt;It’s always nice when project ideas fall into your lap. Let’s build an dashboard that can be used to evaluate nominees for a franchise’s hall of fame.&lt;/p&gt;
&lt;div id=&#34;jaws&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;JAWS&lt;/h2&gt;
&lt;p&gt;For the uninitiated, the first thing you need to understand is &lt;em&gt;WAR&lt;/em&gt;. &lt;a href=&#34;https://www.fangraphs.com/library/misc/war/&#34;&gt;WAR&lt;/a&gt; attempts to encapsulate a player’s yearly contribution into one statistic. &lt;em&gt;JAWS&lt;/em&gt; is calculated by taking the average of a player’s total WAR over their career and the sum of their seven highest WAR values. The JAWS statistic is meant to be a starting point in the discussion of a nominee’s creditials for the Hall of Fame. Jaffe provides a thorough explanation &lt;a href=&#34;https://www.si.com/mlb/2017/11/27/hall-fame-jaws-intro-2018-ballot&#34;&gt;here&lt;/a&gt;. There’s also a more succinct description at &lt;a href=&#34;https://www.baseball-reference.com/about/jaws.shtml&#34;&gt;Baseball-Reference&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The question is can JAWS, whose domain of applicability is the MLB Hall of Fame, be applied to a franchise’s Hall of Fame. Instead of career WAR, we’ll be using only the WAR the player accrued while he played for the Cincinnati Reds. The likely sticking point is the seven year qualification since free agency makes it less likely that players remain with a team for that length of duration. The number of inductees in a franchise hall of fame probably is considerably less than the MLB hall of fame, so the trick will be to chose a tenure long enough to qualify a high percentage of the inductees and still allow JAWS to provide an adequate measure.&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&#34;scrape-hall-of-fame-inductee-names&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Scrape Hall of Fame Inductee Names&lt;/h2&gt;
&lt;p&gt;First, we need to get the names of the members of the Reds Hall of Fame. There are a few places with this information including the Reds Hall of Fame website, Baseball-Reference, and Wikipedia. None had the information available to download or presented it in a tidy format. Wikipedia is a source that has wide-ranging utility. This is a good opportunity to become familiar with scraping the website.&lt;/p&gt;
&lt;div class=&#34;figure&#34;&gt;
&lt;img src=&#34;https://erbo.rbind.io/charts/copy_xpath.gif&#34; /&gt;

&lt;/div&gt;
&lt;p&gt;Honestly, I expected this part to be more of a hassle, but &lt;code&gt;rvest&lt;/code&gt; made the process quite painless. Use Google Chrome’s inspect feature to obtain the xpath by scrolling down to the table with the member names, right-clicking on the page, and clicking inspect. In the left panel under the Elements tab, you’ll see HTML code. Hovering over each line will shade an object on the page. Once we find the line of code that corresponds to the table we want, we right-click that line of code, choose copy, and copy xpath.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;library(tidyverse)
library(rvest)

url &amp;lt;- &amp;quot;https://en.wikipedia.org/wiki/Cincinnati_Reds_Hall_of_Fame_and_Museum#Cincinnati_Reds_Hall_of_Fame_members&amp;quot;
members &amp;lt;- url %&amp;gt;%
      read_html() %&amp;gt;%
      html_nodes(xpath=&amp;#39;//*[@id=&amp;quot;mw-content-text&amp;quot;]/div/table[2]&amp;#39;) %&amp;gt;%
      html_table()
members &amp;lt;- members[[1]]&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;glimpse(members)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## Observations: 86
## Variables: 5
## $ Year     &amp;lt;chr&amp;gt; &amp;quot;1958&amp;quot;, &amp;quot;1958&amp;quot;, &amp;quot;1958&amp;quot;, &amp;quot;1958&amp;quot;, &amp;quot;1958&amp;quot;, &amp;quot;1959&amp;quot;, &amp;quot;1959...
## $ No.      &amp;lt;chr&amp;gt; &amp;quot;30&amp;quot;, &amp;quot;4&amp;quot;, &amp;quot;10&amp;quot;, &amp;quot;33&amp;quot;, &amp;quot;31&amp;quot;, &amp;quot;24&amp;quot;, &amp;quot;18&amp;quot;, &amp;quot;44, 47&amp;quot;, &amp;quot;—...
## $ Inductee &amp;lt;chr&amp;gt; &amp;quot;Paul Derringer&amp;quot;, &amp;quot;Ernie Lombardi&amp;quot;, &amp;quot;Frank McCormick&amp;quot;...
## $ Position &amp;lt;chr&amp;gt; &amp;quot;P&amp;quot;, &amp;quot;C&amp;quot;, &amp;quot;1B&amp;quot;, &amp;quot;P&amp;quot;, &amp;quot;P/3B\nManager&amp;quot;, &amp;quot;RF&amp;quot;, &amp;quot;P&amp;quot;, &amp;quot;P&amp;quot;,...
## $ Tenure   &amp;lt;chr&amp;gt; &amp;quot;1933–1942&amp;quot;, &amp;quot;1932–1941&amp;quot;, &amp;quot;1934–1945&amp;quot;, &amp;quot;1937–1943\n19...&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Year is the year inducted into the Reds HOF.&lt;/li&gt;
&lt;li&gt;No. is the number on the back of player’s jersey.&lt;/li&gt;
&lt;li&gt;Inductee is the player’s name.&lt;/li&gt;
&lt;li&gt;Position is the positions played.&lt;/li&gt;
&lt;li&gt;Tenure is the number of years played as a Red and is formatted as a range&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For our calculation, we’ll need Inductee, Position, and Tenure. There are formatting problems with Position and Tenure, but that information can be obtained elsewhere.&lt;/p&gt;
&lt;p&gt;There are only six nominees in the 2018 class so we can just copy/paste their information from the &lt;a href=&#34;http://www.cincinnati.com/story/sports/mlb/reds/2017/08/21/candidates-announced-2018-reds-hall-fame-ballot/586540001/&#34;&gt;announcement&lt;/a&gt;: third baseman Aaron Boone (1997-2003), outfielder Adam Dunn (2001-2008), pitcher John Franco (1984-1989),pitcher Danny Graves (1997-2005), third baseman Scott Rolen (2009-2012) and outfielder Reggie Sanders (1991-1998).&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&#34;war-values&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;WAR Values&lt;/h2&gt;
&lt;p&gt;To get our WAR values, we’ll utilize two data sets from the &lt;code&gt;openWARData&lt;/code&gt; package: idTT (player IDs) and rWAR (Baseball-Reference WAR). Our member names are used to filter the idTT data to get IDs and the IDs to filter the rWAR data to get the WAR values.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;library(openWARData)

# Combining first and last names to match the member names we scraped
idTTa &amp;lt;- idTT %&amp;gt;%
      select(key_bbref, name_last, name_first) %&amp;gt;%
      mutate(name_whole = paste(name_first, name_last))

# Missing values come along for the ride so they need removed
indID &amp;lt;- map_dfr(members[,&amp;quot;Inductee&amp;quot;], function(x) {
      filter(idTTa, name_whole == x &amp;amp; key_bbref != &amp;quot;&amp;quot;)})&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Some players were excluded from &lt;code&gt;indID&lt;/code&gt; because in &lt;code&gt;members&lt;/code&gt;, they have accent marks in their names. Likewise excluded, a father/son duo who have the same names in &lt;code&gt;idTT&lt;/code&gt; but have Sr/Jr suffixes in &lt;code&gt;members&lt;/code&gt;. All of these players will be added to &lt;code&gt;indID&lt;/code&gt;. Also, administrative personnel were removed during the filtering process, since obviously, they have no WAR values.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;missNamList &amp;lt;- list(&amp;quot;Dolf Luque&amp;quot;, &amp;quot;Leo Cardenas&amp;quot;, &amp;quot;Tony Perez&amp;quot;, &amp;quot;Dave Concepcion&amp;quot;, &amp;quot;Ken Griffey&amp;quot;,
                    &amp;quot;Jose Rijo&amp;quot;, &amp;quot;Cesar Geronimo&amp;quot;, &amp;quot;Pedro Borbon&amp;quot;)
indID &amp;lt;- map_dfr(missNamList, function(x) {
      filter(idTTa, name_whole == x &amp;amp; key_bbref != &amp;quot;&amp;quot;)}) %&amp;gt;% 
      bind_rows(indID) %&amp;gt;% 
      mutate(name_whole = if_else(key_bbref == &amp;quot;griffke02&amp;quot;, &amp;quot;Ken Griffey Jr&amp;quot;, name_whole))&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Not many people have original names, including baseball players, so we need to remove the extra Pete Rose (Jr.), Joe Morgan, Mike McCormick, Pedro Borbon, and George Wright. Sparky Anderson and Fred Hutchinson were managers so they can be dropped as well.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;indID &amp;lt;- filter(indID, key_bbref != &amp;quot;rosepe02&amp;quot; &amp;amp; key_bbref != &amp;quot;morgajo01&amp;quot;
                    &amp;amp; key_bbref != &amp;quot;mccormi03&amp;quot; &amp;amp; key_bbref != &amp;quot;andersp01&amp;quot; 
                    &amp;amp; key_bbref != &amp;quot;wrighge03&amp;quot; &amp;amp; key_bbref != &amp;quot;hutchfr01&amp;quot;
                    &amp;amp; key_bbref != &amp;quot;borbope02&amp;quot;    
                )
glimpse(indID)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## Observations: 81
## Variables: 4
## $ key_bbref  &amp;lt;fct&amp;gt; luquedo01, cardele01, perezto01, conceda01, griffke...
## $ name_last  &amp;lt;fct&amp;gt; Luque, Cardenas, Perez, Concepcion, Griffey, Griffe...
## $ name_first &amp;lt;fct&amp;gt; Dolf, Leo, Tony, Dave, Ken, Ken, Jose, Cesar, Pedro...
## $ name_whole &amp;lt;chr&amp;gt; &amp;quot;Dolf Luque&amp;quot;, &amp;quot;Leo Cardenas&amp;quot;, &amp;quot;Tony Perez&amp;quot;, &amp;quot;Dave C...&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now for the nominees…&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;nomNamList &amp;lt;- list(&amp;quot;Aaron Boone&amp;quot;, &amp;quot;Adam Dunn&amp;quot;, &amp;quot;John Franco&amp;quot;, &amp;quot;Danny Graves&amp;quot;, &amp;quot;Scott Rolen&amp;quot;,
                   &amp;quot;Reggie Sanders&amp;quot;)
nomID &amp;lt;- map_dfr(nomNamList, function(x) {
      filter(idTTa, name_whole == x &amp;amp; key_bbref != &amp;quot;&amp;quot;)})

# Snagged an extra Reggie Sanders
nomID &amp;lt;- filter(nomID, key_bbref != &amp;quot;sandere01&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We have IDs for the inductees and nominees, so now we can get those Reds WAR values.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# Inductees
indWar &amp;lt;- map_dfr(as.character(indID$key_bbref), function(x) {
      filter(rWAR, playerId == x)}) %&amp;gt;%
      select(playerId, yearId, teamId, rWAR) %&amp;gt;%
      mutate_if(is.factor, as.character) %&amp;gt;% 
      filter(teamId == &amp;quot;CIN&amp;quot;)

# add Name column
indWar &amp;lt;- indID %&amp;gt;% 
      select(name_whole, key_bbref) %&amp;gt;% 
      rename(Name = name_whole, playerId = key_bbref) %&amp;gt;% 
      inner_join(indWar, by = &amp;#39;playerId&amp;#39;)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## Warning: Column `playerId` joining factor and character vector, coercing
## into character vector&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# Nominees
nomWar &amp;lt;- map_dfr(as.character(nomID$key_bbref), function(x) {
      filter(rWAR, playerId == x)}) %&amp;gt;%
      select(playerId, yearId, teamId, rWAR) %&amp;gt;%
      mutate_if(is.factor, as.character) %&amp;gt;% 
      filter(teamId == &amp;quot;CIN&amp;quot;)

# add Name column
nomWar &amp;lt;- nomID %&amp;gt;% 
      select(name_whole, key_bbref) %&amp;gt;% 
      rename(Name = name_whole, playerId = key_bbref) %&amp;gt;% 
      inner_join(nomWar, by = &amp;#39;playerId&amp;#39;)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## Warning: Column `playerId` joining factor and character vector, coercing
## into character vector&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;glimpse(indWar)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## Observations: 721
## Variables: 5
## $ Name     &amp;lt;chr&amp;gt; &amp;quot;Dolf Luque&amp;quot;, &amp;quot;Dolf Luque&amp;quot;, &amp;quot;Dolf Luque&amp;quot;, &amp;quot;Dolf Luque...
## $ playerId &amp;lt;chr&amp;gt; &amp;quot;luquedo01&amp;quot;, &amp;quot;luquedo01&amp;quot;, &amp;quot;luquedo01&amp;quot;, &amp;quot;luquedo01&amp;quot;, &amp;quot;...
## $ yearId   &amp;lt;int&amp;gt; 1918, 1919, 1920, 1921, 1922, 1923, 1924, 1925, 1926,...
## $ teamId   &amp;lt;chr&amp;gt; &amp;quot;CIN&amp;quot;, &amp;quot;CIN&amp;quot;, &amp;quot;CIN&amp;quot;, &amp;quot;CIN&amp;quot;, &amp;quot;CIN&amp;quot;, &amp;quot;CIN&amp;quot;, &amp;quot;CIN&amp;quot;, &amp;quot;CIN...
## $ rWAR     &amp;lt;dbl&amp;gt; -0.14, 1.14, 4.56, 4.96, 3.10, 10.77, 2.55, 6.56, 1.1...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div id=&#34;eda&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;EDA&lt;/h2&gt;
&lt;div id=&#34;tenure&#34; class=&#34;section level3&#34;&gt;
&lt;h3&gt;Tenure&lt;/h3&gt;
&lt;p&gt;We can next turn our attention to making a decision on our central quandary: how long should our tenure requirement be? If we look at tables of the different values, we can calculate the percentage of players that would remain at each cutoff.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# Inductees
indYrs &amp;lt;- indWar %&amp;gt;%
      group_by(playerId) %&amp;gt;% 
      summarize(tenure = n())
table(indYrs$tenure)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## 
##  2  3  4  5  6  7  8  9 10 11 12 13 15 16 17 18 19 
##  1  2  3  5  6  9 12  8 10  8  6  2  1  1  1  1  3&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# Nominees
nomYrs &amp;lt;- nomWar %&amp;gt;%
      group_by(playerId) %&amp;gt;% 
      summarize(tenure = n())
table(nomYrs$tenure)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## 
## 4 6 7 8 9 
## 1 1 1 2 1&lt;/code&gt;&lt;/pre&gt;
&lt;table class=&#34;table&#34; style=&#34;margin-left: auto; margin-right: auto;&#34;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&#34;text-align:left;&#34;&gt;
Cutoff
&lt;/th&gt;
&lt;th style=&#34;text-align:left;&#34;&gt;
% Remaining
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:left;text-align: center;&#34;&gt;
7 yrs
&lt;/td&gt;
&lt;td style=&#34;text-align:left;text-align: center;&#34;&gt;
67%
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:left;text-align: center;&#34;&gt;
6 yrs
&lt;/td&gt;
&lt;td style=&#34;text-align:left;text-align: center;&#34;&gt;
78%
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:left;text-align: center;&#34;&gt;
5 yrs
&lt;/td&gt;
&lt;td style=&#34;text-align:left;text-align: center;&#34;&gt;
86%
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:left;font-weight: bold;color: #e78c45;text-align: center;&#34;&gt;
4 yrs
&lt;/td&gt;
&lt;td style=&#34;text-align:left;font-weight: bold;color: #e78c45;text-align: center;&#34;&gt;
92%
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Looking at the nominees, it turns out Scott Rolen only played four seasons for the Reds. The goal of this project is to evaluate nominees, so four years would be the necessary cutoff in order for Rolen to be included. If Rolen wasn’t part of this class, I’d consider five years but not above five.&lt;/p&gt;
&lt;p&gt;So the inductees that didn’t make the cut were the following: Billy Werber, Bill McKechnie, and Wayne Granger. The Wright boys, George and Harry, also aren’t in there. They played with the Reds prior to 1871 and their WAR wasn’t available. Our final inductee pool has 76 players.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;indWar &amp;lt;- filter(indWar, playerId != &amp;quot;grangwa01&amp;quot; &amp;amp; playerId != &amp;quot;mckecbi01&amp;quot;
                     &amp;amp; playerId != &amp;quot;werbebi01&amp;quot; &amp;amp; playerId != &amp;quot;wrighge01&amp;quot;
                     &amp;amp; playerId != &amp;quot;wrighha01&amp;quot;)

warDat &amp;lt;- indWar %&amp;gt;% 
      bind_rows(nomWar)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div id=&#34;position&#34; class=&#34;section level3&#34;&gt;
&lt;h3&gt;Position&lt;/h3&gt;
&lt;p&gt;We need to figure out the primary positions for players as they tend to play multiple positions during their career. When comparing JAWS scores between inductees and nominees, it’s usually done by position. So, if we were to examine Scott Rolen’s case, we’d look at his JAWS score and compare it to other Reds third basemen such as Frank Robinson or Chris Sabo.&lt;/p&gt;
&lt;p&gt;Jaffe makes this determination by calculating the total WAR at each position and selecting the position with the greatest value. Currently, I can’t find a relatively convenient way to obtain the necessary data to make that calculation. We’ll make our determination by using the &lt;code&gt;Lahman&lt;/code&gt; package and its &lt;code&gt;Fielding&lt;/code&gt; data set to find the position which has the most games played as a Red.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;library(Lahman)

head(Fielding, 3)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;##    playerID yearID stint teamID lgID POS  G GS InnOuts PO  A  E DP PB WP
## 1 abercda01   1871     1    TRO   NA  SS  1 NA      NA  1  3  2  0 NA NA
## 2  addybo01   1871     1    RC1   NA  2B 22 NA      NA 67 72 42  5 NA NA
## 3  addybo01   1871     1    RC1   NA  SS  3 NA      NA  8 14  7  0 NA NA
##   SB CS ZR
## 1 NA NA NA
## 2 NA NA NA
## 3 NA NA NA&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# Fielding dataset has different designations for 1800&amp;#39;s Reds teams: CN1 and CN2.
posDat &amp;lt;- map2_dfr(warDat$playerId, warDat$yearId, function(x,y) {
      filter(Fielding, playerID == x &amp;amp; yearID == y)}) %&amp;gt;% 
      filter(teamID == &amp;quot;CIN&amp;quot; | teamID == &amp;quot;CN1&amp;quot; | teamID == &amp;quot;CN2&amp;quot;)

# Getting position with most games as a Red
posDat &amp;lt;- posDat %&amp;gt;%
      select(playerID, POS, G) %&amp;gt;% 
      group_by(playerID, POS) %&amp;gt;% 
      summarize(sumG = sum(G)) %&amp;gt;% 
      filter(sumG == max(sumG)) %&amp;gt;% 
      ungroup() %&amp;gt;% 
      select(playerID, POS)


# Jim O&amp;#39;Toole&amp;#39;s Baseball-Reference ID in the Fielding data set is incorrect but he was a pitcher his whole career. Adding him to the df.
setdiff(warDat$playerId, posDat$playerID)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## [1] &amp;quot;o&amp;#39;tooji01&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;posDat &amp;lt;- posDat %&amp;gt;% 
      add_row(playerID = &amp;quot;o&amp;#39;tooji01&amp;quot;, POS = &amp;quot;P&amp;quot;) %&amp;gt;% 
      rename(playerId = playerID)

glimpse(posDat)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## Observations: 82
## Variables: 2
## $ playerId &amp;lt;chr&amp;gt; &amp;quot;becklja01&amp;quot;, &amp;quot;bellgu01&amp;quot;, &amp;quot;benchjo01&amp;quot;, &amp;quot;billija01&amp;quot;, &amp;quot;b...
## $ POS      &amp;lt;chr&amp;gt; &amp;quot;1B&amp;quot;, &amp;quot;OF&amp;quot;, &amp;quot;C&amp;quot;, &amp;quot;P&amp;quot;, &amp;quot;P&amp;quot;, &amp;quot;3B&amp;quot;, &amp;quot;P&amp;quot;, &amp;quot;OF&amp;quot;, &amp;quot;P&amp;quot;, &amp;quot;C&amp;quot;,...&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The outfield position can be further divided into right field, center field, and left field using the &lt;code&gt;Appearances&lt;/code&gt; data set in &lt;code&gt;Lahman&lt;/code&gt;. It would be desirable to also split the pitching position into relief and starting but unfortunately the &lt;code&gt;Lahman&lt;/code&gt; package doesn’t afford us this capability explicitly. I think the information could be derived from the games started statistic and some others but that task will have be left for another time.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;head(Appearances, 3)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;##   yearID teamID lgID  playerID G_all GS G_batting G_defense G_p G_c G_1b
## 1   1871    TRO   NA abercda01     1 NA         1         1   0   0    0
## 2   1871    RC1   NA  addybo01    25 NA        25        25   0   0    0
## 3   1871    CL1   NA allisar01    29 NA        29        29   0   0    0
##   G_2b G_3b G_ss G_lf G_cf G_rf G_of G_dh G_ph G_pr
## 1    0    0    1    0    0    0    0   NA   NA   NA
## 2   22    0    3    0    0    0    0   NA   NA   NA
## 3    2    0    0    0   29    0   29   NA   NA   NA&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# Get outfielder IDs
ofDat &amp;lt;- posDat %&amp;gt;% 
      filter(POS == &amp;quot;OF&amp;quot;)

# Get yearId from warDat
ofYears &amp;lt;- map_dfr(ofDat$playerId, function(x) {
      filter(warDat, playerId == x)
})
      
# Number of games played at each OF position for each season
ofSplit &amp;lt;- map2_dfr(ofYears$playerId, ofYears$yearId, function(x,y) {
      filter(Appearances, playerID == x &amp;amp; yearID == y)}) %&amp;gt;% 
      rename(LF = G_lf, CF = G_cf, RF = G_rf) %&amp;gt;% 
      gather(&amp;#39;LF&amp;#39;, &amp;#39;CF&amp;#39;, &amp;#39;RF&amp;#39;, key = &amp;quot;POS&amp;quot;, value = &amp;quot;G&amp;quot;)

# Primary outfield position = most games played at that position
splitSum &amp;lt;- ofSplit %&amp;gt;% 
      select(playerID, POS, G) %&amp;gt;%
      rename(playerId = playerID) %&amp;gt;%
      group_by(playerId, POS) %&amp;gt;% 
      summarize(sumG = sum(G)) %&amp;gt;% 
      filter(sumG == max(sumG)) %&amp;gt;% 
      ungroup() %&amp;gt;% 
      select(playerId, POS)

# Replacing &amp;quot;OF&amp;quot; values in posDat
ofPos &amp;lt;- posDat %&amp;gt;% 
      filter(POS == &amp;quot;OF&amp;quot;) %&amp;gt;% 
      select(-POS) %&amp;gt;% 
      inner_join(splitSum, by = &amp;quot;playerId&amp;quot;)

posDat &amp;lt;- posDat %&amp;gt;% 
      filter(POS != &amp;quot;OF&amp;quot;) %&amp;gt;%
      bind_rows(ofPos)

# Add POS column
warDat &amp;lt;- warDat %&amp;gt;% 
      inner_join(posDat, by = &amp;#39;playerId&amp;#39;)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&#34;save-objects&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Save Objects&lt;/h2&gt;
&lt;p&gt;We’ve generated quite a few objects in part one of this series. If you’d like to keep your environment relatively clean, we’ll only need a few of these going into the calculations of part two: &lt;code&gt;indWar&lt;/code&gt;, &lt;code&gt;nomWar&lt;/code&gt;, &lt;code&gt;posDat&lt;/code&gt;, and &lt;code&gt;warDat&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&#34;conclusion&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;We have the ingredients for the JAWS calculation and our dashboard. In this part, we scraped Wikipedia to get the Hall of Fame members’ names. Those names were used to get Baseball-Reference IDs which in turn were used to obtain WAR values. Next, by examining the data, we determined our tenure qualification, and primary player positions were determined by the greatest number of games played at a position. Next, we’ll perform the calculations in &lt;a href=&#34;https://erbo.rbind.io/blog/2018-03-21-a-baseball-dashboard-in-time-for-opening-weekend-part-two/&#34;&gt;part two&lt;/a&gt; and visualize them in a &lt;code&gt;shinydashboard&lt;/code&gt; in &lt;a href=&#34;https://erbo.rbind.io/blog/2018-03-23-a-baseball-dashboard-in-time-for-opening-weekend-part-three/&#34;&gt;part three&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&#34;references&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;p&gt;
&lt;a id=&#39;bib-Baumer_2015&#39;&gt;&lt;/a&gt;&lt;a href=&#34;#cite-Baumer_2015&#34;&gt;[1]&lt;/a&gt;&lt;cite&gt; B. Baumer and G. Matthews. &lt;em&gt;openWARData: Data Associated with openWAR&lt;/em&gt;. R package version 0.1.1.9004. 2015. URL: &lt;a href=&#34;https://github.com/beanumber/openWARData&#34;&gt;https://github.com/beanumber/openWARData&lt;/a&gt;.&lt;/cite&gt;
&lt;/p&gt;
&lt;p&gt;
&lt;a id=&#39;bib-Friendly_2017&#39;&gt;&lt;/a&gt;&lt;a href=&#34;#cite-Friendly_2017&#34;&gt;[2]&lt;/a&gt;&lt;cite&gt; M. Friendly. &lt;em&gt;Lahman: Sean ‘Lahman’ Baseball Database&lt;/em&gt;. R package version 6.0-0. 2017. URL: &lt;a href=&#34;https://CRAN.R-project.org/package=Lahman&#34;&gt;https://CRAN.R-project.org/package=Lahman&lt;/a&gt;.&lt;/cite&gt;
&lt;/p&gt;
&lt;p&gt;
&lt;a id=&#39;bib-Wickham_2016&#39;&gt;&lt;/a&gt;&lt;a href=&#34;#cite-Wickham_2016&#34;&gt;[3]&lt;/a&gt;&lt;cite&gt; H. Wickham. &lt;em&gt;rvest: Easily Harvest (Scrape) Web Pages&lt;/em&gt;. R package version 0.3.2. 2016. URL: &lt;a href=&#34;https://CRAN.R-project.org/package=rvest&#34;&gt;https://CRAN.R-project.org/package=rvest&lt;/a&gt;.&lt;/cite&gt;
&lt;/p&gt;
&lt;p&gt;
&lt;a id=&#39;bib-Wickham_2017&#39;&gt;&lt;/a&gt;&lt;a href=&#34;#cite-Wickham_2017&#34;&gt;[4]&lt;/a&gt;&lt;cite&gt; H. Wickham. &lt;em&gt;tidyverse: Easily Install and Load the ‘Tidyverse’&lt;/em&gt;. R package version 1.2.1. 2017. URL: &lt;a href=&#34;https://CRAN.R-project.org/package=tidyverse&#34;&gt;https://CRAN.R-project.org/package=tidyverse&lt;/a&gt;.&lt;/cite&gt;
&lt;/p&gt;
&lt;/div&gt;
</description>
        </item>
        
        <item>
          <title>JAWS-4 Dashboard User Guide</title>
          <link>https://erbo.rbind.io/blog/2018-03-02-jaws4-dashboard-user-guide/</link>
          <pubDate>Fri, 02 Mar 2018 00:00:00 +0000</pubDate>
          
          <guid>https://erbo.rbind.io/blog/2018-03-02-jaws4-dashboard-user-guide/</guid>
          <description>&lt;div id=&#34;intro&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;The purpose of this &lt;a href=&#34;https://erbo.shinyapps.io/jaws4/&#34;&gt;dashboard&lt;/a&gt; is to provide tools to evaluate nominees for the Reds Hall of Fame. Its primary feature is the JAWS-4 statistic. JAWS-4 is a version of Jay Jaffe’s JAWS statistic that’s been modified to apply to a franchise’s Hall of Fame.&lt;/p&gt;
&lt;p&gt;The JAWS statistic is meant to be a starting point in the discussion of a nominee’s creditials for the Hall of Fame. Jaffe provides a thorough explanation &lt;a href=&#34;https://www.si.com/mlb/2017/11/27/hall-fame-jaws-intro-2018-ballot&#34;&gt;here&lt;/a&gt;. There’s also a more succinct description at &lt;a href=&#34;https://www.baseball-reference.com/about/jaws.shtml&#34;&gt;Baseball-Reference&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Other features include a Hall of Fame score which relates a player’s statistics to a typical member of the Hall of Fame and player stat ranks and percentiles. Lastly, there are multiple tables to compare players in other categories like the postseason, fielding, and awards.&lt;/p&gt;
&lt;div id=&#34;controls&#34; class=&#34;section level3&#34;&gt;
&lt;h3&gt;Controls&lt;/h3&gt;
&lt;p&gt;For those unfamilar with shinydashboard and DataTables, there are some basic functions you should be aware of.&lt;/p&gt;
&lt;ol style=&#34;list-style-type: decimal&#34;&gt;
&lt;li&gt;The toggle button in the header collapses/displays the sidebar.&lt;/li&gt;
&lt;li&gt;The column visibility button adds/removes columns from view.&lt;/li&gt;
&lt;li&gt;The PDF and CSV buttons download the data from that table in .pdf and .csv format.
&lt;ol style=&#34;list-style-type: lower-roman&#34;&gt;
&lt;li&gt;PDF: If the rows are filtered, then only those rows will be present in the downloaded document. It includes only rows on page 1 of the table. The number of entries per page can be specified at the bottom left corner of many of the tables. Regardless of which columns are visible, all columns will be included up to a maximum of 19 columns.&lt;/li&gt;
&lt;li&gt;CSV: Only difference from PDF is that there is no column limit. So every column is included regardless of the size of the table.&lt;/li&gt;
&lt;/ol&gt;&lt;/li&gt;
&lt;li&gt;The filter window uses &lt;a href=&#34;https://www.regular-expressions.info&#34;&gt;regex&lt;/a&gt; to filter the table. Note: you cannot specify the column to which the search criteria is applied. Examples:
&lt;ol style=&#34;list-style-type: lower-roman&#34;&gt;
&lt;li&gt;bench|rose|Larkin returns rows that correspond to those players – ben|bar|ros will also return those players (along with Ernie Lombardi).&lt;/li&gt;
&lt;li&gt;1B|LF selects all rows with positon equal to first base or left field but also returns Dolf Luque.&lt;/li&gt;
&lt;li&gt;e$ returns players whose names end in “e” like Bid McPhee but not Joe Morgan. b$ can also be used to select all rows with position equal to 1B, 2B, or 3B.&lt;/li&gt;
&lt;li&gt;^e returns players’ names that begin with the letter “e” such as Eric Davis&lt;/li&gt;
&lt;li&gt;^[ep] returns all players whose name begins with an “e” or a “p” but also returns every pitcher.&lt;/li&gt;
&lt;li&gt;Numbers may also be used. 3358 will return all rows with that string of numbers in that order.&lt;/li&gt;
&lt;/ol&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;figure&#34;&gt;
&lt;img src=&#34;https://erbo.rbind.io/charts/Home%20Screen%20Labelled.png&#34; /&gt;

&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&#34;features&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Features&lt;/h2&gt;
&lt;div id=&#34;home&#34; class=&#34;section level3&#34;&gt;
&lt;h3&gt;Home&lt;/h3&gt;
&lt;p&gt;This page shows the current JAWS median values and weighted median values for each group/position. The ridge plot visualizes the distributions for each group. Please see the links in the &lt;a href=&#34;#intro&#34;&gt;introduction&lt;/a&gt; for explanations of these values.&lt;/p&gt;
&lt;div class=&#34;figure&#34;&gt;
&lt;img src=&#34;https://erbo.rbind.io/charts/Home%20Screen.png&#34; /&gt;

&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&#34;jaws-4&#34; class=&#34;section level3&#34;&gt;
&lt;h3&gt;JAWS-4&lt;/h3&gt;
&lt;p&gt;The text input allows the user to select a player. Then dot plots are rendered that show how that player’s total WAR as a Red and his JAWS4 score compare with that of a typical Hall of Famer. The line chart shows the player’s WAR for every year he was a Red. Hovering over each point displays its value. The horizontal dashed line is one of two values: the median WAR per season of pitchers or the median WAR per season of position players. The data table shows weighted JAWS and WAR values according to each player’s primary position.&lt;/p&gt;
&lt;div class=&#34;figure&#34;&gt;
&lt;img src=&#34;https://erbo.rbind.io/charts/JAWS%20page.png&#34; /&gt;

&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&#34;profile&#34; class=&#34;section level3&#34;&gt;
&lt;h3&gt;Profile&lt;/h3&gt;
&lt;p&gt;Tables show various statistical categories while the deviation plot gives a calculated score that relates the player’s statistics to a typical Hall of Famer. The score gives users an estimate of where the player has excelled and faltered which many bolster or hinder his case. It also provides the user with an idea of which statistics to examine further on the Stat Rank page.&lt;/p&gt;
&lt;p&gt;Rough Guide:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr class=&#34;header&#34;&gt;
&lt;th align=&#34;center&#34;&gt;Rating&lt;/th&gt;
&lt;th align=&#34;center&#34;&gt;Score&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr class=&#34;odd&#34;&gt;
&lt;td align=&#34;center&#34;&gt;Excellent&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class=&#34;even&#34;&gt;
&lt;td align=&#34;center&#34;&gt;Good&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;0.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class=&#34;odd&#34;&gt;
&lt;td align=&#34;center&#34;&gt;Median&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class=&#34;even&#34;&gt;
&lt;td align=&#34;center&#34;&gt;Poor&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;-0.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class=&#34;odd&#34;&gt;
&lt;td align=&#34;center&#34;&gt;Awful&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;-2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&#34;figure&#34;&gt;
&lt;img src=&#34;https://erbo.rbind.io/charts/Profile%20page.png&#34; /&gt;

&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&#34;stat-rank&#34; class=&#34;section level3&#34;&gt;
&lt;h3&gt;Stat Rank&lt;/h3&gt;
&lt;p&gt;Ranks and percentiles of player statistics for both the Hall of Fame and franchise history can be viewed on this page. There are also Hall of Fame and franchise tables so the user can see which players rank above and below the nominee.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://erbo.rbind.io/charts/Stat%20Rank%20page%20Pitching.png&#34; /&gt; &lt;img src=&#34;https://erbo.rbind.io/charts/Stat%20Rank%20page%20Franchise.png&#34; /&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&#34;numbers&#34; class=&#34;section level3&#34;&gt;
&lt;h3&gt;Numbers&lt;/h3&gt;
&lt;p&gt;Here the user can have all the stats in front of them and compare players. Also, clickable links to player pages at Baseball-Reference and FanGraphs are provided under the Id columns.&lt;/p&gt;
&lt;div class=&#34;figure&#34;&gt;
&lt;img src=&#34;https://erbo.rbind.io/charts/Numbers%20page%20Batting.png&#34; /&gt;

&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&#34;notes&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Notes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;When entering player names and stats, entries are case sensitive.&lt;/li&gt;
&lt;li&gt;All tables contain both nominees and inducteees.&lt;/li&gt;
&lt;li&gt;To qualify for the JAWS-4 calculation the player must have been tenured with the Reds for at least 4 seasons.&lt;/li&gt;
&lt;li&gt;To qualify for other statistical tables players needed at least 1500 plate appearances or 500 innings.&lt;/li&gt;
&lt;li&gt;Known issues and inconsistencies are listed and discussed &lt;a href=&#34;https://github.com/nllspc/Sports-Analysis/blob/master/RedsJAWs/Notes.md#issues&#34;&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Explanations about the decision-making and details involved in the calculations are &lt;a href=&#34;https://github.com/nllspc/Sports-Analysis/blob/master/RedsJAWs/Notes.md#calculations&#34;&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div id=&#34;acknowledgements&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Acknowledgements&lt;/h2&gt;
&lt;div id=&#34;data&#34; class=&#34;section level3&#34;&gt;
&lt;h3&gt;Data&lt;/h3&gt;
&lt;p&gt;Data for this project was gathered from &lt;a href=&#34;https://www.baseball-reference.com&#34;&gt;Baseball-Reference&lt;/a&gt;, &lt;a href=&#34;https://www.fangraphs.com&#34;&gt;FanGraphs&lt;/a&gt;, and &lt;a href=&#34;http://lahman.r-forge.r-project.org/&#34;&gt;Sean Lahman’s Baseball Database&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&#34;packages&#34; class=&#34;section level3&#34;&gt;
&lt;h3&gt;Packages&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://github.com/tidyverse/broom&#34;&gt;broom&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://rstudio.github.io/DT&#34;&gt;DT&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/renkun-ken/formattable&#34;&gt;formattable&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://davidgohel.github.io/ggiraph&#34;&gt;ggiraph&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://www.sthda.com/english/rpkgs/ggpubr&#34;&gt;ggpubr&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/clauswilke/ggridges&#34;&gt;ggridges&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://r-forge.r-project.org/R/?group_id=1221&#34;&gt;Lahman&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://yihui.name/knitr/&#34;&gt;knitr&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/beanumber/openWARData&#34;&gt;openWARData&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/hadley/plyr&#34;&gt;plyr&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/tidyverse/rlang&#34;&gt;rlang&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/hadley/rvest&#34;&gt;rvest&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://shiny.rstudio.com&#34;&gt;shiny&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/lukehaas/css-loaders&#34;&gt;shinycssloaders&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://rstudio.github.io/shinydashboard/&#34;&gt;shinydashboard&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/tidyverse/tidyverse&#34;&gt;tidyverse&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/sjmgarnier/viridis&#34;&gt;viridis&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div id=&#34;support&#34; class=&#34;section level3&#34;&gt;
&lt;h3&gt;Support&lt;/h3&gt;
&lt;p&gt;Special Thanks to&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://erbo.rbind.io/charts/louisville-rstats-hex.png&#34; width=&#34;100px&#34; height=&#34;100px&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/louisville-rstats&#34;&gt;Louisville RStats&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
</description>
        </item>
        
      </channel>
</rss>
      
      
