Loading...

Football

In this example, we use the football dataset to predict the outcomes of games between various teams. You can download the Jupyter Notebook of the study here and the dataset here.

  • date: Date of the game.

  • home_team: Home Team.

  • home_score: Home Team number of goals.

  • away_team: Away Team.

  • away_score: Away Team number of goals.

  • tournament: Game Type (World Cup, Friendly…).

  • city: City where the game took place.

  • country: Country where the game took place.

  • neutral: If the event took place to a neutral location.

We will follow the data science cycle (Data Exploration - Data Preparation - Data Modeling - Model Evaluation - Model Deployment) to solve this problem.

Initialization

This example uses the following version of VerticaPy:

import verticapy as vp

vp.__version__
Out[2]: '1.1.0'

Connect to Vertica. This example uses an existing connection called VerticaDSN. For details on how to create a connection, see the Connection tutorial. You can skip the below cell if you already have an established connection.

vp.connect("VerticaDSN")

Let’s create a Virtual DataFrame of the dataset.

football = vp.read_csv("games.csv")
football.head(5)
📅
date
Date
100%
...
Abc
country
Varchar(64)
100%
010
neutral
Boolean
100%
11876-03-25...Scotland
21877-03-05...Wales
31883-02-24...England
41884-02-09...Wales
51884-03-17...Wales

Data Exploration and Preparation

Let’s explore the data by displaying descriptive statistics of all the columns.

football["date"].describe()
value
name"date"
dtypedate
count41586
min1872-11-30
max2020-02-01

The dataset includes a total of 41,586 games, which take place between 1872 and 2020. Let’s look at our game types and teams.

football["tournament"].describe()
value
"tournament"
varchar(84)
112.0
41586.0
17029
10630
7236
2582
1672
900
813

Different types of tournaments took place (FIFA World Cup, UEFA Euro, etc.) aand most of the games in our data are friendlies or qualifiers for international tournaments.

football.describe()
...
approx_75%
max
"home_score"...2.031.0
"away_score"...2.021.0
"neutral"...0.01.0
football.describe(method = "categorical")
...
top
top_percent
"date"...2012-02-290.159
"home_team"...Brazil1.366
"away_team"...Uruguay1.301
"home_score"...129.57
"away_score"...037.135
"tournament"...Friendly40.949
"city"...Kuala Lumpur1.416
"country"...United States2.787
"neutral"...
75.275

The dataset includes 308 national teams. For most of the games, the home team scores better than the away team. Since some games take place in a neutral location, we can ensure this hypothesis using the variable neutral. Notice also that the number of goals per match is pretty low (median of 1 for both away and home teams).

Goal

Our goal for the study will be to predict the outcomes of games after 2015. Before doing the study, we can notice that some teams names have changed over time. We need to change the old names by the new names otherwise it will add too much bias in the data.

for team in ["home_team", "away_team"]:
    football[team].decode(
        'German DR', 'Germany',
        'Czechoslovakia', 'Czech Republic',
        'Yugoslavia', 'Serbia',
        'Yemen DPR', 'Yemen',
        football[team],
    )

Let’s just consider teams that have played more than five home and away games.

football["cnt_games_1"] = "COUNT(*) OVER (PARTITION BY home_team)"
football["cnt_games_2"] = "COUNT(*) OVER (PARTITION BY away_team)"
football.filter((football["cnt_games_2"] > 5) & (football["cnt_games_1"] > 5))
vp.drop("football_clean", method = "table")
football.to_db(
    name = "football_clean",
    usecols = [
        "date",
        "home_score",
        "home_team",
        "tournament",
        "away_team",
        "away_score",
        "neutral",
        "country",
        "city",
    ],
    relation_type = "table",
    inplace = True,
)
📅
date
Date
100%
...
Abc
country
Varchar(64)
100%
Abc
city
Varchar(56)
100%
11876-03-25...ScotlandGlasgow
21877-03-05...WalesWrexham
31883-02-24...EnglandLiverpool
41884-02-09...WalesWrexham
51884-03-17...WalesWrexham
61885-03-14...ScotlandGlasgow
71887-02-19...ScotlandGlasgow
81888-03-24...Republic of IrelandBelfast
91890-03-22...ScotlandPaisley
101890-04-05...ScotlandGlasgow
111891-02-07...Republic of IrelandBelfast
121891-04-06...EnglandBlackburn
131892-02-27...WalesBangor
141893-03-18...WalesWrexham
151894-03-24...ScotlandKilmarnock
161895-03-18...EnglandLondon
171897-02-20...EnglandNottingham
181897-03-29...EnglandSheffield
191898-03-05...Republic of IrelandBelfast
201899-03-18...WalesWrexham

A lot of things could influence the outcome of a game. Since we only have access to the score, teams, and type of game, we can’t consider external factors like, weather or temperature, which would otherwise help our prediction.

To create a good model using this dataset, we could compute each team’s key performance indicator (KPI), ranking (clusters computed using the number of games in important tournaments like the World Cup, the percentage of victory…), shape (moving windows using the last games information), and other factors.

Here’s our plan: - Identify cup winners - Rank the teams with clustering - Compute teams’ KPIs - Create a machine learning model

Data Preparation for Clustering

To create clusters, we need to find which teams are the winners of main tournaments (mainly the World Cups and Continental Cups). Since all tournaments took place the same year, we could partition by tournament and year to identify the last game of the tournament.

We’ll ignore ties for our analysis since there’s no way to determine a winner.

Cup Winner

Let’s start by creating the feature winner to indicate the winner of a game.

import verticapy.sql.functions as fun

football.filter(fun.year(football["date"]) <= 2015)
football.case_when(
    "winner",
    football["home_score"] > football["away_score"], football["home_team"],
    football["home_score"] < football["away_score"], football["away_team"],
    None,
)
📅
date
Date
100%
...
123
home_score
Int
100%
Abc
winner
Varchar(64)
77%
11876-03-25...4Scotland
21877-03-05...0Scotland
31883-02-24...7England
41884-02-09...6Wales
51884-03-17...0England
61885-03-14...8Scotland
71887-02-19...4Scotland
81888-03-24...2Scotland
91890-03-22...5Scotland
101890-04-05...1[null]
111891-02-07...7Northern Ireland
121891-04-06...2England
131892-02-27...1[null]
141893-03-18...0Scotland
151894-03-24...5Scotland
161895-03-18...1[null]
171897-02-20...6England
181897-03-29...4England
191898-03-05...2England
201899-03-18...0Scotland

Let’s analyze the last game of each tournament.

football["year"] = fun.year(football["date"])
football.analytic(
    "row_number",
    order_by = {"date": "desc"},
    by = ["tournament", "year"] ,
    name = "order_tournament",
)
📅
date
Date
100%
...
123
home_score
Int
100%
123
order_tournament
Integer
100%
11892-04-02...11
21892-03-26...62
31892-03-19...23
41892-03-05...04
51892-03-05...05
61892-02-27...16
71929-09-20...21
81987-05-26...01
91987-05-23...02
101987-05-19...13
112006-12-27...21
122006-12-27...02
132006-12-24...43
142006-12-24...24
152006-12-21...05
162006-12-21...66
172006-12-20...47
182006-12-17...28
192006-12-14...29
201998-10-21...21

We can filter the data by only considering the last games and top tournaments.

football.filter(
    conditions = [
        football["order_tournament"] == 1,
        football["winner"] != None,
        football["tournament"]._in(
            [
                "FIFA World Cup",
                "UEFA Euro",
                "Copa América",
                "African Cup of Nations",
                "AFC Asian Cup",
                "Gold Cup",
            ]
        )
    ]
)
📅
date
Date
100%
...
123
home_score
Int
100%
123
order_tournament
Integer
100%
11937-02-01...21
21984-06-27...21
31997-06-29...11
41934-06-10...21
51990-07-08...11
61992-06-26...21
71946-02-10...21
81956-09-15...51
91978-03-16...21
101989-07-16...11
111923-12-02...21
121996-01-21...31
131990-03-16...11
142015-07-26...11
151980-09-30...31
161936-12-30...21
171958-06-29...21
181996-06-30...11
192004-08-07...11
201957-04-06...21

Let’s consider the World Cup as a special tournament. It is the only one where the confrontations between the top teams is possible.

football["Word_Cup"] = fun.decode(
    football["tournament"], "FIFA World Cup",
    1, 0,
)
123
Word_Cup
Integer
10
20
31
41
50
60
70
80
90
100
110
120
130
140
150
160
170
180
190
200

We can compute all the number of cup-wins by team. As expected, Brazil and Germany are the top football teams.

agg = [
    fun.sum(football["Word_Cup"])._as("nb_World_Cup"),
    fun.sum(1 - football["Word_Cup"])._as("nb_Continental_Cup"),
]
football_cup_winners = football.groupby(["winner"], agg)
football_cup_winners.sort(
    {
        "nb_World_Cup": "desc",
        "nb_Continental_Cup": "desc",
    }
).head(10)
Abc
winner
Varchar(64)
100%
...
123
nb_World_Cup
Integer
100%
123
nb_Continental_Cup
Integer
100%
1Brazil...48
2Germany...43
3Italy...31
4Uruguay...29
5Argentina...29
6Spain...13
7France...12
8England...10
9Egypt...06
10Mexico...05

Let’s export the result to our Vertica database.

vp.drop(
    "football_cup_winners",
    method = "table",
)
football_cup_winners.to_db(
    "football_cup_winners",
    relation_type = "table",
)
Abc
winner
Varchar(64)
100%
...
123
nb_World_Cup
Integer
100%
123
nb_Continental_Cup
Integer
100%
1Brazil...48
2Germany...43
3Italy...31
4Uruguay...29
5Argentina...29
6Spain...13
7France...12
8England...10
9Egypt...06
10Mexico...05
11Japan...04
12Nigeria...04
13Peru...03
14United States...03
15Ecuador...02
16Iran...02
17Chile...02
18Canada...02
19Paraguay...02
20South Korea...02

