<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/visualize-the-world-cup-with-r-part-1-recreating-goals-with-ggsoccer-and-ggplot2/"></div></div><div></div></div><div></div></div>
<div>
(This article was first published on <strong><a href="https://datascienceplus.com/visualize-the-world-cup-with-r-part-1-recreating-goals-with-ggsoccer-and-ggplot2/"> R Programming – DataScience+</a></strong>, and kindly contributed to <a rel="nofollow" href="https://www.r-bloggers.com/">R-bloggers)</a> </div>
<p>After posting a couple of my World Cup viz on <a rel="nofollow" href="https://twitter.com/R_by_Ryo" target="_blank">Twitter</a>, I thought I'll collate some of them into a blog post. This will be <strong>Part 1</strong> of a series as the World Cup goes on and I keep improving my viz skills throughout the tournament. I will also explain how I made improvements in each new plot, practice makes perfect!</p>
<p>Let's look at some of the packages I will use!</p>
<pre><code>library(ggplot2)   # plotting on top of ggsoccer 
library(ggsoccer)  # create soccer pitch overlay
library(dplyr)     # data manipulation
library(purrr)     # create multiple dataframes for tweenr
library(tweenr)    # build frames for animation
library(gganimate) # animate plots
library(extrafont) # insert custom fonts into plots
library(ggimage)   # insert images and emoji into plots
</code></pre>
<p>The important package here is the <code>ggsoccer</code> package made by Ben Torvaney, check out the GitHub repo <a rel="nofollow" href="https://github.com/Torvaney/ggsoccer" target="_blank">here</a>.</p>
<p>Showing is better than telling in this instance so let's take a look at the pitch:</p>
<pre><code>library(ggplot2)
library(ggsoccer)

data &lt;- data.frame(x = 1, y = 1)

ggplot(data) +
  annotate_pitch() +
  theme_pitch() +
  coord_flip(xlim = c(49, 101),
             ylim = c(-1, 101))
</code></pre>
<p>Gives this figure:<br>
<a rel="nofollow" href="https://i1.wp.com/datascienceplus.com/wp-content/uploads/2018/07/half-pitch-template-1.png?ssl=1" target="_blank"><img sizes="(max-width: 490px) 100vw, 490px" src="https://i0.wp.com/datascienceplus.com/wp-content/uploads/2018/07/half-pitch-template-1-490x490.png?w=450&ssl=1" alt="" class="wp-image-14742" data-recalc-dims="1"></a></p>
<p>Basically, <code>annotate_pitch()</code> creates the markings for the soccer field such as the center circle, 18-yard box, penalty spot, etc. while <code>theme_pitch()</code> erases the extraneous axes and background from the default <code>ggplot</code> style. By using the limits arguments in <code>coord_flip()</code>, we can focus on a certain area of the pitch and orient it in a way that we want, as I want to recreate goals I'm going to show only one half of the field and orient the view to face the goal. With this as the base, we can now input positional data and then use a combination of <code>geom_segment()</code> and <code>geom_curve()</code> to show the path of the ball and the players!</p>
<p>The only problem with doing this is manually creating the data points. This is more a problem of access to the data rather than availability as sports analytics firms, most notably Opta, generate a huge amount of data for every player in every match, however it is not easy for a regular guy like me to buy it. </p>
<p>Some people have managed to create some nice <a rel="nofollow" href="https://twitter.com/neilcharles_uk/status/1009181021965778945" target="_blank">heatmaps</a> by scraping <em>WhoScored.com</em> and other sites (that create their viz from purchased data from Opta) with <strong>RSelenium</strong> or some other JS scrapers but that was a bit out of my expertise so I resorted to creating the coordinate positions by hand. Thankfully, due to the plotting system in <code>ggsoccer</code> and <code>ggplot2</code>, it's very easy to figure out the positions on the soccer field plot and with a little bit of practice it doesn't take too much time.</p>
<p>To save space I don't show the data frames with the coordinate points and labelling data for all of the graphics, however you can find all of them <a rel="nofollow" href="https://github.com/Ryo-N7/soccer_ggplots" target="_blank">here</a> in the GitHub repo!</p>
<h3>Gazinsky Scores The First Goal!</h3>
<pre><code>ggplot(pass_data) +
  annotate_pitch() +
  theme_pitch() + 
  theme(text = element_text(family = &quot;Trebuchet MS&quot;)) +
  coord_flip(xlim = c(49, 101),
             ylim = c(-1, 101)) +
  geom_segment(aes(x = x, y = y, xend = x2, yend = y2),
               arrow = arrow(length = unit(0.25, &quot;cm&quot;),
                             type = &quot;closed&quot;)) +
  geom_segment(data = ball_data,
               aes(x = x, y = y, xend = x2, yend = y2), 
               linetype = &quot;dashed&quot;, size = 0.85,
               color = c(&quot;black&quot;, &quot;red&quot;)) +
  geom_segment(data = movement_data,
               aes(x = x, y = y, xend = x2, yend = y2), 
               linetype = &quot;dashed&quot;, size = 1.2,
               color = &quot;darkgreen&quot;) +
  geom_curve(data = curve_data, 
             aes(x = x, y = y, xend = x2, yend = y2), 
             curvature = 0.25, 
             arrow = arrow(length = unit(0.25, &quot;cm&quot;),
                           type = &quot;closed&quot;)) +
  geom_image(data = goal_img,
             aes(x = x, y = y,
                 image = image), 
             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;) +
  geom_label(data = label_data,
    aes(x = x, y = y, 
        label = label,
        hjust = hjust,
        vjust = vjust)) +
  annotate(&quot;text&quot;, x = 69, y = 65, family = &quot;Trebuchet MS&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;)
