The Startup
Published in

The Startup

Source: UEFA.com

Simulating UEFA Champions League Draw With Python

Exciting European football matches are in your hand!

Champions League Trophy. Source: UEFA.com

Drawing Restrictions

As I mentioned before, there are 16 teams that are coming from 8 different groups, with 8 teams being group winners (seeded) and the other 8 teams are group runner-ups (unseeded). We will import the teams' data to Python with Pandas library. You can download the .csv file here.

import pandas as pd
all_teams = pd.read_csv('UCL_roundof16.csv')
display(all_teams)
  1. Teams from the same group in the group stage cannot play against each other
  2. Teams from the same country cannot play against each other
# initiate new columns
all_teams['Possible Opponents'] = [[]] * 16
all_teams['Number of Possible Opponents'] = [0] * 16
all_teams['Picked'] = [False] * 16
# fill the new columns
for i in range(16):
all_teams['Possible Opponents'][i] = []
for j in range(16):
if (all_teams['Seeded'][i] != all_teams['Seeded'][j])& (all_teams['Country'][i] != all_teams['Country'][j]) & (all_teams['Group'][i] != all_teams['Group'][j]):
all_teams['Possible Opponents'][i].append(all_teams['Abbreviation'][j])
all_teams['Number of Possible Opponents'][i] = len(all_teams['Possible Opponents'][i])

Drawing Process: Pick the Ball, Reveal Your Fate!

If you never follow a live UCL Draw before, you can see one from last year in this video, just to get a glimpse of how it’s done.

  1. Put balls that represent all the teams who are eligible to play against Team X (based on the table that we created before) in a bowl, then pick one ball among them, we will call this Team Y. Remove Team Y from the draw so that it won’t get picked up twice. There we have our first fixture, Team X vs Team Y.
  2. Repeat this process until all teams got picked.

Let’s Code!

We will divide the code into three parts based on the steps above. But before that, we will create a Pandas DataFrame where we can store the fixtures that already drawn.

first_team = [''] * 8
vs = ['vs'] * 8
second_team = [''] * 8
fixtures = pd.DataFrame({'First Team': first_team, 'vs': vs, 'Second Team': second_team})

Pick the first ball

Here we pick our first ball, which will represent the seeded team.

# pick the first ballm = 0
seeded_teams_left = len(all_teams[(all_teams['Seeded'] == True) & (all_teams['Picked'] == False)])
first = random.randint(0, seeded_teams_left - 1)
fixtures['First Team'][m] = all_teams['Abbreviation'][first]
all_teams['Picked'][all_teams['Abbreviation'] == fixtures['First Team'][m]] = True
display(fixtures.loc[[m]])

Pick the second ball

Next, we will pick the second ball that is eligible to play against a team from the first ball. We also remove the teams that already drawn from our dataset so it won’t get drawn twice.

# pick the second ballsecond = random.randint(0, all_teams['Number of Possible Opponents'][first] - 1)
fixtures['Second Team'][m] = all_teams['Possible Opponents'][first][second]
# remove teams that already drawn from datasetall_teams['Picked'][all_teams['Abbreviation'] == fixtures['Second Team'][m]] = True
all_teams = all_teams[all_teams['Picked'] == False].reset_index(drop = True)
for i in range(len(all_teams)):
if fixtures['First Team'][m] in all_teams['Possible Opponents'][i]:
all_teams['Possible Opponents'][i].remove(fixtures['First Team'][m])
if fixtures['Second Team'][m] in all_teams['Possible Opponents'][i]:
all_teams['Possible Opponents'][i].remove(fixtures['Second Team'][m])
all_teams['Number of Possible Opponents'][i] = len(all_teams['Possible Opponents'][i])
m += 1
display(fixtures.loc[[m-1]])

Implied Fixtures

We now will create a function to check if our latest draw would result in an implied fixture (this is unlikely after drawing just one fixture, but there is no harm in checking). There is an implied fixture if the minimum number of possible opponents in the remaining teams is equal to 1.

if min(all_teams['Number of Possible Opponents']) > 1:
print('No implied fixtures. Continue picking ball for the first team')
else:
team_index = all_teams[all_teams['Number of Possible Opponents'] == 1].index[0]
if all_teams['Seeded'][team_index] == True:
first = team_index
fixtures['First Team'][m] = all_teams['Abbreviation'][first]
all_teams['Picked'][all_teams['Abbreviation'] == fixtures['First Team'][m]] = True

second = random.randint(0, all_teams['Number of Possible Opponents'][first] - 1)
fixtures['Second Team'][m] = all_teams['Possible Opponents'][first][second]
all_teams['Picked'][all_teams['Abbreviation'] == fixtures['Second Team'][m]] = True

all_teams = all_teams[all_teams['Picked'] == False].reset_index(drop = True)
else:
second = team_index
fixtures['Second Team'][m] = all_teams['Abbreviation'][second]
all_teams['Picked'][all_teams['Abbreviation'] == fixtures['Second Team'][m]] = True

fixtures['First Team'][m] = all_teams['Possible Opponents'][second][0]
all_teams['Picked'][all_teams['Abbreviation'] == fixtures['First Team'][m]] = True

all_teams = all_teams[all_teams['Picked'] == False].reset_index(drop = True)
for i in range(len(all_teams)):
if fixtures['First Team'][m] in all_teams['Possible Opponents'][i]:
all_teams['Possible Opponents'][i].remove(fixtures['First Team'][m])
if fixtures['Second Team'][m] in all_teams['Possible Opponents'][i]:
all_teams['Possible Opponents'][i].remove(fixtures['Second Team'][m])
all_teams['Number of Possible Opponents'][i] = len(all_teams['Possible Opponents'][i])
m += 1
print('There is an implied fixture. Please check again')
display(fixtures.loc[[m-1]])

Final Result

As we go on, here is the full result of our draw.

Conclusions

There we have it, we have done Champions League Round of 16 draw simulation using Python. If you want to play around with the code, go check my Google Colab notebook here, and see if you can get other interesting results. If you have any feedback please feel free to leave a comment. Good luck to all the football fans, I hope your team would get a good result!

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store