Team Confederations

Looking into team confederations could help our analysis. For example, this might help us quantify skill differences between different continents. A team that had played a qualification of a specific location can only belong to that tournament confederation.

First let’s encode the different continents so we can compute the correct aggregations.

football = vp.read_csv("games.csv")
football.case_when(
    'confederation',
    football["tournament"] == 'UEFA Euro qualification', 5,
    football["tournament"] == 'African Cup of Nations qualification', 4,
    football["tournament"] == 'AFC Asian Cup qualification', 3,
    football["tournament"] == 'Copa América', 2,
    football["tournament"] == 'Gold Cup', 1, 0,
)
📅
date
Date
100%
...
Abc
home_team
Varchar(64)
100%
123
confederation
Integer
100%
11876-03-25...Scotland0
21877-03-05...Wales0
31883-02-24...England0
41884-02-09...Wales0
51884-03-17...Wales0
61885-03-14...Scotland0
71887-02-19...Scotland0
81888-03-24...Northern Ireland0
91890-03-22...Scotland0
101890-04-05...Scotland0
111891-02-07...Northern Ireland0
121891-04-06...England0
131892-02-27...Wales0
141893-03-18...Wales0
151894-03-24...Scotland0
161895-03-18...England0
171897-02-20...England0
181897-03-29...England0
191898-03-05...Northern Ireland0
201899-03-18...Wales0

We can aggregate the data and get each team’s continent.

confederation = football.groupby(
    ["home_team"],
    [fun.max(football["confederation"])._as("confederation")],
)
confederation.head(100)
Abc
home_team
Varchar(64)
100%
123
confederation
Integer
100%
1Saudi Arabia3
2Thailand3
3Tamil Eelam0
4Netherlands5
5Haiti2
6Gotland0
7Kabylia0
8British Virgin Islands0
9Ethiopia4
10Niue0
11North Korea3
12Iraqi Kurdistan0
13Dominican Republic0
14Chinese Taipei3
15Laos3
16Ukraine5
17Mali4
18Austria5
19Iraq3
20Guadeloupe1
21Matabeleland0
22Saint Kitts and Nevis0
23Crimea0
24Curaçao1
25Shetland0
26Egypt4
27Bangladesh3
28Székely Land0
29El Salvador1
30Réunion0
31Bhutan3
32Western Sahara0
33Sri Lanka3
34Paraguay2
35Canary Islands0
36Tanzania4
37Peru2
38Cape Verde4
39Albania5
40Guatemala1
41Antigua and Barbuda0
42Abkhazia0
43South Ossetia0
44Frøya0
45Samoa0
46Jordan3
47Comoros4
48Tibet0
49Kenya4
50Provence0
51Iceland5
52Nigeria4
53Burkina Faso4
54Pakistan3
55Rwanda4
56England5
57Latvia5
58Silesia0
59Raetia0
60Kernow0
61Russia5
62Puerto Rico0
63Cayman Islands0
64Norway5
65Guinea-Bissau4
66Azerbaijan5
67Malaysia3
68Central Spain0
69Moldova5
70Italy5
71Slovenia5
72Hong Kong3
73France5
74United Koreans in Japan0
75Saint Lucia0
76Arameans Suryoye0
77German DR5
78Burma3
79Corsica0
80Manchukuo0
81Zambia4
82Bermuda1
83Madagascar4
84Togo4
85Andorra5
86Suriname0
87Chagos Islands0
88Northern Cyprus0
89São Tomé and Príncipe4
90Western Armenia0
91Tuvalu0
92Gozo0
93Romania5
94Vatican City0
95U.S. Virgin Islands0
96Cook Islands0
97Zimbabwe4
98Poland5
99Artsakh0
100Saarland0

We can decode the previous label encoding.

confederation["confederation"].decode(
    5, "UEFA",
    4, "CAF",
    3, "AFC",
    2, "CONMEBOL",
    1, "CONCACAF",
    "OFC",
)
Abc
home_team
Varchar(64)
100%
Abc
confederation
Varchar(8)
100%
1DominicaOFC
2Isle of WightOFC
3FinlandUEFA
4DR CongoCAF
5SomaliaCAF
6BotswanaCAF
7UruguayCONMEBOL
8U.S. Virgin IslandsOFC
9Cook IslandsOFC
10ZimbabweCAF
11PolandUEFA
12ArtsakhOFC
13EcuadorCONMEBOL
14TuvaluOFC
15Chagos IslandsOFC
16Northern CyprusOFC
17São Tomé and PríncipeCAF
18Western ArmeniaOFC
19GozoOFC
20RomaniaUEFA

Let’s export the result to our Vertica database.

vp.drop("confederation")
confederation["home_team"].rename("team")
confederation.to_db(
    name = "confederation",
    relation_type = "table",
)
Abc
confederation
Varchar(8)
100%
Abc
team
Varchar(64)
100%
1OFCCrimea
2AFCSaudi Arabia
3AFCThailand
4OFCGotland
5OFCKabylia
6OFCBritish Virgin Islands
7CAFEthiopia
8OFCNiue
9AFCChinese Taipei
10AFCLaos
11UEFAUkraine
12CAFMali
13CONCACAFGuadeloupe
14OFCMatabeleland
15OFCSaint Kitts and Nevis
16OFCTamil Eelam
17UEFANetherlands
18CONMEBOLHaiti
19CAFTanzania
20CONMEBOLPeru

Team KPIs

We use just two variables to track teams: away_team and home_team. This makes it a bit difficult to compute new features. We need to duplicate the dataset and intervert the two teams. This way, we can compute KPIs using a partition by the first team to avoid double-counting any games.

football = vp.vDataFrame("football_clean")
football.filter(fun.year(football["date"]) <= 2015)
football["home_team"].rename("team1")
football["home_score"].rename("team1_score")
football["away_team"].rename("team2")
football["away_score"].rename("team2_score")
football["neutral"].decode(True, 0, 1)

football2 = vp.vDataFrame("football_clean")
football2.filter(fun.year(football["date"]) <= 2015)
football2["home_team"].rename("team2")
football2["home_score"].rename("team2_score")
football2["away_team"].rename("team1")
football2["away_score"].rename("team1_score")
football2["neutral"].decode(True, 0, 2)

# Merging the 2 interverted datasets
all_matchs = football.append(football2)
all_matchs["neutral"].rename("home_team_id")
📅
date
Date
100%
...
123
team2_score
Integer
100%
123
home_team_id
Integer
100%
11875-03-06...21
21878-03-23...01
31879-04-07...31
41880-03-13...41
51880-03-15...31
61883-03-10...31
71883-03-17...11
81884-03-15...01
91886-03-13...61
101886-03-27...11
111886-11-25...21
121887-02-26...01
131888-03-10...11
141888-03-17...51
151888-04-07...51
161891-03-07...11
171892-03-05...21
181892-03-19...31
191893-03-13...01
201893-04-01...21

To compute the different aggregations, we need to add dummies which indicate the type of game and winner.

all_matchs["World_Tournament"] = fun.case_when(all_matchs["tournament"]._in(
    [
        "FIFA World Cup",
        "Confederations Cup"
    ],
), 1, 0)
all_matchs["Continental_Tournament"] = fun.case_when(
    all_matchs["tournament"]._in(
        [
            "UEFA Euro",
            "Copa América",
            "African Cup of Nations",
            "AFC Asian Cup",
            "Gold Cup",
            "FIFA World Cup qualification",
        ]
    ), 1, 0)
all_matchs["Victory_team1"] = (all_matchs["team1_score"] > all_matchs["team2_score"])
all_matchs["Victory_team1"].astype("int")
all_matchs["Draw"] = (all_matchs["team1_score"] == all_matchs["team2_score"])
all_matchs["Draw"].astype("int")
📅
date
Date
100%
...
123
Victory_team1
Int
100%
123
Draw
Int
100%
11875-03-06...01
21878-03-23...10
31879-04-07...00
41880-03-13...10
51880-03-15...00
61883-03-10...00
71883-03-17...01
81884-03-15...10
91886-03-13...00
101886-03-27...01
111886-11-25...10
121887-02-26...10
131888-03-10...10
141888-03-17...00
151888-04-07...00
161891-03-07...10
171892-03-05...00
181892-03-19...00
191893-03-13...10
201893-04-01...10

Now we can compute each team’s KPI.

