<p><div><div><div><div data-show-faces="false" data-height="21" data-width="100" data-layout="button_count" data-send="true" data-href="https://www.r-bloggers.com/animating-the-goals-of-the-world-cup-comparing-the-old-vs-new-gganimate-and-tweenr-api/"></div></div><div></div></div><div></div></div>
<div>
(This article was first published on <strong><a href="https://datascienceplus.com/animating-the-goals-of-the-world-cup-comparing-the-old-vs-new-gganimate-and-tweenr-api/"> R Programming – DataScience+</a></strong>, and kindly contributed to <a rel="nofollow" href="https://www.r-bloggers.com/">R-bloggers)</a> </div>
<p>Welcome to Part 3 of my series on “Visualizing the World Cup with R”! This is the culmination of this mini project that I've been working on throughout the World Cup (You can check out the Github Repo <a rel="nofollow" href="https://github.com/Ryo-N7/soccer_ggplots" target="_blank">here</a>). In addition, from having listened to Thomas Pedersen's excellent <a rel="nofollow" href="https://www.youtube.com/watch?v=21ZWDrTukEs" target="_blank">keynote</a> at UseR! 2018 in Brisbane on the NEW <code>gganimate</code> and <code>tweenr</code> API, I am taking advantage of the fortuitous timing to also compare the APIs using the goals as the examples!</p>
<p>I've had finished these animations a couple of weeks ago but didn't make them available until I presented at the <a rel="nofollow" href="https://twitter.com/TokyoRCommunity" target="_blank">TokyoR</a> MeetUp last week! Hadley Wickham and Joe Rickert were the special guests and with the amount of speakers and attendees it felt more like a mini-conference than a regular meetup, if you're ever in Tokyo come join us for some R&amp;R…and R! You can check out a recording of my talk on <a rel="nofollow" href="https://www.youtube.com/watch?v=tNncHFd5dVo" target="_blank">YouTube</a>.</p>
<p>Let's get started!</p>
<h2>Coordinate position data</h2>
<p>Since this series started, several people have asked me where I got the data. I thought I made it quite clear in <a rel="nofollow" href="https://datascienceplus.com/visualize-the-world-cup-with-r-part-1-recreating-goals-with-ggsoccer-and-ggplot2/" target="_blank">Part 1</a> but I will reiterate in the next few paragraphs.</p>
<p>I get a lot of my data science/visualization news from Twitter which has made a weird comeback by providing a platform for certain communities like <code>#rstats</code> (never thought I'll be creating a Twitter account in 2017!). Therefore, I've been able to come across some wonderful visualizations for the World Cup by The Financial Times, FiveThirtyEight, and a host of other people. As you can see from a great example of World Cup penalties by the BBC below, data is provided by sports analytics companies, primarily Opta!</p>
<p><a rel="nofollow" href="https://i1.wp.com/ichef.bbci.co.uk/onesport/cps/624/cpsprodpb/79E6/production/_102260213_1_penalties_scored_640-nc.png?ssl=1" target="_blank"><img src="https://i1.wp.com/ichef.bbci.co.uk/onesport/cps/624/cpsprodpb/79E6/production/_102260213_1_penalties_scored_640-nc.png?w=450&ssl=1" data-recalc-dims="1"></a></p>
<p>Great! But can an average joe like me just waltz in, slap down a fiver, and say “GIMME THE DATA”? Well, unfortunately no, it costs quite a lot! This isn't really a knock on Opta or other sports analytics companies since FIFA or the FAs of respective nations didn't do this kind of stuff, the free market stepped in to fill the gap. Still, I'm 100% sure I am not the only one who wishes this kind of data was free though, well besides some datasets of varying quality you see on Kaggle (but none of those are as granular as the stuff Opta provides anyway).</p>
<p>So, envious of those who have the financial backing to procure such data and some mild annoyance at others online who didn't really bother sharing exactly how they got their data or even what tools they used, I started thinking of ways that I could get the data for myself.</p>
<p>One possible way was to use RSelenium or other JavaScript web scrapers on soccer analytics websites and their cool dashboards, like <a rel="nofollow" href="https://www.whoscored.com/" target="_blank">WhoScored.com</a>. However, since I wouldn't have been able to master these tools before the World Cup ended (during which whatever I end up creating would be most relevant), I decided that I'll create the coordinate data positions myself!</p>
<p>With the plotting system in <a rel="nofollow" href="https://github.com/Torvaney/ggsoccer" target="_blank">ggsoccer</a> and <code>ggplot2</code> it's really not that hard to figure out the positions on the soccer field plot, as you can see in the picture below:</p>
<pre><code>ggplot(point_data) +
  annotate_pitch() +
  theme_pitch(aspect_ratio = NULL) +
  coord_flip() +
  geom_point(
    aes(x = x, y = y), 
    size = 1.5) +
  geom_text(
    aes(x = x, y = y,
        label = label),
    vjust = 1.5, color = &quot;red&quot;)
