<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/network-visualization-of-football-transfers-using-the-visnetwork-package/"></div></div><div></div></div><div></div></div>
<div>
(This article was first published on <strong><a href="http://rcrastinate.blogspot.com/2018/09/network-visualization-of-football.html"> Rcrastinate</a></strong>, and kindly contributed to <a rel="nofollow" href="https://www.r-bloggers.com/">R-bloggers)</a> </div>
<div><a rel="nofollow" href="https://i2.wp.com/2.bp.blogspot.com/-zUaep0DFq_Y/W5lcipC3KyI/AAAAAAAADZY/8-HoZrM8ly0xW9CDIFeEbauurP2gJuSIACLcBGAs/s1600/transfer-graph.png?ssl=1" target="_blank"><img border="0" src="https://i2.wp.com/2.bp.blogspot.com/-zUaep0DFq_Y/W5lcipC3KyI/AAAAAAAADZY/8-HoZrM8ly0xW9CDIFeEbauurP2gJuSIACLcBGAs/s400/transfer-graph.png?resize=450%2C1356&ssl=1" data-original-width="450" width="450" data-original-height="1356" data-recalc-dims="1"></a></div>
<div><a rel="nofollow" href="http://www.wolferonline.de/uploads/transfers.html" target="_blank">Click here for the interactive visualization</a></div>
<p>If you’re interested in the visualisation of networks or graphs, you might’ve heard of the great package “<a rel="nofollow" href="https://datastorm-open.github.io/visNetwork/" target="_blank">visNetwork</a>“. I think it’s a really great package and I love playing around with it. The scenarios of graph-based analyses are many and diverse: whenever you can describe your data in terms of “outgoing” and “receiving” entities, a graph-based analysis and/or visualisation is possible. During my work as a linguist, I already used graphs for different purposes like linking-structures within dictionaries, visualising co-occurence patterns of words and so on.</p>
<p>Today, I want to show you something completely different: transfers of male football players in the German “<a rel="nofollow" href="https://www.bundesliga.com/de/bundesliga/" target="_blank">1. Bundesliga</a>“, the first division of male football in Germany. We can also describe this data in terms of outgoing and receiving entities (the nodes in the network): the clubs who are selling the players and the clubs who are buying the players. The edges (connections) within the network are the players themselves. And there are further attributes associated with the edges, e.g. the price of the player.</p>
<p>I’ll spare you the boring details of getting the data (please write a comment if you would like more details on that). I start with the raw data structure created by the scraping process. It’s a dataframe called <span>transfer.df</span> that looks like this:</p>
<div><a rel="nofollow" href="https://i2.wp.com/3.bp.blogspot.com/-EaEw_SYV3NA/W5lVA-bl5_I/AAAAAAAADZM/bo7l0ax3GGgmlTEnkAB77xUi4GuNEQgVgCLcBGAs/s1600/transfer.df.png?ssl=1" target="_blank"><img border="0" src="https://i1.wp.com/3.bp.blogspot.com/-EaEw_SYV3NA/W5lVA-bl5_I/AAAAAAAADZM/bo7l0ax3GGgmlTEnkAB77xUi4GuNEQgVgCLcBGAs/s640/transfer.df.png?resize=450%2C314&ssl=1" data-original-width="450" width="450" data-original-height="314" data-recalc-dims="1"></a></div>
<div></div>
<p>“abloese” (or “Ablöse”) means “transfer fee” and currently holds a string with certain codes and the currency: “ablösefrei” means that no transfer fee had to be payed (remember the famous <a rel="nofollow" href="https://en.wikipedia.org/wiki/Bosman_ruling" target="_blank">Bosman ruling</a>?). “-” means that the information on a transfer fee doesn’t make sense (e.g., when a player finishes his career). “?” means that no information about the transfer fee is available. “Mio.” and “Tsd.” just encodes “million” or “thousand”, we have to deal with that later.</p>
<p>But we have to take care of something else first. In the dataframe, a player always appears twice if he changed teams <b>within </b>the 1. Bundesliga: the first time for his old club (as outgoing) and the second time for his new club (as incoming). I dealt with it this way (also, I loaded the required packages):</p>
<p><span>library(visNetwork)</span><br><span>library(igraph)</span><br><span>library(stringr)</span></p>
<div></div>
<p><span>transfer.df2 &lt;-data.frame()</span><br><span>all.players &lt;-unique(transfer.df$player)</span><br><span>for (pi in all.players) {</span><br><span>  vork &lt;-grep(pi, transfer.df$player, fixed = T)</span><br><span>  if (length(vork) == 1) {</span><br><span>    transfer.df2 &lt;-rbind(transfer.df2, transfer.df[vork,])</span><br><span>  } else {</span><br><span>    transfer.df2 &lt;-rbind(transfer.df2, transfer.df[vork[1],])</span><br><span>  }</span><br><span>}</span><br><span><br></span><span>This basically means that, whenever a player appears more than once in </span><span>transfer.df</span><span>, the first appearance is kept and the second appearance is deleted. The resulting network wouldn’t be any different if we would keep the second appearance. So, now we are using </span><span>transfer.df2</span><span> as our data structure.</span><br><span><br></span><span>Now, we have to deal with the “abloese” (transfer fee) column:</span><br><span><br></span><span>transfer.df2$abloese.num &lt;-sapply(transfer.df2$abloese, USE.NAMES = F, FUN = function (x) {</span><br><span>  if (x %in% c(“-“, “?”)) NA else {</span><br><span>    if (x == “ablösefrei”) 0 else {</span><br><span>      mio &lt;-grepl(“Mio.”, x, fixed = T)</span><br><span>      tsd &lt;-grepl(“Tsd.”, x, fixed = T)</span><br><span>      x2 &lt;-gsub(“,”, “.”, x, fixed = T)</span><br><span>      x3 &lt;-gsub(“Mio. €”, “”, x2, fixed = T)</span><br><span>      x4 &lt;-as.numeric(str_trim(gsub(“Tsd. €”, “”, x3, fixed = T)))</span><br><span>      if (mio) x4*1000000 else {</span><br><span>        if (tsd) x4*1000 else { “FEHLER” }</span><br><span>      }</span><br><span>    }</span><br><span>  }</span></p>
<p><span>})</span></p>
<p><span>Basically, this is what we are doing:</span></p>
<ul>
<li>If abloese is “-” or “?”, we are using NA</li>
<li>If abloese is “ablösefrei” we are putting in 0</li>
<li>Then we see whether “Mio.” appears in the string.</li>
<li>Then we see whether “Tsd.” appears in the string.</li>
<li>Then we are deleting these substrings and the EUR sign and</li>
<li>trim the string and convert it to a numeric value.</li>
<li>If “Mio.” appeared in the string, we are multiplying the result with one million and if “Tsd.” appeared in the string, we are multiplying the result with one thousand (both will never appear in the string, it doesn’t make sense).</li>
</ul>
<p>Alright, now we have the column <span>abloese.num</span> and can move on to group the different transfer fees because we want to assign different colours to the edges in the network dependent on the transfer sum. The thresholds are arbitrary.</p>
<p><span>transfer.df2$abl.group &lt;-cut(transfer.df2$abloese.num, c(0, 200*1000, 1000*1000, 2000*1000, 5000*1000, 10000*1000, 60000*1000), include.lowest = T)</span><br><span><br></span><span>transfer.df2$abl.col &lt;-ifelse(transfer.df2$abloese.num == 0, “green”,</span><br><span>                               ifelse(transfer.df2$abl.group == “[0,2e+05]”, “#ffffcc”,</span><br><span>                                      ifelse(transfer.df2$abl.group == “(2e+05,1e+06]”, “#fed976”,</span><br><span>                                             ifelse(transfer.df2$abl.group == “(1e+06,2e+06]”, “#feb24c”,</span><br><span>                                                    ifelse(transfer.df2$abl.group == “(2e+06,5e+06]”, “#fc4e2a”,</span><br><span>                                                           ifelse(transfer.df2$abl.group == “(5e+06,1e+07]”, “#e31a1c”,</span><br><span>                                                                  ifelse(transfer.df2$abl.group == “(1e+07,6e+07]”, “#800026”, “grey”)))))))</span><br><span>transfer.df2$abl.col &lt;-ifelse(is.na(transfer.df2$abl.group), “grey”, transfer.df2$abl.col) </span><br><span><br></span><span>Now, I am converting the dataframe to an igraph object and this object to visNetwork object. I’m sure the igraph step could be skipped, but this works like a charm and doesn’t take much time.</span><br><span><br></span><span>graph &lt;-graph.data.frame(transfer.df2)</span></p>
<p><span>vn &lt;-toVisNetworkData(graph)</span></p>
<div></div>
<div>I am assigning color codes to the nodes:</div>
<div></div>
<div>
<div><span>vn$nodes$color &lt;-ifelse(vn$nodes$id %in% clubs, “tomato”,</span></div>
<div><span>                         ifelse(vn$nodes$id == “Vereinslos”, “green”,</span></div>
<div><span>                                ifelse(vn$nodes$id == “Karriereende”, “blue”, “grey”)))</span></div>
</div>
<div></div>
<div>All clubs in the 1. Bundesliga get “tomato” (clubs is an object I defined earlier) all clubs that are not in the 1. Bundesliga (e.g., Hamburger SV) get “grey”. There are two other special “clubs”: “Karriereende” for “end of career” and “Vereinslos” for “no club”, both get “green”.</div>
<div></div>
<div>Three things left to be done:</div>
<div></div>
<div>
<div><span>vn$edges$title &lt;-paste(vn$edges$player, vn$edges$abloese, sep = ” – “)</span></div>
<div><span>vn$edges$color &lt;-vn$edges$abl.col</span></div>
<div><span>vn$edges$width &lt;-4</span></div>
</div>
<div>
<ul>
<li>Assign a title to the edges that consists of the player name and the transfer fee. This appears upon hovering the edge.</li>
<li>Assign the grouped transfer fee color we defined earlier.</li>
<li>Increase the width of the edges to make the color more visible.</li>
</ul>
<div>No, for creating the HTML file for the graph:</div>
</div>
<div></div>
<div>
<div><span>visNetwork(nodes = vn$nodes, edges = vn$edges, height = “1000px”, width = “100%”) %>%</span></div>
<div><span>  visOptions(highlightNearest = TRUE) %>%</span></div>
<div><span>  #visIgraphLayout(layout = “layout_with_dh”) %>%</span></div>
<div><span>  visEdges(arrows = “to”, arrowStrikethrough = F) %>% visSave(file = “~/Desktop/transfers.html”, selfcontained = T)</span></div>
</div>
<div><span><br></span></div>
<div>Please visit my <a rel="nofollow" href="http://www.wolferonline.de/uploads/transfers.html" target="_blank">personal webspace</a> for the final result. The “redder” an edge in the network is, the more expensive the transfer was. You can also click on the nodes to only highlight all adjacent nodes (selling and buying clubs), drag nodes around (graph physics!) and hover over edges to see the specific player being transfered. Of course, zooming is enabled. <a rel="nofollow" href="https://datastorm-open.github.io/visNetwork/" target="_blank">visNetwork</a> does all that. I love that package!</div>
<div></div>
	<p>
<div>
<div>To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="http://rcrastinate.blogspot.com/2018/09/network-visualization-of-football.html"> Rcrastinate</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/MP-jWz3CG5I" height="1"></a></div>