Skip to content

Ball Screens

The ball_screens section in the markings response contains a row for each on-ball screen we identify in the tracking data.

Overview

A ball screen occurs when an offensive player attempts to set a screen for the ball handler. This includes both classic picks and handoff-style actions like dribble handoffs. (Each row contains a boolean flag indicating whether we think the action is a handoff-style action, to separate them out if you would like.)

Each row includes:

  • The key players involved (ball handler, screener, and their defenders)
  • A start/end window around the screen action
  • The single frame where we consider the screen to have been closest to being set (screening_he_frame)
  • Additional detailed data about the screening action

A few important notes:

  • We currently only include ball screens that occur when the screener is between 10 and 45 feet from the rim at the screening frame. So this means deep backcourt ball screens are, at this point, not included.
  • When a defender sets a screen and then re-screens, we aim to use the first screen as the one by which we set the screening frame. Both the initial screen and any subsequent re-screens will be judged as part of the same screening action (and produce only one row in the data set) unless the screener goes more than 4 feet away from the screening frame location between the screens or gets more than 10 feet away from the ball handler's primary defender. We also split it up if the ball handler gives up the ball in between, for example if they come off a ball screen and then throw it to the screener and come off for a handoff. In any of those cases, there would be multiple ball screens in the data set.
  • One interesting case we had to make a decision on is what to do with dribble pitch/exchanges, which don't really look like screens, like this: These seem somewhat like a handoff, but are not really intended as screens, more as just a way to set up offense. They often don't even impact the defense, like in the above clip, where the defenders just stay with their matchups. We made the decision not to include these as ball screens, unless the defense is impacted or the screener seems like they are setting a screen. So if the defense switches or traps, it will be included as a ball screen, but if not, it should not be. So this one would be included since the defense switches: As you can imagine, distinguishing between these two cases is not always easy, so this is currently one common source of error.

There are a number of other pretty tricky edge cases that are worth noting here and that are other common cases that trip up our ball screen detection:

  • When a screen is rejected or not used (e.g. the screener slips early or the ball handler waves off the screener), these can be difficult to distinguish from cutting movement or simply a teammate standing nearby or posting up. So a play like this we might miss if the screener doesn't get close enough to the defender: In general it's worth noting that there is a fine line between how far away the screener can be, or how early the ball handler attacks, where we would still want to count it as a ball screen. This is particularly true when there is a lot of other movement and it's not as clear as in the above clip.
  • Quick drag (transition) screens are also a real challenge because it becomes difficult to separate true screens from those where a player is simply running by the defender in transition. For example, here, we ideally would not count this as a ball screen: But being more aggressive filtering those out means we miss some of those like this:
  • For double ball screens, we aim to include each screen as its own ball screen. However, when the second screener slips out of the screen early or the on ball defender is trailing the ball handler a lot because of the first screen, this can cause us to miss the second screen. So on a play like this, the first screen we would capture but the second screen we might not: This is an area that we think there are clear methods to improve. Once we also identify double screens reliably, we can tag them in the data output.
  • To distinguish actual ball screens vs. those that are players coming together but are not intentional screens, we have found one reliable signal to be the screener's defender reacting in some way to the screen. This means, though, that we have a higher miss rate on ball screens where the defender does not react, for example here:
  • When a player drives and passes they sometimes end up drifting toward the defender of the player they just passed to, and it can be very close to looking like a ball screen. For example, here is one that we would not count as a ball screen but looks close to one: But sometimes this exact kind of play happens where the player does try to screen after. These we have a higher rate of missing because of trying to filter out the first type of plays. For example, here is one where it seems like the driver does pause briefly to try to screen after the pass:
  • What we are specifically looking for in this data set is when a player sets a screen on the ball handler's defender. When a screener is screening their own man, that makes things complicated, and so is generally not included in this data set. For example, this would not be included because Sabonis does not set a screen on the on-ball defender, he screens his own man: But this would be included, since the screener gets close enough to the on-ball defender. It just would be listed as screening De'Aaron Fox, not Kevin Huerter:

Sample Response

json
{
  "markings": {
    "ball_screens": [
      {
        "attacking_positive_x_basket": true,
        "ball_defender_id_nba": 1630529,
        "ball_handler_id_nba": 1642843,
        "ball_handler_loc_x": 131.35,
        "ball_handler_loc_y": -152.43,
        "ball_hoop_distance_min": 1.83,
        "ball_screen_id_ctg": "ball_screen_2c8e1b384239e0764593dd34bad84d69",
        "ballhandler_outcome": "pass",
        "chance_id_ctg": "chance_e0f3a7d8f50a4db68216f71a0e6f7a34",
        "did_ball_get_behind_screener_defender": false,
        "did_ballhandler_dribble_after_screen": true,
        "did_ballhandler_pass_to_screener": true,
        "end_game_clock": 710.0,
        "end_he_frame": 658,
        "end_shot_clock": 15.0,
        "end_wall_clock": "2025-12-23T01:10:45.423+00:00",
        "game_id_nba": "0022500404",
        "is_handoff": false,
        "period": 1,
        "possession_id_ctg": "possession_3fdd68c4f8c847c2a7f9f72975f5f4a1",
        "screen_distance_to_baseline": 29.9,
        "screen_distance_to_center_court": 9.26,
        "screener_defender_hoop_distance_diff": 9.59,
        "screener_defender_hoop_distance_max": 16.78,
        "screener_defender_hoop_distance_max_diff": 9.55,
        "screener_defender_hoop_distance_screen_start": 16.74,
        "screener_defender_id_nba": 1642852,
        "screener_hoop_distance": 26.33,
        "screener_hoop_distance_min": 3.23,
        "screener_id_nba": 203076,
        "screener_loc_x": 205.21,
        "screener_loc_y": -111.15,
        "screener_outcome": "shot",
        "screening_game_clock": 711.0,
        "screening_he_frame": 615,
        "screening_shot_clock": 16.0,
        "screening_wall_clock": "2025-12-23T01:10:44.706+00:00",
        "start_game_clock": 711.0,
        "start_he_frame": 588,
        "start_shot_clock": 16.0,
        "start_wall_clock": "2025-12-23T01:10:44.256+00:00",
        "was_screener_defender_in_3pt_area_at_screen": false
      }
    ]
  }
}

Fields

Identifiers

ball_screen_id_ctg

Type: string

CTG-generated unique identifier for this ball screen event


game_id_nba

Type: string

NBA game ID


chance_id_ctg

Type: string | Nullable

CTG-generated identifier linking this ball screen to the chance in which it occurred.


possession_id_ctg

Type: string | Nullable

CTG-generated identifier linking this ball screen to the possession in which it occurred.


Players

ball_handler_id_nba

Type: integer

NBA player ID of the ball handler during the screen action


screener_id_nba

Type: integer

NBA player ID of the offensive player setting the screen


ball_defender_id_nba

Type: integer

NBA player ID of the primary defender guarding the ball handler during the screen action


screener_defender_id_nba

Type: integer

NBA player ID of the primary defender guarding the screener during the screen action


Location

attacking_positive_x_basket

Type: boolean

true if the offensive team is attacking the basket on the positive-x side of the court (i.e., the basket with positive x coordinates)


screener_loc_x

Type: float | Unit: inches

X coordinate of the screener's location at screening_he_frame


screener_loc_y

Type: float | Unit: inches

Y coordinate of the screener's location at screening_he_frame


ball_handler_loc_x

Type: float | Unit: inches

X coordinate of the ball handler's location at screening_he_frame


ball_handler_loc_y

Type: float | Unit: inches

Y coordinate of the ball handler's location at screening_he_frame


Timing

period

Type: integer

Period number (1–4 for regulation, 5+ for overtime)


start_he_frame

Type: integer

Hawk-Eye frame number where the screen window begins.

The screen window is the contiguous frame span around screening_he_frame where:

  • The screener is relatively close their screening frame location
  • The screener is somewhat close to the ball handler's primary defender
  • The ball handler has the ball
  • It is within 3 seconds of the screening frame

