Skip to content

Code to play with tracking data based on the data released as part of the Friends of Training sesssions.

License

Notifications You must be signed in to change notification settings

thecomeonman/MakingFriendsWithTrackingData

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

23 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Before you begin:

Before attempting to run this code, follow the instructions at https://github.com/thecomeonman/CodaBonito to install some dependencies.

Also get the tracking data, recently made public as part of the Friends of Tracking sessions by Metrica from here - https://github.com/metrica-sports/sample-data

Other ongoing experiments with tracking data can be viewed in the other markdown files in the repo which will eventually be moved to a different repo when they are old enough to move out.

The code

library(CodaBonito)
library(data.table)
library(ggplot2)

rm(list = ls())

# Instructions
# You would have downloaded and saved the data from Github at some location on
# your system. In my case, it's /media/ask/Data/Personal/Projects/Personal/sample-data
# Replace the location below with the respective location from your machine
cDataRootFolder = '/media/ask/Data/Personal/Projects/Personal/sample-data/data/'
cGameName = 'Sample_Game_1'
nXLimit = 120
nYLimit = 80
# upper limit of speed to cap weird speed values
nUpperLimitSpeed = 5 * 120 / 105


vcColourAssignment = c(
   'Home' = 'red',
   'Ball' = 'black',
   'Away' = 'blue'
)

# loading the data in
lData = fParseTrackingDataBothTeams(
   cRootPath = cDataRootFolder,
   cGameName = cGameName,
   nXLimit = nXLimit,
   nYLimit = nYLimit,
   xMaxBB = 1,
   yMaxBB = 1,
   nUpperLimitSpeed = nUpperLimitSpeed
)

# Instructions
# getting a random slice
# basically, a row from the tracking data
# you can choose to pick your own slice

dtEventSlice = lData$dtEventsData[
   # Frame == ( sample( Frame[which(Subtype == "ON TARGET-GOAL")], 1) )
   StartFrame == 99005
   # StartFrame == 90005
   # StartFrame == 99032
]

dtTrackingSlice = lData$dtTrackingData[ 
   # Frame <= ( sample( Frame[which(Subtype == "ON TARGET-GOAL")], 1) + 25 )
   Frame <= dtEventSlice[, StartFrame + 25]
][
   Frame >= max(Frame) - 125
][
   # Frame <= max(Frame) - 30
]

dtEventSlice = lData$dtEventsData[
   StartFrame %in% dtTrackingSlice[, Frame] &
   EndFrame %in% dtTrackingSlice[, Frame]
]


voronoiOutput = fDrawVoronoiFromTable(
   dtTrackingSlice[Frame == min(Frame)],
   nXLimit = nXLimit,
   nYLimit = nYLimit
)

print(voronoiOutput)

if ( !file.exists('./README_files/figure-markdown_strict/Voronoi.gif') ) {
      
   voronoiOutput = fDrawVoronoiFromTable(
      dtTrackingSlice,
      nXLimit = nXLimit,
      nYLimit = nYLimit,
      UseOneFrameEvery = 1,
      DelayBetweenFrames = 5
   )
   
   if ( !interactive() ) {
      
      qwe = suppressWarnings(
         file.remove('./README_files/figure-markdown_strict/Voronoi.gif')
      )
      rm(qwe)
   
      qwe = file.copy(
         voronoiOutput,
         './README_files/figure-markdown_strict/Voronoi.gif'
      )
   
      rm(qwe)
      
   }
   
}

I delibrately left that bit there where the player goes outside the pitch. I don’t know what is the best strategy to depict that. Suggestions welcome.

What about adding some annotations?