</code></pre>
<p>Gives this plot:<br>
<a rel="nofollow" href="https://i2.wp.com/datascienceplus.com/wp-content/uploads/2018/07/gazinsky-plot-1.png?ssl=1" target="_blank"><img sizes="(max-width: 490px) 100vw, 490px" src="https://i2.wp.com/datascienceplus.com/wp-content/uploads/2018/07/gazinsky-plot-1-490x490.png?w=450&ssl=1" alt="" class="wp-image-14743" data-recalc-dims="1"></a></p>
<p>Not bad for a first try. Let's take a closer look at how I plotted the soccer ball image into the plot.</p>
<pre><code>goal_img &lt;- data.frame(x = 100,
                       y = 47) %>% 
  mutate(image = &quot;https://d30y9cdsu7xlg0.cloudfront.net/png/43563-200.png&quot;)

## ggplot2 code ##
geom_image(data = goal_img,
             aes(x = x, y = y,
                 image = image), 
             size = 0.035)
## ggplot2 code ##
</code></pre>
<p>I used the <code>ggimage</code> package to be able to create a geom layer for an image. I created a column called <code>image</code> in a dataframe with the URL link to the soccer ball image I wanted and then in the <code>geom_image()</code> function I specified it in the <code>image</code> argument. </p>
<h2>Cristiano's Hattrick!</h2>
<p>In my excitement after seeing <strong>Portugal vs. Spain</strong>, a candidate for match of the tournament for the group stages if not for the whole tournament, I drew up Cristiano Ronaldo's hattrick!</p>
<pre><code>ggplot(goals_data) +
  annotate_pitch() +
  theme_pitch() +
  theme(text = element_text(family = &quot;Dusha V5&quot;),
        legend.position = &quot;none&quot;) +
  coord_flip(xlim = c(55, 112),
             ylim = c(-1, 101)) +
  geom_segment(x = 80, y = 48, 
               xend = 97, yend = 48) +  # 2nd 
  geom_segment(x = 97, y = 48, 
               xend = 100, yend = 45.5,
               arrow = arrow(length = unit(0.25, &quot;cm&quot;),
                             type = &quot;closed&quot;)) +        # degea fumble
  geom_curve(data = curve_data,
             aes(x = x, y = y, 
                 xend = xend, yend = yend),     # FREEKICK
             curvature = 0.3, 
             arrow = arrow(length = unit(0.25, &quot;cm&quot;), type = &quot;closed&quot;)) +
  geom_text(data = annotation_data,
            family = &quot;Dusha V5&quot;, 
            aes(x = x, y = y,
                hjust = hjust, label = label), 
            size = c(6.5, 4.5, 3, 3.5, 3.5, 3.5)) +
  geom_flag(data = flag_data,
            aes(x = x, y = y,
                image = image), size = c(0.08, 0.08)) +       # Portugal + Spain Flag
  ggimage::geom_emoji(aes(x = 105, 
                 y = c(45, 50, 55)),
             image = &quot;26bd&quot;, size = 0.035) +
  geom_point(aes(x = x, y = y), 
             shape = 21, size = 7, color = &quot;black&quot;, fill = &quot;white&quot;) +
  geom_text(aes(x = x, y = y, label = label, family = &quot;Dusha V5&quot;))
</code></pre>
<p>Cristiano plot:<br>
<a rel="nofollow" href="https://i0.wp.com/datascienceplus.com/wp-content/uploads/2018/07/Cristiano-plot-1.png?ssl=1" target="_blank"><img sizes="(max-width: 490px) 100vw, 490px" src="https://i1.wp.com/datascienceplus.com/wp-content/uploads/2018/07/Cristiano-plot-1-490x306.png?w=450&ssl=1" alt="" class="wp-image-14744" data-recalc-dims="1"></a></p>
<p>Compared to the first plot, I increased the x-axis limit so that we could place our <code>geom_text()</code> annotations and flag images together without having to use grobs. This also meant that we put the plot title and subtitle in the <code>geom_text()</code> rather than in the <code>labs()</code> function, which let all the text/label data be organized in one dataframe, <code>annotation_data</code>. </p>
<pre><code>annotation_data &lt;- data.frame(
  hjust = c(0.5, 0.5, 0.5, 0, 0, 0),
  label = c(&quot;Portugal             (3) vs. Spain            (3)&quot;,
            &quot;Cristiano's Hattrick (4', 44', 88')&quot;,
            &quot;by Ryo Nakagawara (@R_by_Ryo)&quot;,
            &quot;1. Fouled by Nacho in the box,\nCristiano confidently strokes the ball\ninto the right corner from the spot.&quot;,
            &quot;2. Guedes lays it off to Cristiano whose\nstrong shot is uncharacteristically\nfumbled by De Gea into the net.&quot;,
            &quot;In the final minutes of the game,\nCristiano wins a freekick against Pique\nand curls it beautifully over the wall.&quot;),
  x = c(110, 105, 53, 76, 66, 66), 
  y = c(30, 20, 85, 5, 5, 55)
)
</code></pre>
<p>Overall, it's a slightly hacky solution to include a lot of blank spaces between the country name and the score to put the flags in between them, but I don't know of any geoms that can incorporate both text and images at the same time so the hacky solution will do!</p>
<p>To show the flags I use the <code>geom_flag()</code> function from the <code>ggimage</code> package. The function requires you to pass a two-digit ISO code in the <strong>image</strong> argument for the flags of the countries you want. You can find the ISO codes for countries with a quick google search, Portugal is <strong>“PT”</strong> and Spain is <strong>“ES”</strong>.</p>
<pre><code>flag_data &lt;- data.frame(
  image = c(&quot;PT&quot;, &quot;ES&quot;),
  x = c(110, 110),
  y = c(19.1, 50.1)
)