end_he_frame

Type: integer

Hawk-Eye frame number where the screen window ends (the last frame in the same window described in start_he_frame).


screening_he_frame

Type: integer

Hawk-Eye frame number that is our best guess for when the screen was initially set. We are trying to find a frame for when the screener is setting up for the screen and close to when the ball handler uses the screen (if they do).


screening_game_clock

Type: float | Unit: seconds

Game clock (time remaining in the period) at screening_he_frame


screening_shot_clock

Type: float | Unit: seconds

Shot clock at screening_he_frame


screening_wall_clock

Type: string

UTC wall clock timestamp for screening_he_frame (ISO-8601)


start_game_clock

Type: float | Unit: seconds

Game clock (time remaining in the period) at start_he_frame


end_game_clock

Type: float | Unit: seconds

Game clock (time remaining in the period) at end_he_frame


start_shot_clock

Type: float | Unit: seconds

Shot clock at start_he_frame


end_shot_clock

Type: float | Unit: seconds

Shot clock at end_he_frame


start_wall_clock

Type: string

UTC wall clock timestamp for start_he_frame (ISO-8601)


end_wall_clock

Type: string

UTC wall clock timestamp for end_he_frame (ISO-8601)


is_handoff

Type: boolean

true if this ball screen is estimated to be a handoff-style ball screen.


Screen Details

screener_hoop_distance

Type: float | Unit: feet

Screener’s distance to the hoop at screening frame.


screener_defender_hoop_distance_screen_start

Type: float | Unit: feet

Screener’s defender’s distance to the hoop at screen start.


screener_defender_hoop_distance_max

Type: float | Unit: feet

Maximum distance of the screener’s defender to the hoop between the screening frame and the end of the ball handler’s touch.


screener_defender_hoop_distance_diff

Type: float | Unit: feet

The difference between the screener's distance to the hoop at the screening frame and the screener's defender's distance to the hoop at the screening frame.


screener_defender_hoop_distance_max_diff

Type: float | Unit: feet

The difference between the screener's distance to the hoop at the screening frame and the maximum screener defender hoop distance between the screening frame and the end of the ball handler's touch. This can help determine how high the screener defender came out on the floor while defending the screen.


was_screener_defender_in_3pt_area_at_screen

Type: boolean

true if the screener’s defender was in a 3-point area at the screening frame.


did_ball_get_behind_screener_defender

Type: boolean

true if the ball got meaningfully behind the screener’s defender (toward the hoop) between the screening frame and the end of the ball handler’s touch.


screener_hoop_distance_min

Type: float | Unit: feet

Minimum distance of the screener to the hoop between the screening frame and the end of the ball handler’s touch, ball handler's pass, or roller's touch (whichever comes last).


ball_hoop_distance_min

Type: float | Unit: feet

Minimum distance of the ball to the hoop by the end of the ball handler’s touch, ball handler's pass, or roller's touch (whichever comes last).


ballhandler_outcome

Type: string

How the ball handler’s possession touch after the screen ended. One of: shot, pass, turnover, foul, violation, stoppage, unknown.


screener_outcome

Type: string | Nullable

How the screener’s next possession touch ended if the ball was passed to the screener. One of: shot, pass, turnover, foul, violation, stoppage, unknown. (Same values as ballhandler_outcome.) null if the screener did not receive the pass.


did_ballhandler_pass_to_screener

Type: boolean

true if the ball handler's possession touch during the screening action ended with a pass to the screener and the pass was completed.


screen_distance_to_center_court

Type: float | Unit: feet

Screener’s sideline-to-sideline distance to the "rim line" (the line that bisects the court from one basket to the other) at the screening frame.


screen_distance_to_baseline

Type: float | Unit: feet

Screener’s baseline-to-baseline distance to the nearest baseline at the screening frame.


did_ballhandler_dribble_after_screen

Type: boolean

true if the ball handler dribbled after the screen (before shot/pass/turnover).