</code></pre>
<p><a rel="nofollow" href="https://i0.wp.com/datascienceplus.com/wp-content/uploads/2018/08/figure1.png?ssl=1" target="_blank"><img sizes="(max-width: 490px) 100vw, 490px" src="https://i2.wp.com/datascienceplus.com/wp-content/uploads/2018/08/figure1-490x306.png?w=450&ssl=1" alt="" class="wp-image-16194" data-recalc-dims="1"></a></p>
<p>There's also a way to make the coordinates be in 120×80 format (which is much more intuitive) and you can do that by adding the <code>*_scale</code> arguments inside the <code>annotate_pitch()</code> function. However, I only realized this after I had embedded the coordinate positions for the 100×100 plot in my head so that's what I kept going with.</p>
<p>There is also the “Soccer event logger” <a rel="nofollow" href="https://github.com/Torvaney/elm-soccer-tracker" target="_blank">here</a> (incidentally also by <a rel="nofollow" href="https://twitter.com/Torvaney" target="_blank">Ben Torvaney</a>) which allows you to mouse-click specific points on the field and then download a <code>.csv</code> file of the coordinate positions you clicked. This might be easier but personally, I like to experiment within the R environment and take notes/ideas in RMarkdown as I do so, it definitely is an option for others though.</p>
<p>… and that's how <a rel="nofollow" href="https://datascienceplus.com/visualize-the-world-cup-with-r-part-1-recreating-goals-with-ggsoccer-and-ggplot2/" target="_blank">Part 1</a> was born! But I wasn't going to stop there, soccer is a moving – flowing game, static images are OK but it just doesn't capture the FEEL of the sport. So this is where <code>gganimate</code> and <code>tweenr</code> came in!</p>
<p>Out of all the World Cup stuff I've animated so far, by far the most complicated was <a rel="nofollow" href="https://www.youtube.com/watch?v=mE79PUhe1_8" target="_blank">Gazinsky's goal</a> in the opening game. This is because I not only have to track the ball movement but the movement of multiple players as well. So most of the comparison aspect of the APIs will be done with this goal.</p>
<p>Let's take a look at the packages that I'll be using:</p>
<pre><code>library(ggplot2)    # general plotting base
library(dplyr)      # data manipulation/tidying
library(ggsoccer)   # draw soccer field plot
library(ggimage)    # add soccer ball emoji + flags
library(extrafont)  # incorporate Dusha font into plots
library(gganimate)  # animate goal plots
library(tweenr)     # create in-between frames for data
library(purrr)      # for creating a list of dataframes for tweenr
library(countrycode)# for finding ISO codes for geom_flag()
# loadfonts()         run once every new session
</code></pre>
<h2>Gazinsky's first goal</h2>
<p>Let's first look at the set of dataframes with the coordinate data points necessary for this to work:</p>
<pre><code>pass_data &lt;- data.frame(
  x = c(100, 94, 82, 82.5,  84, 76.5, 75.5, 94, 99.2),     
  y = c(0,   35, 31, 22,     8, 13, 19, 60, 47.5),
  time = c(1, 2, 3, 4, 5, 6, 7, 8, 9))

golovin_movement &lt;- data.frame(
  x = c(78, 80, 80, 80, 75.5, 74.5, 73.5, 73, 73),  
  y = c(30, 30, 27, 25,   10,    9, 15, 15, 15),
  label = &quot;Golovin&quot;,
  time = c(1, 2, 3,  4,  5,  6,  7,  8,  9)
)

zhirkov_movement &lt;- data.frame(
  x = c(98, 90, 84, 84, 84, 84, 84, 84, 84),
  y = c( 0,  2,  2,  2,  2,  2,  2,  2,  2),
  label = &quot;Zhirkov&quot;,
  time = c(1, 2, 3, 4, 5, 6, 7, 8, 9)
)

gazinsky_movement &lt;- data.frame(
  x = c(0, 0, 0, 0, NA, 92,   92,   92,   92),
  y = c(0, 0, 0, 0, NA, 66.8, 66.8, 66.8, 66.8),
  label = &quot;Gazinsky&quot;,
  time = c(1, 2, 3, 4, 5, 6, 7, 8, 9)
)

# ONLY in static + gganimate versions
segment_data &lt;- data.frame(
  x = c(77.5, 98),
  y = c(22, 2),
  xend = c(75, 84),
  yend = c(15, 3),
  linetype = c(&quot;dashed&quot;, &quot;dashed&quot;),
  color = c(&quot;black&quot;, &quot;black&quot;),
  size = c(1.2, 1.25)
)

# saudi defender
saudi_data &lt;- data.frame(
  x = c(95, 95, 90, 87, 84, 80, 79, 79, 79),
  y = c(35, 35, 35, 32, 28, 25, 24, 25, 26),
  label = &quot;M. Al-Breik&quot;,
  time = c(1, 2, 3, 4, 5, 6, 7, 8, 9)
)