## ggplot2 code ##
geom_flag(data = flag_data,
          aes(x = x, y = y,
              image = image, size = size))  
## ggplot2 code ##
</code></pre>
<p>Some other options to do this include using the <code>ggflags</code> package or if you don't like the flags used in <code>geom_flag()</code>, pass an image of a flag of your choosing to <code>geom_image()</code>.</p>
<p>There is actually a better way to search for the ISO codes which I will show later!</p>
<p>This time, instead of the soccer ball image, I used the <code>emoji_search()</code> function from the <code>emoGG</code> package to find a soccer ball emoji. Then I can use either emoGG or ggimage's <code>geom_emoji()</code> function to insert it into my ggplot!</p>
<pre><code>library(emoGG)
library(ggimage)
emoji_search(&quot;soccer&quot;) # &quot;26bd&quot;</code>
<em>## emoji code keyword
## 2537 soccer 26bd sports
## 2538 soccer 26bd football
## 4130 o 2b55 circle
## 4131 o 2b55 round
## 5234 eritrea 1f1ea\\U0001f1f7 er
## 5956 somalia 1f1f8\\U0001f1f4 so
</em></pre>
<pre><code>## ggplot2 code ##
ggimage::geom_emoji(aes(x = 105, 
                        y = c(45, 50, 55)),
                    image = &quot;26bd&quot;, size = 0.035)