teams_kpi = all_matchs.groupby(
    ["team1"],
    [
        fun.sum(all_matchs["World_Tournament"])._as("Number_Games_World_Tournament"),
        fun.sum(all_matchs["Continental_Tournament"])._as("Number_Games_Continental_Tournament"),
        fun.avg(fun.decode(all_matchs["World_Tournament"], 1, all_matchs["Victory_team1"]))._as("Percent_Victory_World_Tournament"),
        fun.avg(fun.decode(all_matchs["Continental_Tournament"], 1, all_matchs["Victory_team1"]))._as("Percent_Victory_Continental_Tournament"),
        fun.avg(fun.case_when((all_matchs["home_team_id"] == 1) & (all_matchs["World_Tournament"] == 0) & (all_matchs["Continental_Tournament"] == 0), all_matchs["Victory_team1"], None))._as("Percent_Victory_Home"),
        fun.avg(fun.case_when((all_matchs["home_team_id"] != 1) & (all_matchs["World_Tournament"] == 0) & (all_matchs["Continental_Tournament"] == 0), all_matchs["Victory_team1"], None))._as("Percent_Victory_Away"),
        fun.avg(all_matchs["Victory_team1"])._as("Percent_Victory"),
        fun.avg(all_matchs["Draw"])._as("Percent_Draw"),
        fun.avg(all_matchs["team1_score"])._as("Avg_goals"),
        fun.avg(all_matchs["team2_score"])._as("Avg_goals_conceded"),
    ],
).sort({"Number_Games_World_Tournament": "desc"})
teams_kpi.head(100)
Abc
team1
Varchar(64)
100%
...
123
Avg_goals
Float(22)
100%
123
Avg_goals_conceded
Float(22)
100%
1Brazil...2.193756727664160.93756727664155
2Germany...2.09983221476511.17953020134228
3Italy...1.695883134130150.98273572377158
4Argentina...1.865446716899891.05059203444564
5Mexico...1.74751.07625
6France...1.758312020460361.34271099744246
7Spain...1.967441860465120.908527131782946
8England...2.199373695198330.992693110647182
9Uruguay...1.575685339690111.26460071513707
10Netherlands...2.062078272604591.24966261808367
11United States...1.419614147909971.37138263665595
12Sweden...2.005192107995851.30633437175493
13Serbia...1.816011235955061.37921348314607
14Belgium...1.684357541899441.60614525139665
15Russia...1.719814241486070.93343653250774
16Czech Republic...1.84379172229641.23497997329773
17South Korea...1.783042394014960.897755610972569
18Chile...1.421499292786421.46534653465347
19Switzerland...1.441489361702131.7313829787234
20Japan...1.720070422535211.16021126760563
21Hungary...2.072072072072071.4954954954955
22Cameroon...1.421711899791231.05427974947808
23Poland...1.684415584415581.37012987012987
24Austria...1.797829036635011.59294436906377
25Paraguay...1.335787923416791.43888070692194
26Australia...2.031712473572941.11205073995772
27Bulgaria...1.431924882629111.47104851330203
28Portugal...1.631294964028781.20143884892086
29Saudi Arabia...1.596119929453261.04585537918871
30Nigeria...1.496183206106871.00381679389313
31Colombia...1.2061.21
32Scotland...1.75033557046981.2255033557047
33Romania...1.645705521472391.28834355828221
34Denmark...1.777629826897471.42876165113182
35South Africa...1.33994334277621.00849858356941
36Croatia...1.751879699248120.981203007518797
37Costa Rica...1.670309653916211.16757741347905
38New Zealand...1.747023809523811.61309523809524
39Peru...1.229020979020981.46853146853147
40Tunisia...1.433712121212121.06439393939394
41Turkey...1.33205374280231.42802303262956
42Greece...1.239543726235741.42015209125475
43Morocco...1.388655462184870.873949579831933
44Algeria...1.354347826086961.0304347826087
45Northern Ireland...1.043117744610281.95356550580431
46Republic of Ireland...1.401529636711281.24665391969407
47Ghana...1.64234875444841.04092526690391
48Iran...1.840354767184040.835920177383592
49Ivory Coast...1.634577603143421.05304518664047
50Ecuador...1.194915254237291.65042372881356
51Egypt...1.643835616438361.03595890410959
52Bolivia...1.03818615751791.94272076372315
53Honduras...1.491489361702131.22340425531915
54Norway...1.501308900523561.68455497382199
55North Korea...1.611.03666666666667
56Canada...1.048991354466861.39769452449568
57Iraq...1.621457489878540.945344129554656
58Slovenia...1.239436619718311.27230046948357
59El Salvador...1.226293103448281.48706896551724
60United Arab Emirates...1.414893617021281.28510638297872
61Senegal...1.28751.00208333333333
62Ukraine...1.396475770925110.973568281938326
63Wales...1.253246753246751.69318181818182
64Slovakia...1.441767068273091.30120481927711
65Angola...1.17475728155341.042071197411
66DR Congo...1.50241545893721.2536231884058
67Israel...1.454773869346731.45226130653266
68Kuwait...1.555555555555561.07962962962963
69Togo...1.082857142857141.39142857142857
70Haiti...1.56220095693781.31818181818182
71Trinidad and Tobago...1.739967897271271.27447833065811
72China PR...1.837294332723951.08775137111517
73Tahiti...2.518324607329841.70157068062827
74Cuba...1.320512820512821.43910256410256
75Bosnia and Herzegovina...1.428571428571431.39010989010989
76Jamaica...1.318618042226491.33781190019194
77Indonesia...1.673114119922631.67504835589942
78British Virgin Islands...0.8765432098765433.11111111111111
79Kazakhstan...1.036809815950921.6441717791411
80Libya...1.290540540540541.21621621621622
81Madagascar...1.333333333333331.63333333333333
82Gotland...2.461538461538462.0
83Mali...1.272925764192141.17685589519651
84Burma...1.713836477987421.4811320754717
85Hong Kong...1.529577464788731.68450704225352
86Arameans Suryoye...1.428571428571431.14285714285714
87Nicaragua...0.8253.19166666666667
88Qatar...1.41.18297872340426
89Liechtenstein...0.4390243902439022.83536585365854
90Northern Mariana Islands...0.8888888888888894.16666666666667
91Mayotte...1.611111111111112.05555555555556
92Oman...1.264851485148511.28960396039604
93Papua New Guinea...1.907216494845362.22680412371134
94North Macedonia...1.084158415841581.38118811881188
95Lithuania...1.097560975609761.78353658536585
96East Timor...0.8947368421052633.68421052631579
97Bahamas...1.148148148148153.33333333333333
98Hitra...1.166666666666674.83333333333333
99Belarus...1.253588516746411.44019138755981
100Saarland...0.8333333333333333.0

We can join the different information about the cup winners to enrich our dataset. We’ll be using this later, so let’s export it to our Vertica database.

vp.drop("teams_kpi", method = "table")
teams_kpi = teams_kpi.join(
    football_cup_winners,
    on = {"team1": "winner"},
    how = "left",
    expr2 = [
        "nb_World_Cup",
        "nb_Continental_Cup",
    ],
).to_db("teams_kpi", relation_type = "table")
teams_kpi.head(100)
Abc
team1
Varchar(64)
100%
...
123
nb_World_Cup
Integer
14%
123
nb_Continental_Cup
Integer
14%
1Italy...31
2France...12
3England...10
4Netherlands...01
5Russia...01
6Austria...[null][null]
7Paraguay...02
8Saudi Arabia...[null][null]
9Nigeria...05
10Peru...03
11Egypt...06
12Norway...[null][null]
13North Korea...[null][null]
14El Salvador...[null][null]
15Slovenia...[null][null]
16Iraq...01
17Ukraine...[null][null]
18Togo...[null][null]
19Haiti...[null][null]
20Western Australia...[null][null]
21Dominican Republic...[null][null]
22Raetia...[null][null]
23Kernow...[null][null]
24Guadeloupe...[null][null]
25Sri Lanka...[null][null]
26South Ossetia...[null][null]
27Arameans Suryoye...[null][null]
28Abkhazia...[null][null]
29Samoa...[null][null]
30Andorra...[null][null]
31Puerto Rico...[null][null]
32Bhutan...[null][null]
33Moldova...[null][null]
34Réunion...[null][null]
35Mali...[null][null]
36Hong Kong...[null][null]
37Saint Kitts and Nevis...[null][null]
38Saint Lucia...[null][null]
39Frøya...[null][null]
40Tamil Eelam...[null][null]
41Jordan...[null][null]
42Burma...[null][null]
43Kenya...[null][null]
44Guinea-Bissau...[null][null]
45Iceland...[null][null]
46Burkina Faso...[null][null]
47Antigua and Barbuda...[null][null]
48Bermuda...[null][null]
49Cape Verde...[null][null]
50Pakistan...[null][null]
51Albania...[null][null]
52Laos...[null][null]
53Latvia...[null][null]
54Chinese Taipei...[null][null]
55Iraqi Kurdistan...[null][null]
56Silesia...[null][null]
57Shetland...[null][null]
58Curaçao...[null][null]
59Suriname...[null][null]
60Cayman Islands...[null][null]
61Azerbaijan...[null][null]
62Thailand...[null][null]
63Székely Land...[null][null]
64Ethiopia...01
65British Virgin Islands...[null][null]
66Malaysia...[null][null]
67Provence...[null][null]
68Tibet...[null][null]
69Bangladesh...[null][null]
70Gotland...[null][null]
71Comoros...[null][null]
72Zambia...01
73Corsica...[null][null]
74Guatemala...[null][null]
75Tanzania...[null][null]
76Madagascar...[null][null]
77Rwanda...[null][null]
78Argentina...28
79Uruguay...29
80Belgium...[null][null]
81Czech Republic...[null][null]
82Poland...[null][null]
83Bulgaria...[null][null]
84Scotland...[null][null]
85Romania...[null][null]
86Denmark...01
87Turkey...[null][null]
88Ecuador...02
89Senegal...[null][null]
90DR Congo...01
91Estonia...[null][null]
92Saarland...[null][null]
93Rhodes...[null][null]
94Sápmi...[null][null]
95Guyana...[null][null]
96Bahrain...[null][null]
97Isle of Wight...[null][null]
98Liberia...[null][null]
99Malawi...[null][null]
100São Tomé and Príncipe...[null][null]

Let’s add each team’s confederation to our dataset.

teams_kpi = teams_kpi.join(
    confederation,
    how = "left",
    on = {"team1": "team"},
    expr2 = ["confederation"],
)
teams_kpi.head(100)
Abc
team1
Varchar(64)
100%
...
123
nb_Continental_Cup
Integer
14%
Abc
confederation
Varchar(8)
99%
1Wallis Islands and Futuna...[null]OFC
2Maldives...[null]AFC
3Kuwait...1AFC
4Basque Country...[null]OFC
5Yemen...[null]AFC
6Belarus...[null]UEFA
7Saint Vincent and the Grenadines...[null]OFC
8Orkney...[null]OFC
9Uganda...[null]CAF
10Eritrea...[null]CAF
11Benin...[null]CAF
12Japan...4AFC
13Australia...1AFC
14Sudan...[null]CAF
15Angola...[null]CAF
16Wales...[null]UEFA
17Liechtenstein...[null]UEFA
18Sierra Leone...[null]CAF
19United States...2CONMEBOL
20Morocco...[null]CAF
21Sweden...[null]UEFA
22Cambodia...[null]AFC
23Cameroon...2CAF
24Uzbekistan...[null]AFC
25Alderney...[null]OFC
26North Macedonia...[null]UEFA
27Bahamas...[null]OFC
28Saint Martin...[null]OFC
29Barbados...[null]OFC
30Algeria...1CAF
31Switzerland...[null]UEFA
32Mayotte...[null]OFC
33Honduras...[null]CONMEBOL
34Tonga...[null]OFC
35Gabon...[null]CAF
36Lithuania...[null]UEFA
37East Timor...[null]OFC
38Djibouti...[null]CAF
39India...[null]AFC
40Åland Islands...[null]OFC
41Serbia...[null]UEFA
42Republic of Ireland...[null]UEFA
43Northern Mariana Islands...[null]OFC
44Ellan Vannin...[null]OFC
45Costa Rica...[null]CONMEBOL
46Hitra...[null]OFC
47Ghana...2CAF
48Ivory Coast...2CAF
49South Korea...2AFC
50Luxembourg...[null]UEFA
51Seychelles...[null]CAF
52Solomon Islands...[null]OFC
53Gibraltar...[null]UEFA
54Lebanon...[null]AFC
55United Arab Emirates...[null]AFC
56Kosovo...[null]UEFA
57Aruba...[null]OFC
58Spain...3UEFA
59Germany...3UEFA
60Oman...[null]AFC
61Qatar...[null]AFC
62South Sudan...[null]CAF
63Israel...1UEFA
64Ynys Môn...[null]OFC
65Papua New Guinea...[null]OFC
66Turkmenistan...[null]AFC
67Galicia...[null]OFC
68Artsakh...[null]OFC
69Palestine...[null]AFC
70Turkey...[null]UEFA
71Sápmi...[null]OFC
72Ecuador...2CONMEBOL
73Cook Islands...[null]OFC
74Catalonia...[null]OFC
75Denmark...1UEFA
76Nicaragua...[null]CONCACAF
77Turks and Caicos Islands...[null]OFC
78Montserrat...[null]OFC
79Saarland...[null]OFC
80Belgium...[null]UEFA
81Venezuela...[null]CONMEBOL
82Botswana...[null]CAF
83Argentina...9CONMEBOL
84Dominica...[null]OFC
85Kazakhstan...[null]UEFA
86San Marino...[null]UEFA
87French Guiana...[null]CONCACAF
88Scotland...[null]UEFA
89Bulgaria...[null]UEFA
90Guam...[null]AFC
91Northern Cyprus...[null]OFC
92Poland...[null]UEFA
93São Tomé and Príncipe...[null]CAF
94Romania...[null]UEFA
95Liberia...[null]CAF
96Tuvalu...[null]OFC
97Saint Pierre and Miquelon...[null]OFC
98Namibia...[null]CAF
99American Samoa...[null]OFC
100Malawi...[null]CAF

Since clustering will use different statistics, we need to normalize the data. We’ll also create a dummy that will equal 1 if the team won at least one World Cup.

teams_kpi.normalize(
    columns = [
        "Number_Games_Continental_Tournament",
        "Number_Games_World_Tournament",
        "nb_Continental_Cup",
    ],
    method = "minmax",
)
teams_kpi["Word_Cup_Victory"] = teams_kpi["nb_World_Cup"] > 0
teams_kpi["Word_Cup_Victory"].astype("int")
Abc
team1
Varchar(64)
100%
...
Abc
confederation
Varchar(8)
99%
123
Word_Cup_Victory
Int
14%
1Suriname...OFC[null]
2Norway...UEFA[null]
3Raetia...OFC[null]
4British Virgin Islands...OFC[null]
5Cape Verde...CAF[null]
6Guinea-Bissau...CAF[null]
7Kernow...OFC[null]
8Frøya...OFC[null]
9Sri Lanka...AFC[null]
10Curaçao...CONCACAF[null]
11Mali...CAF[null]
12Arameans Suryoye...OFC[null]
13Togo...CAF[null]
14Azerbaijan...UEFA[null]
15Russia...UEFA0
16Netherlands...UEFA0
17England...UEFA1
18Shetland...OFC[null]
19Comoros...CAF[null]
20Cayman Islands...OFC[null]

Some data is missing; this is because only top teams won major tournaments. Besides, some non-professional teams may not have a stadium.

teams_kpi.count()
count
272.0
272.0
272.0
77.0
213.0
243.0
263.0
272.0
272.0
272.0
272.0
39.0
39.0
271.0
39.0

Let’s impute the missing values by 0.

teams_kpi.fillna(
    {
        "Percent_Victory_Away": 0,
        "Percent_Victory_Home": 0,
        "Percent_Victory_Continental_Tournament": 0,
        "Percent_Victory_World_Tournament": 0,
        "nb_World_Cup": 0,
        "Word_Cup_Victory": 0,
        "nb_Continental_Cup": 0,
        "confederation": "OFC",
    },
)
Abc
team1
Varchar(64)
100%
...
Abc
confederation
Varchar(8)
100%
123
Word_Cup_Victory
Int
100%
1French Guiana...CONCACAF0
2Scotland...UEFA0
3Menorca...OFC0
4Macau...AFC0
5Turks and Caicos Islands...OFC0
6Argentina...CONMEBOL1
7Dominica...OFC0
8Kazakhstan...UEFA0
9San Marino...UEFA0
10Montserrat...OFC0
11Saarland...OFC0
12Belgium...UEFA0
13Venezuela...CONMEBOL0
14Botswana...CAF0
15Galicia...OFC0
16Artsakh...OFC0
17Palestine...AFC0
18Bulgaria...UEFA0
19Denmark...UEFA0
20Nicaragua...CONCACAF0

Let’s export the result to our Vertica database.

vp.drop("football_clustering", method = "table")
teams_kpi.to_db(
    "football_clustering",
    relation_type = "table",
    inplace = True,
)
Abc
team1
Varchar(64)
100%
...
Abc
confederation
Varchar(8)
100%
123
Word_Cup_Victory
Int
100%
1Alderney...OFC0
2Algeria...CAF0
3Angola...CAF0
4Aruba...OFC0
5Australia...AFC0
6Bahamas...OFC0
7Barbados...OFC0
8Basque Country...OFC0
9Belarus...UEFA0
10Benin...CAF0
11Cambodia...AFC0
12Cameroon...CAF0
13Costa Rica...CONMEBOL0
14Djibouti...CAF0
15East Timor...OFC0
16Ellan Vannin...OFC0
17Eritrea...CAF0
18Gabon...CAF0
19Germany...UEFA1
20Ghana...CAF0

Team Rankings with k-means

To compute a KMeans model, we need to find a value for k. Let’s draw an elbow() curve to find a suitable number of clusters.

from verticapy.machine_learning.model_selection import elbow

predictors = [
    'Word_Cup_Victory',
    'nb_Continental_Cup',
    'Number_Games_World_Tournament',
    'Number_Games_Continental_Tournament',
    'Percent_Victory_World_Tournament',
    'Percent_Victory_Continental_Tournament',
    'Percent_Victory_Home',
    'Percent_Victory_Away',
]
elbow(
    "football_clustering",
    predictors,
    n_cluster = (1, 11),
)

6 seems to be a good number of clusters. To help the algorithm to converge to meaningful clusters, we can initialize the clusters with different types of centroid levels. For example, we can associate very good teams (champions) to World Cups Winners, good teams to continental Cup Winners, etc. This will let us to properly weigh the performance of each team relatve to the strength of their region.

from verticapy.machine_learning.vertica import KMeans

    # w_cup c_cup w_games c_games w_vict c_vict h_vict a_vict
init =  [
    (0,    0,       0,  0.05,      0,    0,      0, 0.05), # very bad
    (0,    0,       0,  0.30,      0, 0.25,   0.30, 0.10), # bad
    (0,    0,    0.05,  0.40,   0.15, 0.35,   0.40, 0.20), # outsiders
    (0, 0.10,    0.15,  0.50,   0.20, 0.45,   0.50, 0.30), # good
    (0, 0.20,    0.30,  0.40,   0.40, 0.55,   0.60, 0.40), # strong
    (1,  0.5,       1,  0.80,   0.70, 0.65,   0.75, 0.55), # champions
]


model_kmeans = KMeans(
    n_cluster = 6,
    init = init,
)


model_kmeans.fit("football_clustering", predictors)


=======
centers
=======
word_cup_victory|nb_continental_cup|number_games_world_tournament|number_games_continental_tournament|percent_victory_world_tournament|percent_victory_continental_tournament|percent_victory_home|percent_victory_away
----------------+------------------+-----------------------------+-----------------------------------+--------------------------------+--------------------------------------+--------------------+--------------------
     0.00000    |      0.00000     |           0.00000           |              0.03559              |             0.00000            |                0.03892               |       0.06897      |       0.17548      
     0.00000    |      0.00000     |           0.00000           |              0.03447              |             0.00000            |                0.06034               |       0.55445      |       0.23662      
     0.00000    |      0.00253     |           0.00406           |              0.16676              |             0.01458            |                0.30350               |       0.45371      |       0.27334      
     0.00000    |      0.09091     |           0.07211           |              0.50548              |             0.12824            |                0.39520               |       0.51557      |       0.31154      
     0.00000    |      0.11877     |           0.21369           |              0.42376              |             0.35454            |                0.51460               |       0.55522      |       0.36849      
     1.00000    |      0.47222     |           0.63504           |              0.61032              |             0.52921            |                0.58711               |       0.61893      |       0.43467      


=======
metrics
=======
Evaluation metrics:
     Total Sum of Squares: 64.504819
     Within-Cluster Sum of Squares: 
         Cluster 0: 2.6477373
         Cluster 1: 4.2616057
         Cluster 2: 3.1519907
         Cluster 3: 2.932969
         Cluster 4: 2.3812356
         Cluster 5: 2.0500045
     Total Within-Cluster Sum of Squares: 17.425543
     Between-Cluster Sum of Squares: 47.079276
     Between-Cluster SS / Total SS: 72.99%
 Number of iterations performed: 12
 Converged: True
 Call:
kmeans('"public"."_verticapy_tmp_kmeans_v_mldb_2792e83297b111efa8720242ac120002_"', 'football_clustering', '"Word_Cup_Victory", "nb_Continental_Cup", "Number_Games_World_Tournament", "Number_Games_Continental_Tournament", "Percent_Victory_World_Tournament", "Percent_Victory_Continental_Tournament", "Percent_Victory_Home", "Percent_Victory_Away"', 6
USING PARAMETERS max_iterations=300, epsilon=0.0001, initial_centers_table='"public"."_verticapy_tmp_kmeans_init_v_mldb_27e16cf097b111efa8720242ac120002_"', distance_method='euclidean')

model_kmeans.clusters_
Out[7]: 
array([[0.        , 0.        , 0.        , 0.03559237, 0.        ,
        0.03892218, 0.06897399, 0.17548141],
       [0.        , 0.        , 0.        , 0.03447122, 0.        ,
        0.06033562, 0.55445231, 0.23662273],
       [0.        , 0.00252525, 0.00406437, 0.16675794, 0.01458333,
        0.30349671, 0.45371031, 0.27334108],
       [0.        , 0.09090909, 0.07210794, 0.50547645, 0.12823886,
        0.39519921, 0.51556972, 0.31153749],
       [0.        , 0.11877395, 0.21369242, 0.42376402, 0.35453761,
        0.5146026 , 0.55522045, 0.3684891 ],
       [1.        , 0.47222222, 0.6350365 , 0.61031627, 0.52921206,
        0.58710705, 0.61893195, 0.43467394]])

Let’s add the prediction to the vDataFrame.

model_kmeans.predict(
    teams_kpi,
    name = "fifa_rank",
)
Abc
team1
Varchar(64)
100%
...
123
Number_Games_World_Tournament
Numeric(34,15)
100%
123
fifa_rank
Integer
100%
1Alderney...0.00
2Algeria...0.0948905109489053
3Angola...0.0218978102189782
4Aruba...0.02
5Australia...0.189781021897814
6Bahamas...0.02
7Barbados...0.02
8Basque Country...0.01
9Belarus...0.02
10Benin...0.02
11Cambodia...0.01
12Cameroon...0.2262773722627744
13Costa Rica...0.1094890510948913
14Djibouti...0.00
15East Timor...0.00
16Ellan Vannin...0.00
17Eritrea...0.01
18Gabon...0.02
19Germany...0.8759124087591245
20Ghana...0.0875912408759124

Let’s look at the strongest group, which includes well-known teams like Argentina, Brazil, and France.

teams_kpi.search(
    conditions = [teams_kpi["fifa_rank"] == 5],
    usecols = ["team1", "fifa_rank"],
    order_by = ["fifa_rank"],
).head(10)
Abc
team1
Varchar(64)
100%
123
fifa_rank
Integer
100%
1Argentina5
2Uruguay5
3Brazil5
4Germany5
5Spain5
6England5
7France5
8Italy5

The weakest group includes less well-known teams.

teams_kpi.search(
    conditions = [teams_kpi["fifa_rank"] == 0],
    usecols = ["team1", "fifa_rank"],
    order_by = ["fifa_rank"],
).head(10)
Abc
team1
Varchar(64)
100%
123
fifa_rank
Integer
100%
1U.S. Virgin Islands0
2Tuvalu0
3Turks and Caicos Islands0
4São Tomé and Príncipe0
5San Marino0
6Saint Pierre and Miquelon0
7Saarland0
8Saare County0
9Palestine0
10Menorca0

A bubble plot will let us visualize the differences in strength between each confederation.

We can see the strongest group at the top right of the graphic and weakest teams at the bottom left. Some teams may be very good in their location but very bad in World Tournaments. They are mainly at the bottom right of the graph.

teams_kpi.scatter(
    [
        "Percent_Victory_Continental_Tournament",
        "Percent_Victory_World_Tournament",
    ],
    size = "fifa_rank",
    by = "confederation",
)

We can also look at the Percent of Victory by rank to confirm our hypothesis.

teams_kpi.scatter(
    [
        "Percent_Victory_Continental_Tournament",
        "Percent_Victory_World_Tournament",
    ],
    size = "Percent_Victory",
    by = "fifa_rank",
)

A box plot can also show us the differences in skill between teams. We can look at rank 1, where the percent of victory is high because of the confederation.

Note that the best team in a weaker confederation might not be particularly strong, but still have a high Percent of Victory.

teams_kpi["Percent_Victory"].boxplot(by = "fifa_rank")

Let’s export the KPIs to our Vertica database.

vp.drop(
    "team_kpi",
    method = "table",
)
teams_kpi.to_db(
    name = "team_kpi",
    relation_type = "table",
    inplace = True,
)
Abc
team1
Varchar(64)
100%
...
123
Number_Games_World_Tournament
Numeric(34,15)
100%
123
fifa_rank
Int
100%
1Alderney...0.00
2Algeria...0.0948905109489053
3Angola...0.0218978102189782
4Aruba...0.02
5Australia...0.189781021897814
6Bahamas...0.02
7Barbados...0.02
8Basque Country...0.01
9Belarus...0.02
10Benin...0.02
11Cambodia...0.01
12Cameroon...0.2262773722627744
13Costa Rica...0.1094890510948913
14Djibouti...0.00
15East Timor...0.00
16Ellan Vannin...0.00
17Eritrea...0.01
18Gabon...0.02
19Germany...0.8759124087591245
20Ghana...0.0875912408759124

Features Engineering

Many very interesting features can be to use to evaluate each team. Moving windows of the previous games can drastically improve our model.

Since a team can by a home or away team, we’ll intervert the away and home teams. By using this technique, we will never get twice the same game and we will get the proper moving windows.

football = vp.vDataFrame("football_clean")

football["home_team"].rename("team1");

football["home_score"].rename("team1_score");

football["away_team"].rename("team2");

football["away_score"].rename("team2_score");

# will be to use to filter the data after the features engineering
football["match_sample"] = "1";

football2 = vp.vDataFrame("football_clean");

football2["home_team"].rename("team2");

football2["home_score"].rename("team2_score");

football2["away_team"].rename("team1");

football2["away_score"].rename("team1_score");

# will be to use to filter the data after the features engineering
football2["match_sample"] = "2";

# Merging the 2 interverted datasets
all_matchs = football.append(football2);

Let’s add the different KPIs to our dataset.

all_matchs = all_matchs.join(
    teams_kpi,
    on = {"team1": "team1"},
    how = "left",
    expr2 = [
        "nb_World_Cup AS nb_World_Cup_1",
        "fifa_rank AS fifa_rank_1",
        "Avg_goals AS Avg_goals_1",
        "Percent_Draw AS Percent_Draw_1",
        "Number_Games_World_Tournament AS Number_Games_World_Tournament_1",
        "Percent_Victory_World_Tournament AS Percent_Victory_World_Tournament_1",
        "Percent_Victory_Away AS Percent_Victory_Away_1",
        "Percent_Victory_Continental_Tournament AS Percent_Victory_Continental_Tournament_1",
        "confederation AS confederation_1",
        "Percent_Victory_Home AS Percent_Victory_Home_1",
        "Avg_goals_conceded AS Avg_goals_conceded_1",
        "Number_Games_Continental_Tournament AS Number_Games_Continental_Tournament_1",
        "nb_Continental_Cup AS nb_Continental_Cup_1",
        "Percent_Victory AS Percent_Victory_1",
    ],
)


all_matchs = all_matchs.join(
    teams_kpi,
    on = {"team2": "team1"},
    how = "left",
    expr2 = [
        "nb_World_Cup AS nb_World_Cup_2",
        "fifa_rank AS fifa_rank_2",
        "Avg_goals AS Avg_goals_2",
        "Percent_Draw AS Percent_Draw_2",
        "Number_Games_World_Tournament AS Number_Games_World_Tournament_2",
        "Percent_Victory_World_Tournament AS Percent_Victory_World_Tournament_2",
        "Percent_Victory_Away AS Percent_Victory_Away_2",
        "Percent_Victory_Continental_Tournament AS Percent_Victory_Continental_Tournament_2",
        "confederation AS confederation_2",
        "Percent_Victory_Home AS Percent_Victory_Home_2",
        "Avg_goals_conceded AS Avg_goals_conceded_2",
        "Number_Games_Continental_Tournament AS Number_Games_Continental_Tournament_2",
        "nb_Continental_Cup AS nb_Continental_Cup_2",
        "Percent_Victory AS Percent_Victory_2",
    ],
)

We can add dumies to do aggregations on the different games.