### soccer ball
ball_data &lt;- tribble(
  ~x,  ~y, ~time,
  100,   0,   1,
  94,   35,   2,
  82,   31,   3,
  82.5, 25,   4,
  84,    6,   5, 
  77,   13,   6,
  76,   19,   7,
  94,   60,   8,
  99.2, 47.5, 9

) 
</code></pre>
<p>If you're manually creating these, you could also use <code>tribble()</code> instead of a <code>dataframe()</code>. It takes up a bit more space, as you can see in <code>ball_data</code>, but it is probably more readable for when you're sharing the code (like creating a reprex on SO or RStudio Community).</p>
<p>And here is the <code>ggplot</code> code for the <code>gganimate</code> version (no tween frames)!</p>
<p>Note: You need to be careful about the ordering of the <code>ggplot</code> elements. You need to make sure the soccer ball emoji code is near the end, after the labels, so that the player name labels don't cover the soccer ball as it's moving around!</p>
<pre><code>ggplot(pass_data) +
  annotate_pitch() +
  theme_pitch() +
  coord_flip(
    xlim = c(49, 101),
    ylim = c(-1, 101)) +
  geom_segment(
    data = segment_data, 
    aes(x = x, y = y, 
        xend = xend, yend = yend),
    size = segment_data$size,
    color = segment_data$color,
    linetype = c(&quot;dashed&quot;, &quot;dashed&quot;)) +
  geom_label(
    data = saudi_data,
    aes(x = x, y = y,
        label = label),
    color = &quot;darkgreen&quot;) +
  geom_label(data = zhirkov_movement,
    aes(x = x, y = y,
        frame = time,
        label = label),
    color = &quot;red&quot;) +
  geom_label(data = golovin_movement,
    aes(x = x, y = y,
        frame = time,
        label = label),
    color = &quot;red&quot;) +
  geom_label(
    data = gazinsky_movement,
    aes(x = x, y = y,
        label = label),
    color = &quot;red&quot;) +
  ggimage::geom_emoji(
    data = ball_data,
    aes(x = x, y = y, frame = time),   
    image = &quot;26bd&quot;, size = 0.035) +
  ggtitle(
    label = &quot;Russia (5) vs. (0) Saudi Arabia&quot;, 
    subtitle = &quot;First goal, Yuri Gazinsky (12th Minute)&quot;) +
  labs(caption = &quot;By Ryo Nakagawara (@R_by_Ryo)&quot;) +
  annotate(
    &quot;text&quot;, x = 69, y = 65, family = &quot;Dusha V5&quot;,
    label = &quot;After a poor corner kick clearance\n from Saudi Arabia, Golovin picks up the loose ball, \n exchanges a give-and-go pass with Zhirkov\n before finding Gazinsky with a beautiful cross!&quot;) +
  theme(text = element_text(family = &quot;Dusha V5&quot;))
</code></pre>
<p><a rel="nofollow" href="https://i1.wp.com/datascienceplus.com/wp-content/uploads/2018/08/V9drm0I.gif?ssl=1" target="_blank"><img alt="" src="https://i1.wp.com/datascienceplus.com/wp-content/uploads/2018/08/V9drm0I.gif?w=450&ssl=1" class="wp-image-16016" data-recalc-dims="1"></a></p>
<p>Now let's check out how we would do it with the in-between frames added in using <code>tweenr</code>!</p>
<p>The important bit with the old API was that you had to create a list of dataframes of the different states of your data. In this case, it is a dataframe for each observation of the data or to put it more simply, the “time” variable (a dataframe of coordinate positions for time = 1, time = 2, etc.). This is done with <code>pmap()</code> with <code>dataframe()</code> being passed to the <code>.f</code> argument.</p>
<p>With this list of dataframes created, we can pass it into <code>tween_states()</code> function to create the in-between frames to connect each of the dataframes in the list. Take note of the arguments in <code>tweent_states()</code> as they'll show up again in the new API later.</p>
<pre><code>### soccer ball
b_list &lt;- ball_data %>% pmap(data.frame)

ball_tween &lt;- b_list %>% 
  tween_states(tweenlength = 0.5, statelength = 0.00000001, ease = &quot;linear&quot;, nframes = 75)

### Golovin
golovin_movement_list &lt;- golovin_movement %>% pmap(data.frame)

golovin_tween &lt;- golovin_movement_list %>% 
  tween_states(tweenlength = 0.5, statelength = 0.00000001, ease = &quot;linear&quot;, nframes = 75)

golovin_tween &lt;- golovin_tween %>% mutate(label = &quot;Golovin&quot;)

### Zhirkov
zhirkov_movement_list &lt;- zhirkov_movement %>% pmap(data.frame)

zhirkov_tween &lt;- zhirkov_movement_list %>% 
  tween_states(tweenlength = 0.5, statelength = 0.00000001, ease = &quot;linear&quot;, nframes = 75)

zhirkov_tween &lt;- zhirkov_tween %>% mutate(label = &quot;Zhirkov&quot;)
</code></pre>
<p>Now with these newly created tween dataframes, we pass them into our <code>ggplot</code> code as before and specify the <code>frame</code> argument with the newly created “.frame” variable.</p>
<pre><code>ggplot(pass_data) +
  annotate_pitch() +
  theme_pitch() +
  coord_flip(xlim = c(49, 101),
             ylim = c(-1, 101)) +
  geom_label(
    data = saudi_data,
    aes(x = x, y = y,
        label = label),
    color = &quot;darkgreen&quot;) +
  geom_label(data = zhirkov_tween,
    aes(x = x, y = y,
        frame = .frame,
        label = label),
    color = &quot;red&quot;) +
  geom_label(data = golovin_tween,
    aes(x = x, y = y,
        frame = .frame,
        label = label),
    color = &quot;red&quot;) +
  geom_label(
    data = gazinsky_movement,
    aes(x = x, y = y,
        label = label),
    color = &quot;red&quot;) +
  ggimage::geom_emoji(
    data = ball_tween,
    aes(x = x, y = y, frame = .frame),   
    image = &quot;26bd&quot;, size = 0.035) +
  ggtitle(label = &quot;Russia (5) vs. (0) Saudi Arabia&quot;, 
          subtitle = &quot;First goal, Yuri Gazinsky (12th Minute)&quot;) +
  labs(caption = &quot;By Ryo Nakagawara (@R_by_Ryo)&quot;) +
  annotate(&quot;text&quot;, x = 69, y = 65, family = &quot;Dusha V5&quot;,
           label = &quot;After a poor corner kick clearance\n from Saudi Arabia, Golovin picks up the loose ball, \n exchanges a give-and-go pass with Zhirkov\n before finding Gazinsky with a beautiful cross!&quot;) +
  theme(text = element_text(family = &quot;Dusha V5&quot;))
</code></pre>
<p><a rel="nofollow" href="https://i2.wp.com/datascienceplus.com/wp-content/uploads/2018/08/GQYSJSH.gif?ssl=1" target="_blank"><img alt="" src="https://i2.wp.com/datascienceplus.com/wp-content/uploads/2018/08/GQYSJSH.gif?w=450&ssl=1" class="wp-image-16017" data-recalc-dims="1"></a></p>
<p>Looks good. Now, let's check out how things changed with the new API!</p>
<h2>New gganimate &amp; tweenr</h2>
<p>Once again, let's start by looking at just animating across the “time” variable without creating in-between frames. </p>
<pre><code>ggplot(pass_data) +
  annotate_pitch() +
  theme_pitch() +
  theme(text = element_text(family = &quot;Dusha V5&quot;)) +
  coord_flip(xlim = c(49, 101),
             ylim = c(-1, 101)) +
  geom_label(
    data = zhirkov_movement,
    aes(x = x, y = y,
        label = label),
    color = &quot;red&quot;) +
  geom_label(
    data = golovin_movement,
    aes(x = x, y = y,
        label = label),
    color = &quot;red&quot;) + 
  geom_label(
    data = gazinsky_movement,
    aes(x = x, y = y,
        label = label),
    color = &quot;red&quot;) +
  geom_label(
    data = saudi_data,
    aes(x = x, y = y,
        label = label),
    color = &quot;darkgreen&quot;) +
  ggimage::geom_emoji(
    data = ball_data,
    aes(x = x, y = y),   
    image = &quot;26bd&quot;, size = 0.035) +
  ggtitle(label = &quot;Russia (5) vs. (0) Saudi Arabia&quot;, 
          subtitle = &quot;First goal, Yuri Gazinsky (12th Minute)&quot;) +
  labs(caption = &quot;By Ryo Nakagawara (@R_by_Ryo)&quot;) +
  annotate(&quot;text&quot;, x = 69, y = 65, family = &quot;Dusha V5&quot;,
           label = &quot;After a poor corner kick clearance\n from Saudi Arabia, Golovin picks up the loose ball, \n exchanges a give-and-go pass with Zhirkov\n before finding Gazinsky with a beautiful cross!&quot;) +
  # new gganimate code:
  transition_manual(frames = time)
</code></pre>
<p><a rel="nofollow" href="https://i1.wp.com/datascienceplus.com/wp-content/uploads/2018/08/figure2.gif?ssl=1" target="_blank"><img alt="" src="https://i1.wp.com/datascienceplus.com/wp-content/uploads/2018/08/figure2.gif?w=450&ssl=1" class="wp-image-16195" data-recalc-dims="1"></a></p>
<p>It's quite nice that I don't have to specify <code>frame = some_time_variable</code> in every geom that I want animated now!</p>
<p>However, you can see that like in the old gganimate code the new <code>transition_manual()</code> function just speeds through the specified “time” variable without actually creating in-between frames. Let's use the other <code>transition_*()</code> functions to specify the tween frames and set the animation speed.</p>
<p>Here I will use <code>transition_states()</code> with “time” being the column I pass to the <code>states</code> argument. Instead of having to create a “.frame” column with the <code>tween_states()</code> function I can just pass the “time” variable into the <code>transition_states()</code> function and it will tween the frames for you in addition to the <code>ggplot</code> code! The <code>transition_length</code> argument is the same as the <code>tween_length</code> argument from the old <code>tween_states()</code> function and <code>state_length</code> argument is the same here too.</p>
<p>Unlike in the version I showed in my presentation, I added Mohammed Al-Breik's movement as well. I felt it was a bit silly (and unfair) to show him just standing there after his headed clearance!</p>
<pre><code>ggplot(pass_data) +
  annotate_pitch() +
  theme_pitch() +
  coord_flip(xlim = c(49, 101),
             ylim = c(-1, 101)) +
  geom_label(
    data = saudi_data,
    aes(x = x, y = y,
        label = label),
    color = &quot;darkgreen&quot;) +
  geom_label(
    data = zhirkov_movement,
    aes(x = x, y = y,
        label = label),
    color = &quot;red&quot;) +
  geom_label(
    data = golovin_movement,
    aes(x = x, y = y,
        label = label),
    color = &quot;red&quot;) +
  geom_label(
    data = gazinsky_movement,
    aes(x = x, y = y,
        label = label),
    color = &quot;red&quot;) +
  enter_grow(fade = TRUE) +
  ggimage::geom_emoji(
    data = ball_data,
    aes(x = x, y = y),   
    image = &quot;26bd&quot;, size = 0.035) +
  ggtitle(
    label = &quot;Russia (5) vs. (0) Saudi Arabia&quot;, 
    subtitle = &quot;First goal, Yuri Gazinsky (12th Minute)&quot;) +
  labs(caption = &quot;By Ryo Nakagawara (@R_by_Ryo)&quot;) +
  annotate(
    &quot;text&quot;, x = 69, y = 65, family = &quot;Dusha V5&quot;,
    label = &quot;After a poor corner kick clearance\n from Saudi Arabia, Golovin picks up the loose ball, \n exchanges a give-and-go pass with Zhirkov\n before finding Gazinsky with a beautiful cross!&quot;) +
  theme(text = element_text(family = &quot;Dusha V5&quot;)) +
  # new gganimate code:
  transition_states(
    time, 
    transition_length = 0.5, 
    state_length = 0.0001,
    wrap = FALSE) +
  ease_aes(&quot;linear&quot;)

