├── 1.ScatterPlots&CrossHairs.md ├── 2.BuildingARadar.md ├── 3.CreateAPitch.md ├── 4.MappingRecieves.md ├── 5.TernaryPlotsandKMeans.md ├── 6.PassingOrigins.md ├── EPLStylinginGGplot.Rmd ├── Images ├── JP.png ├── Plot2.png ├── Plot3.png ├── Plot4.png ├── Plot5.png ├── Plot6.png ├── Plot7.png ├── Rplot08.jpeg ├── Unknown-1.png ├── Unknown-2.png ├── Unknown-3.png ├── Unknown-4.png ├── Unknown-5.png ├── Unknown-6.png ├── Unknown-7.png ├── Unknown-8.png ├── Unknown-9.png ├── Unknown.png ├── d ├── example.jpg ├── plot1.png ├── radarimage1.png ├── radarimage2.png ├── radarimage3.png ├── radarimage4.png ├── radarimage5.png ├── radarimage6.png ├── radarimage7.png ├── ternary1.png ├── ternary2.png ├── ternary3.png ├── ternary4.png ├── ternary5.png ├── unnamed-chunk-10-1.png ├── unnamed-chunk-11-1.png ├── unnamed-chunk-12-1.png ├── unnamed-chunk-13-1.png ├── unnamed-chunk-14-1.png ├── unnamed-chunk-15-1.png ├── unnamed-chunk-16-1.png ├── unnamed-chunk-17-1.png ├── unnamed-chunk-18-1.png ├── unnamed-chunk-19-1.png ├── unnamed-chunk-20-1.png ├── unnamed-chunk-21-1.png ├── unnamed-chunk-21-2.png ├── unnamed-chunk-21-3.png ├── unnamed-chunk-7-1.png ├── unnamed-chunk-8-1.png └── unnamed-chunk-9-1.png └── README.md /1.ScatterPlots&CrossHairs.md: -------------------------------------------------------------------------------- 1 | Scatter Plots & Crosshairs with ggPlot2 2 | ================ 3 | ======================================= 4 | 5 | This is a R conversion of a tutorial by [FC Python](https://fcpython.com/python-basics-fcpython). I take no credit for the idea and have their blessing to make this conversion. All text is a direct copy unless changes were relevant. Please follow them on [twitter](www.twitter.com/FC_Python) and if you have a desire to learn Python then they are a fantastic resource! 6 | 7 | Across R’s many visualisation libraries, you will find several ways to create scatter plots. ggPlot2, being one of the fundamental visualisation libraries, offers perhaps the simplest way to do so. In a few lines, we will be able to create scatter plots that show the relationship between two variables. It also offers easy ways to customise these charts, through adding crosshairs, text, colour and more. 8 | 9 | This article will plot goals for and against from a season, taking you through the initial creation of the chart, then some customisation that ggplot offers. Import the packages and off we go. 10 | 11 | ``` r 12 | require(rvest) 13 | ``` 14 | 15 | ## Loading required package: rvest 16 | 17 | ## Loading required package: xml2 18 | 19 | ``` r 20 | require(ggplot2) 21 | ``` 22 | 23 | ## Loading required package: ggplot2 24 | 25 | The Data 26 | -------- 27 | 28 | Using the web scrapping lessons that we learn in a previous tutorial we can grab live data for the Premier League season. I won't go into details here but running the code below will grab the data we need. 29 | 30 | ``` r 31 | page <- "https://uk.soccerway.com/national/england/premier-league/20172018/regular-season/r41547/?ICID=SN_01_01" 32 | scraped_page <- read_html(page) 33 | Teams <- scraped_page %>% html_nodes("#page_competition_1_block_competition_tables_6_block_competition_league_table_1_table .large-link") %>% html_text() %>% as.character() 34 | Teams <- gsub("\n","",Teams) 35 | Teams <- trimws(Teams) 36 | League <- scraped_page %>% html_node("h1") %>% html_text() %>% as.character() 37 | GoalsFor <- scraped_page %>% html_nodes(".team_rank .total_gf") %>% html_text() %>% as.numeric() 38 | GoalsAgainst <- scraped_page %>% html_nodes(".team_rank .total_ga") %>% html_text() %>% as.numeric() 39 | df <- data.frame(Teams, GoalsFor, GoalsAgainst) 40 | ``` 41 | 42 | Plotting with ggplot2 43 | --------------------- 44 | 45 | ggplot2 is a very powerful plotting package in R which follows a 'grammar' of graphics. It's my go to solution when wanting to plot anything in R. It's quick to get working. Firstly, let's plot a basic version of a scatter plot. 46 | 47 | ``` r 48 | ggplot(data = df, aes(x=GoalsFor, y=GoalsAgainst)) + 49 | geom_point() 50 | ``` 51 | 52 |  53 | 54 |  55 | 56 | Great, it's that easy. But let's build on this base plot. Let's add the crosshairs, which are essentially the mean of each axis. This allows us to say anything above the line is 'above average' and anything below the line is 'below average'. 57 | 58 | ``` r 59 | ggplot(data = df, aes(x=GoalsFor, y=GoalsAgainst)) + 60 | geom_point() + 61 | geom_vline(xintercept=mean(GoalsFor)) + 62 | geom_hline(yintercept=mean(GoalsAgainst)) 63 | ``` 64 | 65 |  66 | 67 |  68 | 69 | That work's but the lines are a little brash and heavy. Let's make the line a dashed line, change the colour to red and make it a little see-through. 70 | 71 | ``` r 72 | ggplot(data = df, aes(x=GoalsFor, y=GoalsAgainst)) + 73 | geom_point() + 74 | geom_vline(xintercept=mean(GoalsFor), linetype="dashed", alpha = 0.4, colour = "red") + 75 | geom_hline(yintercept=mean(GoalsAgainst), linetype="dashed", alpha = 0.4, colour = "red") 76 | ``` 77 | 78 |  79 | 80 |  81 | 82 | This now works pretty well, although it's hard to know which team is which! So we let's add some labels. 83 | 84 | ``` r 85 | ggplot(data = df, aes(x=GoalsFor, y=GoalsAgainst, label = Teams)) + 86 | geom_point() + 87 | geom_vline(xintercept=mean(GoalsFor), linetype="dashed", alpha = 0.4, colour = "red") + 88 | geom_hline(yintercept=mean(GoalsAgainst), linetype="dashed", alpha = 0.4, colour = "red") + 89 | geom_text(size = 2, nudge_y = -0.5) 90 | ``` 91 | 92 |  93 | 94 |  95 | 96 | Now we want to create two labels to go in the top left and the bottom right corners to help the reader understand the plot better. To do this we create a new dataframe for the annotations and use that to position them in the corner. 97 | 98 | ``` r 99 | annotations <- data.frame( 100 | xpos = c(-Inf,Inf), 101 | ypos = c(Inf,-Inf), 102 | annotateText = c("Poor Attack, Poor Defense","Strong Attack, Strong Defense"), 103 | hjustvar = c(0,1) , 104 | vjustvar = c(1,0)) 105 | 106 | ggplot(data = df, aes(x=GoalsFor, y=GoalsAgainst, label = Teams)) + 107 | geom_point() + 108 | geom_vline(xintercept=mean(GoalsFor), linetype="dashed", alpha = 0.4, colour = "red") + 109 | geom_hline(yintercept=mean(GoalsAgainst), linetype="dashed", alpha = 0.4, colour = "red") + 110 | geom_label(data = annotations, aes(x=xpos,y=ypos,hjust=hjustvar, vjust=vjustvar,label=annotateText, colour = "red")) + 111 | geom_text(size = 2.5, nudge_y = -0.5) 112 | ``` 113 | 114 |  115 | 116 |  117 | 118 | This works pretty well however, it covers up the Stoke City data point. So we change the ordering so the data point is on top of the annotations. We can also remove the legend and add a title. 119 | 120 | ``` r 121 | annotations <- data.frame( 122 | xpos = c(-Inf,Inf), 123 | ypos = c(Inf,-Inf), 124 | annotateText = c("Poor Attack, Poor Defense","Strong Attack, Strong Defense"), 125 | hjustvar = c(0,1) , 126 | vjustvar = c(1,0)) 127 | 128 | ggplot(data = df, aes(x=GoalsFor, y=GoalsAgainst, label = Teams)) + 129 | geom_vline(xintercept=mean(GoalsFor), linetype="dashed", alpha = 0.4, colour = "red") + 130 | geom_hline(yintercept=mean(GoalsAgainst), linetype="dashed", alpha = 0.4, colour = "red") + 131 | geom_label(data = annotations, aes(x=xpos,y=ypos,hjust=hjustvar, vjust=vjustvar,label=annotateText, colour = "red", size = 1)) + 132 | geom_text(size = 2.5, nudge_y = -0.5) + 133 | geom_point() + 134 | theme(legend.position="none") + 135 | ggtitle(paste0("Goals For & Against : ",League,"2017/18") 136 | ``` 137 | 138 |  139 | 140 |  141 | 142 | Have a play around with the settings of the plots to discover how it works and make it your own. 143 | 144 | The main thing I would improve here is that some team labels overlap, we could use the package ggrepel to help us better distribute the team labels. 145 | 146 | ``` r 147 | require(ggrepel) 148 | ``` 149 | 150 | ## Loading required package: ggrepel 151 | 152 | ``` r 153 | ggplot(data = df, aes(x=GoalsFor, y=GoalsAgainst)) + 154 | geom_vline(xintercept=mean(GoalsFor), linetype="dashed", alpha = 0.4, colour = "red") + 155 | geom_hline(yintercept=mean(GoalsAgainst), linetype="dashed", alpha = 0.4, colour = "red") + 156 | geom_label(data = annotations, aes(x=xpos,y=ypos,hjust=hjustvar, vjust=vjustvar,label=annotateText, colour = "red", size = 1)) + 157 | geom_text_repel(aes(GoalsFor, GoalsAgainst, label = Teams)) + 158 | geom_point() + 159 | theme(legend.position="none") + 160 | ggtitle(paste0("Goals For & Against : ",League,"2017/18") 161 | ``` 162 | 163 |  164 | 165 |  166 | 167 | There is a few alignment issues, maybe I prefer the previous version. How about you? The code is setup so all you have to do to create the graph for another league is to just choose another from soccerway.com and set it's URL as the page variable in line 5. 168 | 169 | Final Code 170 | ---------- 171 | 172 | ``` r 173 | require(rvest) 174 | require(ggplot2) 175 | require(ggrepel) 176 | 177 | page <- "https://uk.soccerway.com/national/england/premier-league/20172018/regular-season/r41547/?ICID=SN_01_01" 178 | scraped_page <- read_html(page) 179 | Teams <- scraped_page %>% html_nodes("#page_competition_1_block_competition_tables_6_block_competition_league_table_1_table .large-link") %>% html_text() %>% as.character() 180 | Teams <- gsub("\n","",Teams) 181 | Teams <- trimws(Teams) 182 | League <- scraped_page %>% html_node("h1") %>% html_text() %>% as.character() 183 | GoalsFor <- scraped_page %>% html_nodes(".team_rank .total_gf") %>% html_text() %>% as.numeric() 184 | GoalsAgainst <- scraped_page %>% html_nodes(".team_rank .total_ga") %>% html_text() %>% as.numeric() 185 | df <- data.frame(Teams, GoalsFor, GoalsAgainst) 186 | 187 | annotations <- data.frame( 188 | xpos = c(-Inf,Inf), 189 | ypos = c(Inf,-Inf), 190 | annotateText = c("Poor Attack, Poor Defense","Strong Attack, Strong Defense"), 191 | hjustvar = c(0,1) , 192 | vjustvar = c(1,0)) 193 | 194 | ggplot(data = df, aes(x=GoalsFor, y=GoalsAgainst)) + 195 | geom_vline(xintercept=mean(GoalsFor), linetype="dashed", alpha = 0.4, colour = "red") + 196 | geom_hline(yintercept=mean(GoalsAgainst), linetype="dashed", alpha = 0.4, colour = "red") + 197 | geom_label(data = annotations, aes(x=xpos,y=ypos,hjust=hjustvar, vjust=vjustvar,label=annotateText, colour = "red", size = 1)) + 198 | geom_text_repel(aes(GoalsFor, GoalsAgainst, label = Teams)) + 199 | geom_point() + 200 | theme(legend.position="none") + 201 | ggtitle(paste0("Goals For & Against : ",League,"2017/18")) 202 | ``` 203 | 204 |  205 | -------------------------------------------------------------------------------- /2.BuildingARadar.md: -------------------------------------------------------------------------------- 1 | Building a Radar Plot: from the ground up in ggplot2 2 | ================ 3 | 4 | This is a R conversion of a tutorial by [FC Python](https://fcpython.com/tag/radar-chart). I take no credit for the idea and have their blessing to make this conversion. All text is a direct copy unless changes were relevant. Please follow them on [twitter](www.twitter.com/FC_Python) and if you have a desire to learn Python then they are a fantastic resource! 5 | 6 | In football analysis and video games, radar charts have been popularised in a number of places, from the FIFA series, to [Ted Knutson’s](https://twitter.com/mixedknuts) innovative ways of displaying player data. 7 | 8 | Radar charts are an engaging way to show data that typically piques more attention than a bar chart although you can often use both of these to show the same data. 9 | 10 | The Data 11 | -------- 12 | 13 | This article runs through the creation of basic radar charts in R, plotting the FIFA Ultimate Team data of a couple of players, before creating a function to streamline the process. To start, let’s get our libraries and data pulled together. 14 | 15 | ``` r 16 | #Create a data frame from Messi and AdamaTraore's FIFA 2018 attribute data 17 | 18 | LionelMessi <- c(Pace = 96, Shooting = 97, Passing = 98, Dribbling = 99,Defending = 45,Physical = 81) 19 | AdamaTraore <- c(Pace = 93, Shooting = 60, Passing = 59, Dribbling = 80,Defending = 24,Physical = 70) 20 | 21 | data <- rbind(LionelMessi, AdamaTraore) 22 | data 23 | ``` 24 | 25 | ## Pace Shooting Passing Dribbling Defending Physical 26 | ## LionelMessi 96 97 98 99 45 81 27 | ## AdamaTraore 93 60 59 80 24 70 28 | 29 | Plotting data in a radar has lots of similarities to plotting along a straight line (like a bar chart). We still need to provide data on where our line goes, we need to label our axes and so on. However, as it is a circle, we will also need to provide the angle at which the lines run. This is much easier than it sounds with R. 30 | 31 | Firstly, let’s do the easy bits and take a list of Attributes for our labels, along with a basic count of how many there are. 32 | 33 | ``` r 34 | Attributes = colnames(data) 35 | AttNo = length(Attributes) 36 | ``` 37 | 38 | We then take a list of the values that we want to plot, then copy the first value to the end. When we plot the data, this will be the line that the radar follows. 39 | 40 | ``` r 41 | data <- cbind(data, data[,1]) 42 | ``` 43 | 44 | Building the Radar 45 | ------------------ 46 | 47 | There are packages that can help us create radar plots but these won't be as flexible to our needs and we miss out on a create learning experience. So we are going to construct our radar from the ground up as its a great way to show how ggplot2 works and it could inspire you to create other visulations. Let's get cracking... 48 | 49 | ``` r 50 | ## first make sure ggplot2 package is installed and loaded 51 | require(ggplot2) 52 | ``` 53 | 54 | ## Loading required package: ggplot2 55 | 56 | ``` r 57 | ## create a empty plot with a size of x -120,120 and y of -120,150 and save it to object 'p' 58 | ggplot() + xlim(c(-120, 120)) + ylim(c(-120, 150)) 59 | ``` 60 |  61 | 62 | Pretty easy, and we have our base plot sorted. In ggplot2 R will read from start to finish, this means we can build a graphic up layer by layer. Newer layers are displayed on top. So lets build the cirlces of our radar. 63 | 64 | Strangly, R doesn't have a good way of plotting circles with ggplot2 so we create a custom function called cirlceFun and create the data for 4 circles for values 25, 50, 75, 100. 65 | 66 | ``` r 67 | circleFun <- function(center = c(0,0),diameter = 1, npoints = 100){ 68 | r = diameter / 2 69 | tt <- seq(0,2*pi,length.out = npoints) 70 | xx <- center[1] + r * cos(tt) 71 | yy <- center[2] + r * sin(tt) 72 | return(data.frame(x = xx, y = yy)) 73 | } 74 | 75 | circle1 <- circleFun(c(0,0),200,npoints = 100) 76 | circle2 <- circleFun(c(0,0),150,npoints = 100) 77 | circle3 <- circleFun(c(0,0),100,npoints = 100) 78 | circle4 <- circleFun(c(0,0),50,npoints = 100) 79 | ``` 80 | 81 | Now let's plot them usuing the geom\_polygon method. We give them alternative colours and give the outer border a darker line. Feel free to change the colours to what you want. 82 | 83 | ``` r 84 | ## Add the radar background 85 | ggplot() + xlim(c(-120, 120)) + ylim(c(-120, 150)) + 86 | ## Add circles 87 | geom_polygon(data = circle1, aes(x=x,y=y),fill = "#F0F0F0", colour = "#969696") + geom_polygon(data = circle2, aes(x=x,y=y),fill = "#FFFFFF", colour = "#d9d9d9") + geom_polygon(data = circle3, aes(x=x,y=y),fill = "#F0F0F0", colour = "#d9d9d9") + geom_polygon(data = circle4, aes(x=x,y=y),fill = "#FFFFFF", colour = "#d9d9d9") 88 | ``` 89 | 90 |  91 | 92 | This looks but but we don't want the axis and grpah background so let's remove that with the help of theme\_void(). 93 | 94 | ``` r 95 | ## Add the radar background 96 | ggplot() + xlim(c(-120, 120)) + ylim(c(-120, 150)) + 97 | ## Add circles 98 | geom_polygon(data = circle1, aes(x=x,y=y),fill = "#F0F0F0", colour = "#969696") + geom_polygon(data = circle2, aes(x=x,y=y),fill = "#FFFFFF", colour = "#d9d9d9") + geom_polygon(data = circle3, aes(x=x,y=y),fill = "#F0F0F0", colour = "#d9d9d9") + geom_polygon(data = circle4, aes(x=x,y=y),fill = "#FFFFFF", colour = "#d9d9d9") + 99 | ## Change the theme to void 100 | theme_void() 101 | ``` 102 | 103 |  104 | 105 | Much better! Now we want to visually divide our radar plot into the number of To do this we divide the radians in a circle by the number of attributes we have to plot. Then create an arrays of the results with the seq() function. 106 | 107 | ``` r 108 | angle_spilt <- (2*pi) / (AttNo) 109 | angle_spilt_seq <- seq(0,(2*pi),angle_spilt) 110 | angle_spilt_seq 111 | ``` 112 | 113 | ## [1] 0.000000 1.047198 2.094395 3.141593 4.188790 5.235988 6.283185 114 | 115 | In the code below we will create some empty datafame to store our information then calculate x,y positions of our radars segment deviding lines and for the labelling of attributes and the value key. 116 | 117 | ``` r 118 | # empty dataframes to catch results 119 | LineData <- data.frame(x = numeric, y = numeric, stringsAsFactors = F) 120 | TitlePositioning <- data.frame(title = character, x = numeric, y = numeric, stringsAsFactors = F) 121 | 122 | ## create plot background construction data 123 | for (i in 1:NCOL(data)) { 124 | angle_multiplier <- if(i < NCOL(data)){i}else{1} 125 | radians_for_segment <- angle_spilt_seq[i] 126 | 127 | x <- 100 * cos(radians_for_segment) 128 | y <- 100 * sin(radians_for_segment) 129 | temp <- data.frame(x = x, y = y, stringsAsFactors = F) 130 | LineData <- rbind(temp, LineData) 131 | 132 | x <- 112 * cos(radians_for_segment) 133 | y <- 112 * sin(radians_for_segment) 134 | title <- colnames(data)[i] 135 | temp <- data.frame(title = title, x = x, y = y, stringsAsFactors = F) 136 | TitlePositioning <- rbind(temp, TitlePositioning) 137 | } 138 | 139 | ## create the value labellings data 140 | values <- c(25,50,75) 141 | radian_for_values <- angle_spilt / 2 142 | x <- values * cos(radian_for_values) 143 | y <- values * sin(radian_for_values) 144 | ValuePositioning <- data.frame(values = values, x = x, y = y, stringsAsFactors = F) 145 | 146 | ## Add the origin values for the lines 147 | LineData$x2 <- 0 148 | LineData$y2 <- 0 149 | 150 | ## check the data output 151 | LineData 152 | ``` 153 | 154 | ## x y x2 y2 155 | ## 1 100 -2.449294e-14 0 0 156 | ## 2 50 -8.660254e+01 0 0 157 | ## 3 -50 -8.660254e+01 0 0 158 | ## 4 -100 1.224647e-14 0 0 159 | ## 5 -50 8.660254e+01 0 0 160 | ## 6 50 8.660254e+01 0 0 161 | ## 7 100 0.000000e+00 0 0 162 | 163 | ``` r 164 | TitlePositioning 165 | ``` 166 | 167 | ## title x y 168 | ## 1 112 -2.743209e-14 169 | ## 2 Physical 56 -9.699485e+01 170 | ## 3 Defending -56 -9.699485e+01 171 | ## 4 Dribbling -112 1.371604e-14 172 | ## 5 Passing -56 9.699485e+01 173 | ## 6 Shooting 56 9.699485e+01 174 | ## 7 Pace 112 0.000000e+00 175 | 176 | ``` r 177 | ValuePositioning 178 | ``` 179 | 180 | ## values x y 181 | ## 1 25 21.65064 12.5 182 | ## 2 50 43.30127 25.0 183 | ## 3 75 64.95191 37.5 184 | 185 | Fantastic! that was a lot of the hard work done. Now lets plot these on the radar and see what it looks like. 186 | 187 | ``` r 188 | ## Add the radar background 189 | ggplot() + xlim(c(-120, 120)) + ylim(c(-120, 150)) + 190 | ## Add circles 191 | geom_polygon(data = circle1, aes(x=x,y=y),fill = "#F0F0F0", colour = "#969696") + geom_polygon(data = circle2, aes(x=x,y=y),fill = "#FFFFFF", colour = "#d9d9d9") + geom_polygon(data = circle3, aes(x=x,y=y),fill = "#F0F0F0", colour = "#d9d9d9") + geom_polygon(data = circle4, aes(x=x,y=y),fill = "#FFFFFF", colour = "#d9d9d9") + 192 | ## Change the theme to void 193 | theme_void() + 194 | ## Add the segment lines and attribute/value titles 195 | geom_segment(data=LineData, aes(x = LineData$x, y = LineData$y, xend = LineData$x2, yend = LineData$y2),colour = "#d9d9d9", linetype = "dashed") + 196 | annotate("text", x = TitlePositioning$x , y = TitlePositioning$y, label = TitlePositioning$title, size= 2.5) + 197 | annotate("text", x = ValuePositioning$x , y = ValuePositioning$y, label = ValuePositioning$values, size= 2.5, colour = "#969696") 198 | ``` 199 | 200 |  201 | 202 | I am happy with the style and the look, yourself? If not, have a play around with the colours and see for yourself. 203 | 204 | Adding the Player Data 205 | ---------------------- 206 | 207 | It's ready for player data! We need to calculate the x,y details of the player data we want to plot. 208 | 209 | ``` r 210 | # empty dataframe to catch result 211 | polydata <- data.frame(player = character, value = numeric, radians = numeric, x = numeric, y = numeric, stringsAsFactors = F) 212 | 213 | ## create polygon data for the players 214 | for (i in 1:NCOL(data)) { 215 | 216 | for (p in 1:NROW(data)) { 217 | 218 | player2calc <- data[p,] 219 | angle_multiplier <- if(i < NCOL(data)){i}else{1} 220 | radians_for_segment <- angle_spilt_seq[i] 221 | x <- player2calc[i] * cos(radians_for_segment) 222 | y <- player2calc[i] * sin(radians_for_segment) 223 | player <- rownames(data)[p] 224 | temp <- data.frame(player = player, value = player2calc[i], radians = radians_for_segment, x = x, y = y, stringsAsFactors = F) 225 | polydata <- rbind(temp, polydata) 226 | } 227 | } 228 | head(polydata) 229 | ``` 230 | 231 | ## player value radians x y 232 | ## 1 AdamaTraore 93 6.283185 93.0 -2.277843e-14 233 | ## 11 LionelMessi 96 6.283185 96.0 -2.351322e-14 234 | ## Physical AdamaTraore 70 5.235988 35.0 -6.062178e+01 235 | ## Physical1 LionelMessi 81 5.235988 40.5 -7.014806e+01 236 | ## Defending AdamaTraore 24 4.188790 -12.0 -2.078461e+01 237 | ## Defending1 LionelMessi 45 4.188790 -22.5 -3.897114e+01 238 | 239 | Calculations finished, let's first spilt the data into player 1 and player 2. 240 | 241 | ``` r 242 | ## split the data up into player 1 and 2 243 | playersDB <- unique(polydata$player) 244 | player1 <- polydata[which(polydata$player == playersDB[1]),] 245 | player2 <- polydata[which(polydata$player == playersDB[2]),] 246 | ``` 247 | 248 | Great, let's plot the data for player one using the geom\_polgon() function to add the area and geom\_point() function to add some points. 249 | 250 | ``` r 251 | ## Add the radar background 252 | ggplot() + xlim(c(-120, 120)) + ylim(c(-120, 150)) + 253 | ## Add circles 254 | geom_polygon(data = circle1, aes(x=x,y=y),fill = "#F0F0F0", colour = "#969696") + geom_polygon(data = circle2, aes(x=x,y=y),fill = "#FFFFFF", colour = "#d9d9d9") + geom_polygon(data = circle3, aes(x=x,y=y),fill = "#F0F0F0", colour = "#d9d9d9") + geom_polygon(data = circle4, aes(x=x,y=y),fill = "#FFFFFF", colour = "#d9d9d9") + 255 | ## Change the theme to void 256 | theme_void() + 257 | ## Add the segment lines and attribute/value titles 258 | geom_segment(data=LineData, aes(x = LineData$x, y = LineData$y, xend = LineData$x2, yend = LineData$y2),colour = "#d9d9d9", linetype = "dashed") + 259 | annotate("text", x = TitlePositioning$x , y = TitlePositioning$y, label = TitlePositioning$title, size= 2.5) + 260 | annotate("text", x = ValuePositioning$x , y = ValuePositioning$y, label = ValuePositioning$values, size= 2.5, colour = "#969696") + 261 | ## Add player 1 data 262 | geom_polygon(data = player1, aes(x=x,y=y),fill = "#A30845", colour = "#A30845", alpha = 0.3) + geom_point(data = player1, aes(x = x, y = y),size=0.3, colour= "#A30845") 263 | ``` 264 | 265 |  266 | 267 | We just need to add a title for the graph and we are good to go! 268 | 269 | ``` r 270 | ## create the title string for player 1 271 | Player1_title <- gsub('([[:upper:]])', ' \\1', playersDB[1]) 272 | Player1_title <- trimws(Player1_title) 273 | 274 | ## Add the radar background 275 | ggplot() + xlim(c(-120, 120)) + ylim(c(-120, 150)) + 276 | ## Add circles 277 | geom_polygon(data = circle1, aes(x=x,y=y),fill = "#F0F0F0", colour = "#969696") + geom_polygon(data = circle2, aes(x=x,y=y),fill = "#FFFFFF", colour = "#d9d9d9") + geom_polygon(data = circle3, aes(x=x,y=y),fill = "#F0F0F0", colour = "#d9d9d9") + geom_polygon(data = circle4, aes(x=x,y=y),fill = "#FFFFFF", colour = "#d9d9d9") + 278 | ## Change the theme to void 279 | theme_void() + 280 | ## Add the segment lines and attribute/value titles 281 | geom_segment(data=LineData, aes(x = LineData$x, y = LineData$y, xend = LineData$x2, yend = LineData$y2),colour = "#d9d9d9", linetype = "dashed") + 282 | annotate("text", x = TitlePositioning$x , y = TitlePositioning$y, label = TitlePositioning$title, size= 2.5) + 283 | annotate("text", x = ValuePositioning$x , y = ValuePositioning$y, label = ValuePositioning$values, size= 2.5, colour = "#969696") + 284 | ## Add player 1 data 285 | geom_polygon(data = player1, aes(x=x,y=y),fill = "#A30845", colour = "#A30845", alpha = 0.3) + geom_point(data = player1, aes(x = x, y = y),size=0.3, colour= "#A30845") + 286 | ## Add Chart Title 287 | annotate("text", x = -110 , y = 130, label = Player1_title, size= 5, colour = "#A30845", family = "Helvetica", fontface = "bold", hjust = 0) + 288 | annotate("text", x = 110 , y = 130, label = "FIFA 18 Data", size= 4, colour = "#969696", family = "Helvetica", fontface = "bold", hjust = 1) 289 | ``` 290 | 291 |  292 | 293 | Fantastic, we now have the graph working for one player which is very useful on its own but we can use a radar plot to compare two players perfectly. It's a tough call to decide who is better out of Lionel Messi and Adama Traore, let's get some help from the radar plot. 294 | 295 | ``` r 296 | ## Create Title Strings for Player 2 297 | Player2_title <- gsub('([[:upper:]])', ' \\1', playersDB[2]) 298 | Player2_title <- trimws(Player2_title) 299 | 300 | ## Add the radar background 301 | ggplot() + xlim(c(-120, 120)) + ylim(c(-120, 150)) + 302 | ## Add circles 303 | geom_polygon(data = circle1, aes(x=x,y=y),fill = "#F0F0F0", colour = "#969696") + geom_polygon(data = circle2, aes(x=x,y=y),fill = "#FFFFFF", colour = "#d9d9d9") + geom_polygon(data = circle3, aes(x=x,y=y),fill = "#F0F0F0", colour = "#d9d9d9") + geom_polygon(data = circle4, aes(x=x,y=y),fill = "#FFFFFF", colour = "#d9d9d9") + 304 | ## Change the theme to void 305 | theme_void() + 306 | ## Add the segment lines and attribute/value titles 307 | geom_segment(data=LineData, aes(x = LineData$x, y = LineData$y, xend = LineData$x2, yend = LineData$y2),colour = "#d9d9d9", linetype = "dashed") + 308 | annotate("text", x = TitlePositioning$x , y = TitlePositioning$y, label = TitlePositioning$title, size= 2.5) + 309 | annotate("text", x = ValuePositioning$x , y = ValuePositioning$y, label = ValuePositioning$values, size= 2.5, colour = "#969696") + 310 | ## Add player 1 data 311 | geom_polygon(data = player1, aes(x=x,y=y),fill = "#A30845", colour = "#A30845", alpha = 0.3) + geom_point(data = player1, aes(x = x, y = y),size=0.3, colour= "#A30845") + 312 | ## Add Chart Title 313 | annotate("text", x = -110 , y = 130, label = Player1_title, size= 5, colour = "#A30845", family = "Helvetica", fontface = "bold", hjust = 0) + 314 | annotate("text", x = 110 , y = 130, label = "FIFA 18 Data", size= 4, colour = "#969696", family = "Helvetica", fontface = "bold", hjust = 1) + 315 | ## Add the player 2 polygon and data points 316 | geom_polygon(data = player2, aes(x=x,y=y),fill = "#00B20B", colour = "#00B20B", alpha = 0.3) + 317 | geom_point(data = player2, aes(x = x, y = y),size=0.3, colour= "#00B20B") + 318 | ## Add the titles for player 2 319 | annotate("text", x = -110 , y = 116, label = Player2_title, size= 5, colour = "#00B20B", family = "Helvetica", fontface = "bold", hjust = 0) + 320 | annotate("text", x = -110 , y = 123 , label = "vrs", size= 3, colour = "#969696", family = "Helvetica", hjust = 0) 321 | ``` 322 | 323 |  324 | 325 | Wow! What a surprise Messi is better! The best way to understand the code is to play with it, choose other players from the [EA Database](https://www.easports.com/fifa/ultimate-team/fut/database), change the colours and titles... 326 | 327 | Full Code 328 | --------- 329 | 330 | ``` r 331 | #Create a data frame from Messi and AdamaTraore's FIFA 2018 attribute data 332 | 333 | LionelMessi <- c(Pace = 96, Shooting = 97, Passing = 98, Dribbling = 99,Defending = 45,Physical = 81) 334 | AdamaTraore <- c(Pace = 93, Shooting = 60, Passing = 59, Dribbling = 80,Defending = 24,Physical = 70) 335 | 336 | data <- rbind(LionelMessi, AdamaTraore) 337 | 338 | Attributes = colnames(data) 339 | AttNo = length(Attributes) 340 | 341 | data <- cbind(data, data[,1]) 342 | 343 | circleFun <- function(center = c(0,0),diameter = 1, npoints = 100){ 344 | r = diameter / 2 345 | tt <- seq(0,2*pi,length.out = npoints) 346 | xx <- center[1] + r * cos(tt) 347 | yy <- center[2] + r * sin(tt) 348 | return(data.frame(x = xx, y = yy)) 349 | } 350 | 351 | circle1 <- circleFun(c(0,0),200,npoints = 100) 352 | circle2 <- circleFun(c(0,0),150,npoints = 100) 353 | circle3 <- circleFun(c(0,0),100,npoints = 100) 354 | circle4 <- circleFun(c(0,0),50,npoints = 100) 355 | 356 | angle_spilt <- (2*pi) / (AttNo) 357 | angle_spilt_seq <- seq(0,(2*pi),angle_spilt) 358 | 359 | # empty dataframes to catch results 360 | LineData <- data.frame(x = numeric, y = numeric, stringsAsFactors = F) 361 | TitlePositioning <- data.frame(title = character, x = numeric, y = numeric, stringsAsFactors = F) 362 | 363 | ## create plot background construction data 364 | for (i in 1:NCOL(data)) { 365 | angle_multiplier <- if(i < NCOL(data)){i}else{1} 366 | radians_for_segment <- angle_spilt_seq[i] 367 | 368 | x <- 100 * cos(radians_for_segment) 369 | y <- 100 * sin(radians_for_segment) 370 | temp <- data.frame(x = x, y = y, stringsAsFactors = F) 371 | LineData <- rbind(temp, LineData) 372 | 373 | x <- 112 * cos(radians_for_segment) 374 | y <- 112 * sin(radians_for_segment) 375 | title <- colnames(data)[i] 376 | temp <- data.frame(title = title, x = x, y = y, stringsAsFactors = F) 377 | TitlePositioning <- rbind(temp, TitlePositioning) 378 | } 379 | 380 | ## create the value labellings data 381 | values <- c(25,50,75) 382 | radian_for_values <- angle_spilt / 2 383 | x <- values * cos(radian_for_values) 384 | y <- values * sin(radian_for_values) 385 | ValuePositioning <- data.frame(values = values, x = x, y = y, stringsAsFactors = F) 386 | 387 | ## Add the origin values for the lines 388 | LineData$x2 <- 0 389 | LineData$y2 <- 0 390 | 391 | # empty dataframe to catch result 392 | polydata <- data.frame(player = character, value = numeric, radians = numeric, x = numeric, y = numeric, stringsAsFactors = F) 393 | 394 | ## create polygon data for the players 395 | for (i in 1:NCOL(data)) { 396 | 397 | for (p in 1:NROW(data)) { 398 | 399 | player2calc <- data[p,] 400 | angle_multiplier <- if(i < NCOL(data)){i}else{1} 401 | radians_for_segment <- angle_spilt_seq[i] 402 | x <- player2calc[i] * cos(radians_for_segment) 403 | y <- player2calc[i] * sin(radians_for_segment) 404 | player <- rownames(data)[p] 405 | temp <- data.frame(player = player, value = player2calc[i], radians = radians_for_segment, x = x, y = y, stringsAsFactors = F) 406 | polydata <- rbind(temp, polydata) 407 | } 408 | } 409 | 410 | ## split the data up into player 1 and 2 411 | playersDB <- unique(polydata$player) 412 | player1 <- polydata[which(polydata$player == playersDB[1]),] 413 | player2 <- polydata[which(polydata$player == playersDB[2]),] 414 | 415 | ## create the title string for player 1 416 | Player1_title <- gsub('([[:upper:]])', ' \\1', playersDB[1]) 417 | Player1_title <- trimws(Player1_title) 418 | 419 | ## Create Title Strings for Player 2 420 | Player2_title <- gsub('([[:upper:]])', ' \\1', playersDB[2]) 421 | Player2_title <- trimws(Player2_title) 422 | 423 | require(ggplot2) 424 | 425 | 426 | ## Add the radar background 427 | ggplot() + xlim(c(-120, 120)) + ylim(c(-120, 150)) + 428 | ## Add circles 429 | geom_polygon(data = circle1, aes(x=x,y=y),fill = "#F0F0F0", colour = "#969696") + geom_polygon(data = circle2, aes(x=x,y=y),fill = "#FFFFFF", colour = "#d9d9d9") + geom_polygon(data = circle3, aes(x=x,y=y),fill = "#F0F0F0", colour = "#d9d9d9") + geom_polygon(data = circle4, aes(x=x,y=y),fill = "#FFFFFF", colour = "#d9d9d9") + 430 | ## Change the theme to void 431 | theme_void() + 432 | ## Add the segment lines and attribute/value titles 433 | geom_segment(data=LineData, aes(x = LineData$x, y = LineData$y, xend = LineData$x2, yend = LineData$y2),colour = "#d9d9d9", linetype = "dashed") + 434 | annotate("text", x = TitlePositioning$x , y = TitlePositioning$y, label = TitlePositioning$title, size= 2.5) + 435 | annotate("text", x = ValuePositioning$x , y = ValuePositioning$y, label = ValuePositioning$values, size= 2.5, colour = "#969696") + 436 | ## Add player 1 data 437 | geom_polygon(data = player1, aes(x=x,y=y),fill = "#A30845", colour = "#A30845", alpha = 0.3) + geom_point(data = player1, aes(x = x, y = y),size=0.3, colour= "#A30845") + 438 | ## Add Chart Title 439 | annotate("text", x = -110 , y = 130, label = Player1_title, size= 5, colour = "#A30845", family = "Helvetica", fontface = "bold", hjust = 0) + 440 | annotate("text", x = 110 , y = 130, label = "FIFA 18 Data", size= 4, colour = "#969696", family = "Helvetica", fontface = "bold", hjust = 1) + 441 | ## Add the player 2 polygon and data points 442 | geom_polygon(data = player2, aes(x=x,y=y),fill = "#00B20B", colour = "#00B20B", alpha = 0.3) + 443 | geom_point(data = player2, aes(x = x, y = y),size=0.3, colour= "#00B20B") + 444 | ## Add the titles for player 2 445 | annotate("text", x = -110 , y = 116, label = Player2_title, size= 5, colour = "#00B20B", family = "Helvetica", fontface = "bold", hjust = 0) + 446 | annotate("text", x = -110 , y = 123 , label = "vrs", size= 3, colour = "#969696", family = "Helvetica", hjust = 0) 447 | ``` 448 | 449 | -------------------------------------------------------------------------------- /3.CreateAPitch.md: -------------------------------------------------------------------------------- 1 | Building a OPTA ready Pitch in R with ggplot2 2 | ================ 3 | 4 | This post is a R conversion of a previous [FC Python's article](https://fcpython.com/tag/radar-chart), follow them on [twitter](www.twitter.com/FC_Python) if you have a desire to learn Python then they are a fantastic resource! 5 | 6 | There are lots of reasons why we might want to draw a line or circle on our charts. We could look to add an average line, highlight a key data point or even draw a picture. This article will show how to add lines, circles and arcs with the example of a football pitch map. 7 | 8 | This example works with FIFA’s offical pitch sizes, but you might want to change them according to your data/sport/needs. We will create this plot with the ggplot2 package so make sure it's installed. 9 | 10 | Before we start, the plotting will throw up some warnings from R, I am going to cheat and turn these off. Do not get into a habit of this! 11 | 12 | ``` r 13 | options(warn=-1) 14 | ``` 15 | 16 | Setting a Theme 17 | --------------- 18 | 19 | Firstly, we need to set a colour theme, I have gone with a adventurous pink scheme which I chose with the help of [Adobe Color Tool](https://color.adobe.com/). 20 | 21 | ``` r 22 | require(ggplot2) 23 | ``` 24 | 25 | ## Loading required package: ggplot2 26 | 27 | ``` r 28 | grass_colour <- "#775D6A" 29 | line_colour <- "#F4828C" 30 | background_colour <- "#775D6A" 31 | goal_colour <- "#7E3C5D" 32 | ``` 33 | 34 | Next we want to create a theme for our plots which will help us control how the plot is displayed via ggplot. We turn a lot of the plot feautres off using element\_blank() and set others to be inline with our colour scheme. 35 | 36 | ``` r 37 | theme_blankPitch = function(size=12) { 38 | theme( 39 | #axis.line=element_blank(), 40 | axis.text.x=element_blank(), 41 | axis.text.y=element_blank(), 42 | #axis.ticks.y=element_text(size=size), 43 | # axis.ticks=element_blank(), 44 | axis.ticks.length=unit(0, "lines"), 45 | #axis.ticks.margin=unit(0, "lines"), 46 | axis.title.x=element_blank(), 47 | axis.title.y=element_blank(), 48 | legend.background=element_rect(fill=background_colour, colour=NA), 49 | legend.key=element_rect(colour=background_colour,fill=background_colour), 50 | legend.key.size=unit(1.2, "lines"), 51 | legend.text=element_text(size=size), 52 | legend.title=element_text(size=size, face="bold",hjust=0), 53 | strip.background = element_rect(colour = background_colour, fill = background_colour, size = .5), 54 | panel.background=element_rect(fill=background_colour,colour=background_colour), 55 | # panel.border=element_blank(), 56 | panel.grid.major=element_blank(), 57 | panel.grid.minor=element_blank(), 58 | panel.spacing=element_blank(), 59 | plot.background=element_blank(), 60 | plot.margin=unit(c(0, 0, 0, 0), "lines"), 61 | plot.title=element_text(size=size*1.2), 62 | strip.text.y=element_text(colour=background_colour,size=size,angle=270), 63 | strip.text.x=element_text(size=size*1))} 64 | ``` 65 | 66 | Creating the Dimension Data 67 | --------------------------- 68 | 69 | Next we define the overall pitch size and store them as variables, we use the unit of cm for added precision. 70 | 71 | ``` r 72 | ymin <- 0 73 | ymax <- 7040 74 | xmin <- 0 75 | xmax <- 10600 76 | ``` 77 | 78 | Our next job is to define and calculate the pitch markings we want to plot 79 | 80 | ``` r 81 | # Defining dimensions 82 | GoalWidth <- 732 83 | penspot <- 1100 84 | boxedgeW <- 4032 85 | boxedgeL <- 1650 86 | box6yardW <- 1832 87 | box6yardL <- 550 88 | 89 | ## dimensions calculations 90 | # The 18 Yard Box 91 | TheBoxWidth <- c(((ymax / 2) + (boxedgeW / 2)),((ymax / 2) - (boxedgeW / 2))) 92 | TheBoxHeight <- c(boxedgeL,xmax-boxedgeL) 93 | GoalPosts <- c(((ymax / 2) + (GoalWidth / 2)),((ymax / 2) - (GoalWidth / 2))) 94 | 95 | # The 6 Yard Box 96 | box6yardWidth <- c(((ymax / 2) + (box6yardW / 2)),((ymax / 2) - (box6yardW / 2))) 97 | box6yardHeight <- c(box6yardL,xmax-box6yardL) 98 | ``` 99 | 100 | Next is to calculate the data we need to plot the circles and arcs. We reuse the circle function from our previous [radar tutorial](https://github.com/FCrSTATS/Visualisations/blob/master/2.BuildingARadar.md) to help. 101 | 102 | ``` r 103 | ## Centre circle dimensions 104 | centreCirle_d <- 1830 105 | 106 | ## define the circle function 107 | circleFun <- function(center = c(0,0),diameter = 1, npoints = 100){ 108 | r = diameter / 2 109 | tt <- seq(0,2*pi,length.out = npoints) 110 | xx <- center[1] + r * cos(tt) 111 | yy <- center[2] + r * sin(tt) 112 | return(data.frame(x = xx, y = yy)) 113 | } 114 | 115 | #### create leftD arc #### 116 | Dleft <- circleFun(c((penspot),(ymax/2)),centreCirle_d,npoints = 1000) 117 | ## remove part that is in the box 118 | Dleft <- Dleft[which(Dleft$x >= (boxedgeL)),] 119 | 120 | ## create rightD arc #### 121 | Dright <- circleFun(c((xmax-(penspot)),(ymax/2)),centreCirle_d,npoints = 1000) 122 | ## remove part that is in the box 123 | Dright <- Dright[which(Dright$x <= (xmax-(boxedgeL))),] 124 | 125 | #### create center circle #### 126 | center_circle <- circleFun(c((xmax/2),(ymax/2)),centreCirle_d,npoints = 100) 127 | 128 | ## create corner flag radius #### 129 | TopLeftCorner <- circleFun(c(xmin,ymax),200,npoints = 1000) 130 | TopRightCorner <- circleFun(c(xmax,ymax),200,npoints = 1000) 131 | BottomLeftCorner <- circleFun(c(xmin,ymin),200,npoints = 1000) 132 | BottomRightCorner <- circleFun(c(xmax,ymin),200,npoints = 1000) 133 | ``` 134 | 135 | Let's Get Plotting 136 | ------------------ 137 | 138 | Now we have all the data we will need to generate the pitch plot. So let's build the plot layer-by-layer, step-by-step to help us understand the code. 139 | 140 | ``` r 141 | ## initiate the plot and set its boundaries 142 | ggplot() + xlim(c(-10,xmax+10)) + ylim(c(-10,ymax+10)) 143 | ``` 144 | 145 |  146 | 147 | Now let's utilise the theme that we have created to give us the blank canvas and styling rules we want. 148 | 149 | ``` r 150 | ## initiate the plot and set its boundaries 151 | ggplot() + xlim(c(-10,xmax+10)) + ylim(c(-10,ymax+10))+ 152 | # add the theme 153 | theme_blankPitch() 154 | ``` 155 | 156 |  157 | 158 | Now that we have a blank canvas let's start adding the pitch elements. 159 | 160 | The border: 161 | 162 | ``` r 163 | ## initiate the plot and set its boundaries 164 | ggplot() + xlim(c(-10,xmax+10)) + ylim(c(-10,ymax+10))+ 165 | # add the theme 166 | theme_blankPitch() + 167 | # add the base rectangle of the pitch 168 | geom_rect(aes(xmin=0, xmax=xmax, ymin=0, ymax=ymax), fill = grass_colour, colour = line_colour) 169 | ``` 170 | 171 |  172 | 173 | The 18 yard boxes: 174 | 175 | ``` r 176 | ## initiate the plot and set its boundaries 177 | ggplot() + xlim(c(-10,xmax+10)) + ylim(c(-10,ymax+10))+ 178 | # add the theme 179 | theme_blankPitch() + 180 | # add the base rectangle of the pitch 181 | geom_rect(aes(xmin=0, xmax=xmax, ymin=0, ymax=ymax), fill = grass_colour, colour = line_colour) + 182 | # add the 18 yard box Left 183 | geom_rect(aes(xmin=0, xmax=TheBoxHeight[1], ymin=TheBoxWidth[1], ymax=TheBoxWidth[2]), fill = grass_colour, colour = line_colour) + 184 | # add the 18 yard box Right 185 | geom_rect(aes(xmin=TheBoxHeight[2], xmax=xmax, ymin=TheBoxWidth[1], ymax=TheBoxWidth[2]), fill = grass_colour, colour = line_colour) 186 | ``` 187 | 188 |  189 | 190 | The 6 yard boxes: 191 | 192 | ``` r 193 | ## initiate the plot and set its boundaries 194 | ggplot() + xlim(c(-10,xmax+10)) + ylim(c(-10,ymax+10))+ 195 | # add the theme 196 | theme_blankPitch() + 197 | # add the base rectangle of the pitch 198 | geom_rect(aes(xmin=0, xmax=xmax, ymin=0, ymax=ymax), fill = grass_colour, colour = line_colour) + 199 | # add the 18 yard box Left 200 | geom_rect(aes(xmin=0, xmax=TheBoxHeight[1], ymin=TheBoxWidth[1], ymax=TheBoxWidth[2]), fill = grass_colour, colour = line_colour) + 201 | # add the 18 yard box Right 202 | geom_rect(aes(xmin=TheBoxHeight[2], xmax=xmax, ymin=TheBoxWidth[1], ymax=TheBoxWidth[2]), fill = grass_colour, colour = line_colour) + 203 | # add the six yard box Left 204 | geom_rect(aes(xmin=0, xmax=box6yardHeight[1], ymin=box6yardWidth[1], ymax=box6yardWidth[2]), fill = grass_colour, colour = line_colour) + 205 | # add the six yard box Right 206 | geom_rect(aes(xmin=box6yardHeight[2], xmax=xmax, ymin=box6yardWidth[1], ymax=box6yardWidth[2]), fill = grass_colour, colour = line_colour) 207 | ``` 208 | 209 |  210 | 211 | The half-way line: 212 | 213 | ``` r 214 | ## initiate the plot and set its boundaries 215 | ggplot() + xlim(c(-10,xmax+10)) + ylim(c(-10,ymax+10))+ 216 | # add the theme 217 | theme_blankPitch() + 218 | # add the base rectangle of the pitch 219 | geom_rect(aes(xmin=0, xmax=xmax, ymin=0, ymax=ymax), fill = grass_colour, colour = line_colour) + 220 | # add the 18 yard box Left 221 | geom_rect(aes(xmin=0, xmax=TheBoxHeight[1], ymin=TheBoxWidth[1], ymax=TheBoxWidth[2]), fill = grass_colour, colour = line_colour) + 222 | # add the 18 yard box Right 223 | geom_rect(aes(xmin=TheBoxHeight[2], xmax=xmax, ymin=TheBoxWidth[1], ymax=TheBoxWidth[2]), fill = grass_colour, colour = line_colour) + 224 | # add the six yard box Left 225 | geom_rect(aes(xmin=0, xmax=box6yardHeight[1], ymin=box6yardWidth[1], ymax=box6yardWidth[2]), fill = grass_colour, colour = line_colour) + 226 | # add the six yard box Right 227 | geom_rect(aes(xmin=box6yardHeight[2], xmax=xmax, ymin=box6yardWidth[1], ymax=box6yardWidth[2]), fill = grass_colour, colour = line_colour) + 228 | # Add half way line 229 | geom_segment(aes(x = xmax/2, y = ymin, xend = xmax/2, yend = ymax),colour = line_colour) 230 | ``` 231 | 232 |  233 | 234 | The Left and Right D arcs: 235 | 236 | ``` r 237 | ## initiate the plot and set its boundaries 238 | ggplot() + xlim(c(-10,xmax+10)) + ylim(c(-10,ymax+10))+ 239 | # add the theme 240 | theme_blankPitch() + 241 | # add the base rectangle of the pitch 242 | geom_rect(aes(xmin=0, xmax=xmax, ymin=0, ymax=ymax), fill = grass_colour, colour = line_colour) + 243 | # add the 18 yard box Left 244 | geom_rect(aes(xmin=0, xmax=TheBoxHeight[1], ymin=TheBoxWidth[1], ymax=TheBoxWidth[2]), fill = grass_colour, colour = line_colour) + 245 | # add the 18 yard box Right 246 | geom_rect(aes(xmin=TheBoxHeight[2], xmax=xmax, ymin=TheBoxWidth[1], ymax=TheBoxWidth[2]), fill = grass_colour, colour = line_colour) + 247 | # add the six yard box Left 248 | geom_rect(aes(xmin=0, xmax=box6yardHeight[1], ymin=box6yardWidth[1], ymax=box6yardWidth[2]), fill = grass_colour, colour = line_colour) + 249 | # add the six yard box Right 250 | geom_rect(aes(xmin=box6yardHeight[2], xmax=xmax, ymin=box6yardWidth[1], ymax=box6yardWidth[2]), fill = grass_colour, colour = line_colour) + 251 | # Add half way line 252 | geom_segment(aes(x = xmax/2, y = ymin, xend = xmax/2, yend = ymax),colour = line_colour) + 253 | # add left D 254 | geom_path(data=Dleft, aes(x=x,y=y), colour = line_colour) + 255 | # add Right D 256 | geom_path(data=Dright, aes(x=x,y=y), colour = line_colour) 257 | ``` 258 | 259 |  260 | 261 | The Center-Circle: 262 | 263 | ``` r 264 | ## initiate the plot and set its boundaries 265 | ggplot() + xlim(c(-10,xmax+10)) + ylim(c(-10,ymax+10))+ 266 | # add the theme 267 | theme_blankPitch() + 268 | # add the base rectangle of the pitch 269 | geom_rect(aes(xmin=0, xmax=xmax, ymin=0, ymax=ymax), fill = grass_colour, colour = line_colour) + 270 | # add the 18 yard box Left 271 | geom_rect(aes(xmin=0, xmax=TheBoxHeight[1], ymin=TheBoxWidth[1], ymax=TheBoxWidth[2]), fill = grass_colour, colour = line_colour) + 272 | # add the 18 yard box Right 273 | geom_rect(aes(xmin=TheBoxHeight[2], xmax=xmax, ymin=TheBoxWidth[1], ymax=TheBoxWidth[2]), fill = grass_colour, colour = line_colour) + 274 | # add the six yard box Left 275 | geom_rect(aes(xmin=0, xmax=box6yardHeight[1], ymin=box6yardWidth[1], ymax=box6yardWidth[2]), fill = grass_colour, colour = line_colour) + 276 | # add the six yard box Right 277 | geom_rect(aes(xmin=box6yardHeight[2], xmax=xmax, ymin=box6yardWidth[1], ymax=box6yardWidth[2]), fill = grass_colour, colour = line_colour) + 278 | # Add half way line 279 | geom_segment(aes(x = xmax/2, y = ymin, xend = xmax/2, yend = ymax),colour = line_colour) + 280 | # add left D 281 | geom_path(data=Dleft, aes(x=x,y=y), colour = line_colour) + 282 | # add Right D 283 | geom_path(data=Dright, aes(x=x,y=y), colour = line_colour) + 284 | # add centre circle 285 | geom_path(data=center_circle, aes(x=x,y=y), colour = line_colour) 286 | ``` 287 | 288 |  289 | 290 | The Penalty and Center-Cirle Spots: 291 | 292 | ``` r 293 | ## initiate the plot and set its boundaries 294 | ggplot() + xlim(c(-10,xmax+10)) + ylim(c(-10,ymax+10))+ 295 | # add the theme 296 | theme_blankPitch() + 297 | # add the base rectangle of the pitch 298 | geom_rect(aes(xmin=0, xmax=xmax, ymin=0, ymax=ymax), fill = grass_colour, colour = line_colour) + 299 | # add the 18 yard box Left 300 | geom_rect(aes(xmin=0, xmax=TheBoxHeight[1], ymin=TheBoxWidth[1], ymax=TheBoxWidth[2]), fill = grass_colour, colour = line_colour) + 301 | # add the 18 yard box Right 302 | geom_rect(aes(xmin=TheBoxHeight[2], xmax=xmax, ymin=TheBoxWidth[1], ymax=TheBoxWidth[2]), fill = grass_colour, colour = line_colour) + 303 | # add the six yard box Left 304 | geom_rect(aes(xmin=0, xmax=box6yardHeight[1], ymin=box6yardWidth[1], ymax=box6yardWidth[2]), fill = grass_colour, colour = line_colour) + 305 | # add the six yard box Right 306 | geom_rect(aes(xmin=box6yardHeight[2], xmax=xmax, ymin=box6yardWidth[1], ymax=box6yardWidth[2]), fill = grass_colour, colour = line_colour) + 307 | # Add half way line 308 | geom_segment(aes(x = xmax/2, y = ymin, xend = xmax/2, yend = ymax),colour = line_colour) + 309 | # add left D 310 | geom_path(data=Dleft, aes(x=x,y=y), colour = line_colour) + 311 | # add Right D 312 | geom_path(data=Dright, aes(x=x,y=y), colour = line_colour) + 313 | # add centre circle 314 | geom_path(data=center_circle, aes(x=x,y=y), colour = line_colour) + 315 | # add penalty spot left 316 | geom_point(aes(x = penspot , y = ymax/2), colour = line_colour) + 317 | # add penalty spot right 318 | geom_point(aes(x = (xmax-(penspot)) , y = ymax/2), colour = line_colour) + 319 | # add centre spot 320 | geom_point(aes(x = (xmax/2) , y = ymax/2), colour = line_colour) 321 | ``` 322 | 323 |  324 | 325 | The corner flag borders: 326 | 327 | ``` r 328 | ## initiate the plot and set its boundaries 329 | ggplot() + xlim(c(-10,xmax+10)) + ylim(c(-10,ymax+10))+ 330 | # add the theme 331 | theme_blankPitch() + 332 | # add the base rectangle of the pitch 333 | geom_rect(aes(xmin=0, xmax=xmax, ymin=0, ymax=ymax), fill = grass_colour, colour = line_colour) + 334 | # add the 18 yard box Left 335 | geom_rect(aes(xmin=0, xmax=TheBoxHeight[1], ymin=TheBoxWidth[1], ymax=TheBoxWidth[2]), fill = grass_colour, colour = line_colour) + 336 | # add the 18 yard box Right 337 | geom_rect(aes(xmin=TheBoxHeight[2], xmax=xmax, ymin=TheBoxWidth[1], ymax=TheBoxWidth[2]), fill = grass_colour, colour = line_colour) + 338 | # add the six yard box Left 339 | geom_rect(aes(xmin=0, xmax=box6yardHeight[1], ymin=box6yardWidth[1], ymax=box6yardWidth[2]), fill = grass_colour, colour = line_colour) + 340 | # add the six yard box Right 341 | geom_rect(aes(xmin=box6yardHeight[2], xmax=xmax, ymin=box6yardWidth[1], ymax=box6yardWidth[2]), fill = grass_colour, colour = line_colour) + 342 | # Add half way line 343 | geom_segment(aes(x = xmax/2, y = ymin, xend = xmax/2, yend = ymax),colour = line_colour) + 344 | # add left D 345 | geom_path(data=Dleft, aes(x=x,y=y), colour = line_colour) + 346 | # add Right D 347 | geom_path(data=Dright, aes(x=x,y=y), colour = line_colour) + 348 | # add centre circle 349 | geom_path(data=center_circle, aes(x=x,y=y), colour = line_colour) + 350 | # add penalty spot left 351 | geom_point(aes(x = penspot , y = ymax/2), colour = line_colour) + 352 | # add penalty spot right 353 | geom_point(aes(x = (xmax-(penspot)) , y = ymax/2), colour = line_colour) + 354 | # add centre spot 355 | geom_point(aes(x = (xmax/2) , y = ymax/2), colour = line_colour) + 356 | # add Corner Flag corners 357 | geom_path(data=TopLeftCorner, aes(x=x,y=y), colour = line_colour) + 358 | geom_path(data=TopRightCorner, aes(x=x,y=y), colour = line_colour) + 359 | geom_path(data=BottomLeftCorner, aes(x=x,y=y), colour = line_colour) + 360 | geom_path(data=BottomRightCorner, aes(x=x,y=y), colour = line_colour) 361 | ``` 362 | 363 |  364 | 365 | Last but not least the Goals 366 | 367 | ``` r 368 | ## initiate the plot and set its boundaries 369 | ggplot() + xlim(c(-10,xmax+10)) + ylim(c(-10,ymax+10))+ 370 | # add the theme 371 | theme_blankPitch() + 372 | # add the base rectangle of the pitch 373 | geom_rect(aes(xmin=0, xmax=xmax, ymin=0, ymax=ymax), fill = grass_colour, colour = line_colour) + 374 | # add the 18 yard box Left 375 | geom_rect(aes(xmin=0, xmax=TheBoxHeight[1], ymin=TheBoxWidth[1], ymax=TheBoxWidth[2]), fill = grass_colour, colour = line_colour) + 376 | # add the 18 yard box Right 377 | geom_rect(aes(xmin=TheBoxHeight[2], xmax=xmax, ymin=TheBoxWidth[1], ymax=TheBoxWidth[2]), fill = grass_colour, colour = line_colour) + 378 | # add the six yard box Left 379 | geom_rect(aes(xmin=0, xmax=box6yardHeight[1], ymin=box6yardWidth[1], ymax=box6yardWidth[2]), fill = grass_colour, colour = line_colour) + 380 | # add the six yard box Right 381 | geom_rect(aes(xmin=box6yardHeight[2], xmax=xmax, ymin=box6yardWidth[1], ymax=box6yardWidth[2]), fill = grass_colour, colour = line_colour) + 382 | # Add half way line 383 | geom_segment(aes(x = xmax/2, y = ymin, xend = xmax/2, yend = ymax),colour = line_colour) + 384 | # add left D 385 | geom_path(data=Dleft, aes(x=x,y=y), colour = line_colour) + 386 | # add Right D 387 | geom_path(data=Dright, aes(x=x,y=y), colour = line_colour) + 388 | # add centre circle 389 | geom_path(data=center_circle, aes(x=x,y=y), colour = line_colour) + 390 | # add penalty spot left 391 | geom_point(aes(x = penspot , y = ymax/2), colour = line_colour) + 392 | # add penalty spot right 393 | geom_point(aes(x = (xmax-(penspot)) , y = ymax/2), colour = line_colour) + 394 | # add centre spot 395 | geom_point(aes(x = (xmax/2) , y = ymax/2), colour = line_colour) + 396 | # add Corner Flag corners 397 | geom_path(data=TopLeftCorner, aes(x=x,y=y), colour = line_colour) + 398 | geom_path(data=TopRightCorner, aes(x=x,y=y), colour = line_colour) + 399 | geom_path(data=BottomLeftCorner, aes(x=x,y=y), colour = line_colour) + 400 | geom_path(data=BottomRightCorner, aes(x=x,y=y), colour = line_colour) + 401 | # add the goal left 402 | geom_segment(aes(x = xmin, y = GoalPosts[1], xend = xmin, yend = GoalPosts[2]),colour = goal_colour, size = 1) + 403 | # add the goal right 404 | geom_segment(aes(x = xmax, y = GoalPosts[1], xend = xmax, yend = GoalPosts[2]),colour = goal_colour, size = 1) 405 | ``` 406 | 407 |  408 | 409 | Changing it to a Function 410 | ------------------------- 411 | 412 | This is a lot of code and would be much easier to manage and re-use if it was a function. 413 | 414 | We need to decide which variables we may want to change in later use and then pass these to the function rather than defining them within the function. The obvious variables to change are the color scheme and the pitch dimensions. 415 | 416 | We will use function() and then pass the variables like this: 417 | 418 | function(xmax, ymax, grass\_colour, line\_colour, background\_colour, goal\_colour) 419 | 420 | We then delete where we have defined these in the original code as we wouldn't want to define them twice. 421 | 422 | The full function code below: 423 | 424 | ``` r 425 | createPitch <- function(xmax, ymax, grass_colour, line_colour, background_colour, goal_colour){ 426 | theme_blankPitch = function(size=12) { 427 | theme( 428 | #axis.line=element_blank(), 429 | axis.text.x=element_blank(), 430 | axis.text.y=element_blank(), 431 | #axis.ticks.y=element_text(size=size), 432 | # axis.ticks=element_blank(), 433 | axis.ticks.length=unit(0, "lines"), 434 | #axis.ticks.margin=unit(0, "lines"), 435 | axis.title.x=element_blank(), 436 | axis.title.y=element_blank(), 437 | legend.background=element_rect(fill=background_colour, colour=NA), 438 | legend.key=element_rect(colour=background_colour,fill=background_colour), 439 | legend.key.size=unit(1.2, "lines"), 440 | legend.text=element_text(size=size), 441 | legend.title=element_text(size=size, face="bold",hjust=0), 442 | strip.background = element_rect(colour = background_colour, fill = background_colour, size = .5), 443 | panel.background=element_rect(fill=background_colour,colour=background_colour), 444 | # panel.border=element_blank(), 445 | panel.grid.major=element_blank(), 446 | panel.grid.minor=element_blank(), 447 | panel.spacing=element_blank(), 448 | plot.background=element_blank(), 449 | plot.margin=unit(c(0, 0, 0, 0), "lines"), 450 | plot.title=element_text(size=size*1.2), 451 | strip.text.y=element_text(colour=background_colour,size=size,angle=270), 452 | strip.text.x=element_text(size=size*1))} 453 | 454 | ymin <- 0 455 | xmin <- 0 456 | 457 | # Defining dimensions 458 | GoalWidth <- 732 459 | penspot <- 1100 460 | boxedgeW <- 4032 461 | boxedgeL <- 1650 462 | box6yardW <- 1832 463 | box6yardL <- 550 464 | 465 | ## dimensions calculations 466 | # The 18 Yard Box 467 | TheBoxWidth <- c(((ymax / 2) + (boxedgeW / 2)),((ymax / 2) - (boxedgeW / 2))) 468 | TheBoxHeight <- c(boxedgeL,xmax-boxedgeL) 469 | GoalPosts <- c(((ymax / 2) + (GoalWidth / 2)),((ymax / 2) - (GoalWidth / 2))) 470 | 471 | # The 6 Yard Box 472 | box6yardWidth <- c(((ymax / 2) + (box6yardW / 2)),((ymax / 2) - (box6yardW / 2))) 473 | box6yardHeight <- c(box6yardL,xmax-box6yardL) 474 | 475 | ## Centre circle dimensions 476 | centreCirle_d <- 1830 477 | 478 | ## define the circle function 479 | circleFun <- function(center = c(0,0),diameter = 1, npoints = 100){ 480 | r = diameter / 2 481 | tt <- seq(0,2*pi,length.out = npoints) 482 | xx <- center[1] + r * cos(tt) 483 | yy <- center[2] + r * sin(tt) 484 | return(data.frame(x = xx, y = yy)) 485 | } 486 | 487 | #### create leftD arc #### 488 | Dleft <- circleFun(c((penspot),(ymax/2)),centreCirle_d,npoints = 1000) 489 | ## remove part that is in the box 490 | Dleft <- Dleft[which(Dleft$x >= (boxedgeL)),] 491 | 492 | ## create rightD arc #### 493 | Dright <- circleFun(c((xmax-(penspot)),(ymax/2)),centreCirle_d,npoints = 1000) 494 | ## remove part that is in the box 495 | Dright <- Dright[which(Dright$x <= (xmax-(boxedgeL))),] 496 | 497 | #### create center circle #### 498 | center_circle <- circleFun(c((xmax/2),(ymax/2)),centreCirle_d,npoints = 100) 499 | 500 | ## create corner flag radius #### 501 | TopLeftCorner <- circleFun(c(xmin,ymax),200,npoints = 1000) 502 | TopRightCorner <- circleFun(c(xmax,ymax),200,npoints = 1000) 503 | BottomLeftCorner <- circleFun(c(xmin,ymin),200,npoints = 1000) 504 | BottomRightCorner <- circleFun(c(xmax,ymin),200,npoints = 1000) 505 | 506 | p <- ggplot() + xlim(c(-10,xmax+10)) + ylim(c(-10,ymax+10)) + 507 | # add the theme 508 | theme_blankPitch() + 509 | # add the base rectangle of the pitch 510 | geom_rect(aes(xmin=0, xmax=xmax, ymin=0, ymax=ymax), fill = grass_colour, colour = line_colour) + 511 | # add the 18 yard box Left 512 | geom_rect(aes(xmin=0, xmax=TheBoxHeight[1], ymin=TheBoxWidth[1], ymax=TheBoxWidth[2]), fill = grass_colour, colour = line_colour) + 513 | # add the 18 yard box Right 514 | geom_rect(aes(xmin=TheBoxHeight[2], xmax=xmax, ymin=TheBoxWidth[1], ymax=TheBoxWidth[2]), fill = grass_colour, colour = line_colour) + 515 | # add the six yard box Left 516 | geom_rect(aes(xmin=0, xmax=box6yardHeight[1], ymin=box6yardWidth[1], ymax=box6yardWidth[2]), fill = grass_colour, colour = line_colour) + 517 | # add the six yard box Right 518 | geom_rect(aes(xmin=box6yardHeight[2], xmax=xmax, ymin=box6yardWidth[1], ymax=box6yardWidth[2]), fill = grass_colour, colour = line_colour) + 519 | # Add half way line 520 | geom_segment(aes(x = xmax/2, y = ymin, xend = xmax/2, yend = ymax),colour = line_colour) + 521 | # add left D 522 | geom_path(data=Dleft, aes(x=x,y=y), colour = line_colour) + 523 | # add Right D 524 | geom_path(data=Dright, aes(x=x,y=y), colour = line_colour) + 525 | # add centre circle 526 | geom_path(data=center_circle, aes(x=x,y=y), colour = line_colour) + 527 | # add penalty spot left 528 | geom_point(aes(x = penspot , y = ymax/2), colour = line_colour) + 529 | # add penalty spot right 530 | geom_point(aes(x = (xmax-(penspot)) , y = ymax/2), colour = line_colour) + 531 | # add centre spot 532 | geom_point(aes(x = (xmax/2) , y = ymax/2), colour = line_colour) + 533 | # add Corner Flag corners 534 | geom_path(data=TopLeftCorner, aes(x=x,y=y), colour = line_colour) + 535 | geom_path(data=TopRightCorner, aes(x=x,y=y), colour = line_colour) + 536 | geom_path(data=BottomLeftCorner, aes(x=x,y=y), colour = line_colour) + 537 | geom_path(data=BottomRightCorner, aes(x=x,y=y), colour = line_colour) + 538 | geom_segment(aes(x = xmin, y = GoalPosts[1], xend = xmin, yend = GoalPosts[2]),colour = goal_colour, size = 1) + 539 | # add the goal right 540 | geom_segment(aes(x = xmax, y = GoalPosts[1], xend = xmax, yend = GoalPosts[2]),colour = goal_colour, size = 1) 541 | return(p) 542 | } 543 | ``` 544 | Now that we have created our function we can use it to create varations of pitches: 545 | 546 | ``` r 547 | ## you can define the values first like below 548 | grass_colour <- "#775D6A" 549 | line_colour <- "#F4828C" 550 | background_colour <- "#775D6A" 551 | goal_colour <- "#7E3C5D" 552 | ymax <- 7040 553 | xmax <- 10600 554 | 555 | createPitch(xmax, ymax, grass_colour, line_colour, background_colour, goal_colour) 556 | ``` 557 | 558 |  559 | 560 | ``` r 561 | ## or you can define the values as you call the function 562 | 563 | createPitch(10600, 7040, "#775D6A", "#F4828C", "#775D6A", "#7E3C5D") 564 | ``` 565 | 566 | Try some other variations 567 | ------------------------- 568 | 569 | ``` r 570 | ## green 571 | createPitch(10600, 7040, "#538032", "#ffffff", "#538032", "#000000") 572 | ``` 573 | 574 |  575 | 576 | ``` r 577 | ## night theme 578 | createPitch(10600, 7040, "#202020", "#797876", "#202020", "#131313") 579 | ``` 580 | 581 |  582 | 583 | ``` r 584 | ## Blue 585 | createPitch(10600, 7040, "#224C56", "#B3CED9", "#224C56", "#15393D") 586 | ``` 587 | 588 |  589 | 590 | ## Take the Challenge!! 591 | 592 | See if you can recreate the Juego de Posicion pitch markings like this: 593 | 594 |  595 | -------------------------------------------------------------------------------- /4.MappingRecieves.md: -------------------------------------------------------------------------------- 1 | Mapping Recieves on an Football Pitch with ggplot2 2 | ================ 3 | 4 | For this activity we are going to use the pitch plot function from the [last tutorial](https://github.com/FCrSTATS/Visualisations/blob/master/3.CreateAPitch.md). We could copy and paste the code into this file, this would work but our new script would be a bit messy. A better way to achieve this is to put the function script in a different file and the load it. 5 | 6 | I have saved the function in a file called PitchCreate.r and stored it in my working directory. It's easy to load: 7 | 8 | ``` r 9 | source("PitchCreate.R") 10 | options(warn=-1) # this turns warnings off, don't get into a habit of doing this 11 | require(ggplot2) 12 | ``` 13 | 14 | ## Loading required package: ggplot2 15 | 16 | Now it's loaded lets feed the function some variables to get the base of our plot: 17 | 18 | ``` r 19 | grass_colour <- "#202020" 20 | line_colour <- "#797876" 21 | background_colour <- "#202020" 22 | goal_colour <- "#131313" 23 | jp_colour <- "#F76065" 24 | jp_include <- TRUE 25 | ymax <- 7040 26 | xmax <- 10600 27 | jp_alpha <- 0.1 28 | 29 | pitchBase <- createPitchJP(xmax, ymax, grass_colour, line_colour, background_colour, goal_colour, jp_colour, jp_include, jp_alpha) 30 | 31 | pitchBase 32 | ``` 33 | 34 |  35 | 36 | Our base pitch map is ready! We are going to map the locations of every time Mesut Ozil recevies the ball. We will do this by producing some fake data as we don't have access to OPTA data. Let's say he recevied the ball 50 times in a match, we use the knowledge we developed in our [random number and expected goals tutorial](https://github.com/FCrSTATS/R_basics/blob/master/9.RandomExpectedGoals.md): 37 | 38 | ``` r 39 | ## let's create a function to generate some fake locations 40 | x <- runif(50,0,100) 41 | y <- runif(50,0,100) 42 | 43 | # now we have the OPTA style x,y which is displayed as a % of the x and a % of the y of a pitch. But we want to convert these to use on our pitch. 44 | x2 <- (x/100) * xmax 45 | y2 <- (y/100) * ymax 46 | ``` 47 | 48 | Now let's plot the touches using geom\_point(). We need to feed it the x2 and y2 data and then tell it which colour the dots should be: 49 | 50 | ``` r 51 | pitchBase + geom_point(aes(x=x2,y=y2),colour = "blue") 52 | ``` 53 | 54 |  55 | 56 | Ozil is attacking from left to right. Let's try and make the fake data more realistic for his position. We do this by generating a random number centered around a different mean and adjusting the standard deviation. 57 | 58 | First we setup a function that takes the variables: n - number of numbers you want to generate mean - the mean of the probabilty distribution sd - standard deviation of the sample 59 | 60 | Below we create 50 numbers for our x values, we put a mean of 70 which means our locations are more likely to be in the opposition half. Our y mean at 60 to give it a slight bias to being on the left of the pitch. The standard deviation we set at 25. 61 | 62 | Let's see the results 63 | 64 | ``` r 65 | ## 66 | 67 | give_a_bias_number <- function(n,mean,sd) { mean+sd*scale(rnorm(n)) } 68 | 69 | x <- give_a_bias_number(50,70,25) 70 | y <- give_a_bias_number(50,60,25) 71 | 72 | # now we have the OPTA style x,y which is displayed as a % of the x and a % of the y of a pitch. But we want to convert these to use on our pitch. 73 | x2 <- (x/100) * xmax 74 | y2 <- (y/100) * ymax 75 | 76 | pitchBase + geom_point(aes(x=x2,y=y2),colour = "blue") 77 | ``` 78 | 79 |  80 | 81 | This is much more realistic! Try playing with the variables of our give\_a\_bias\_number() function. Play with the variables to try and create realistic receiving coorindates for other positions... like right full-back. 82 | 83 |  84 | -------------------------------------------------------------------------------- /5.TernaryPlotsandKMeans.md: -------------------------------------------------------------------------------- 1 | Ternary Plots and K-means Clustering 2 | ================ 3 | 4 | If a player gets possession of the ball there are only three positive onward outcomes, she passess, she shoots or she dribbles. We can present these as decision %'s ... i.e. She passes 65% of the time, whilst shooting 23% of the time and then dribbles 12% of the time. 5 | 6 | Let's create some fake data for a league of players using the knowledge we learnt [here](https://github.com/FCrSTATS/R_basics/blob/master/5.WhileLoops.md) and [here](https://github.com/FCrSTATS/R_basics/blob/master/9.RandomExpectedGoals.md). 7 | 8 | ``` r 9 | ## Create an empty dataframe that we use to 'catch' the results from our while loop. 10 | Results <- data.frame(Club = character(), Player = character(), Pass = numeric(), Dribble = numeric(), Shoot = numeric(), stringsAsFactors = F) 11 | 12 | ## We define a few varibles here which will allow us to monitor our while loop as well as create club and player IDs 13 | noPlayers <- 500 14 | noClubs <- 25 15 | PlayerCounter <- 1 16 | ClubCounter <- 1 17 | PlayersInTeamCounter <- 1 18 | 19 | ## Run a while loop for the number of players in our league 20 | while(PlayerCounter <= noPlayers){ 21 | 22 | Player <- paste0("Player",PlayerCounter) # create a playerID 23 | Club <- paste0("Club",ClubCounter) # create a club ID 24 | x <- runif(1,0,100) # create a random number 25 | y <- runif(1,0,(100-x)) # create another random number witbin the remaining range (100 - x) 26 | z <- 100 - (x+y) # calculate last number which is simply 100 - the first two numbers 27 | 28 | # create a dataframe for this one player 29 | temp <- data.frame(Club = Club, Player = Player, Pass = x, Dribble = y, Shoot = z, stringsAsFactors = F) 30 | 31 | # bind the player results to the overall league results 32 | Results <- rbind(Results, temp) 33 | 34 | # Add 1 to the player counter as we have processed another player 35 | PlayerCounter <- PlayerCounter + 1 36 | 37 | # Add 1 to the players in the team counter 38 | PlayersInTeamCounter <- PlayersInTeamCounter + 1 39 | 40 | # Work out it the team contains 25 players 41 | UpTeam <- if((PlayersInTeamCounter / noClubs)%%1==0){TRUE}else{FALSE} 42 | 43 | # Up date the players in the team counter 44 | PlayersInTeamCounter <- if((PlayersInTeamCounter / noClubs)%%1==0){1}else{PlayersInTeamCounter} 45 | 46 | # If the players were more than 25 then update the club counter 47 | ClubCounter <- if(UpTeam){ClubCounter + 1}else{ClubCounter} 48 | } 49 | 50 | ## Print the top 5 rows to see if everything worked ok 51 | head(Results) 52 | ``` 53 | 54 | ## Club Player Pass Dribble Shoot 55 | ## 1 Club1 Player1 83.989731 10.404360 5.6059098 56 | ## 2 Club1 Player2 72.140048 27.267614 0.5923382 57 | ## 3 Club1 Player3 8.271803 82.067260 9.6609370 58 | ## 4 Club1 Player4 96.656463 1.475396 1.8681409 59 | ## 5 Club1 Player5 47.617233 19.929625 32.4531419 60 | ## 6 Club1 Player6 34.791456 49.700572 15.5079716 61 | 62 | But how do we plot this concisely? If we use two axis, i.e. Pass and Shoot, we will struggle to show the Dribble information. What we really want to do is to plot our data on three axis... enter Ternary Plots. 63 | 64 | Ternary Plots allow us to plot information on three axis with ease, they take a little time to get use to reading but once you get your eye in they are very useful. 65 | 66 | With many things that you want to achieve in R, there is often a package. ggtern provides us with a massive helping hand, make sure it's installed and let's plot our data. 67 | 68 | ``` r 69 | require(ggtern) 70 | ``` 71 | 72 | ## Loading required package: ggtern 73 | 74 | ## Loading required package: ggplot2 75 | 76 | ## -- 77 | ## Consider donating at: http://ggtern.com 78 | ## Even small amounts (say $10-50) are very much appreciated! 79 | ## Remember to cite, run citation(package = 'ggtern') for further info. 80 | ## -- 81 | 82 | ## 83 | ## Attaching package: 'ggtern' 84 | 85 | ## The following objects are masked from 'package:ggplot2': 86 | ## 87 | ## %+%, aes, annotate, calc_element, ggplot, ggplot_build, 88 | ## ggplot_gtable, ggplotGrob, ggsave, layer_data, theme, 89 | ## theme_bw, theme_classic, theme_dark, theme_gray, theme_light, 90 | ## theme_linedraw, theme_minimal, theme_void 91 | 92 | ``` r 93 | #Create the plot and store 94 | plot <- ggtern(data = Results, aes(x = Pass, y = Dribble, z = Shoot)) + 95 | geom_point(size = 2, 96 | shape = 21, 97 | color = "black", 98 | fill="black") 99 | 100 | #Render 101 | plot 102 | ``` 103 | 104 |  105 | 106 | 107 | Fantastic, we have each of our players plotted and this provides us an overview of the players in our fake league. Maybe, we want to see how one specific player lays within the dataset... let's see where Player12 sits. 108 | 109 | ``` r 110 | PlayerSelect <- "Player27" 111 | playerHightlight <- Results[which(Results$Player == PlayerSelect),] 112 | 113 | require(ggtern) 114 | #Create the plot and store 115 | plot <- ggtern(data = Results, aes(x = Pass, y = Dribble, z = Shoot)) + 116 | geom_point(size = 2, 117 | shape = 21, 118 | color = "black", 119 | fill= "black") + 120 | geom_point(data = playerHightlight, aes(x = Pass, y = Dribble, z = Shoot), 121 | size = 2, 122 | shape = 21, 123 | color = "black", 124 | fill= "red") 125 | ``` 126 | 127 | ## Warning: Ignoring unknown aesthetics: z 128 | 129 | ``` r 130 | #Render 131 | plot 132 | ``` 133 | 134 |  135 | 136 | This is a little difficult to see so let's make the other datapoints grey and not black. 137 | 138 | ``` r 139 | #Create the plot and store 140 | plot <- ggtern(data = Results, aes(x = Pass, y = Dribble, z = Shoot)) + 141 | geom_point(size = 2, 142 | shape = 21, 143 | color = "grey", 144 | fill= "grey") + 145 | geom_point(data = playerHightlight, aes(x = Pass, y = Dribble, z = Shoot), 146 | size = 2, 147 | shape = 21, 148 | color = "black", 149 | fill= "red") 150 | ``` 151 | 152 | ## Warning: Ignoring unknown aesthetics: z 153 | 154 | ``` r 155 | #Render 156 | plot 157 | ``` 158 | 159 |  160 | 161 | This is much better and it's easy to see how the player fits within the overal dataset. Maybe we would want to see how a full team of players fit within the dataset. 162 | 163 | ``` r 164 | ClubSelect <- "Club5" 165 | ClubHightlight <- Results[which(Results$Club == ClubSelect),] 166 | 167 | require(ggtern) 168 | #Create the plot and store 169 | plot <- ggtern(data = Results, aes(x = Pass, y = Dribble, z = Shoot)) + 170 | geom_point(size = 2, 171 | shape = 21, 172 | color = "grey", 173 | fill= "grey") + 174 | geom_point(data = ClubHightlight, aes(x = Pass, y = Dribble, z = Shoot), 175 | size = 2, 176 | shape = 21, 177 | color = "black", 178 | fill= "red") 179 | ``` 180 | 181 | ## Warning: Ignoring unknown aesthetics: z 182 | 183 | ``` r 184 | #Render 185 | plot 186 | ``` 187 | 188 |  189 | 190 | Fantastic, I think this works pretty well, have a play around with the code and further feautures of the ggtern package and see if you can make it better. 191 | 192 | K-Means Clustering 193 | ------------------ 194 | 195 | K-means Clustering is a way of dividing datapoints into groups or 'clusters' depending on how 'similar' they. A quick google will give you much more details, but let's see it in action with out fake data. 196 | 197 | ``` r 198 | # create a new dataframe of just the passing, shooting and dribbling data. 199 | ResultsKmean <- Results[3:5] 200 | 201 | # use the kmeans() function to perform the analysis, we chose to devide the data into 6 'clusters' 202 | kmeans <- kmeans(ResultsKmean, 6) 203 | 204 | # We want to bind the kMeans Clusters to the original dataframe 205 | Results$Cluster <- as.character(kmeans$cluster) 206 | 207 | ## print the top 5 rows to check we are on the right path 208 | head(Results) 209 | ``` 210 | 211 | ## Club Player Pass Dribble Shoot Cluster 212 | ## 1 Club1 Player1 83.989731 10.404360 5.6059098 1 213 | ## 2 Club1 Player2 72.140048 27.267614 0.5923382 6 214 | ## 3 Club1 Player3 8.271803 82.067260 9.6609370 4 215 | ## 4 Club1 Player4 96.656463 1.475396 1.8681409 1 216 | ## 5 Club1 Player5 47.617233 19.929625 32.4531419 2 217 | ## 6 Club1 Player6 34.791456 49.700572 15.5079716 3 218 | 219 | Great now we have all the data and the clusters of our players. Let's plot the full dataset again but colour it by cluster. 220 | 221 | ``` r 222 | #Create the plot and store 223 | plot <- ggtern(data = Results, aes(x = Pass, y = Dribble, z = Shoot)) + 224 | geom_point(aes(fill = Cluster), size = 2, 225 | shape = 21, 226 | color = "black") 227 | 228 | #Render 229 | plot 230 | ``` 231 | 232 |  233 | 234 | We can see that players within Cluster 3 have a high tendancy to Pass with over 80% of their actions being passes. Maybe we are about to sell our best midfielder who dictates and maintains our possessions. If we were looking across the league for a replacement we could focus our efforts on players that also reside in Cluster 3. 235 | 236 | Granted we are using fake data but this article may spark a thought about how you could use ternary plots and/or k-means clustering with other data. 237 | -------------------------------------------------------------------------------- /6.PassingOrigins.md: -------------------------------------------------------------------------------- 1 | Layered Histograms to show Passing Origins 2 | ================ 3 | 4 | Women's Football Data 5 | --------------------- 6 | Earlier this month [Statsbomb](https://statsbomb.com/) announced their [data product](https://statsbomb.com/data/) which looks to improve the current data offerings on the market. Interestingly, Statsbomb will be collecting data on women's football. The other day [Ted Knutson](https://twitter.com/mixedknuts) [tweeted](https://twitter.com/mixedknuts/status/997188477434425344): 7 | 8 | "\#WhatIf we collect data on top flight women's football, on the same spec as the men, and give it away to clubs and fans alike, for free? Can we better support and help create the next generation of women's football coaches, player, writers, and fans?" 9 | 10 | The [tweet](https://twitter.com/mixedknuts/status/997188477434425344) got a lot of traction and we hope Statsbomb follow through with the idea. To do my part I thought I would use the data distributed at their launch event in this tutorial. 11 | 12 | An Idea 13 | ------- 14 | 15 | I love myself a histogram as they are accessible ways of seeing patterns within the data. [Mara Averick](https://twitter.com/dataandme) [tweeted](https://twitter.com/dataandme/status/997297983472402437) about [Simon Jackson's](https://twitter.com/drsimonj) [work](https://drsimonj.svbtle.com/plotting-background-data-for-groups-with-ggplot2?utm_content=buffer2b686&utm_medium=social&utm_source=twitter.com&utm_campaign=buffer) on histograms and how they can be layered to great effect. 16 | 17 |  18 | 19 | I was searching for that could utilise this technique to good effect. I was intrigued to use the pressure metrics that are new to Statsbomb but I just couldn't wrestle an idea together in the time slot I had to make this tutorial. I therefore settled on our old friend passes... 20 | 21 | After bouncing some ideas around I settled on showing the frequency of vertical origins of passes, this will allow us to see patterns at team level as well as overlay individual player stats. 22 | 23 | Setting up the Packages 24 | ----------------------- 25 | 26 | We will be using a few packages today so lets load them (make sure to install them if you haven't already). 27 | 28 | ``` r 29 | ## load packages 30 | require(dplyr) ## package for data tidying 31 | require(formattable) ## package to make tables look nice 32 | require(ggplot2) ## package for plotting 33 | require(patchwork) ## package for organising our plots 34 | require(plyr) ## for help with summaries 35 | ``` 36 | 37 | Loading & Prepping the Data 38 | --------------------------- 39 | 40 | First we load the csv file that Statsbomb included in their free data drop at the event. 41 | 42 | ``` r 43 | df <- read.csv("Files/Manchester City WFC_Chelsea LFC_7298.csv", stringsAsFactors = F) 44 | ``` 45 | 46 | Next we store a list of the participlated teams which we will use as variables to filter data later. 47 | 48 | ``` r 49 | Teams <- unique(df$team_name) 50 | ``` 51 | 52 | Then we create a dataset for Team 1 (which in this case is Manchester City Women's FC) and filtering out everything but passes and filter out goal-kicks. 53 | 54 | ``` r 55 | df_pass_T1 <- df %>% filter(event_type_name == "Pass", team_name == Teams[1], play_pattern_name != "From Goal Kick") 56 | ``` 57 | 58 | Seperately, I want to make a summary table of the top 3 passers for MCWFC which I will use as part of my player selection method. 59 | 60 | ``` r 61 | PassTotals <- count(df_pass_T1, 'player_name') 62 | PassTotals <- PassTotals %>% arrange(-freq) %>% head(3) 63 | formattable(PassTotals) 64 | ``` 65 | 66 |
70 | player\_name 71 | | 72 |73 | freq 74 | | 75 |
---|---|
80 | Demi Stokes 81 | | 82 |83 | 66 84 | | 85 |
88 | Esme Beth Morgan 89 | | 90 |91 | 54 92 | | 93 |
96 | Jennifer Beattie 97 | | 98 |99 | 53 100 | | 101 |