Appearance
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 ballhandler. 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 (ballhandler, 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 ballhandler's primary defender. We also split it up if the ballhandler 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 currently trip up our ball screen detection that we will aim to improve in the future:
- When a screen is rejected or not used (e.g. the screener slips early or the ballhandler 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 ballhandler 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 ballhandler 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:
- 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 ballhandler'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": false,
"ball_defender_id_nba": 1630230,
"ball_handler_id_nba": 1630180,
"ball_handler_loc_x": -156.78,
"ball_handler_loc_y": -102.65,
"ball_hoop_distance_min": 6.09,
"ball_screen_id_ctg": "ball_screen_790295edbb16a587380cd3d49f5063ef",
"ballhandler_defender_initial_route": "over",
"ballhandler_defender_switch_type": "none",
"ballhandler_movement_side": "right",
"ballhandler_outcome": "shot",
"ballhandler_possession_touch_id": "possession_touch_71435614ec7ccbc094fe1f0bb3b8e98c",
"ballhandler_possession_touch_outcome_id": "shot_bab5015a8c75579fc1cb24b82f3df764",
"chance_id_ctg": "chance_4c33ded97f95174cc013b446c3981347",
"defensive_weak_side_player_count": 1,
"did_ball_get_behind_screener_defender": false,
"did_ballhandler_dribble_after_screen": true,
"did_ballhandler_pass_to_screener": false,
"double_screen_id_ctg": null,
"double_screen_screener_id_nba": null,
"double_screen_screening_he_frame": null,
"double_screen_side": null,
"drive_id": "drive_4c0859e1242cf39607d5896f6dae865c",
"end_game_clock": 691.0,
"end_he_frame": 1813,
"end_shot_clock": 9.0,
"end_wall_clock": "2025-12-23T01:11:04.671+00:00",
"event_frame_number_he": 1802,
"event_type": "shot",
"game_id_nba": "0022500404",
"gap_defender_id": null,
"has_offensive_off_ball_player_moving_at_play_start": true,
"high_weak_side_defender_id": null,
"is_direct": true,
"is_handoff": true,
"led_to_event": true,
"low_man_id": 1642948,
"min_ball_hoop_distance": 6.09,
"min_screener_hoop_distance": 8.29,
"offensive_weak_side_player_count": 1,
"pbp_event_id": 37,
"period": 1,
"rejected_screen": false,
"possession_id_ctg": "possession_90461f431f3586f0ba39f25f0cefdec7",
"screen_distance_to_baseline": 31.78,
"screen_distance_to_center_court": 4.5,
"screener_action": "roll",
"screener_defender_after_screen_defense": "drop",
"screener_defender_hoop_distance_diff": 3.68,
"screener_defender_hoop_distance_max": 23.23,
"screener_defender_hoop_distance_max_diff": 3.68,
"screener_defender_hoop_distance_screen_start": 23.23,
"screener_defender_id_nba": 203076,
"screener_defender_initial_position": "level",
"screener_defender_switch_type": "none",
"screener_hoop_distance": 26.91,
"screener_hoop_distance_min": 8.29,
"screener_id_nba": 1642852,
"screener_loc_x": -182.64,
"screener_loc_y": -54.05,
"screener_outcome": null,
"screening_game_clock": 692.0,
"screening_he_frame": 1747,
"screening_shot_clock": 10.0,
"screening_wall_clock": "2025-12-23T01:11:03.571+00:00",
"slip_screen_type": "none",
"start_game_clock": 692.0,
"start_he_frame": 1731,
"start_shot_clock": 10.0,
"start_wall_clock": "2025-12-23T01:11:03.305+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.
double_screen_id_ctg
Type: string | Nullable
If this ball screen is part of a double screen action, this is the CTG id of the other ball screen in that pair. If the ball screen is not part of a double screen action, this field is null.
Players
ball_handler_id_nba
Type: integer
NBA player ID of the ballhandler during the screen action
screener_id_nba
Type: integer
NBA player ID of the offensive player setting the screen
double_screen_screener_id_nba
Type: integer | Nullable
If this ball screen is part of a double screen action, this is the NBA player id of the screener on the other screen in the action. If not part of a double screen action, this field is null.
ball_defender_id_nba
Type: integer
NBA player ID of the primary defender guarding the ballhandler 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 ballhandler's location at screening_he_frame
ball_handler_loc_y
Type: float | Unit: inches
Y coordinate of the ballhandler'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 ballhandler's primary defender
- The ballhandler 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 ballhandler uses the screen (if they do).
double_screen_screening_he_frame
Type: integer | Nullable
If this ball screen is part of a double screen action, this is the screening_he_frame of the other screen in that action. If not part of a double screen action, this field is null.
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.
is_direct
Type: boolean
true when the ball screen action results in a direct scoring outcome: the ballhandler or screener shoots, is fouled and goes to the free throw line, or turns the ball over — or either player makes a final pass and the receiver shoots within one dribble of catching it (with no further passes before the shot). false otherwise.
led_to_event
Type: boolean
true when this ball screen is deemed to have led to the chance ending event, such as a shot, turnover, foul, kicked ball, or jump ball. We say a ball screen leads to an event when we think the screen was the main factor in the chance ending event: the defense doesn't reset after and the offense doesn't try to run a new action after the ball screen.
event_type
Type: string | Nullable
The type of play by play event attributed to this ball screen. This is null when led_to_event is false.
pbp_event_id
Type: integer | Nullable
Play-by-play event ID for the attributed play by play event. This is null when no play-by-play event is linked.
event_frame_number_he
Type: integer | Nullable
Hawk-Eye frame number for the attributed play by play event. This is null when led_to_event is false.
double_screen_side
Type: string | Nullable
If this ball screen is part of a double screen action, this field indicates whether the screeners set up on either side of the ballhandler, giving them an option to go either direction, or are staggered on the same side of the ballhandler.
Possible values:
same_side: both screeners are on the same side of the ballhandler-to-rim line.opposite_side: the screeners are on opposite sides of that line.
This field is null when the ball screen is not part of a double screen action.
ballhandler_defender_initial_route
Type: string | Nullable
The route the original ballhandler defender took around the screen (i.e. going under or over). Note that this just attempts to measure the first route around the screen, so that if the defender goes under first and later comes back over on a re-screen, this field would be under.
The possible values are:
under: the defender navigates the screen by going below the screener on the rim side. This also includes situations where the defender switches by getting below the screener initially on the switch.over: the defender navigates the screen by going above the screener on the high side away from the rim. This also includes situations where the defender switches by going above the screener on the switch.none: the defender did not go either over or under, for example on rejected screens, when the defender dies on screen and does not navigate it at all, or when there is a switch and the defender stays even with the screener as they roll so that there is not a clear over or under.
This field can be null when the route could not be determined from the available tracking data around the screening frame. Note that null is different from none here.
ballhandler_defender_switch_type
Type: string | Nullable
Whether the original ballhandler defender switched off the ballhandler as a result of this screen and, if so, whether it was a conventional switch or a late switch. Note that this includes cases where the ballhandler defender switches onto someone other than the screener. As long as the ballhandler's matchup intentionally switches as a result of the screen, then this field will be one of the two switch types.
Sometimes the ballhandler defender will initially switch briefly but then pursue later. In these cases we do not count this as a switch because the matchup only changed very briefly.
The possible values are:
none: the original ballhandler defender aims to stay on the ballhandler or recover to them.normal: the original ballhandler defender switches off the ballhandler around the time of the screen.late: the original ballhandler defender first looks like they are still pursuing or recovering, but later switches onto another matchup. This can include cases where the screener pops and the ballhandler defender switches out onto them after an initial pursuit. It also includes cases where the ballhandler drives while the screener rolls, and the ballhandler defender initially pursues but later veers off onto the roller to take away the pass to the roller or to box out.
This field can be null when the switch type could not be determined from the available tracking data around the screening frame. Note that null is different from none here.
screener_defender_switch_type
Type: string | Nullable
Whether the original screener defender switched off the screener during this screen and, if so, whether this was done with normal switch timing or a late switch. Note that the switch timing is determined by the ballhandler defender, not the screener defender. So this field essentially matches the ballhandler defender switch type in all cases except when the screener defender stays with their matchup (which might happen on a blown switch or if there is a triple-switch or peel switch or something similar going on and someone else is taking ballhandler).
The possible values are:
none: the screener defender stays matched up with the screener after defending the ball screen action.normal: the screener defender switches onto the ballhandler around the time of the screen.late: the screener defender switches onto the ballhandler, and the original ballhandler defender first looks like they are still pursuing or recovering, but later switches onto another matchup. This can include cases where the screener pops and the ballhandler defender switches out onto them after an initial pursuit. It also includes cases where the ballhandler drives while the screener rolls, and the ballhandler defender initially pursues but later veers off onto the roller to take away the pass to the roller or to box out.
This field can be null when the switch type could not be determined from the available tracking data around the screening frame. Note that null is different from none here.
screener_defender_initial_position
Type: string | Nullable
The position of the screener defender relative to screener as the ball screen is being set. The goal here is to to capture the defender's starting position separately from their coverage after the screen. A defender can be level and then drop after, or can be level and then stay high and contain. Those are captured in screener_defender_after_screen_defense below. This field is meant just to indicate where the defender was initially.
The possible values are:
above: the defender is above the screen or at the level of the screen and moves out above the level shortly after the screen is set.level: the defender is up at or near the screener level of the screen, close to arm's distance of the screener.drop: the defender is dropped back from the level of the screen.deep: the defender is around the free throw line or below, or otherwise is considerably far from the level of the screen.
This field can be null when missing required tracking data around the screening frame.
screener_defender_after_screen_defense
Type: string | Nullable
How the screener defender covers the ball screen after the screen is set. This is mostly independent of where the screener defender starts, and instead aims to use their actions after the screen to best determine the coverage type.
The possible values are:
blitz: the screener defender jumps out above the screen in an effort to trap the ballhandler.bluff: the screener defender briefly steps into the ball handler's path before recovering to the screener, even if the ball is not stopped, relying on the on ball defender recovering or help from teammates.contain: the screener defender tries to stay close to the ballhandler and keep the ballhandler in front of them, often moving laterally to do so. They are more worried about staying up near the ball handler than about the screener getting behind them.drop: the screener defender stays deep or retreats toward the rim while defending the action with the goal of always staying below both the screener and the ballhandler unless the ball gets deep into the paint in which case they may commit to the ball. If the ballhandler pulls up for a jump shot, the defender will not contest it and rely on the ballhandler's defender to try to contest the shot in pursuit.drop_contest: drop-style coverage except that the defender will contest pull up jump shots.hedge: the screener defender jumps out above the screen to disrupt the ballhandler's path and then recovers back to the screener or another offensive player regardless of whether the ballhandler picks up the ball or not.none: the screener defender does not really leave the screener to defend the screen (or we can't really judge what they are doing). Examples would be when the screen is rejected and the screener defender does not make an effort to help, or the screener defender just follows the screener's path mostly and doesn't get their chest between the ballhandler and the rim.switch: the screener defender switches onto the ballhandler immediately. This does not include situations like a drop into a late switch (that would be classified asdrop).
This field can be null when the after-screen defense model output is unavailable for that ball screen.
screener_action
Type: string | Nullable
What the screener does after the ball screen: roll, pop, etc.
The possible values are:
roll: the screener's first movement after setting the screen is towards the basket, and after heading toward the basket they get reasonably close to the rim (within around 14 feet). This includes rolling into the post if the screener gets fairly deep before posting up.short_roll: the screener's first movement is toward the rim but then gets the ball early or stops short to try to be an outlet for the passerpop: the screener's first movement is out toward the perimeter instead of going towards the rim, ideally to be open for a passmid_post: the screener goes right into a post up (not too close to the basket) and calls for the ball in an attempt to isolate a mismatch. (This is not the same as rolling into the post, which would be aroll.)move_to_screen: the screener quickly flows into another screen (on ball or off ball) instead of doing any of the abovenone: the screener's action doesn’t match any of the above, and the screener doesn't really do anything after the screen, with minimal movement in any direction before the ballhandler shoots or there's a stoppage of play.
This field can be null when the screener-action model output is unavailable for that ball screen.
rejected_screen
Type: boolean | Nullable
true if the ballhandler rejected the ball screen by attacking away from the screen instead of using it. This field can be null when required tracking data around the screening frame is unavailable.
slip_screen_type
Type: string | Nullable
Classifies whether and how the screener slipped the screen. A "slip" is defined as the screener leaving the area before the ballhandler uses or rejects the screen.
The possible values are:
none: the screener did not slip the screen.quick: the screener slipped early, before the screen was really set.slow: the screener lingered, trying to set the screen, but then left the area without the screen being used.
This field can be null when required tracking data around the screening frame is unavailable.
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 ballhandler’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 ballhandler'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 ballhandler’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 ballhandler’s touch, ballhandler'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 ballhandler’s touch, ballhandler's pass, or roller's touch (whichever comes last).
ballhandler_outcome
Type: string
How the ballhandler’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 ballhandler'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).
Off-ball context fields
defensive_weak_side_player_count
Type: integer | Nullable
How many of the three defensive off-ball players were on the weak side at the start of the pick.
gap_defender_id
Type: integer | Nullable
NBA player ID of the defender who is on the strong side, is guarding a player who is one pass away, and is not guarding a player in the corner. null if no such defender is identified on this play.
has_offensive_off_ball_player_moving_at_play_start
Type: boolean | Nullable
true if at least one offensive off-ball player is moving at a reasonably fast speed when the pick starts.
high_weak_side_defender_id
Type: integer | Nullable
NBA player ID of the defender identified as the highest weak-side defender when more than one weak-side defender is present. null if no such defender is identified on this play.
low_man_id
Type: integer | Nullable
NBA player ID of the defender estimated to have low-man responsibility at pick start (typically the lowest weak-side defender, or the lowest strong-side defender when there are no weak-side defenders). null if no such defender is identified on this play.
min_ball_hoop_distance
Type: float | Unit: feet | Nullable
Closest distance from the ball to the hoop by the end of the ball handler's touch, the screener's touch (if they received the ball), or the ball handler's pass (if passed to a non-screener).
min_screener_hoop_distance
Type: float | Unit: feet | Nullable
Closest distance from the screener to the hoop by the end of the ball handler's touch, the screener's touch (if they received the ball), or the ball handler's pass (if passed to a non-screener).
offensive_weak_side_player_count
Type: integer | Nullable
How many of the three offensive off-ball players were on the weak side at the start of the pick.
ballhandler_movement_side
Type: string | Nullable
Direction the ballhandler moved during the action (left or right). null if the movement side could not be determined.
ballhandler_possession_touch_id
Type: string | Nullable
CTG-generated possession touch ID for the ballhandler touch linked to this ball screen.
ballhandler_possession_touch_outcome_id
Type: string | Nullable
CTG-generated event ID for how the linked ballhandler possession touch ended. This can be null when the outcome does not have a specific CTG event ID.
drive_id
Type: string | Nullable
CTG-generated drive ID for the earliest drive by the ballhandler that overlaps the linked possession touch after the screening frame. null if no such drive is identified.