anim_save(filename = &quot;gazin_new_tweenr.gif&quot;)
</code></pre>
<p><a rel="nofollow" href="https://i1.wp.com/datascienceplus.com/wp-content/uploads/2018/08/figure3.gif?ssl=1" target="_blank"><img alt="" src="https://i1.wp.com/datascienceplus.com/wp-content/uploads/2018/08/figure3.gif?w=450&ssl=1" class="wp-image-16196" data-recalc-dims="1"></a></p>
<p>Now you may be wondering why I didn't use the more logical choice, the <code>transition_time()</code> function here so let me explain.</p>
<p>I manually created the timing of the coordinate data so naturally, the transitions would be slightly off compared to real data. This goal animation was split into 9 “time” values for each important bit of the play that I thought would transition well when connected with eachother. Then I ran it through <code>gganimate</code> to see if it flowed well and once I was satisfied, I let <code>tweenr</code> fill in the blanks between each “time” value.</p>
<p>With the new API however, using <code>transition_time()</code> function wouldn't allow me to control transition length and state length like with <code>transition_states()</code>! Try running the code above with <code>transition_time(time = time)</code> instead and you'll see what I mean.</p>
<p>If I had real data and the proper timing values in the “time” column that seamlessly worked with the coordinate data points it would have then been appropriate to use <code>transition_time()</code>. Some examples of these kind of data sets include the gapminder data set used in the package README which used the “year” variable or the data set in the cool NBA animation by James Curley seen <a rel="nofollow" href="https://rpubs.com/jalapic/nbaplaybyplay" target="_blank">here</a> that had very granular data recording the coordinate positions and the exact times.</p>
<p>A cool new thing that you can play around with in the new gganimate are the different enter/exit animations! However, I couldn't really get it to work for Gazinsky's label… In the <code>mtcars</code> example on the gganimate GitHub Repo, the boxplots disappeared when there was no data for the specific combination of variables but I can't seem to properly set up the Gazinsky label dataframe correctly to implement it.</p>
<p>Ideally, I want Gazinsky's label to only show up from <code>time = 6</code> onwards. I tried filling the coordinate positions for <code>time = 1</code> to <code>time = 5</code> with NAs or 0s but it didn't seem trigger the effect … when I tried with “x = 0, y = 0” in <code>time = 5</code>, the player label zipped in from the bottom of the screen to the penalty box at <code>time = 6</code> and it was unintentionally very funny!</p>
<p>Any help here will be greatly appreciated!</p>
<h2>Osako's goal vs. Colombia</h2>
<p>Japan faced a tough opponent in Colombia, even with the man-advantage early on, in our opening game of the World Cup. Even with our passing tiring out the tenacious Colombians we were finding it hard to find a breakthrough. In came Keisuke Honda, who within minutes of his arrival, delivered a beautiful cross from a corner kick for Osako to head home!</p>
<p>This goal was a lot easier to animate and to be honest this was the first one I was able to actually get working a few weeks ago! This was mainly because the ball movement was the only thing I really had to worry about.</p>
<pre><code>cornerkick_data &lt;- data.frame(
  x = 99, y = 0.3,
  x2 = 94, y2 = 47)

osako_gol &lt;- data.frame(
  x = 94, y = 49,
  x2 = 100, y2 = 55.5)

ball_data &lt;- data.frame(
  x = c(99, 94, 100),
  y = c(0.3, 47, 55.5),
  time = c(1, 2, 3))

player_label &lt;- data.frame(
  x = c(92, 99), 
  y = c(49, 2),
  label = c(&quot;Osako&quot;, &quot;Honda&quot;))