## ggplot2 code ##
</code></pre>
<p>From now on, instead of the soccer ball image in the first graphic, I will be using the emoji version!</p>
<p>The official World Cup font, <em>“Dusha”</em>, was created by a Portugese design agency back in 2014 and has been used in all official World Cup prints and graphics. Some of the letters may look a bit squished but overall I quite like it, so I wanted to incorporate it in my plots. To do so you need to download the <code>.TTF</code> file from <a rel="nofollow" href="http://fifa2018wiki.com/fifa-2018-font-typeface-download-dusha-font-ttf/509/" target="_blank">here</a>, then right-click and install it. Now, we need to make sure R can use it, this can be done by using the <code>extrafont</code> package!</p>
<pre><code>font_import()  # import font files in your computer
font_install() # install any new font files added to your computer
loadfonts()    # run every new session once!
fonts()        # to check out what fonts are ready for use in R!
</code></pre>
<p>For more details check out the package <strong>README</strong> <a rel="nofollow" href="https://cran.r-project.org/web/packages/extrafont/README.html" target="_blank">here</a>. Again, remember to run <code>loadfont()</code> everytime you open up a new session!</p>
<h2>Osako's Winner vs. Colombia</h2>
<p>I wasn't expecting much from Japan's World Cup journey this time around due to our poor performances in the friendlies (besides the Paraguay game) and the fact that we changed our manager in April! However, with a historic win (our first against South American opposition in the World Cup), I couldn't resist making another R graphic:</p>
<pre><code>library(ggplot2)
library(dplyr)
library(ggsoccer)
library(extrafont)
library(ggimage)
library(countrycode)

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)
player_label &lt;- data.frame(x = c(92, 99), 
                           y = c(49, 2))
annotation_data &lt;- data.frame(
  x = c(110, 105, 70, 92, 53), 
  y = c(30, 30, 45, 81, 85),
  hjust = c(0.5, 0.5, 0.5, 0.5, 0.5),
  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;Bonus: Ospina looking confused and\ndoing a lil' two-step-or-god-knows-what.&quot;,
            &quot;by Ryo Nakagawara (@R_by_Ryo)&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)
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;)
</code></pre>
<pre><code>ggplot(osako_gol) +
  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_curve(data = cornerkick_data,
             aes(x = x, y = y, xend = x2, yend = y2),
             curvature = -0.15, 
             arrow = arrow(length = unit(0.25, &quot;cm&quot;),
                             type = &quot;closed&quot;)) +
  geom_segment(aes(x = x, y = y, xend = x2, yend = y2),
               arrow = arrow(length = unit(0.25, &quot;cm&quot;),
                             type = &quot;closed&quot;)) +
  geom_label(data = player_label, 
             aes(x = x, y = y),
             label = c(&quot;Osako&quot;, &quot;Honda&quot;), family = &quot;Dusha V5&quot;) +
  geom_point(aes(x = 98, y = 50), size = 3, color = &quot;green&quot;) +
  geom_text(aes(x = 99.7, y = 50), size = 5, label = &quot;???&quot;, family = &quot;Dusha V5&quot;) +
  geom_text(data = annotation_data,
            family = &quot;Dusha V5&quot;, 
            aes(x = x, y = y,
                hjust = hjust, label = label), 
            size = c(6.5, 4.5, 4, 3.5, 3)) +
  ggimage::geom_flag(data = flag_data,
                     aes(x = x, y = y,
                         image = image),       
                     size = c(0.08, 0.08)) +
  ggimage::geom_emoji(aes(x = 95, 
                          y = 50),
             image = &quot;26bd&quot;, size = 0.035) +
  geom_image(data = wc_logo,
             aes(x = x, y = y,
                 image = image), size = 0.17)
</code></pre>
<p>Osako winner plot:<br>
<a rel="nofollow" href="https://i2.wp.com/datascienceplus.com/wp-content/uploads/2018/07/Osako-winner-plot-1.png?ssl=1" target="_blank"><img sizes="(max-width: 490px) 100vw, 490px" src="https://i2.wp.com/datascienceplus.com/wp-content/uploads/2018/07/Osako-winner-plot-1-490x350.png?w=450&ssl=1" alt="" class="wp-image-14745" data-recalc-dims="1"></a></p>
<p>I could have used the <code>annotate()</code> function to add the little comment about Ospina being stuck in no-man's-land but I prefer to have all of my text in a single dataframe. Like before, I again had to expand the x-axis limits in the <code>coord_flip()</code>. This is also so we can insert the World Cup image on the top right without using grobs/Magick and such. To grab that World Cup logo, we do the same things as we did when we added the soccer ball image in the first plot with <code>ggimage</code>.</p>
<p>For finding the ISO codes to input for the <code>geom_flag()</code> function we can do one better than previous attempts by using the <code>countrycode</code> package to find ISO codes without having to manually search online!</p>
<p>By passing country names into <code>countrycode()</code> function and labelling them as <strong>“country.name”</strong> in the <strong>origin</strong> argument, the function will know that the input is the regular name for a country. Then you specify the output such as <strong>“iso2c”</strong> for the two-digit ISO codes such as in our case, <strong>“wb”</strong> for World Bank codes, <strong>“eurostat.name”</strong> for country names in the Eurostat database and so on…!</p>
<pre><code>library(countrycode)

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)

glimpse(flag_data)</code>
<em>## Observations: 2
## Variables: 3
## $ x </em></pre>