all_matchs["victory_team1"] = all_matchs["team1_score"] > all_matchs["team2_score"]
all_matchs["victory_team1"].astype("int")
all_matchs["draw"] = all_matchs["team1_score"] == all_matchs["team2_score"]
all_matchs["draw"].astype("int")
all_matchs["victory_team2"] = all_matchs["team1_score"] < all_matchs["team2_score"]
all_matchs["victory_team2"].astype("int")
📅
date
Date
100%
...
123
draw
Int
100%
123
victory_team2
Int
100%
12017-10-10...01
22017-10-10...00
32017-10-10...10
42017-10-10...01
52017-10-10...00
62017-10-10...00
72017-10-10...00
82017-10-10...00
92017-10-10...00
102017-10-10...10
112017-10-10...00
122017-10-10...00
132017-10-11...00
142017-11-08...10
152017-11-09...01
162017-11-09...01
172017-11-09...00
182017-11-09...00
192017-11-10...00
202017-11-11...10

Let’s use moving windows to compute some additional features.

The teams’ performance in their recent games

# TEAM 1

# Victory 10 previous games
all_matchs.rolling(
    func = "avg",
    window = (-10, -1),
    columns = "victory_team1",
    by = ["team1"],
    order_by = ["date"],
    name = "avg_victory_team1_1_10",
)
# Victory 3 previous games
all_matchs.rolling(
    func = "avg",
    window = (-3, -1),
    columns = "victory_team1",
    by = ["team1"],
    order_by = ["date"],
    name = "avg_victory_team1_1_3",
)
# Draw 5 previous games
all_matchs.rolling(
    func = "avg",
    window = (-5, -1),
    columns = "draw",
    by = ["team1"],
    order_by = ["date"],
    name = "avg_draw_team1_1_5",
)

# TEAM 2

# Victory 10 previous games
all_matchs.rolling(
    func = "avg",
    window = (-10, -1),
    columns = "victory_team2",
    by = ["team2"],
    order_by = ["date"],
    name = "avg_victory_team2_1_10",
)
# Victory 3 previous games
all_matchs.rolling(
    func = "avg",
    window = (-3, -1),
    columns = "victory_team2",
    by = ["team2"],
    order_by = ["date"],
    name = "avg_victory_team2_1_3",
)
# Draw 5 previous games
all_matchs.rolling(
    func = "avg",
    window = (-5, -1),
    columns = "draw",
    by = ["team2"],
    order_by = ["date"],
    name = "avg_draw_team2_1_5",
)
📅
date
Date
100%
...
123
avg_victory_team2_1_3
Float(22)
99%
123
avg_draw_team2_1_5
Float(22)
99%
12001-06-30...[null][null]
22006-05-31...0.00.0
32006-11-21...0.00.0
42007-11-02...0.00.0
52018-05-31...0.00.0
62018-06-02...0.00.0
71915-01-03...[null][null]
81915-02-07...1.00.0
91915-05-13...0.50.5
101916-05-21...0.6666666666666670.333333333333333
111916-05-22...0.6666666666666670.25
121916-06-04...0.6666666666666670.4
131924-06-15...0.6666666666666670.4
141930-06-08...0.6666666666666670.2
151931-01-01...1.00.2
161937-05-06...1.00.2
171937-06-05...0.6666666666666670.0
181937-06-09...0.3333333333333330.0
191937-08-22...0.3333333333333330.0
201937-08-29...0.6666666666666670.0

The teams’ performance in the last same tournament

# TEAM 1

# Victory 10 previous games
all_matchs.rolling(
    func = "avg",
    window = (-10, -1),
    columns = "victory_team1",
    by = ["team1", "tournament"],
    order_by = ["date"],
    name = "avg_victory_same_tournament_team1_1_10",
)
# Victory 3 previous games
all_matchs.rolling(
    func = "avg",
    window = (-3, -1),
    columns = "victory_team1",
    by = ["team1", "tournament"],
    order_by = ["date"],
    name = "avg_victory_same_tournament_team1_1_3",
)
# Draw 5 previous games
all_matchs.rolling(
    func = "avg",
    window = (-5, -1),
    columns = "draw",
    by = ["team1", "tournament"],
    order_by = ["date"],
    name = "avg_draw_same_tournament_team1_1_5",
)

# TEAM 2

# Victory 10 previous games
all_matchs.rolling(
    func = "avg",
    window = (-10, -1),
    columns = "victory_team2",
    by = ["team2", "tournament"],
    order_by = ["date"],
    name = "avg_victory_same_tournament_team2_1_10",
)
# Victory 3 previous games
all_matchs.rolling(
    func = "avg",
    window = (-3, -1),
    columns = "victory_team2",
    by = ["team2", "tournament"],
    order_by = ["date"],
    name = "avg_victory_same_tournament_team2_1_3",
)
# Draw 5 previous games
all_matchs.rolling(
    func = "avg",
    window = (-5, -1),
    columns = "draw",
    by = ["team2", "tournament"],
    order_by = ["date"],
    name = "avg_draw_same_tournament_team2_1_5",
)
📅
date
Date
100%
...
123
Float(22)
97%
123
Float(22)
97%
11946-10-07...
21946-10-09...
31946-10-13...
41947-05-25...
51947-06-15...
61947-08-20...
71947-09-14...
81948-05-02...
91948-05-23...
101948-06-27...
111955-08-14...
121955-08-16...
131955-08-19...
141955-08-22...
151955-08-24...
161967-01-12...
171967-01-14...
181967-01-17...
191967-01-20...
201971-10-01...

Direct Confrontation

# Victory 5 previous games
all_matchs.rolling(
    func = "avg",
    window = (-5, -1),
    columns = "victory_team1",
    by = ["team1", "team2"],
    order_by = ["date"],
    name = "avg_victory_direct_team1_1_5",
)
# Victory 3 previous games
all_matchs.rolling(
    func = "avg",
    window = (-3, -1),
    columns = "victory_team1",
    by = ["team1", "team2"],
    order_by = ["date"],
    name = "avg_victory_direct_team1_1_3",
)
# Draw 5 previous games
all_matchs.rolling(
    func = "avg",
    window = (-5, -1),
    columns = "draw",
    by = ["team1", "team2"],
    order_by = ["date"],
    name = "avg_draw_direct_team1_1_5",
)
📅
date
Date
100%
...
Abc
tournament
Varchar(84)
100%
123
avg_draw_direct_team1_1_5
Float(22)
84%
12002-10-12...UEFA Euro qualification[null]
22003-06-11...UEFA Euro qualification0.0
32014-10-10...UEFA Euro qualification0.0
42015-10-10...UEFA Euro qualification0.0
51934-05-27...FIFA World Cup[null]
62002-06-12...FIFA World Cup0.0
72013-02-06...Friendly0.5
81998-08-18...Friendly[null]
91987-06-09...Korea Cup[null]
101996-06-02...FIFA World Cup qualification[null]
111997-09-06...FIFA World Cup qualification0.0
122014-11-16...UEFA Euro qualification0.0
132015-06-12...UEFA Euro qualification0.0
142016-10-08...FIFA World Cup qualification0.25
152017-09-01...FIFA World Cup qualification0.2
161936-12-27...Copa América[null]
171942-01-21...Copa América0.0
181949-04-24...Copa América0.0
191952-04-10...Pan American Championship0.0
201953-03-19...Copa América0.25

Games against an opponents with the same rank

# TEAM 1

# Victory 5 previous games
all_matchs.rolling(
    func = "avg",
    window = (-5, -1),
    columns = "victory_team1",
    by = ["team1", "fifa_rank_2"],
    order_by = ["date"],
    name = "avg_victory_rank2_team1_1_5",
)
# Draw 5 previous games
all_matchs.rolling(
    func = "avg",
    window = (-5, -1),
    columns = "draw",
    by = ["team1", "fifa_rank_2"],
    order_by = ["date"],
    name = "avg_draw_rank2_team1_1_5",
)

# TEAM 2

# Victory 5 previous games
all_matchs.rolling(
    func = "avg",
    window = (-5, -1),
    columns = "victory_team2",
    by = ["team2", "fifa_rank_1"],
    order_by = ["date"],
    name = "avg_victory_rank1_team2_1_5",
)
# Draw 5 previous games
all_matchs.rolling(
    func = "avg",
    window = (-5, -1),
    columns = "draw",
    by = ["team2", "fifa_rank_1"],
    order_by = ["date"],
    name = "avg_draw_rank1_team2_1_5",
)
📅
date
Date
100%
...
Abc
tournament
Varchar(84)
100%
123
avg_draw_rank1_team2_1_5
Float(22)
98%
11937-10-13...Friendly[null]
21952-11-29...Friendly0.0
31957-05-01...FIFA World Cup qualification0.0
41957-05-26...FIFA World Cup qualification0.0
51970-10-07...UEFA Euro qualification0.0
61971-04-21...UEFA Euro qualification0.2
71971-06-16...UEFA Euro qualification0.2
81971-10-27...UEFA Euro qualification0.2
91975-04-20...UEFA Euro qualification0.2
101975-11-23...UEFA Euro qualification0.2
111977-03-30...FIFA World Cup qualification0.0
121977-11-16...FIFA World Cup qualification0.0
131980-11-19...FIFA World Cup qualification0.0
141981-05-27...FIFA World Cup qualification0.0
151981-09-09...FIFA World Cup qualification0.0
161981-09-23...FIFA World Cup qualification0.0
171983-03-27...UEFA Euro qualification0.2
181983-04-16...UEFA Euro qualification0.4
191986-05-29...Friendly0.4
201986-10-15...UEFA Euro qualification0.4

Games between teams with rank 1 and rank 2

# Victory 5 previous games
all_matchs.rolling(
    func = "avg",
    window = (-5, -1),
    columns = "victory_team1",
    by = ["fifa_rank_1", "fifa_rank_2"],
    order_by = ["date"],
    name = "avg_victory_rank1_rank2_team1_1_5",
)
# Draw 5 previous games
all_matchs.rolling(
    func = "avg",
    window = (-5, -1),
    columns = "draw",
    by = ["fifa_rank_1", "fifa_rank_2"],
    order_by = ["date"],
    name = "avg_draw_rank1_rank2_team1_1_5",
)
📅
date
Date
100%
...
Abc
tournament
Varchar(84)
100%
123
avg_draw_rank1_rank2_team1_1_5
Float(22)
99%
12016-08-26...World Unity Cup[null]
22016-08-26...World Unity Cup0.0
32018-04-08...Friendly0.0
42018-04-08...Friendly0.0
52018-05-31...CONIFA World Football Cup0.0
62018-05-31...CONIFA World Football Cup0.2
72018-06-07...CONIFA World Football Cup0.4
82018-06-07...CONIFA World Football Cup0.4
92018-06-09...CONIFA World Football Cup0.4
102018-06-09...CONIFA World Football Cup0.4
112016-05-29...CONIFA World Football Cup[null]
122016-05-30...CONIFA World Football Cup0.0
132016-05-31...CONIFA World Football Cup0.0
142016-06-05...CONIFA World Football Cup0.0
152016-08-25...World Unity Cup0.25
162018-05-31...CONIFA World Football Cup0.2
172018-06-03...CONIFA World Football Cup0.2
182018-06-05...CONIFA World Football Cup0.2
192019-06-03...CONIFA European Football Cup0.2
202019-06-06...CONIFA European Football Cup0.0

Before we use the neutral variable with our model, we should convert it to an integer.

We need also to create our response column: the outcome of the game.

all_matchs["neutral"].astype("int")
all_matchs.case_when(
    "result",
    all_matchs["team1_score"] > all_matchs["team2_score"], "1",
    all_matchs["team1_score"] < all_matchs["team2_score"], "2",
    "X",
)
📅
date
Date
100%
...
123
avg_draw_rank1_rank2_team1_1_5
Float(22)
99%
Abc
result
Varchar(1)
100%
11912-02-10...[null]1
21912-12-01...0.02
31924-03-13...0.01
41934-02-02...0.01
51934-06-17...0.02
61934-06-24...0.0X
71947-10-19...0.22
81953-08-09...0.21
91964-01-12...0.21
101990-05-08...0.2X
111995-09-06...0.41
121998-12-22...0.22
132002-05-18...0.21
142002-09-07...0.21
152003-10-11...0.21
162003-12-27...0.02
172004-05-25...0.01
182004-10-13...0.01
192004-12-29...0.01
202005-03-30...0.01

We have some missing values here. This might be because the two teams never played together, the competition was one or both teams’ first, etc.

all_matchs.count()
count
82818.0
82818.0
82818.0
82818.0
82818.0
82818.0
82818.0
82818.0
82818.0
82818.0
82765.0
82765.0
82765.0
82765.0
82765.0
82765.0
82765.0
82765.0
82765.0
82765.0
82765.0
82765.0
82765.0
82765.0
82765.0
82765.0
82765.0
82765.0
82765.0
82765.0
82765.0
82765.0
82765.0
82765.0
82765.0
82765.0
82765.0
82765.0
82818.0
82818.0
82818.0
82539.0
82539.0
82539.0
82539.0
82539.0
82539.0
80706.0
80706.0
80706.0
80706.0
80706.0
80706.0
69759.0
69759.0
69759.0
81443.0
81443.0
81443.0
81443.0
82771.0
82771.0
82818.0

We need to impute these missing values.

all_matchs["avg_victory_direct_team1_1_5"] = fun.coalesce(
    all_matchs["avg_victory_direct_team1_1_5"],
    all_matchs["avg_victory_rank2_team1_1_5"],
    all_matchs["avg_victory_rank1_rank2_team1_1_5"],
)
all_matchs["avg_victory_direct_team1_1_3"] = fun.coalesce(
    all_matchs["avg_victory_direct_team1_1_3"],
    all_matchs["avg_victory_rank2_team1_1_5"],
    all_matchs["avg_victory_rank1_rank2_team1_1_5"],
)
all_matchs["avg_draw_direct_team1_1_5"] = fun.coalesce(
    all_matchs["avg_draw_direct_team1_1_5"],
    all_matchs["avg_draw_rank2_team1_1_5"],
    all_matchs["avg_draw_rank1_rank2_team1_1_5"],
)
all_matchs["avg_victory_same_tournament_team1_1_10"].fillna(expr = "avg_victory_team1_1_10")
all_matchs["avg_victory_same_tournament_team1_1_3"].fillna(expr = "avg_victory_team1_1_3")
all_matchs["avg_draw_same_tournament_team1_1_5"].fillna(expr = "avg_draw_team1_1_5")
all_matchs["avg_victory_same_tournament_team2_1_10"].fillna(expr = "avg_victory_team2_1_10")
all_matchs["avg_victory_same_tournament_team2_1_3"].fillna(expr = "avg_victory_team2_1_3")
all_matchs["avg_draw_same_tournament_team2_1_5"].fillna(expr = "avg_draw_team2_1_5")
📅
date
Date
100%
...
123
avg_draw_rank1_rank2_team1_1_5
Float(22)
99%
Abc
result
Varchar(1)
100%
11912-02-10...[null]1
21912-12-01...0.02
31924-03-13...0.01
41934-02-02...0.01
51934-06-17...0.02
61934-06-24...0.0X
71947-10-19...0.22
81953-08-09...0.21
91964-01-12...0.21
101990-05-08...0.2X
111995-09-06...0.41
121998-12-22...0.22
132002-05-18...0.21
142002-09-07...0.21
152003-10-11...0.21
162003-12-27...0.02
172004-05-25...0.01
182004-10-13...0.01
192004-12-29...0.01
202005-03-30...0.01

Let’s export the result to our Vertica database using the variable match_sample to avoid counting the same game twice.

vp.drop("football_train", method = "table")
all_matchs.to_db(
    name = "football_train",
    relation_type = "table",
    db_filter = (fun.year(all_matchs["date"]) <= 2015) & (fun.year(all_matchs["date"]) > 1980) & (all_matchs["match_sample"] == 1),
)

vp.drop("football_test", method = "table")
all_matchs.to_db(
    name = "football_test",
    relation_type = "table",
    db_filter = (fun.year(all_matchs["date"]) > 2015) & (all_matchs["match_sample"] == 1),
)
📅
date
Date
100%
...
123
avg_draw_rank1_rank2_team1_1_5
Float(22)
99%
Abc
result
Varchar(1)
100%
11911-10-29...[null]1
21913-04-20...0.01
31914-02-08...0.02
41927-05-21...0.01
51934-03-11...0.01
61934-04-15...0.01
71935-08-18...0.01
81936-09-27...0.01
91937-03-21...0.01
101938-03-20...0.01
111939-03-26...0.02
121951-12-23...0.01
131952-04-20...0.01
141953-09-20...0.01
151953-12-17...0.01
161954-03-28...0.01
171954-06-05...0.01
181957-03-10...0.01
191960-10-19...0.01
201961-09-28...0.01

Machine Learning

It’s time to make predictions about the outcomes of games. We have a lot of variables, so we need trees deep enough to pick up the most important features. We also need to consider a minimum number of games in each leaf to avoid over-fitting.

predictors = all_matchs.get_columns(
    exclude_columns = [
        "match_sample",
        "team2_score",
        "team1_score",
        "date",
        "city",
        "country",
        "result",
        "victory_team1",
        "victory_team2",
        "draw",
    ],
)


from verticapy.machine_learning.vertica import RandomForestClassifier

model = RandomForestClassifier(
    max_depth = 25,
    n_estimators = 20,
    sample = 0.7,
    nbins = 50,
    max_leaf_nodes = 11000,
    min_samples_leaf = 3,
)


model.fit(
    "football_train",
    predictors,
    "result",
    "football_test",
)



===========
call_string
===========
SELECT rf_classifier('"public"."_verticapy_tmp_randomforestclassifier_v_mldb_89b4b22097b111efa8720242ac120002_"', '"public"."_verticapy_tmp_view_v_mldb_8a08551a97b111efa8720242ac120002_"', 'result', '"tournament", "neutral", "team1", "team2", "nb_World_Cup_1", "fifa_rank_1", "Avg_goals_1", "Percent_Draw_1", "Number_Games_World_Tournament_1", "Percent_Victory_World_Tournament_1", "Percent_Victory_Away_1", "Percent_Victory_Continental_Tournament_1", "confederation_1", "Percent_Victory_Home_1", "Avg_goals_conceded_1", "Number_Games_Continental_Tournament_1", "nb_Continental_Cup_1", "Percent_Victory_1", "nb_World_Cup_2", "fifa_rank_2", "Avg_goals_2", "Percent_Draw_2", "Number_Games_World_Tournament_2", "Percent_Victory_World_Tournament_2", "Percent_Victory_Away_2", "Percent_Victory_Continental_Tournament_2", "confederation_2", "Percent_Victory_Home_2", "Avg_goals_conceded_2", "Number_Games_Continental_Tournament_2", "nb_Continental_Cup_2", "Percent_Victory_2", "avg_victory_team1_1_10", "avg_victory_team1_1_3", "avg_draw_team1_1_5", "avg_victory_team2_1_10", "avg_victory_team2_1_3", "avg_draw_team2_1_5", "avg_victory_same_tournament_team1_1_10", "avg_victory_same_tournament_team1_1_3", "avg_draw_same_tournament_team1_1_5", "avg_victory_same_tournament_team2_1_10", "avg_victory_same_tournament_team2_1_3", "avg_draw_same_tournament_team2_1_5", "avg_victory_direct_team1_1_5", "avg_victory_direct_team1_1_3", "avg_draw_direct_team1_1_5", "avg_victory_rank2_team1_1_5", "avg_draw_rank2_team1_1_5", "avg_victory_rank1_team2_1_5", "avg_draw_rank1_team2_1_5", "avg_victory_rank1_rank2_team1_1_5", "avg_draw_rank1_rank2_team1_1_5"' USING PARAMETERS exclude_columns='', ntree=20, mtry=18, sampling_size=0.7, max_depth=25, max_breadth=11000, min_leaf_size=3, min_info_gain=0, nbins=50);