if ( !file.exists('./README_files/figure-markdown_strict/VoronoiAnnotated.gif') ) {

   voronoiOutput = fDrawVoronoiFromTable(
      dtTrackingSlice,
      nXLimit = nXLimit,
      nYLimit = nYLimit,
      UseOneFrameEvery = 1,
      DelayBetweenFrames = 5,
      markTrajectoryFor = c(
         'HomePlayer5',
         'HomePlayer4',
         'AwayPlayer17'
      ),
      markOffsideLineFor = list(
         c('Away', 'AwayPlayer25', min)
      ),
      markLineBetween = list(
         c('HomePlayer4','AwayPlayer17')
      )
   )
   
   if ( !interactive() ) {
      
      qwe = suppressWarnings(
         file.remove('./README_files/figure-markdown_strict/Voronoi.gif')
      )
      rm(qwe)
   
      qwe = file.copy(
         voronoiOutput,
         './README_files/figure-markdown_strict/VoronoiAnnotated.gif'
      )
   
      rm(qwe)
      
   }

}

What’s the line that the defense is holding?

Could the player making the cross have gotten closed quicker to prevent the cross from happening?

Note how the recipient of the cross is in an offside position but recovers in time for the cross.

Pitch control models are a more accurate reflection than voronois so porting Laurie Shaw’s code from William Spearman’s paper -

if ( !file.exists('./README_files/figure-markdown_strict/PitchControl.gif') ) {

    viTrackingFrame = dtTrackingSlice[, seq(min(Frame) - 10, max(Frame), 5)]
   
    params = c()
    params['time_to_control_veto'] = 3
    # model parameters
    params['max_player_accel'] = 7. # maximum player acceleration m/s/s, not used in this implementation
    params['max_player_speed'] = nUpperLimitSpeed # maximum player speed m/s
    params['reaction_time'] = 0.7 # seconds, time taken for player to react and change trajectory. Roughly determined as vmax/amax
    params['tti_sigma'] = 0.45 # Standard deviation of sigmoid function in Spearman 2018 ('s') that determines uncertainty in player arrival time
    params['kappa_def'] =  1. # kappa parameter in Spearman 2018 (=1.72 in the paper) that gives the advantage defending players to control ball, I have set to 1 so that home & away players have same ball control probability
    params['lambda_att'] = 4.3 # ball control parameter for attacking team
    params['lambda_def'] = 4.3 * params['kappa_def'] # ball control parameter for defending team
    params['average_ball_speed'] = 15. # average ball travel speed in m/s
    # numerical parameters for model evaluation
    params['int_dt'] = 0.04 # integration timestep (dt)
    params['max_int_time'] = 10 # upper limit on integral time
    params['model_converge_tol'] = 0.01 # assume convergence when PPCF>0.99 at a given location.
    # The following are 'short-cut' parameters. We do not need to calculated PPCF explicitly when a player has a sufficient head start.
    # A sufficient head start is when the a player arrives at the target location at least 'time_to_control' seconds before the next player
    # params['time_to_control_att'] = params['time_to_control_veto']*np.log(10) * (np.sqrt(3)*params['tti_sigma']/np.pi + 1/params['lambda_att'])
    # params['time_to_control_def'] = params['time_to_control_veto']*np.log(10) * (np.sqrt(3)*params['tti_sigma']/np.pi + 1/params['lambda_def'])
    params['time_to_control_att'] = params['time_to_control_veto']*log(10) * (sqrt(3)*params['tti_sigma']/pi + 1/params['lambda_att'])
    params['time_to_control_def'] = params['time_to_control_veto']*log(10) * (sqrt(3)*params['tti_sigma']/pi + 1/params['lambda_def'])

    lPitchControl = fGetPitchControlProbabilities (
        lData,
        viTrackingFrame,
        params = params,
        nYLimit = nYLimit,
        nXLimit = nXLimit,
        iGridCellsX = nYLimit
    )
        
    pPlotPitchControl = fPlotPitchControl(
        lPitchControl
    )
   
   if ( !interactive() ) {
      
      qwe = suppressWarnings(
         file.remove('./README_files/figure-markdown_strict/PitchControl.gif')
      )
      rm(qwe)
   
      qwe = file.copy(
         pPlotPitchControl,
         './README_files/figure-markdown_strict/PitchControl.gif'
      )
   
      rm(qwe)
      
   }

}

About

Code to play with tracking data based on the data released as part of the Friends of Training sesssions.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages