Quantifying Second-Ball Wins

Bet you won’t find this stat on your Wyscouts or FBrefs

Chun Hang
9 min readNov 25, 2023

Written by Lee Chun Hang (@chunhang7 on X and IG)

Writer’s Note: Open to football analytics opportunities. Freelance or part-time. DM on socials or email me at leechunhang04@gmail.com to connect!

Controlling Chaos

Pretty sure we’ve heard it all before — “Win your 50/50s”, “Dominate the Duels”, “Winning the Midfield Battle”, etc. Each mantra echoing the significance of the physical battles on the pitch, yet the narrative still centres around the technical prowess displayed with the ball.

Credit @CoachesVoice on X

True control of the game, however, is often dictated by what teams do without it. Consequently, the failure to anticipate chaos becomes rather self-sabotaging to a team’s dominance on the pitch.

While chaos is not conventionally quantified within traditional football statistics, its influence is undeniably critical when analyzing a team’s playstyle or assessing a player’s dominance — more precisely, their intelligence on the field. It is from this realization that the inspiration arises to quantify an often-overlooked yet pivotal aspect of football: Second-Ball Wins.

The 3 Golden Rules of Second-Ball Wins

To put it simply, Second-Ball Wins are possession regains (3) following interventions (2) from a long pass (1). Now let’s break the definition down bit by bit –

1. Long Pass: Any second-ball have to start with a long pass, or rather a pass over 35 yards. We aren’t interested at the success rate of these passes, so long as the pass meets a player up the pitch.

2. Interventions: Second-Balls must be an acknowledgement that the first point of contact from the long ball is an intervention, be it from their teammate or not. This intervention can be a successful duel from the opposition but leading to an unsuccessful pass, or a haphazard clearance from the defender meeting the initial pass. There are many variations and combinations of these interventions and will be explained later, but the general idea is that there must be a first point of contact before a second-ball can be contested.

3. Possession Regain: As long as the player takes control of the loose possession, we will count it as a second-ball win. It’s also important that the player has to complete an action successfully upon retrieving the ball via second-ball, so that it’s actually valuable.

Second-Ball Chains

Now for the interesting bit.

In order to tabulate these second ball wins, we’d need to understand the different combinations that constitute a ‘Second-Ball Win’, inclusive of set pieces. These combinations are more easily interpreted through a series of chains that details events leading up to a second-ball.

Figuring out the dynamics between each event can be difficult, but they generally follow the same chain of events. Nevertheless, I must add that most of these chains are influenced by my own definition of a second-ball, which may vary with the public. Though, I feel like it’s best to share it.

Chain 1: ABA

The classic.

Team A hits a long hopeful ball forward, only to be met by the defender for a clearance. But the loose ball is swept up by one of Team A’s players, and they pile on the pressure from there.

df['SecondBall1'] = np.where((df['isTouch'] == True) & (df['SetPieceTaken'] == 0) &
(((df['type_displayName'] != 'BallRecovery') & (df['outcomeType_displayName'] == 'Successful')) |
((df['type_displayName'] == 'BallRecovery') & (df['outcomeType_displayName'].shift(-1) == 'Successful') & (df['teamId'].shift(-1) == df['teamId']))) &

((df['Longball'].shift(2) == True) & (df['teamId'].shift(2) == df['teamId']) &
(df['outcomeType_displayName'].shift(2) == 'Unsuccessful') &
(df['SetPieceTaken'].shift(2) == 0) & (df['SetPieceTaken'].shift(3) == 0) & (df['SetPieceTaken'].shift(4) == 0) &
(df['endX'].shift(2) < 120) & (df['endY'].shift(2) < 80)) &

(((df['type_displayName'].shift(1) == 'Pass') | (df['type_displayName'].shift(1) == 'Clearance') |
(df['type_displayName'].shift(1) == 'Interception')) & (df['Longball'].shift(1) != True) &
(df['teamId'].shift(1) != df['teamId']) & (df['SetPieceTaken'].shift(1) == 0) &
(df['position'].shift(1) != 'GK')),1,0)

Chain 2: ABABA

Another long ball forward from Team A, towards Team A attacker and Team B defender tangled with each other as they contest for the aerial battle.

Team B defender is successful with the duel, Team A is not. But the loose pass thereafter is collected by another one of (or the same) Team A’s attacker.

df['SecondBall2'] = np.where((df['isTouch'] == True) & (df['SetPieceTaken'] == 0) & (df['type_displayName'] != 'BallTouch') &
(((df['type_displayName'] != 'BallRecovery') & (df['outcomeType_displayName'] == 'Successful')) |
((df['type_displayName'] == 'BallRecovery') & (df['outcomeType_displayName'].shift(-1) == 'Successful') & (df['teamId'].shift(-1) == df['teamId']))) &

((df['Longball'].shift(4) == True) & (df['teamId'].shift(4) == df['teamId']) &
(df['outcomeType_displayName'].shift(4) == 'Unsuccessful') &
(df['SetPieceTaken'].shift(4) == 0) & (df['SetPieceTaken'].shift(5) == 0) & (df['SetPieceTaken'].shift(6) == 0) &
(df['endX'].shift(4) < 120) & (df['endY'].shift(4) < 80)) &

((df['type_displayName'].shift(3) == 'Aerial') & (df['outcomeType_displayName'].shift(3) == 'Successful') &
(df['teamId'].shift(3) != df['teamId'])) &

((df['type_displayName'].shift(2) == 'Aerial') & (df['outcomeType_displayName'].shift(2) == 'Unsuccessful') &
(df['teamId'].shift(2) == df['teamId'])) &

(((df['type_displayName'].shift(1) == 'Pass') | (df['type_displayName'].shift(1) == 'Clearance') |
(df['type_displayName'].shift(1) == 'Interception')) & (df['Longball'].shift(1) != True) &
(df['teamId'].shift(1) != df['teamId']) & (df['SetPieceTaken'].shift(1) == 0) &
(df['position'].shift(1) != 'GK')),1,0)

Chain 3: ABBA

A long pass from Team A is intercepted, meaning possession is recovered from Team B. But they make a fuss of the pass and lose out the second-ball to Team A, as they regain the possession.

df['SecondBall3'] = np.where((df['isTouch'] == True) & (df['SetPieceTaken'] == 0) & 
(((df['type_displayName'] != 'BallRecovery') & (df['outcomeType_displayName'] == 'Successful')) |
((df['type_displayName'] == 'BallRecovery') & (df['outcomeType_displayName'].shift(-1) == 'Successful') & (df['teamId'].shift(-1) == df['teamId']))) &

((df['Longball'].shift(3) == True) & (df['teamId'].shift(3) == df['teamId']) &
(df['outcomeType_displayName'].shift(3) == 'Unsuccessful') &
(df['SetPieceTaken'].shift(3) == 0) & (df['SetPieceTaken'].shift(4) == 0) & (df['SetPieceTaken'].shift(5) == 0) &
(df['endX'].shift(3) < 120) & (df['endY'].shift(3) < 80)) &

((df['type_displayName'].shift(2) == 'BallRecovery') & (df['teamId'].shift(2) != df['teamId'])) &

(((df['type_displayName'].shift(1) == 'Pass') | (df['type_displayName'].shift(1) == 'Clearance') |
(df['type_displayName'].shift(1) == 'Interception')) & (df['Longball'].shift(1) != True) &
(df['teamId'].shift(1) != df['teamId']) & (df['SetPieceTaken'].shift(1) == 0) &
(df['position'].shift(1) != 'GK')),1,0)

Chain 4: AAA

The flick-on, well normally.

Long ball from Team A is successful, in that it meets the intended target as they cushion the ball towards another one of their attacker to take control of possession.

df['SecondBall4'] = np.where((df['isTouch'] == True) & (df['SetPieceTaken'] == 0) &
(((df['type_displayName'] != 'BallRecovery') & (df['outcomeType_displayName'] == 'Successful')) |
((df['type_displayName'] == 'BallRecovery') & (df['outcomeType_displayName'].shift(-1) == 'Successful') & (df['teamId'].shift(-1) == df['teamId']))) &

((df['Longball'].shift(2) == True) & (df['teamId'].shift(2) == df['teamId']) &
(df['outcomeType_displayName'].shift(2) == 'Successful') &
(df['SetPieceTaken'].shift(2) == 0) & (df['SetPieceTaken'].shift(3) == 0) & (df['SetPieceTaken'].shift(4) == 0) &
(df['endX'].shift(2) < 120) & (df['endY'].shift(2) < 80)) &

((df['HeadPass'].shift(1) == True) & (df['Longball'].shift(1) != True) &
(df['teamId'].shift(1) == df['teamId']) & (df['SetPieceTaken'].shift(1) == 0) &
(df['position'].shift(1) != 'GK')),1,0)

Chain 5: AABAB

Technically a reversed version of Chain 2: ABABA.

A long pass from Team A is now successfully won by Team A attacker whilst contesting the duel with Team B defender.

However the flick-on or pass forward is unsuccessful and cleared away by Team A.

df['SecondBall5'] = np.where((df['isTouch'] == True) & (df['SetPieceTaken'] == 0) & 
(df['type_displayName'] != 'BallTouch') &
(((df['type_displayName'] != 'BallRecovery') & (df['outcomeType_displayName'] == 'Successful')) |
((df['type_displayName'] == 'BallRecovery') & (df['outcomeType_displayName'].shift(-1) == 'Successful') & (df['teamId'].shift(-1) == df['teamId']))) &

((df['Longball'].shift(4) == True) & (df['teamId'].shift(4) != df['teamId']) &
(df['outcomeType_displayName'].shift(4) == 'Successful') &
(df['SetPieceTaken'].shift(4) == 0) & (df['SetPieceTaken'].shift(5) == 0) & (df['SetPieceTaken'].shift(6) == 0) &
(df['endX'].shift(4) < 120) & (df['endY'].shift(4) < 80)) &

((df['type_displayName'].shift(3) == 'Aerial') & (df['outcomeType_displayName'].shift(3) == 'Successful') &
(df['teamId'].shift(3) != df['teamId'])) &

((df['type_displayName'].shift(2) == 'Aerial') & (df['outcomeType_displayName'].shift(2) == 'Unsuccessful') &
(df['teamId'].shift(2) == df['teamId'])) &

(((df['type_displayName'].shift(1) == 'Pass') & (df['outcomeType_displayName'].shift(1) == 'Unsuccessful')) &
(df['Longball'].shift(1) != True) & (df['teamId'].shift(1) != df['teamId']) &
(df['SetPieceTaken'].shift(1) == 0) & (df['position'].shift(1) != 'GK')),1,0)

Chain 6: AABB

Quite simply a miscontrol following an aerial battle between Team A attacker and Team B defender. Team B sweeping up loose possession via the second ball.

df['SecondBall6'] = np.where((df['isTouch'] == True) & (df['SetPieceTaken'] == 0) & 
(df['type_displayName'] != 'BallTouch') &
(((df['type_displayName'] != 'BallRecovery') & (df['outcomeType_displayName'] == 'Successful')) |
((df['type_displayName'] == 'BallRecovery') & (df['outcomeType_displayName'].shift(-1) == 'Successful') & (df['teamId'].shift(-1) == df['teamId']))) &

((df['Longball'].shift(3) == True) & (df['teamId'].shift(3) != df['teamId']) &
(df['outcomeType_displayName'].shift(3) == 'Successful') &
(df['SetPieceTaken'].shift(3) == 0) & (df['SetPieceTaken'].shift(4) == 0) & (df['SetPieceTaken'].shift(5) == 0) &
(df['endX'].shift(3) < 120) & (df['endY'].shift(3) < 80)) &

((df['type_displayName'].shift(2) == 'Aerial') & (df['outcomeType_displayName'].shift(2) == 'Successful') &
(df['teamId'].shift(2) != df['teamId'])) &

((df['type_displayName'].shift(1) == 'Aerial') & (df['outcomeType_displayName'].shift(1) == 'Unsuccessful') &
(df['teamId'].shift(1) == df['teamId'])),1,0)

Chain 7: ABB

And we end with the simplest of chains. An unsuccessful long pass from Team A gets cleared and retrieved by another one of Team B’s player via the second-ball.

df['SecondBall7'] = np.where((df['isTouch'] == True) & (df['SetPieceTaken'] == 0) & 
(((df['type_displayName'] != 'BallRecovery') & (df['outcomeType_displayName'] == 'Successful')) |
((df['type_displayName'] == 'BallRecovery') & (df['outcomeType_displayName'].shift(-1) == 'Successful') & (df['teamId'].shift(-1) == df['teamId']))) &

((df['Longball'].shift(2) == True) & (df['teamId'].shift(2) != df['teamId']) &
(df['outcomeType_displayName'].shift(2) == 'Unsuccessful') &
(df['SetPieceTaken'].shift(2) == 0) & (df['SetPieceTaken'].shift(3) == 0) & (df['SetPieceTaken'].shift(4) == 0) &
(df['endX'].shift(2) < 120) & (df['endY'].shift(2) < 80)) &

(((df['type_displayName'].shift(1) == 'Pass') | (df['type_displayName'].shift(1) == 'Clearance') |
(df['type_displayName'].shift(1) == 'Interception')) & (df['Longball'].shift(1) != True) &
(df['teamId'].shift(1) == df['teamId']) & (df['SetPieceTaken'].shift(1) == 0) &
(df['position'].shift(1) != 'GK')),1,0)

Second-Ball: Set-Pieces

The 7 chains above detail the common sequences leading up to a second-ball. An additional 7 are created with similar chain of events are created to cater for set-pieces.

All together I have 14 separate chains to win a second-ball (so far).

Winning the third-ball

Winning the second-ball is half the battle, what they do with the ball after, is perhaps where the next phase of analysis can be done. Nevertheless, tabulating and visualizing for the location of second-ball is valuable for unravelling a team’s playstyle and understanding how they leverage this advantage.

As we zero in on player-specific metrics, measuring second-ball wins requires careful contextualization for each player’s unique role within the team. This metric becomes a measure not only of their technical skill but also of their positioning and anticipation on the field.

Credit: Author @chunhang7 on X

Despite the infancy of the application of second-ball wins in football analytics, this article aspires to catalyse more comprehensive discovery and discussion. By highlighting this often-neglected aspect of the game, I hope to reveal fresh opportunities for analysis and understanding in the complex realm of football tactics.

Written by Lee Chun Hang (@chunhang7)

Once again, if you’re looking for a freelance football analyst, I am available for hire.

--

--

Chun Hang

Football Data Analyst | More Vizzes on Twitter @chunhang7