wc_logo &lt;- data.frame(
  x = 107,
  y = 85) %>% 
  mutate(
    image = &quot;https://upload.wikimedia.org/wikipedia/en/thumb/6/67/2018_FIFA_World_Cup.svg/1200px-2018_FIFA_World_Cup.svg.png&quot;)

flag_data &lt;- data.frame(
  x = c(110, 110),
  y = c( 13, 53),
  team = c(&quot;japan&quot;, &quot;colombia&quot;)
  ) %>% 
  mutate(
    image = team %>% 
           countrycode(., origin = &quot;country.name&quot;, destination = &quot;iso2c&quot;)
  ) %>% 
  select(-team)
</code></pre>
<p>For this animation, I used one of the many easing functions available in <code>tweenr</code>, <code>quadratic-out</code>, to get the speed of the ball from a corner kick just about right. You can refer to <a rel="nofollow" href="https://easings.net/" target="_blank">this</a> awesome website to check out most of the different easing functions you can use in <code>ease_aes()</code>!</p>
<pre><code>ggplot(ball_data) +
  annotate_pitch() +
  theme_pitch() +
  theme(
    text = element_text(family = &quot;Dusha V5&quot;),
    plot.margin=grid::unit(c(0,0,0,0), &quot;mm&quot;)) +
  coord_flip(
    xlim = c(55, 112),
    ylim = c(-1, 101)) +
  geom_label(
    data = player_label, 
    aes(x = x, y = y,
        label = label),
    family = &quot;Dusha V5&quot;) +
  geom_point(
    aes(x = 98, y = 50), size = 3, color = &quot;green&quot;) +
  annotate(
    geom = &quot;text&quot;, family = &quot;Dusha V5&quot;, 
    hjust = c(0.5, 0.5, 0.5, 0.5),
    size = c(6.5, 4.5, 5, 3),
    label = c(&quot;Japan             (2) vs. Colombia             (1)&quot;,
              &quot;Kagawa (PEN 6'), Quintero (39'), Osako (73')&quot;,
              &quot;Japan press their man advantage, substitute Honda\ndelivers a delicious corner kick for Osako to (somehow) tower over\nColombia's defense and flick a header into the far corner!&quot;,
              &quot;by Ryo Nakagawara (@R_by_Ryo)&quot;),
    x = c(110, 105, 70, 53), 
    y = c(30, 30, 47, 85)) +
  ggimage::geom_emoji(              # soccer ball emoji
    aes(x = x, 
        y = y),
    image = &quot;26bd&quot;, size = 0.035) +
  ggimage::geom_flag(               # Japan + Colombia flag
    data = flag_data,
    aes(x = x, y = y,
        image = image),       
    size = c(0.08, 0.08)) +
  geom_image(                       # World Cup logo
    data = wc_logo,     
    aes(x = x, y = y,
        image = image), size = 0.17) +
  # new gganimate code:
  transition_states(
    time, 
    transition_length = 0.5, 
    state_length = 0.0001,
    wrap = FALSE) +
  ease_aes(&quot;quadratic-out&quot;)
</code></pre>
<p><a rel="nofollow" href="https://i0.wp.com/datascienceplus.com/wp-content/uploads/2018/08/figure4.gif?ssl=1" target="_blank"><img alt="" src="https://i0.wp.com/datascienceplus.com/wp-content/uploads/2018/08/figure4.gif?w=450&ssl=1" class="wp-image-16197" data-recalc-dims="1"></a></p>
<p>As you can see it's quite easy and fun to make these! I am hoping to make more in the future, especially when the new season begins!</p>
<p>A small note on the flags: I used a bit of a hacky solution to get them into the title but both Ben and Hadley recommended I use the <a rel="nofollow" href="https://github.com/hadley/emo" target="_blank">emo::ji()</a> package which allows you to insert emoji into RMarkdown and inline. So that's something I'll be looking into in the near future!</p>
<h2>Japan's Offside Trap vs. Senegal!</h2>
<p>For the final animation, I tried to recreate something you don't see everyday – an offside trap!</p>
<pre><code># PLAYERS
# JAPAN: x, y (blue)     Senegal: x2, y2  (lightgreen)
trap_data &lt;- data.frame(

  time = c(1, 2, 3, 4, 5),

  # ball trajectory
  x = c(70, 70, 70, 87, 95),       
  y = c(85, 85, 85, 52, 33),

  # offside line bar
  #xo =    c(83, 81.2, 79, 77.5, 70),
  xoend = c(83.8, 81.8, 79, 78.5, 71),

  yo =    c( 5,  5,  5,  5, 5),
  yoend = c(95, 95, 95, 95, 95),

  # players: japan
  jx  = c(83, 81, 77, 75, 70),
  jy  = c(rep(65, 5)),

  jx2 = c(83, 81.8, 78.5, 77, 70),
  jy2 = c(rep(60.5, 5)),

  jx3 = c(83, 81, 76.5, 75, 71),
  jy3 = c(rep(55, 5)),

  jx4 = c(83, 81.2, 76.3, 75, 70),
  jy4 = c(rep(52, 5)),

  jx5 = c(82.8, 81, 77, 74, 70),
  jy5 = c(rep(49, 5)),

  jx6 = c(83, 81.8, 77, 74, 70),
  jy6 = c(rep(45, 5)),

  jx7 = c(83.8, 81, 79, 77.5, 70),
  jy7 = c(rep(40, 5)),

  # players: senegal
  sx = c(83, 84, 84, 84, 84),
  sy = c(rep(33, 5)),

  sx2 = c(83, 85, 87, 92, 95),
  sy2 = c(38, 37, 35, 34, 33),

  sx3 = c(83, 84, 84, 83, 83),
  sy3 = c(rep(41, 5)),

  sx4 = c(83, 84, 83, 78, 78),
  sy4 = c(rep(45, 5)),

  sx5 = c(83, 84, 87, 88, 89),
  sy5 = c(rep(52, 5)),

  sx6 = c(83, 85, 84, 84, 83),
  sy6 = c(rep(69, 5))
)

# flags
flag_data &lt;- data.frame(
  x = c( 48, 93),
  y = c(107, 107),
  team = c(&quot;japan&quot;, &quot;senegal&quot;)
  ) %>% 
  mutate(
    image = team %>% 
           countrycode(., origin = &quot;country.name&quot;, destination = &quot;iso2c&quot;)
  ) %>% 
  select(-team)

# extra players:
goalkeeper_data &lt;- data.frame(
  x = c(98),
  y = c(50)
)

senegal_data &lt;- data.frame(
  x = c(55, 55, 68.5),
  y = c(50, 60, 87)
)
</code></pre>
<p>In the code below, take note of the “wrap” option in <code>transition_states()</code>. You can set it to false if you don't want the last state to transition back to the first state (default == TRUE).</p>
<pre><code>ggplot(trap_data) +
  annotate_pitch() +
  theme_pitch(aspect_ratio = NULL) +
  coord_fixed(
    xlim = c(30, 101),
    ylim = c(-5, 131)) +
  # offside line
  geom_segment(aes(x = xoend, y = yo, 
                   xend = xoend, yend = yoend), 
               color = &quot;black&quot;, size = 1.3) +
  # japan
  geom_point(aes(x = jx, y = jy), size = 4, color = &quot;blue&quot;) +
  geom_point(aes(x = jx2, y = jy2), size = 4, color = &quot;blue&quot;) +
  geom_point(aes(x = jx3, y = jy3), size = 4, color = &quot;blue&quot;) +
  geom_point(aes(x = jx4, y = jy4), size = 4, color = &quot;blue&quot;) +
  geom_point(aes(x = jx5, y = jy5), size = 4, color = &quot;blue&quot;) +
  geom_point(aes(x = jx6, y = jy6), size = 4, color = &quot;blue&quot;) +
  geom_point(aes(x = jx7, y = jy7), size = 4, color = &quot;blue&quot;) +
  # senegal
  geom_point(aes(x = sx, y = sy), size = 4, color = &quot;green&quot;) +
  geom_point(aes(x = sx2, y = sy2), size = 4, color = &quot;green&quot;) +
  geom_point(aes(x = sx3, y = sy3), size = 4, color = &quot;green&quot;) +
  geom_point(aes(x = sx4, y = sy4), size = 4, color = &quot;green&quot;) +
  geom_point(aes(x = sx5, y = sy5), size = 4, color = &quot;green&quot;) +
  geom_point(aes(x = sx6, y = sy6), size = 4, color = &quot;green&quot;) +

  # free kick spot (reference)
  geom_point(aes(x = 70, y = 85), color = &quot;blue&quot;, size = 1.2) +
  # goalkeeper
  geom_point(
    data = goalkeeper_data,
    aes(x = x, y = y), size = 4, color = &quot;blue&quot;) +
  # senegal defenders
  geom_point(
    data = senegal_data,
    aes(x = x, y = y), size = 4, color = &quot;green&quot;) +
  annotate(
    geom = &quot;text&quot;, family = &quot;Dusha V5&quot;, 
    hjust = c(0, 0),
    size = c(6, 6.5),
    label = c(&quot;Japan          (2) vs. Senegal         (2)&quot;,
              &quot;The Perfect Offside Trap&quot;),
    x = c(30,  30), 
    y = c(107, 115)) +
  ggimage::geom_flag(
    data = flag_data,
    aes(x = x, y = y,
        image = image),       
    size = c(0.07, 0.07)) +
  ggimage::geom_emoji(
    aes(x = x, y = y),
    image = &quot;26bd&quot;, size = 0.035) +
  # NEW gganimate code
  transition_states(
    states = time, 
    transition_length = 0.2, 
    state_length = 0.00000001,
    wrap = FALSE) +
  ease_aes(&quot;linear&quot;)
</code></pre>
<p><a rel="nofollow" href="https://i2.wp.com/datascienceplus.com/wp-content/uploads/2018/08/figure5.gif?ssl=1" target="_blank"><img alt="" src="https://i2.wp.com/datascienceplus.com/wp-content/uploads/2018/08/figure5.gif?w=450&ssl=1" class="wp-image-16198" data-recalc-dims="1"></a></p>
<p>So against the height advantage and physicality of Senegal, the thinking behind Japan's strategy was…</p>
<p>Let's take a few minutes to reflect on the new API.</p>
<h2>Personal thoughts</h2>
<p>The best thing about the new API is without a doubt, no more intermediary steps between tweening the data and plotting. As long as you have some kind of “time” variable you don't have to manually go and create the list of data frame for each state yourself as <code>transition_*()</code> functions does it all for you in the <code>ggplot</code> code!</p>
<p>The <code>ease_aes()</code> also allows you to specify the easing function of the transitions within the <code>ggplot</code> code as well. From “linear” to “quartic” to “elastic” along with modifiers such as “in”, “out”, “in-out” you have a lot to choose from to satisfy your animation needs. Thomas did mention in his keynote that he wants a better name for this, so any suggestions? Maybe something like <code>ease_tween()</code>, <code>easing_fun()</code>, <code>ease_trans()</code>, <code>ease_transitions()</code>?</p>
<p>With everything streamlined so that you can add in the animation code seamlessly with <code>ggplot</code> grammar I feel you can read the entirety of the code better. As in, you don't have to refer back to a separate chunk of code that showed how you created the tween frames. The transition to a “grammar of animation” is truly in motion!</p>
<h2>New options in gganimate and tweenr!</h2>
<p>Now I'll talk about a few other new things that I didn't have a chance to show this time around.</p>
<p>There are a host of different <code>enter_*()</code> and <code>exit_*()</code> functions to choose from to show how data appear and disappear throughout the duration of your animation. Some of the built-in effects that are available include, <code>*_fade()</code>, <code>*_grow()</code>, <code>*_shrink()</code> with extra arguments like <code>early</code> that change whether the data appears or disappears at the beginning of the transition or at the end.</p>
<p>With the old API, since you had to create the frames yourself with <code>tween_states()</code>, you got a dataframe output with the expanded tween-frames that you could view at your leisure. Now with the tweening done in the <code>ggplot</code> code you might think that you can't explicitly access them, but this is where the <code>frame_vars()</code> function comes in! Using this function you can access metadata about each of the frames rendered in your latest animation:</p>
<pre><code>frames_data &lt;- frame_vars(animation = last_animation())
</code></pre>
<p><a rel="nofollow" href="https://i2.wp.com/datascienceplus.com/wp-content/uploads/2018/08/figure6.jpg?ssl=1" target="_blank"><img sizes="(max-width: 490px) 100vw, 490px" src="https://i2.wp.com/datascienceplus.com/wp-content/uploads/2018/08/figure6-490x101.jpg?w=450&ssl=1" alt="" class="wp-image-16199" data-recalc-dims="1"></a></p>
<p>The “frame_source” column shows you where each individual frame image is saved so you can copy them, re-animate them with <code>magick</code> instead, anything you want!</p>
<p>Panning and zooming across different states in the animation is another new concept introduced in the new <code>gganimate</code> with the series of <code>view_*()</code> functions like <code>view_zoom()</code> and <code>view_step()</code>. Within these you can use arguments like <code>pause_length</code> to specify the length of the zoomed view and <code>step_length</code> to specify the length of the transition between view points. I didn't implement them in these GIFs because I had already used the <code>coord_*()</code> functions to focus on certain areas of the pitch and the events I was animating needed a wide perspective of the field. This may come into play in future goal or play-by-play animations where I'm recreating a neat bit of build-up play from a full field view then zoom in on the off-the-ball movement of a certain player, so definitely a set of functions to keep an eye on!</p>
<p>Finally, in previous versions you used the <code>gganimate()</code> function to save the animation on your computer but now that is done with <code>anim_save()</code>. The README on GitHub has a very clear explanation on this so take a look under the “Where is my animation?” section <a rel="nofollow" href="https://github.com/thomasp85/gganimate#where-is-my-animation" target="_blank">here</a>.</p>
<p>There's still much to learn from the new API and I'm sure there will still be more changes/fixes to come before the first CRAN release but this was a great step in the right direction. I will eagerly await the next release!</p>
<ul></ul>
<p><strong>
<p>Related Post</p>
<p></p></strong>
<ol>
<li><a rel="nofollow" href="https://datascienceplus.com/machine-learning-results-one-plot-to-rule-them-all/" title="Permanent Link toMachine Learning Results in R: one plot to rule them all! (Part 1 – Classification Models)" target="_blank">Machine Learning Results in R: one plot to rule them all! (Part 1 – Classification Models)</a></li>
<li><a rel="nofollow" href="https://datascienceplus.com/seaborn-categorical-plots-in-python/" title="Permanent Link toSeaborn Categorical Plots in Python" target="_blank">Seaborn Categorical Plots in Python</a></li>
<li><a rel="nofollow" href="https://datascienceplus.com/matplotlib-library-tutorial-with-examples-python/" title="Permanent Link toMatplotlib Library Tutorial with Examples – Python" target="_blank">Matplotlib Library Tutorial with Examples – Python</a></li>
<li><a rel="nofollow" href="https://datascienceplus.com/visualize-the-world-cup-with-r-part-1-recreating-goals-with-ggsoccer-and-ggplot2/" title="Permanent Link toVisualize the World Cup with R! Part 1: Recreating Goals with ggsoccer and ggplot2" target="_blank">Visualize the World Cup with R! Part 1: Recreating Goals with ggsoccer and ggplot2</a></li>
<li><a rel="nofollow" href="https://datascienceplus.com/creating-slopegraphs-with-r/" title="Permanent Link toCreating Slopegraphs with R" target="_blank">Creating Slopegraphs with R</a></li>
</ol>
	<p>
<div>
<div>To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="https://datascienceplus.com/animating-the-goals-of-the-world-cup-comparing-the-old-vs-new-gganimate-and-tweenr-api/"> R Programming – DataScience+</a></strong>.</div>
<hr>
<a rel="nofollow" href="https://www.r-bloggers.com/">R-bloggers.com</a> offers <strong><a rel="nofollow" href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers">daily e-mail updates</a></strong> about <a rel="nofollow" href="https://www.r-project.org/" title="The R Project for Statistical Computing">R</a> news and <a rel="nofollow" href="https://www.r-bloggers.com/search/tutorial" title="R tutorials">tutorials</a> on topics such as: <a rel="nofollow" href="https://www.r-bloggers.com/search/data%20science" title="Data science">Data science</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/Big%20Data" title="Big Data">Big Data, <a rel="nofollow" href="https://www.r-users.com/" title="R jobs">R jobs</a>, visualization (<a rel="nofollow" href="https://www.r-bloggers.com/search/ggplot2" title="ggplot and ggplot2 tutorials">ggplot2</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/boxplot" title="Boxplots using lattice and ggplot2 tutorials">Boxplots</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/map" title="Maps and gis">maps</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/animation" title="Animation in R">animation</a>), programming (<a rel="nofollow" href="https://www.r-bloggers.com/search/RStudio" title="RStudio IDE for R">RStudio</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/sweave" title="Sweave and literate programming">Sweave</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/LaTeX" title="LaTeX in R">LaTeX</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/SQL" title="SQL and databases">SQL</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/eclipse" title="Eclipse IDE for R">Eclipse</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/git" title="git and github, Version Control System">git</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/hadoop" title="Large data in R using Hadoop">hadoop</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/Web+Scraping" title="Web Scraping of google, facebook, yahoo, twitter and more using R">Web Scraping</a>) statistics (<a rel="nofollow" href="https://www.r-bloggers.com/search/regression" title="Regressions and ANOVA analysis tutorials">regression</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/PCA" title="principal component analysis tutorial">PCA</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/time+series" title="Time series">time series</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/trading" title="finance trading">trading</a>) and more...
<img width="1" alt="" src="http://feeds.feedburner.com/~r/RBloggers/~4/L925WjEYXUA" height="1"></a></div>