=======
details
=======
               predictor                |      type      
----------------------------------------+----------------
               tournament               |char or varchar 
                neutral                 |      int       
                 team1                  |char or varchar 
                 team2                  |char or varchar 
             nb_world_cup_1             |      int       
              fifa_rank_1               |      int       
              avg_goals_1               |float or numeric
             percent_draw_1             |float or numeric
    number_games_world_tournament_1     |float or numeric
   percent_victory_world_tournament_1   |float or numeric
         percent_victory_away_1         |float or numeric
percent_victory_continental_tournament_1|float or numeric
            confederation_1             |char or varchar 
         percent_victory_home_1         |float or numeric
          avg_goals_conceded_1          |float or numeric
 number_games_continental_tournament_1  |float or numeric
          nb_continental_cup_1          |float or numeric
           percent_victory_1            |float or numeric
             nb_world_cup_2             |      int       
              fifa_rank_2               |      int       
              avg_goals_2               |float or numeric
             percent_draw_2             |float or numeric
    number_games_world_tournament_2     |float or numeric
   percent_victory_world_tournament_2   |float or numeric
         percent_victory_away_2         |float or numeric
percent_victory_continental_tournament_2|float or numeric
            confederation_2             |char or varchar 
         percent_victory_home_2         |float or numeric
          avg_goals_conceded_2          |float or numeric
 number_games_continental_tournament_2  |float or numeric
          nb_continental_cup_2          |float or numeric
           percent_victory_2            |float or numeric
         avg_victory_team1_1_10         |float or numeric
         avg_victory_team1_1_3          |float or numeric
           avg_draw_team1_1_5           |float or numeric
         avg_victory_team2_1_10         |float or numeric
         avg_victory_team2_1_3          |float or numeric
           avg_draw_team2_1_5           |float or numeric
 avg_victory_same_tournament_team1_1_10 |float or numeric
 avg_victory_same_tournament_team1_1_3  |float or numeric
   avg_draw_same_tournament_team1_1_5   |float or numeric
 avg_victory_same_tournament_team2_1_10 |float or numeric
 avg_victory_same_tournament_team2_1_3  |float or numeric
   avg_draw_same_tournament_team2_1_5   |float or numeric
      avg_victory_direct_team1_1_5      |float or numeric
      avg_victory_direct_team1_1_3      |float or numeric
       avg_draw_direct_team1_1_5        |float or numeric
      avg_victory_rank2_team1_1_5       |float or numeric
        avg_draw_rank2_team1_1_5        |float or numeric
      avg_victory_rank1_team2_1_5       |float or numeric
        avg_draw_rank1_team2_1_5        |float or numeric
   avg_victory_rank1_rank2_team1_1_5    |float or numeric
     avg_draw_rank1_rank2_team1_1_5     |float or numeric


===============
Additional Info
===============
       Name       |Value
------------------+-----
    tree_count    | 20  
rejected_row_count| 478 
accepted_row_count|25465
model.classification_report()
...
avg_weighted
avg_micro
auc...0.719654661347637[null]
prc_auc...0.586595555622269[null]
accuracy...0.69636580939798230.7097196751375425
log_loss...0.2409562168930823[null]
precision...0.52755777966854570.5645795127063139
recall...0.56457951270631390.5645795127063139
f1_score...0.52148337529763780.5645795127063139
mcc...0.2770399751542620.3468692690594708
informedness...0.25717204912752450.3468692690594708
markedness...0.30196899516434320.3468692690594708
csi...0.3759954986723920.3933199488957839

Our model is excellent! 57% of accuracy on 3 categories - it’s almost twice as good as a random model.

model.score(metric = "accuracy")
Out[27]: 0.5645795127063139

Looking at the importance of each feature, it seems like direct confrontations and victories against teams of another rank seem to be the strongest indicators of a team’s success.

model.features_importance()

Let’s add the predictions to the vDataFrame.

Draws are pretty rare, so we’ll only consider them if a tie was very likely to occur.

test = vp.vDataFrame("football_test")
model.predict_proba(test, name = "prob_1", pos_label = "1")
model.predict_proba(test, name = "prob_X", pos_label = "X")
model.predict_proba(test, name = "prob_2", pos_label = "2")
test.case_when(
    "prediction",
    test["prob_1"] > test["prob_2"] + 0.05, "1",
    test["prob_2"] > test["prob_1"] + 0.05, "2",
    (test["prob_X"] > test["prob_1"]) & (test["prob_X"] > test["prob_2"]), "X",
    fun.abs(test["prob_1"] - test["prob_2"]) < 0.03, "X",
    test["prob_1"] > test["prob_2"], "1",
    test["prob_1"] < test["prob_2"], "2",
)
📅
date
Date
100%
...
Abc
prob_2
Varchar(128)
98%
Abc
prediction
Varchar(1)
98%
12016-05-31...[null][null]
22016-06-01...[null][null]
32016-06-03...[null][null]
42016-06-04...[null][null]
52018-06-05...[null][null]
62018-11-12...[null][null]
72019-03-19...[null][null]
82019-11-19...[null][null]
92018-10-13...[null][null]
102016-05-22...[null][null]
112016-05-31...0.8928622
122016-06-03...0.5831942
132016-09-04...0.8504232
142016-09-06...0.8575272
152016-10-06...0.6110262
162016-10-07...0.9024632
172016-10-10...0.9179262
182016-10-10...0.9205812
192016-11-13...0.9192992
202017-03-26...0.9236452

Let’s look at our predictions for the 2018 World Cup.

test.search(
    conditions = [test["tournament"] == 'FIFA World Cup'],
    usecols = [
        "date",
        "team1",
        "result",
        "prediction",
        "team2",
        "prob_1",
        "prob_X",
        "prob_2",
    ],
    order_by = ["date"],
).head(128)
📅
date
Date
100%
...
Abc
prob_X
Varchar(128)
100%
Abc
prob_2
Varchar(128)
100%
12018-06-14...0.1121690.141219
22018-06-15...0.443080.211021
32018-06-15...0.3898030.402894
42018-06-15...0.2309630.54255
52018-06-16...0.1566130.0941131
62018-06-16...0.2291460.133139
72018-06-16...0.26370.396381
82018-06-16...0.2825370.203966
92018-06-17...0.3305220.132605
102018-06-17...0.2012890.17434
112018-06-17...0.3359480.316127
122018-06-18...0.5096560.369847
132018-06-18...0.3018830.133133
142018-06-18...0.2833820.403889
152018-06-19...0.1952660.154328
162018-06-19...0.248740.268448
172018-06-19...0.2334890.225991
182018-06-20...0.2999910.254443
192018-06-20...0.2692690.193197
202018-06-20...0.1938420.662316
212018-06-21...0.333710.209679
222018-06-21...0.1975540.178763
232018-06-21...0.2394190.188169
242018-06-22...0.09645990.0964599
252018-06-22...0.1670570.352703
262018-06-22...0.2992540.357587
272018-06-23...0.1769430.274156
282018-06-23...0.4338730.171731
292018-06-23...0.2835840.349558
302018-06-24...0.1886040.0923535
312018-06-24...0.3473340.266015
322018-06-24...0.2403910.283395
332018-06-25...0.3063320.21782
342018-06-25...0.3018190.180569
352018-06-25...0.2131820.364591
362018-06-25...0.2054170.128935
372018-06-26...0.2392030.130096
382018-06-26...0.2954670.502459
392018-06-26...0.2180510.51336
402018-06-26...0.17510.52045
412018-06-27...0.2258210.29528
422018-06-27...0.2848930.382993
432018-06-27...0.2289650.562647
442018-06-27...0.3669630.349914
452018-06-28...0.3332260.223011
462018-06-28...0.2871830.398119
472018-06-28...0.5602890.154843
482018-06-28...0.2909350.206162
492018-06-30...0.4646580.205698
502018-06-30...0.3290750.249336
512018-07-01...0.3835640.266147
522018-07-01...0.4080060.27107
532018-07-02...0.1397320.0897325
542018-07-02...0.1898410.239945
552018-07-03...0.3673820.42055
562018-07-03...0.4082070.259183
572018-07-06...0.225130.158969
582018-07-06...0.2581650.3237
592018-07-07...0.5545830.180844
602018-07-07...0.2638550.273384
612018-07-10...0.2355660.302676
622018-07-11...0.4573460.259006
632018-07-14...0.2459010.364848
642018-07-15...0.3069940.318836

Fantastic: we built a very efficient model which predicted that France will win almost all of its games (except the game against Argentina which is really hard to predict). In reality, France did indeed win the 2018 World Cup!

test.search(
    conditions = [
        test["tournament"] == 'FIFA World Cup',
        (test["team1"] == 'France') | (test["team2"] == 'France'),
    ],
    usecols = [
        "date",
        "team1",
        "result",
        "prediction",
        "team2",
        "prob_1",
        "prob_X",
        "prob_2",
    ],
    order_by = ["date"],
).head(128)
📅
date
Date
100%
...
Abc
prob_X
Varchar(128)
100%
Abc
prob_2
Varchar(128)
100%
12018-06-16...0.2291460.133139
22018-06-21...0.1975540.178763
32018-06-26...0.2180510.51336
42018-06-30...0.3290750.249336
52018-07-06...0.2581650.3237
62018-07-10...0.2355660.302676
72018-07-15...0.3069940.318836

Conclusion

We’ve solved our problem in a Pandas-like way, all without ever loading data into memory!