{ "metadata": { "name": "" }, "nbformat": 3, "nbformat_minor": 0, "worksheets": [ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Predicting the World Cup with the Google Cloud Platform\n", "\n", "This notebook builds a machine learning model that can be used to predict professional soccer games. The notebook was created for the \"Predicting the Future with the Google Cloud Platform\" talk at Google I/O 2014 by Jordan Tigani and Felipe Hoffa. A link to the presentation is here: https://www.youtube.com/watch?v=YyvvxFeADh8\n", "\n", "Once the machine learning model is built, we use it to predict outcomes in the World Cup. If you are seeing this after the world cup is over, you can use it to predict hypothetical matchups (how would the 2010 World Cup winners do against the current champions?). You can also see how various different strategies would affect prediction outcomes. Maybe you'd like to add player salary data and see how that affects predictions (likely it will help a lot). Maybe you'd like to try Poisson Regression instead of Logistic Regression. Or maybe you'd like to try data coercion techniques like whitening or PCA.\n", "\n", "The model uses Logistic Regression, built from touch-by-touch data about three different soccer leagues (English Premier League, Spainish La Liga, and American Major League Soccer) over multiple seasons. Because the data is licensed, only the aggregated statistics about those games are available. (If you have ideas of other statistics you'd like to see, create a new issue in the https://github.com/GoogleCloudPlatform/ipython-soccer-predictions GitHub repo and we'll see what we can do.) The match_stats.py file shows the raw queries that were used to generate the stats.\n", "\n", "There are four python files that are used by this notebook. They must be in the path. These are:\n", "\n", "* match_stats: Reads the match statistics from BigQuery. Because we are using the pre-aggregated data, most of the code here is disabled, but it is kept in order to show the data transformations that are done from the raw data in order to build the stats.\n", "* features: Turns raw statistics into features that get fed into the machine learning model. These features combine statistics from the trailing N games to predict the next game. \n", "* world_cup: Helper methods for cleaning the data and building and running the logistic regression model.\n", "* power: Computes a \"power\" statistic over a number of teams who have played against eachother, attempting to come up with a ranking.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Setting up BigQuery Authentication\n", "\n", "Since we're providing this notebook as part of a Docker image that can be run on Google Compute Engine, we'll override the authorization used in the Pandas BigQuery connector to use GCE auth. This will mean that you don't have to do any authorization on your own. You must, however, have the BigQuery API enabled in your Google Cloud Project (https://console.developers.google.com). Because the data sizes (after aggregation) are quite small, you may not need to enable billing." ] }, { "cell_type": "code", "collapsed": false, "input": [ "from oauth2client.gce import AppAssertionCredentials\n", "from bigquery_client import BigqueryClient\n", "from pandas.io import gbq\n", "\n", "def GetMetadata(path):\n", " import urllib2\n", " BASE_PATH = 'http://metadata/computeMetadata/v1/'\n", " request = urllib2.Request(BASE_PATH + path, headers={'Metadata-Flavor': 'Google'})\n", " return urllib2.urlopen(request).read()\n", "\n", "credentials = AppAssertionCredentials(scope='https://www.googleapis.com/auth/bigquery')\n", "\n", "client = BigqueryClient(credentials=credentials,\n", " api='https://www.googleapis.com',\n", " api_version='v2',\n", " project_id=GetMetadata('project/project-id'))\n", "\n", "gbq._authenticate = lambda: client" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 1 }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Verifying setup\n", "Loads the required modules and run a quick BigQuery query. This will test to make sure we have authentication and the pandas bigquery connector working correctly." ] }, { "cell_type": "code", "collapsed": false, "input": [ "from pandas.io import gbq\n", "\n", "# Import the four python modules that we use.\n", "import match_stats\n", "import features\n", "import world_cup\n", "import power\n", "query = \"SELECT * FROM (%(summary_query)s) LIMIT 1\" % {\n", " 'summary_query': match_stats.team_game_summary_query()}\n", "gbq.read_gbq(query)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "\r", "Waiting on bqjob_r337c26ad9dfd06bd_00000147233b4372_1 ... (0s) Current status: DONE \n" ] }, { "html": [ "
\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
matchidteamidpassesbad_passespass_ratiocornersfoulsshotscardspass_80pass_70timestampgoalsis_hometeam_namecompetitionidseasonidexpected_goalson_targetlength
0 731825 838 2.111111 0.912698 0.696335 0.007937 0.119048 0.087302 4 0.02381 0.063492 1404604038607 0 0 Costa Rica 4 2013 0.254178 0.007937 126
\n", "

1 rows \u00d7 20 columns

\n", "
" ], "metadata": {}, "output_type": "pyout", "prompt_number": 1, "text": [ " matchid teamid passes bad_passes pass_ratio corners fouls \\\n", "0 731825 838 2.111111 0.912698 0.696335 0.007937 0.119048 \n", "\n", " shots cards pass_80 pass_70 timestamp goals is_home \\\n", "0 0.087302 4 0.02381 0.063492 1404604038607 0 0 \n", "\n", " team_name competitionid seasonid expected_goals on_target length \n", "0 Costa Rica 4 2013 0.254178 0.007937 126 \n", "\n", "[1 rows x 20 columns]" ] } ], "prompt_number": 1 }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Building features\n", "This will return a pandas dataframe that contains the features that will be used to build a model.\n", "\n", "The features query will read from the game summary table that has prepared per-game statistics that will be used to predict outcomes.\n", "The data has been aggregated from touch-by-touch data from Opta. However, since that data is not public, we use these\n", "prepared statistics instead of the raw data.\n", "\n", "In order to predict a game, we look at the previous N games of history for each team, where N is defined here as history_size." ] }, { "cell_type": "code", "collapsed": false, "input": [ "import features\n", "reload(features)\n", "\n", "# Sets the history size. This is how far back we will look before each game to aggregate statistics\n", "# to predict the next game. For example, a history size of 5 will look at the previous 5 games played\n", "# by a particular team in order to predict the next game.\n", "history_size = 6\n", "\n", "game_summaries = features.get_game_summaries()\n", "data = features.get_features(history_size)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "\r", "Waiting on bqjob_rdad0a47a2ca5106_0000014722ccfb80_2 ... (0s) Current status: DONE \n", "\r", "Waiting on bqjob_r61ce926a2f57863e_0000014722cd178a_3 ... (0s) Current status: DONE " ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\n" ] } ], "prompt_number": 3 }, { "cell_type": "markdown", "metadata": {}, "source": [ "The features include rollups from the last K games. Most of them are averages that are computed per-minute of game time. Per-minute stats are used in order to be able to normalize for games in the world cup that go into overtime.\n", "\n", "### Feature columns:\n", "The following columns are the features that will be used to build the prediction model:\n", "\n", "* is_home: Whether a team is playing at home or away. This turns out to be a big deal in soccer.\n", "* avg_points: Average number of points (3 for a win, 1 for a draw, 0 for a loss) earned in the last K games.\n", "* avg_goals: Average number of goals scored in the last K games.\n", "* op_average_goals: Average number of goals scored by the opponent in the last K games.\n", "* pass\\_{70/80}: Number of completed passes per minute in the attacking 30%/20% of the field.\n", "* op_pass\\_{70/80}: Number of completed passes by the opponent in their attacking 30%/20% of the field.\n", "* expected_goals: Average number of expected goals in the last K games, where expected goals is computed based on the number of shots taken and their distance from the goal.\n", "* passes: Number of passes completed per minute.\n", "* bad_passes: Number of passes that didn't complete successfully per minute.\n", "* pass_ratio: Percentage of completed passes.\n", "* corners: Number of corner kicks awareded per minute.\n", "* fouls: Number of fouls committed per minute.\n", "* cards: Number of cards recieved (red or yellow) per game.\n", "* shots: Number of shots taken per minute.\n", "* op\\_\\*: Statistics about the opponent in the historical games. This is not the opoonent shown in op_team_name; instead, these stats show how the primary team's opponents have fared against them. For example, op_corners is how many corners the teams opponents have been awarded per minute.\n", "* \\*\\_op\\_ratio: Ratio of a team's statistics to their opponents.\n", "\n", "### Non-feature columns:\n", "The following columns are included as metadata about the match:\n", "\n", "* matchid: Unique id for the match\n", "* teamid: Unique id for the team whose historical statistics we're looking at.\n", "* op_teamid: Unique id for the opposing team. None of these statistics reflect this opponent.\n", "* team_name: Name of the team whose historical statistics we're looking at.\n", "* op_team_name: Name of the opposing team.\n", "* timestamp: Time at which the game was played.\n", "* competitionid: Unique id for the competition (separates MLS from FIFA World CUp from EPL).\n", "\n", "\n", "### Target columns:\n", "The following columns are target variables that we will be attempting to predict. These columns must be dropped before any prediction is done, but are useful when building a model. The models that we will build below will just try to predict outcome (points) but other models may choose to predict goals, which is why they are also included here.\n", "\n", "* points: The outcome of the game. 3 points for a win, 1 point for a draw, 0 for a loss. (Points are not goals!)\n", "* goals: The number of goals the team referenced by teamid scored.\n", "* op_goals: The number of goals the team referenced by op_teamid scored." ] }, { "cell_type": "code", "collapsed": false, "input": [ "# Partition the world cup data and the club data. We're only going to train our model using club data.\n", "\n", "club_data = data[data['competitionid'] <> 4]\n", "# Show the features latest game in competition id 4, which is the world cup.\n", "data[data['competitionid'] == 4].iloc[0]\n" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 4, "text": [ "matchid 731828\n", "teamid 366\n", "op_teamid 632\n", "competitionid 4\n", "seasonid 2013\n", "is_home 0\n", "team_name Netherlands\n", "op_team_name Argentina\n", "timestamp 2014-07-09 21:00:00.000000\n", "goals 0\n", "op_goals 0\n", "points 1\n", "avg_points 2.166667\n", "avg_goals 2\n", "op_avg_goals 0.8333333\n", "pass_70 0.412262\n", "pass_80 0.1391892\n", "op_pass_70 0.3897345\n", "op_pass_80 0.114534\n", "expected_goals 1.799292\n", "op_expected_goals 0.7054955\n", "passes 3.518422\n", "bad_passes 1.014758\n", "pass_ratio 0.7588293\n", "corners 0.04906867\n", "fouls 0.1302936\n", "cards 2.666667\n", "shots 0.1469179\n", "op_passes 4.158118\n", "op_bad_passes 1.018166\n", "op_corners 0.04081354\n", "op_fouls 0.1938453\n", "op_cards 2.5\n", "op_shots 0.1107791\n", "goals_op_ratio 1.75\n", "shots_op_ratio 1.428914\n", "pass_op_ratio 0.9701803\n", "Name: 0, dtype: object" ] } ], "prompt_number": 4 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Compute the crosstabs for goals scored vs outcomes. Scoring more than 5 goals means you're guaranteed to win, and scoring no goals means you lose about 75% of the time (sometimes you tie!)." ] }, { "cell_type": "code", "collapsed": false, "input": [ "import pandas as pd\n", "pd.crosstab(\n", " club_data['goals'], \n", " club_data.replace(\n", " {'points': {\n", " 0: 'lose', 1: 'tie', 3: 'win'}})['points'])" ], "language": "python", "metadata": {}, "outputs": [ { "html": [ "
\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
pointslosetiewin
goals
0 727 267 0
1 477 394 320
2 131 205 500
3 21 40 314
4 2 6 148
5 0 2 65
6 0 0 12
7 0 0 6
8 0 0 1
\n", "
" ], "metadata": {}, "output_type": "pyout", "prompt_number": 5, "text": [ "points lose tie win\n", "goals \n", "0 727 267 0\n", "1 477 394 320\n", "2 131 205 500\n", "3 21 40 314\n", "4 2 6 148\n", "5 0 2 65\n", "6 0 0 12\n", "7 0 0 6\n", "8 0 0 1" ] } ], "prompt_number": 5 }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Training the model\n", "\n", "We're going to train a logistic regression model based on the club data only. This will use an external code file world_cup.py to build the model.\n", "\n", "The output of this cell this will be a logistic regression model and a test set that we can use to test how good we are at predicting outcomes.\n", "The cell will also print out the Rsquared value for the regression. This is a measaure of how good the fit was to the model (higher is better)." ] }, { "cell_type": "code", "collapsed": false, "input": [ "import world_cup\n", "reload(world_cup)\n", "import match_stats\n", "pd.set_option('display.max_rows', 5000)\n", "pd.set_option('display.max_columns', 500)\n", "pd.set_option('display.width', 1000)\n", "\n", "# Don't train on games that ended in a draw, since they have less signal.\n", "train = club_data.loc[club_data['points'] <> 1] \n", "# train = club_data\n", "\n", "(model, test) = world_cup.train_model(\n", " train, match_stats.get_non_feature_columns())\n", "print \"\\nRsquared: %0.03g\" % model.prsquared" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "\n", "Rsquared: 0.164\n" ] } ], "prompt_number": 6 }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Picking important features\n", "\n", "The logistic regression model is built using regularization; this means that it penalizes complex models. It has the side effect of helping us with feature selection. Features that are not important will be dropped out of the model completely. \n", "\n", "We can divide the features into three buckets:\n", "\n", "* Positive features: These features mean that a team is more likely to win.\n", "* Negative features: These features mean that a team is less likely to win.\n", "* Dropped features: These features aren't important, and if we included them in the model, we'd probably be overfitting." ] }, { "cell_type": "code", "collapsed": false, "input": [ "def print_params(model, limit=None): \n", " params = model.params.copy()\n", " params.sort(ascending=False)\n", " del params['intercept']\n", " \n", " if not limit:\n", " limit = len(params)\n", "\n", " print(\"Positive features\")\n", " params.sort(ascending=False)\n", " print np.exp(params[[param > 0.001 for param in params]]).sub(1)[:limit]\n", "\n", " print(\"\\nDropped features\")\n", " print params[[param == 0.0 for param in params]][:limit]\n", "\n", " print(\"\\nNegative features\")\n", " params.sort(ascending=True)\n", " print np.exp(params[[param < -0.001 for param in params]]).sub(1)[:limit]\n", "\n", "print_params(model, 10)\n" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Positive features\n", "is_home 0.712618\n", "pass_70 0.215699\n", "opp_op_expected_goals 0.198712\n", "opp_op_corners 0.180812\n", "shots 0.146956\n", "opp_bad_passes 0.145576\n", "op_passes 0.091629\n", "expected_goals 0.079620\n", "avg_points 0.075306\n", "fouls 0.047963\n", "dtype: float64\n", "\n", "Dropped features\n", "op_avg_goals 0\n", "goals_op_ratio 0\n", "op_cards 0\n", "op_bad_passes 0\n", "op_shots 0\n", "corners 0\n", "cards 0\n", "opp_pass_op_ratio 0\n", "pass_ratio 0\n", "passes 0\n", "dtype: float64\n", "\n", "Negative features\n", "opp_pass_70 -0.177428\n", "op_expected_goals -0.165771\n", "op_corners -0.153125\n", "opp_shots -0.128127\n", "bad_passes -0.127077\n", "opp_op_passes -0.083938\n", "opp_expected_goals -0.073748\n", "opp_avg_points -0.070032\n", "opp_fouls -0.045768\n", "opp_avg_goals -0.020472\n", "dtype: float64\n" ] } ], "prompt_number": 7 }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Predicting wins in club data\n", "\n", "This cell uses the test set (which was not used during the creation of the model) to predict outcomes. We can a few of the predictions to see how well we did. We'll show 5 each from two buckets: cases where we got it right, and cases where we got it wrong. We can see if these make sense. When we display these, the home team is always on the left. \n", "\n", "For example, it might show that we predicted Manchester United playing at home beating Sunderland. This is completely reasonable and we'd expect that the outcome would be 3 points (a victory).\n", "\n", "The columns of the output are:\n", "\n", "* team_name: Home team\n", "* op_team_name: Away team\n", "* predicted: The percentage chance that we believe the home team will win.\n", "* points: What actually happenned. 3 points for a win, 1 point for a draw, 0 points for a loss." ] }, { "cell_type": "code", "collapsed": false, "input": [ "reload(world_cup)\n", "results = world_cup.predict_model(model, test, \n", " match_stats.get_non_feature_columns())\n", "\n", "predictions = world_cup.extract_predictions(\n", " results.copy(), results['predicted'])\n", "\n", "print 'Correct predictions:'\n", "predictions[(predictions['predicted'] > 50) & (predictions['points'] == 3)][:5]" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Correct predictions:\n" ] }, { "html": [ "
\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
team_nameop_team_namepredictedexpectedwinnerpoints
5 Vancouver Whitecaps Portland Timbers 50.746754 Vancouver Whitecaps Vancouver Whitecaps 3
23 Sporting Kansas City Montreal Impact 71.255427 Sporting Kansas City Sporting Kansas City 3
49 Real Madrid Real Sociedad 70.565179 Real Madrid Real Madrid 3
59 Real Betis Levante 57.020318 Real Betis Real Betis 3
65 Seattle Sounders FC Montreal Impact 53.362012 Seattle Sounders FC Seattle Sounders FC 3
\n", "
" ], "metadata": {}, "output_type": "pyout", "prompt_number": 8, "text": [ " team_name op_team_name predicted expected winner points\n", "5 Vancouver Whitecaps Portland Timbers 50.746754 Vancouver Whitecaps Vancouver Whitecaps 3\n", "23 Sporting Kansas City Montreal Impact 71.255427 Sporting Kansas City Sporting Kansas City 3\n", "49 Real Madrid Real Sociedad 70.565179 Real Madrid Real Madrid 3\n", "59 Real Betis Levante 57.020318 Real Betis Real Betis 3\n", "65 Seattle Sounders FC Montreal Impact 53.362012 Seattle Sounders FC Seattle Sounders FC 3" ] } ], "prompt_number": 8 }, { "cell_type": "code", "collapsed": false, "input": [ "print '\\nIncorrect predictions:'\n", "predictions[(predictions['predicted'] > 50) & (predictions['points'] < 3)][:5]" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "\n", "Incorrect predictions:\n" ] }, { "html": [ "
\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
team_nameop_team_namepredictedexpectedwinnerpoints
8 Sporting Kansas City D.C. United 52.268257 Sporting Kansas City D.C. United 0
17 Celta de Vigo Valencia CF 53.402876 Celta de Vigo Valencia CF 0
19 Real Madrid Celta de Vigo 69.646704 Real Madrid Celta de Vigo 0
28 Atl\u00e9tico de Madrid Levante 63.517874 Atl\u00e9tico de Madrid Levante 0
29 LA Galaxy Colorado Rapids 55.278595 LA Galaxy Colorado Rapids 0
\n", "
" ], "metadata": {}, "output_type": "pyout", "prompt_number": 9, "text": [ " team_name op_team_name predicted expected winner points\n", "8 Sporting Kansas City D.C. United 52.268257 Sporting Kansas City D.C. United 0\n", "17 Celta de Vigo Valencia CF 53.402876 Celta de Vigo Valencia CF 0\n", "19 Real Madrid Celta de Vigo 69.646704 Real Madrid Celta de Vigo 0\n", "28 Atl\u00e9tico de Madrid Levante 63.517874 Atl\u00e9tico de Madrid Levante 0\n", "29 LA Galaxy Colorado Rapids 55.278595 LA Galaxy Colorado Rapids 0" ] } ], "prompt_number": 9 }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Validating our predictions\n", "\n", "Next, we want to actually quantify how good our predictions are. We can compute the lift (\"How much better are we doing than random chance?\"), AUC (the area under the ROC curve) and plot the ROC curve. AUC is arguable the most interesting number, it ranges between 0.5 (your model is no better than dumb luck) and 1.0 (perfect prediction)." ] }, { "cell_type": "code", "collapsed": false, "input": [ "import pylab as pl\n", "# Compute a baseline, which is the percentage of overall outcomes are actually wins.\n", "# (remember in soccer we can have draws too).\n", "baseline = (sum([yval == 3 for yval in club_data['points']]) \n", " * 1.0 / len(club_data))\n", "y = [yval == 3 for yval in test['points']]\n", "world_cup.validate(3, y, results['predicted'], baseline, \n", " compute_auc=True)\n", "pl.show()" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "(3) Lift: 1.45 Auc: 0.745\n" ] }, { "metadata": {}, "output_type": "display_data", "png": "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\n/w9DVLLQWqUmNwAAAABJRU5ErkJggg==\n", "text": [ "" ] } ], "prompt_number": 10 }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Need.... more .... power!\n", "\n", "One thing that is missing, if you're predicting the next game based on the previous few games, is that some teams may have just played a really tough schedule, while other teams have played against much weaker competition.\n", "\n", "We can solve for schedule difficulty by running another regression; this one computes a power ranking, similar to the FIFA/CocaCola power ranking for international soccer teams (there are power rankings for other sports like college (american) football that may be familiar.)\n", "\n", "Once we compute the power ranking (which creates a stack ranking of all of the teams), we can add that power ranking as a feature to our model, then rebuild it and re-validate it. The regression essentailly automated the process of looking at relationships like \"Well, team A beat team B and team B beat team C, so A is probably better than C\".\n", "\n", "The output here will show the power ranking for various teams. This can be useful to spot check the ranking, since if we rank Wiggan at 1.0 and Chelsea at 0.0, something is likely wrong. \n", "\n", "Note that because there isn't a strict ordering to the data (if team A beats team B and team B beats team C, sometimes team C will then beat team A) we sometimes fail to assign ordering to all of the teams (especially where the data is sparse). For teams that we can't rank, we put them in the middle (0.5).\n", "\n", "Additionally, because the rankings for international teams are noisy and sparse, we chunk the rankings into quartiles. So teams that have been ranked will show up as 0, .33, .66, or 1.0.\n", "\n", "Once we add this to the model, the performance generally improves significantly." ] }, { "cell_type": "code", "collapsed": false, "input": [ "import power\n", "reload(power)\n", "reload(world_cup)\n", "def points_to_sgn(p):\n", " if p > 0.1: return 1.0\n", " elif p < -0.1: return -1.0\n", " else: return 0.0\n", "power_cols = [\n", " ('points', points_to_sgn, 'points'),\n", "]\n", "\n", "power_data = power.add_power(club_data, game_summaries, power_cols)\n", "power_train = power_data.loc[power_data['points'] <> 1] \n", "\n", "# power_train = power_data\n", "(power_model, power_test) = world_cup.train_model(\n", " power_train, match_stats.get_non_feature_columns())\n", "print \"\\nRsquared: %0.03g, Power Coef %0.03g\" % (\n", " power_model.prsquared, \n", " math.exp(power_model.params['power_points']))\n", "\n", "power_results = world_cup.predict_model(power_model, power_test, \n", " match_stats.get_non_feature_columns())\n", "power_y = [yval == 3 for yval in power_test['points']]\n", "world_cup.validate(3, power_y, power_results['predicted'], baseline, \n", " compute_auc=True, quiet=False)\n", "\n", "pl.plot([0, 1], [0, 1], '--', color=(0.6, 0.6, 0.6), label='Luck')\n", "# Add the old model to the graph\n", "world_cup.validate('old', y, results['predicted'], baseline, \n", " compute_auc=True, quiet=True)\n", "pl.legend(loc=\"lower right\")\n", "pl.show()\n", "\n", "print_params(power_model, 8)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "New season 2014\n", "New season 2013" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\n", "QC check did not pass for 19 out of 20 parameters\n", "Try increasing solver accuracy or number of iterations, decreasing alpha, or switch solvers" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\n", "Could not trim params automatically due to failed QC check. Trimming using trim_mode == 'size' will still work.\n", "New season 2013" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\n", "New season 2012" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\n", "QC check did not pass for 24 out of 24 parameters\n", "Try increasing solver accuracy or number of iterations, decreasing alpha, or switch solvers" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\n", "Could not trim params automatically due to failed QC check. Trimming using trim_mode == 'size' will still work.\n", "New season 2012" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\n", "New season 2011" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\n", "QC check did not pass for 24 out of 24 parameters\n", "Try increasing solver accuracy or number of iterations, decreasing alpha, or switch solvers" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\n", "Could not trim params automatically due to failed QC check. Trimming using trim_mode == 'size' will still work.\n", "[u'Blackburn Rovers: 0.000', u'Real Betis: 0.000', u'D.C. United: 0.000', u'Celta de Vigo: 0.004', u'Deportivo de La Coru\\xf1a: 0.009', u'Wolverhampton Wanderers: 0.021', u'Reading: 0.022', u'Real Zaragoza: 0.026', u'Real Valladolid: 0.044', u'Granada CF: 0.062', u'Queens Park Rangers: 0.073', u'Mallorca: 0.089', u'Aston Villa: 0.092', u'Bolton Wanderers: 0.102', u'Osasuna: 0.109', u'Espanyol: 0.112', u'Wigan Athletic: 0.124', u'Sunderland: 0.130', u'Rayo Vallecano: 0.138', u'Almer\\xeda: 0.145', u'Levante: 0.148', u'Elche: 0.154', u'Getafe: 0.170', u'Swansea City: 0.192', u'Southampton: 0.197', u'Norwich City: 0.206', u'Toronto FC: 0.211', u'Chivas USA: 0.218', u'West Ham United: 0.220', u'West Bromwich Albion: 0.224', u'Villarreal: 0.231', u'Stoke City: 0.255', u'Fulham: 0.274', u'Valencia: 0.296', u'Valencia CF: 0.296', u'M\\xe1laga: 0.305', u'Newcastle United: 0.342', u'Sevilla: 0.365', u'Columbus Crew: 0.366', u'Athletic Club: 0.386', u'Liverpool: 0.397', u'Everton: 0.417', u'Philadelphia Union: 0.466', u'Montreal Impact: 0.470', u'Chelsea: 0.530', u'Real Sociedad: 0.535', u'Tottenham Hotspur: 0.551', u'Arsenal: 0.592', u'Houston Dynamo: 0.593', u'FC Dallas: 0.612', u'Chicago Fire: 0.612', u'Vancouver Whitecaps: 0.615', u'San Jose Earthquakes: 0.632', u'New England Revolution: 0.634', u'Atl\\xe9tico de Madrid: 0.672', u'Colorado Rapids: 0.743', u'Barcelona: 0.759', u'Seattle Sounders FC: 0.781', u'New York Red Bulls: 0.814', u'Sporting Kansas City: 0.854', u'LA Galaxy: 0.882', u'Real Salt Lake: 0.922', u'Manchester City: 0.928', u'Real Madrid: 1.000', u'Manchester United: 1.000', u'Portland Timbers: 1.000']" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\n", "\n", "Rsquared: 0.238, Power Coef 2.22" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\n", "(3) Lift: 1.48 Auc: 0.762" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\n", " Base: 0.375 Acc: 0.682 P(1|t): 0.742 P(0|f): 0.646\n", " Fp/Fn/Tp/Tn p/n/c: 100/228/288/416 516/516/1032\n", "(old) Lift: 1.45 Auc: 0.745\n" ] }, { "metadata": {}, "output_type": "display_data", "png": "iVBORw0KGgoAAAANSUhEUgAAAYoAAAEeCAYAAACUiVJFAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzs3Xd8Tff/wPHXvVmyZRAyCAkZNdLWJsSqIopq1R6tUf0q\nUUWNKn5qtNVqq5TWCmqUGrUbaYwiVs2kUYQEEW4SkZB57/n9cZvLleFm3NyMz/PxyCP3nPM557xv\nyH3ncz5LJkmShCAIgiDkQ27oAARBEISyTSQKQRAEoUAiUQiCIAgFEolCEARBKJBIFIIgCEKBRKIQ\nBEEQCiQShVAmhYWFIZfLtb6sra155ZVX+Oqrr8jOzs733CNHjvD222/j7OyMmZkZTk5OdO/enZ07\ndxZ4z6tXr/LBBx/g7e2NlZUVFhYWeHl5MXr0aM6cOVPSb1EQyg2ZGEchlEVhYWF06NCBAQMG0K1b\nNyRJIi4ujuDgYC5fvszgwYNZu3ZtrvOmTZvGggULcHd3Z/DgwdSpU4e4uDh++eUXrly5wuDBg1m9\nejVyufbfSCtXrmTMmDFYWFjQv39//Pz8MDY2Jioqim3bthEdHU1ERATe3t6l9SMQhLJDEoQy6M8/\n/5RkMpm0aNEirf2PHz+W3NzcJJlMJsXFxWkd+/nnnyWZTCa99tprUlpamtax7OxsaejQoZJMJpNm\nzpypdeyPP/6Q5HK51KhRo1zXzDl38eLFUkRERAm9u+JRqVRSamqqocMQKhHx6EkoVywsLGjevDkA\nt27d0uzPzMxkxowZWFtbs2HDBqpUqaJ1npGREcuXL6dWrVp89dVXKBQKzbEpU6Ygk8nYvHkzNWrU\nyHVPIyMjxo8fj4+Pzwvje/ToEdOnT8fHxwdzc3McHR3x9/dn8+bNmjIBAQHUqVMn17k3b95ELpcz\ne/Zszb6cR3Br167lhx9+wNfXF3Nzc7766iveeecdzMzMSExMzHWtqKgo5HI5H330kdb+zZs306ZN\nG2xsbLC0tKRFixZs27bthe9LqNxEohDKnevXryOTyXB2dtbs++uvv4iPj6dnz544OjrmeZ6ZmRmD\nBg0iLS2NvXv3AhAdHc3ff/9NmzZtiv1Y6eHDh7Rq1Yr58+fTqFEjvvzySz799FPq1q3Lnj17tMrK\nZLJ8r5PXscWLF7Nw4UIGDBjAkiVLaNGiBcOGDSMrK4uNGzfmKh8cHAzA0KFDNftmzJhB//79sbW1\nZe7cuSxcuBALCwvefvttli5dWtS3LVQCxoYOQBAK8vjxYxQKBZIkce/ePX788UfOnz9Pr169cHNz\n05S7fPkyAK+88kqB18s5nlM+57ufn1+xY502bRoRERGsWLGCESNGaB2TitkUGBsbyz///KOVBFUq\nFTVq1CA4OJj//e9/Wvdav349jRo1onHjxgCcO3eOefPmMW3aNObOnaspO3bsWHr37s3UqVMZMmQI\nVlZWxYpTqJhEjUIo0z777DOqV6+Ok5MTjRs3ZtmyZXz00Uds2rRJq9yjR48AsLW1LfB6NjY2ACQn\nJ2udl7O/qFQqFZs2bcLX1zdXkoCCaxC6GDJkSK6aklwuZ+DAgZw+fZqoqCjN/rCwMGJjY7VqExs2\nbEAmkzFkyBAUCoXWV48ePUhJSeHEiRPFilGouESiEMq00aNHExISwr59+1i4cCH29vZs2bKF+/fv\na5V7PgHk5/mEknNeSkpKseJUKBQ8fPiwRGomealfv36e+3OSQc6jppzXxsbGDBw4ULMvMjISSZLw\n9vamevXqWl8jRoxAJpPl+pkKQg7x6Eko0+rVq0eHDh0A6NKlC23atKFNmzaMGDGC/fv3a8o1bNgQ\ngLNnzxZ4vXPnzmmVb9Cggdb+0pBf7aKgsSEWFhZ57m/QoAF+fn5s2LCBzz//nCdPnrBt2zZee+01\nqlevriknSRIymYz9+/djZGSU57V8fX0L8S6EykQkCqFcadmyJYMHDyY4OJhDhw7RsWNHAFq1aoWT\nkxM7d+4kISEBBweHXOemp6ezfv16zM3N6dq1KwB16tTh5Zdf5q+//iIqKgovL68ixeXo6IidnR3n\nz59/YVl7e/s8E9ONGzeKdO+hQ4cyYcIE/vzzT+7evUtqaqrWYydQ10gOHDiAm5ubGAsiFJp49CSU\nO59++ilGRkZa3UhNTU2ZM2cOqampDBo0iPT0dK1zlEolH3zwATExMUyaNEnref/ChQsB6NevH/Hx\n8bnup1QqWbx4MZGRkfnGJJfL6d+/PxEREaxatarA+L28vEhJSeH06dOafSqVim+++abgN56PAQMG\nYGxsTHBwMMHBwVStWpWePXtqlRk8eDCgbnBXqVS5rpHX+xaEHKJGIZQ7Hh4e9OvXjw0bNvDnn3/S\nvn17AEaOHMm1a9f48ssv8fX1ZciQIdSuXZt79+6xceNGzYjuzz77TOt6nTp1YsWKFYwZMwYvLy/6\n9+9P48aNMTY25tq1a5qR2a+//nqBcc2dO5fQ0FBGjBjBwYMHad26NZIk8ffff6NUKjXtCKNGjWLR\nokX07t2b8ePHY2JiwtatW1EqlUX6eVSrVo2uXbuydetW0tPTGTFiBKamplplmjRpwqxZs5g1axZ+\nfn68/fbb1KxZk7i4OM6ePcu+ffvIyMgo0v2FSsCQo/0EIT/5jczOERkZKRkZGUnt27fPdSwsLEzq\n06ePVLNmTcnU1FSqVq2a1K1bN2nHjh0F3jMqKkoaM2aMVL9+fcnCwkKqUqWKVL9+fWnkyJHSuXPn\ndIr74cOH0uTJkyVPT0/J1NRUcnBwkNq2bSv9+uuvWuX27t0r+fn5SWZmZpKLi4v0ySefSFFRUZJM\nJpNmz56t9XOQy+XS2rVrC7zvtm3bJJlMJsnlcun48eP5ltuzZ4/UpUsXyd7eXjIzM5Nq1aoldevW\nTVq+fLlO70+onMRcT4IgCEKBSq2N4t1338XJyUnT2yQv48aNo169ejRu3Ji///67tEITBEEQClBq\niWL48OFa3Rmft3fvXq5du8a///6reV4sCIIgGF6pJQp/f3/s7OzyPb5r1y5Nl77mzZvz8OFD0RND\nEAShDCgzvZ7u3LmjNXePq6srt2/fxsnJSatccadCEARBqKyK2iRdZhIF5H4T+SUF0f6ultPdURA/\ni2eJn8VTZfFnoVLBxo3w8KF6+/p1WL8eHjz4r4DZI3w6nCWyWSeM0qthY2VEkvKu1jWaWPaimdXb\nutyN7OybKJV3WBn0Y5FjLjOJwsXFhdjYWM327du3cXFxMWBEgiAIBdu1C8LCcu/PGTspl4NkcR/s\nr2uOSc+Od7SKh0brwN+catVAUWMTkkxJztDOWjUt6FhXPftApjKT8c3H41fDD7nsxa0GCoWCsLAw\nrKya4O8/oWIkijfeeIMlS5bQr18/Tp48SdWqVXM9dhIEQTC0q1dh2jTIylInCgBra+0yxsbQoAEE\nBsLPRl25Jyt4LjErEytsrJywkmqRnp3OpFaTaO7anFZurYoU48WLFzl//jwtWrSgXr16xX5kX2qJ\non///hw+fBiFQoGbmxuzZ88mKysLUM8Q2q1bN/bu3YunpyeWlpasXr26tEIrtwICAgwdQpkhfhZP\niZ/FU0X9WcTHw+7dsH073LgBz86jeDkyA2zuULMmeLeEoUPhhO0EdkXtwszIDAAjIPK/rwxlBh3r\ndGRSq0l53sve3J6mLk2LFGd+qlevTp8+fbC0tCyR65W7AXcymUy0UQiCUGjZqmwO3zxMWnZavmVu\n3lQnh9DQZ3a6Hae632lkkvrv6nib/Lv5T2k9Jc/977z0Di/XfLkoYZeY4nx2ikQhCEKFdv/xfdqt\nacc/in+KdZ1mLs0AdWcaBwsH+jfor3W8lVsrPO09i3UPfSrOZ2eZaaMQBEEoDqVKSfidcL4L/44q\nxlU0+9deWAuAg4kr0k1/Evd9CCoTatfWPl+SID0dhgyBd97RPuZq40oNqxr6fguFolQq+fvvvzE1\nNaVRo0Z6vZeoUQiCUG6kZaWxM2onGdlPZ7r98viXmJuYc+buGa2ytpI6E0goSbldG2ltCGRXoXt3\n6NgRJkwo1dBL1NMeTVb4+/vr1BYhahSCIFQ4KRkpJKUnEZ0Uze9Xf2fzlc3cfnQ73/Ly611RyTLh\nyAy41ZZkSbsLadWq8N138N/SHOVSTi0iIiKixHo06ULUKARBKFNUkoq/Yv6i7Zq2zx2Qg1wFx6bA\n2VEg/fcBKclxt6+FpJJRo4a6ITqflWPLvWPHjpGamqpzLeJZojFbEIRyTaWClMdZPE5T8fmacJam\ntQPA6P7LqE6ORUrwgFvtaNkSBgxAq33B2xvq1TNQ4KUsMzMTExOTItUiRKIQBKFcefwYoqPVr8+c\nT2f4j99C50+0C21fA5cGMmqEMaamMHUqODuXeqgVhkgUgiCUWaEX/+Gjr86Smfl0X2QE4B4GNS6A\ny9O1w9sZT8LWzI6AljaMfHkUluYmVMZ5QJVKJVlZWVSpUuXFhXUkEoUgCGXOnqt7CNwY+MJyjSw7\n42hjxbxuU2ju2rwUIivbcno01a1bl1deeaXErit6PQmCYFCSpJ4FdcSy5cSY7Udh/DcpRrfUB+81\ngpMTuLK/NSbPfeJUt6yObRXb0g+4DMqrR1NZIWoUgiAUS1wcjB8Pv+5OgCmOkG6DUWptpCoJWO3f\nhFm8Pz/8AG/rMit2JVWUcRGFJR49CYJQqp48gU2bYEf4OX6/8xOYpIGfegT0+698wLIePxg4wvLl\n4sWLVKlSRa/jIkSiEARBr9LSYOFCSElRP2b65hvAJRxGtgDAXOWIsVkmPX3eILhXsFiJsgwSiUIQ\nBL1QqpRsPnyBteHbOXhAjpExyGWQ5RoKtY8BEOjZk98H7jBwpMKLiEQhCILOVCrYtg1mzlQvsFPQ\nH///+Awlyzc43+O/vPkL/Rv2z/e4oE2hUJCZmYmzAQaEiF5PgiC80LJl8MEHuff37q29nWBxgn9q\nfMYTkxhUJrGYp9dlQv0f6NP4dV5+ueDEIuTt2R5Nbdq0MXQ4hSZqFIJQQUkSTJkCMTHq1doePwZs\nY2jh/xhvH+j7tnoqjJwP/pAbIUQlRPHD6acN0W/5vsWb3m+KWkMxlEaPJl2IR0+CIGhIknpNhZMn\n4do19T6X1mHEtRyIyuruC8+3MbMhqEUQ0/2nY2pkqudoK7bLly9z7ty5Up3pNT8iUQiCAKhrDUuW\nwCdz43DotBqjOse4b7tPq8yy7suwq2KX5/nNXZvjXtW9FCKtHB48eICFhYXBahHPEolCECq56Gj4\n5BMIC4P7zd6HJsu1jvdv0J9Rr46iTa02GMtF02RlJBKFIFQC6elw4ABak+utWAGpaRmcrPUWVL0F\nlvFgdR+A1T1XM7DhQIzkRshl8nyuKlQWIlEIQgWWnAwrV8KaNXDp0n87W34NtjHq1x4HoVokAL28\nevE46zGftv0U/9r+Bom3ssnp0SSTyXj11VcNHU6+RKIQhAoqKwtMn2tP/uPoQzofssNMXgVTIzOU\nZFHFuAoX37+Ii42LYQKtpMpKjyZdiHEUglABTZsG8+erX9ep/5jjJ5WYmsKx20cAWNh5AeNbjDdg\nhJWXodauNhSRKAShjFCpIDgYZswAIyP1+AeARv03c9GrHzW/0y5fx65O6QcpAHD69GkePnxInz59\nynQtoqSIR0+CYECSpE4Iv/yirkE8q97EERi5/M0j6R53U+4yv+N8zbiGZi7NaFOr/I3wrSiys7Mx\nMjIqV7UI0UYhCOVMRIQ6QXTtqr3/s89g5Ej12tAW88xxtnbGt5ovde3qsrjL4nL1wSSULaXWRhET\nE8Nnn33GwYMHuX//PgcOHKBDhw48ePCAyZMn88EHH9C0adMiBSIIFVVcHOzcqa49/PsvbNgA9+9r\nl1mzBurWBX9/kCSJYzHHUKqUvOX7Fgs7LTRI3IK6LSIjIwMLCwtDh2JQOieK6OhomjdvTkZGBs2b\nNycuLk5zrFq1apw5c4aff/5ZJAqhUrp/X11DaNVK3VPJxOTpsays3OVbtID33gM/P3j5ZXWbBEB8\najyfHPqENefXAGBrJpYJNZScHk21atWiWbNmhg7HoHROFNOnT0cul3Pp0iUsLCyoXr261vFu3bqx\ne/fuEg9QEMqqW7fg5k0IDYU5c57ul8vh44+1y9aq9XSWVgsLsLZWv054ksDf8dEAHLh2gBl/ztCc\nkzNgTihdZXntakPROVGEhIQwduxYatWqhUKhyHW8du3axMbGlmhwglCWtW4Nd+483Z49G5o0gW7d\nXnzutcRrfHn8S1acXZH7um6tCRkSQhXjKiUYraCLZ8dFVJYeTbrQOVE8evSowMU2MjMzyc7OLpGg\nBKEsy8xULwt65w68/TaMGQMODtCoUd7ld1/dTY+NPTA1MtX0WkrNTNUc/6DpB3T1VLdq+9Xww9XG\nVe/vQcibQqGgUaNGFX5cRGHpnChcXV25cuVKvsfDw8Px9PQskaAEoay5fBkWLAClEjZterp/9mzw\n8cn/vCxlFr03q585NXNpRjOXp8+6fR19ecv3LWyriHaIssLb29vQIZRJOieKPn36sGzZMt59991c\nNYtt27axZcsWZs+eXeIBCoKhREfD8ePq1zt2wNatUK8eeHiAu7u6K2tBSWL5meW8v+d9ADrU6cCh\nIYf0H7Qg6IHO4yiSk5Np1aoVN2/epG3bthw4cIDOnTuTnJzMqVOn8PPz46+//sLc3Fy/AYtxFIKe\nnTwJBw+qE8Gz7OxAoVA3Vj8vIzuDSEWkZvsfxT/036ZeFe5t37dZHrgcO/O814AQSp9CoeDx48fU\nrl3b0KGUmlIbcJecnMzMmTPZsGEDiYmJAFStWpWBAwfy+eefY2NjU+D5+/fvJygoCKVSyYgRI5gy\nZYrWcYVCwaBBg7h37x7Z2dl8/PHHDBs2TDtgkSgEPTp5Elq2fLodEKCeyhvA0VGdLHJcuHeBd7a+\ng725PSdun8jzel90+oJJrSfpL2ChUJ7t0dS6dWs8PDwMHVKpKfWR2ZIk8eDBAyRJolq1asjz+hPr\nOUqlEi8vL0JCQnBxcaFp06Zs3LgRn2fq7rNmzSIjI4P58+ejUCjw8vIiPj4eY+OnT8hEohBKkiTB\n1Klw9Kh6LMODB/DPP7BsmXqEtFz+dE3pRxmPiE+NZ9X5VSw4tkBzDRszG5q5NMPSxJJhfsO09rd3\nby8aRcuI8jTTqz6Uysjs2bNn06dPHxo0aIBMJss1juLKlSts27aNmTNn5nn+qVOn8PT0xN3dHYB+\n/fqxc+dOrURRs2ZNLl68CKh7WTk4OGglCUEoKZmZsHo1vP/+033t20PNmvDSS+o1p3MGwWUps+i/\nrT/bIrdpXcPH0Yfp/tMZ2EiMdSjrIiMjOX36dKWY6VUfCpUo6tWrR4MGDfI8funSJWbPnp1vorhz\n5w5ubm6abVdXV8LDw7XKjBw5kg4dOuDs7ExKSgpbtmzJ81qzZs3SvA4ICCAgIEDXtyFUEtnZ6uky\n8voDauvW3O0P0dHqBmpQ15glJJ5kpbPuwjpNgzTAx60+5tWar9L3pb5i1bhypEaNGpVuXERYWBhh\nYWElcq0S+3M9PT0do5w/wfKgSwafN28efn5+hIWFcf36dTp37syFCxewzhnG+p9nE4UgPCslRd1D\naciQF5f9/HP146Vq1dTbkiQx/9h8podOz1XWxsyGmKAY0ZW1nLKzq3wdCZ7/I7o4vVILTBTJyckk\nJydrnmspFApicibJf0ZCQgK//PKLVo3heS4uLlojt2NjY3F11R5YdPz4caZPV/+Senh4UKdOHaKi\nomjSpInu70io1L766ul0GtWqwZIleZdzc3vaaJ3wJIHQ6FB+OvcTf9z4AwAPOw+GNB6CqZEpo18d\nLXoslSOSJIlHSyWswESxePFirSwUFBREUFBQvuUXLsx/lssmTZrw77//cvPmTZydndm8eTMbN27U\nKuPt7U1ISAitW7cmPj6eqKgo6tatq+t7ESoplUpdO8h56mlpCefOQf36+Z9z6s4pZoXtBWDO4TlI\nPH1GdfH9izR0aqjPkAU9yOnRlJWVRctnu64JxVZgr6dnn3HNmTOH3r1707Ch9i+QTCbDysqKli1b\n0qpVqwJvtm/fPk332Pfee4+pU6eyfPlyAEaPHo1CoWD48OHExMSgUqmYOnUqAwYMyHU/0etJeNb1\n6+DpqZ6x1dUVvvwS3nxT4u97f5OSkaIpl5qZytIzS7E0seTXiF+1rlHLthb7Bu6jtm1tLE0rz3Ps\niqKy92jSRal0jx02bBjvv/8+LVq0KNKNSopIFJVbejps3gxpaertkyfhjz/g7l1Ytw4GDQLFEwWf\n/vkpP575Md/reDt6079Bf2a2y7vzhVA+VLa1q4tDrHAnVEhnz6qXCN22TT2lt6mpultrXt55R90+\n4eIiUXNRTeIfxwPwfdfveanaS5pyVqZWNHFuIj5MKoizZ8/y4MEDUYvQQamtcAfqtWKjoqJISkpC\npVLlOt62bdsiBSIIObKyYOBA+PW/p0M5nek++kj93cREPf4hZ5yntbW6XQIgJjlWkyQeTnkoeilV\ncH5+fsjlcpH49axQiWLBggUsWLCAR48eae3PyVQymQylUlmiAQqVz4oVT5PETz/BiBG6n5vzF9Oq\nN1aJJFEJFNQlXyg5Oo8YWrlyJdOmTePll19m7ty5AEyYMIHJkydjZ2dHkyZNWLVqld4CFSqHlBQY\nO1b9+t69wiUJlaTi0v1L+glMMCilUklqauqLCwp6oXOiWLZsGc2bNyc0NJRRo0YB0L17dxYsWMCl\nS5e4deuWWLhIKBaVCnKWA2jTBpycdD9XkiTqfFuHHht7AOq2CKFiUCgUbN++nUuXxB8BhqJzooiM\njKRv377IZDLN88Ccx0w1a9Zk1KhRfPfdd/qJUqjwbt5Ut0Xcvave3rfvxedkZGdwLfEaF+MvMn7/\neGKS1YNBQ4eE0tunt/6CFUqFUqnkzJkz7N27l0aNGhm8x2VlpnMbhZGRkaZXQc73hIQEzfHatWtz\n9erVEg5PqOgkCYKC4Nm/Me7eBasXVAgylZnUWlyL+4/va+3/re9vtK/TXg+RCqVJrF1dtuicKNzc\n3IiOjgagSpUquLq6cuTIEfr16wfAmTNnsLe310+UQoU1derTJDFvHnzyydNpvfOy85+dRDyIYFro\nNM2+db3XYW1qTQ+vHmKivgoiOTlZrF1dhuicKNq1a8fu3buZP38+AH379uWbb74hLS0NlUrF+vXr\neffdd/UWqFDxXLigXgcCIC4OatTIv2y2Kpt639fj5sObmn3VLatzfdx10R5RAVWmBYXKA50Txbhx\n42jcuDFPnjzBwsKCWbNmcfXqVdauXYtMJuO1115jwYIFL76QIKCedsPPT/26ZcuCk8TjzMf8eOZH\nTZK48sEVPOw8MDM203+ggiAUf2T2w4cPMTIyyjUVuL6IkdnlV2ameuK+hw/hvym+GD0aFi4E23yG\nPMSnxrPu4jom/aFeTjRkcAgd63YspYgFfVMoFDx8+BBPT09Dh1LhlYkpPJRKJRs2bGCILgsBFINI\nFOXXtm3w1lvqpGBsDO3aqffl5/Sd0zT7uZlmO2psFPUdCpgSVig3np2jqVWrViJRlAKDJors7GyC\ng4OZN28e0dHReh+ZLRJF+XHxIoSFwePH6obqnPFShw/Di2Z6uf3oNm7fqNc3mdluJq1cW9HFs4t+\nAxZKhZjp1TD0OtdTSEgIixYt0qwj8eGHH9KrVy8ANm7cyPTp07l58ybW1tZMmTKlSEEI5ZNCAZcu\nQYcOupX38FDP1+TvX3C5LGUWtRfXBsDRwpFPWn+CuYl5MaMVyoKoqCjCw8PFTK/lzAvXo+jUqZPW\n5H9yuZzdu3ezefNm1q5dS9WqVRk3bhzjx48vleUGRY2ibPj0U/hvJhcAAgLyriU0aQKtW6sn8tO1\nGWvk7yP5+dzPWJta82jqoxefIJQbjx490hqTJZQevT16CgwM5MiRI2zYsIEOHTpw/fp1Bg8ezN27\nd0lISGD06NHMnz+fqlWrFjn4QgcsEoVBnT0LXbvCgwfq7ZkzoXFjeOMNdbtDcYVGh9IxWN1YHRMU\ng5tt/svrCoKgO709ejp16hSjRo2iRw/1/DmNGjVi0aJFvPbaawwZMoRly5YV6aZC+ZSaqp6k78ED\ndXfWlSuhW7eSvUfEgwgAtr+zXSSJck6sXV1xFJgoEhMTadCggdY+X19fAHr3FnPpVCbp6eDiAo8e\nqcc9HD+un/uoJPVjzja12ujnBoLe5fRoevz4Me3atTN0OEIJKHC+A5VKhampqda+nO3SGjchlA1P\nnqiTRN++sHq1fu6Rpcxi/P7xABjJxDoD5VHOTK8KhYImTZoYOhyhhLzwqXJqaiqJiYma7aSkJEDd\nKPXs/hxivqeKrXVr8PIqmWsduXWEO4/uAPD9qe81tYmXa7yMnbn+O0YIJUesXV2xFdiYLZcXboK1\n0ljhTjRmG0ZiIjg4wLffwrhxRb+OJEmERofSY2MP0rLTch3v4tGFBZ0W4FfDrxjRCqXt4sWL3L17\nV4yLKMP01phd2FHW4i+Iiuunn4p+riRJBB0I4vaj2/wW+Ztmf3v39kzzn4abjRsymQxPe08x+2s5\n1aBBAxo2bCg+AyqoEpvCo7SIGoVhtGsHR47A5cvw0ku6nydJEkduHSFgbQA1rWpib25PpjKT/2v/\nf/R9qa/4YBGEUqLXkdmCsGmTOkm8+mrhksS3J78l6ECQZntp96X08u6lhwiF0pKzdrVtfrM4ChWS\nSBRCviZMUA+wy1kzYuZM3c+VJIkfTv+AmZEZU9tM5VXnVwmsH6ifQIVSkTNHU40aNWjTRnRfrkzE\noychT0qlejlSR0dwc4M+fWDiRN3OvZtylwkHJrDlyhaqGFchbXruRmuh/BA9mioG8ehJKDEXL8LH\nH8Mff6i3Bw2C/xY11IlKUuG9xJuUzBRAvX6EUH6JtasFEIlCQL3a3KBBYGMDBw+q98lkMGYMvP9+\n4a4VnxpPSmYKcpmcrE+zRC+mcu7Jkydi7WpBJIrKbsIEWLz46Xbz5tCsGXz3XdGuFxodCsDSbktF\nkqgAatVKsUrtAAAgAElEQVSqZegQhDJAJIpKKDsbzpxRz9mUY948+OQTdU2iKBKeJHA05igL/1oI\ngG813xKIVBCEsqBQieLRo0d88803HDx4kPv37xMcHEzLli1RKBQsXbqUvn374u3tra9YhWK4cQOi\no9U1hV27nu43NVUfc3HR7ToJTxJY+fdKZoTOwM7cTjMnU1xqnKZMV8+u+Nd+wepEQpmiUChQKBTi\n91fIk86J4sGDB7Ru3Zro6Gg8PDy4fv06aWnq3iwODg6sXbuWpKQkvvnmG70FKxSeUglDhsAvv2jv\nb9VK3d21iw6ri6okFXMOz2HRiUWkZqZq9stlcq0ur9Usq9HXty8e9h4lFb6gZ8/2aGr5bBVTEJ6h\nc6KYMWMG8fHxnDx5ktq1a1O9enXNMZlMxhtvvEFoaKheghSKzs9PPZoa4Isv1JP6NWmirkm8SFxK\nHH239uVYzDHNPntze+Z3nM/gRoPF8qTlnOjRJOhK50Sxe/duxowZw6uvvopCoch1vG7duqxZs6Yk\nYxOK6fTpp0kiMRF0XalWJalYcGwB5+LOcSzmGK3dWlPFuArreq+jpnVN/QUslJp///2XEydOiHER\ngk50ThQKhYJ69erle1wul5Oenl4iQQklY+tW9fcNG3RLEoonCt7+9W3uPLrDv4n/AuBi7cIvfX6h\nlq3o/VKRODs7i1qEoDOdE4WTkxPXr1/P9/j58+dFV7oyRKmEFStALocBA15cPjopmrrf1dVst6vd\njhU9VlDfob4eoxQMRSQIoTB07ujevXt3Vq5cyd27d3MdCw8PJzg4mJ49exZ4jf379+Pt7U29evVY\nuHBhnmXCwsJ4+eWXadCgAQEBAbqGJzwnMhIePtStbJYyizc2vQFA57qdyf40m7BhYSJJVBBiyhuh\n2CQd3b17V3J2dpacnJykkSNHSjKZTBoyZIj0zjvvSCYmJlLt2rUlhUKR7/nZ2dmSh4eHFB0dLWVm\nZkqNGzeWIiIitMokJSVJvr6+UmxsrCRJkvTgwYNc1ylEyJXa+fOSBJL0228Fl1t4bKHELDRf91Lu\nlU6Agt5lZ2dLp0+flkJCQgwdilAGFOezU+caRc2aNTlx4gTNmzdn5cqVAKxbt45ff/2VLl26cOzY\nMRwcHPI9/9SpU3h6euLu7o6JiQn9+vVj586dWmV++eUX+vTpg6urKwCOjo6Fz3yVXFoaPH4MH3yg\n3n5RG+XS00txtHAksH4gD6c8xMnKSf9BCnr37NrVLVq0MHQ4QjlXqAF3tWrVYufOnSQnJxMVFYUk\nSXh6ehaYIHLcuXMHNzc3zbarqyvh4eFaZf7991+ysrJo3749KSkpjB8/nsGDB+e61qxZszSvAwIC\nxCOq/yxfnntuprZtc5dTSSqO3jrKuovruJV8i9c9X+f3/r+XTpCCXomZXoUcYWFhhIWFlci1dE4U\nCQkJmoRga2tLs2bNCnUjXf6zZmVlce7cOQ4dOsSTJ09o2bKl5j/7s55NFAKcO6duk8hJEjNnqqcI\nDwyEqnYq0rMztcq/uuJVIh5EaLYXdFxQmuEKehQVFYVCoRA9moRcf0TPnj27yNfSOVE4OzvTrVs3\nhg4dSmBgIMbGhZsmysXFhdjYWM12bGys5hFTDjc3NxwdHTE3N8fc3Jy2bdty4cKFArvlVnYXLqhX\nnssxdizMng2ZykymHprK11u+zvfcsKFh1LKtRR27OqUQqVAafHx88PHxEbUIoUTpvHBR//792blz\nJ+np6Tg4ONCvXz+GDh1KkyZNdLpRdnY2Xl5eHDp0CGdnZ5o1a8bGjRvx8fHRlPnnn38YO3YsBw4c\nICMjg+bNm7N582Z8fZ9OMCcWLnrqzBlo2lT9+uOPYdgw8PWFs3Fn6LK+C4lpiQC0dG3JG15vaM6T\nIaPvS31FghCESqRUFi7auHEjjx494tdffyU4OJilS5fyww8/4OPjw9ChQxk0aBDOzs7538jYmCVL\nltClSxeUSiXvvfcePj4+LF++HIDRo0fj7e3N66+/TqNGjZDL5YwcOVIrSQjaYmLU3+fPV8/8muPI\nrSMkpiUSWD+Q5YHLcbbO/99FKJ+USiXJycnY29sbOhShEijyUqg3b95k3bp1rFu3jmvXrmFkZESH\nDh04cOBASceoRdQonvrtN/USpRcuQKNGT/d/feJrJh6cSPInydiY2RguQEEvFAoFhw8fxtHRkXbt\n2hk6HKGcKM5nZ5FXlnF3d+fTTz/l6tWrrF+/HgsLC0JCxLKXZcGOf3YYOgRBD5RKJWfOnGHv3r00\nbNiQtnl1aRMEPSjywkUpKSls2bKFdevWcfToUSRJokGDBiUZm1CAlJSnE/4961HGI47GHAWginGV\nUo5K0JeEhATCwsKwtLQUPZqEUleoRKFSqThw4ADBwcGahm1HR0c+/PBDhg4dyssvv6yvOIVnZGdD\ngwZP2yisrdXfbz28xfCdwwH4otMXmBrpMJe4UC5kZmbSsGFDMS5CMAidE8XEiRP55ZdfiI+Px9TU\nlMDAQIYMGUK3bt0K3VVWKDpJguHDnyaJixehzn+dl87cPcOfN/+ktVtrutbrargghRJXs2ZNatYU\nU7wLhqFzY7ZcLqdp06YMHTqU/v37Y6fr4gYlrDI3Zj95Ak5OkPrfInNRUVD/v3n7jsUcY/Ifkzlx\n+wQX379IQ6eGhgtUEIQyp1S6x165ckVrzINQ+i5depokbt9+us61SlLx1pa3iH8cj38tf9yruhss\nRqF4FAoFcXFxNGwoEr1QduicKESSMJzsbPj6a7h6Vb29d+/TJLH8zHLe36Oeu8PK1Iojw48YKEqh\nOJ6fo0kQypJ8Hz2tXbsWmUzGoEGDkMvlmu0XGTJkSIkH+azK+OipVSs4cUL92sICTp2Cl16C1MxU\nrOerW7Kn+0/n41YfU7VKVQNGKhRFzrgIS0tL/P39RY8mQS+K89mZb6KQy+XIZDLS0tIwNTVFLn/x\nkAuZTIZSqSxSILqqTIkiKwv691fXIHKmD7eweHp877976f5LdwY1GsS63usMF6hQZDdu3ODYsWNi\npldB7/TSRhEaGgqAiYmJ1rZQeu7fh23bwNsbPvtMO0mcvH2S7r90B2BQw0EGilAoLrF2tVAeFHkK\nD0OpLDWKzz+HK1dg40b12tcjR6r3K1VKhuwYwi+XfgHg/Sbvs6z7MgNGKghCeVAqU3gMHz4810JD\nzzp16hTvvvtukYIQtKWkwIwZsHs3uLtrz+P0V+xfmiSxInCFSBLliEqlMnQIglAkOieKtWvXcv36\n9XyP37hxgzVr1pRETJWaJMEXX6hfz5oF0dHQvLl6+3HmY9qtUU8Cd2DQAUa+OtIwQQqFkjNHk74n\nzBQEfSmxIdWPHz/WtGcIhffzz3D+PPzww9N9PXs+fS1JEs1+Vq8qWNu2Nh3qdCjlCIWieLZHk5jE\nTyivCkwUt27d4tatW5rnWpGRkRw5kruffkJCAsuWLcPT01M/UVYCEydCRoa6wdrPT73GhIfH0+P9\ntvXTLF8a+b9IjOVi2pSyTKxdLVQkBTZmz5o1izlz5uh0IblczqpVq8Q4ikKKi4MePdTrXgcFqQfW\nPS8tKw2LeeouT0lTksRYiXLg33//5fr162JchFBm6G0Kj169euHu7g7Au+++y6hRo3KNGpXJZFhZ\nWdGsWTPc3NyKFERlJUkwbhycPQsdO6rHTORl99XdAPjX8hdJopzw9PTE09NT1CKECqHAROHn54ef\nnx+gXtGuT58+Yg6aEvT117B1q/r1Tz89nQX2eZnKTAB+DPyxlCITikskCKEi0flB96xZs/QYRuV0\n7576+507kNdy4ypJxaX4S/x+9XcATOSis0BZo1QqSUpKwtHR0dChCILe5JsoDh8+jEwmw9/fH5lM\nlmcjdl5Ez47CsbDIO0lkq7Lx+M6DmGT1whM2ZjY4WDiUcnRCQXJ6NNnZ2dGhg+iFJlRcYq4nA0lP\nB3NzMDZWz+mUY8XZFXwb/q2mhxPA7v67ec3jNUyMRI2iLBA9moTySC+N2atWrUImk2lWr1u1alXR\nohPyFBmp/v58bWL07tEAtHBtgbWpNZve2oS9uX0pRyfkJzExkT///FOsXS1UKmKuJwP58kuYPBl2\n7NAeWCebLePDZh/yXdfvDBeckC+FQkFiYqKoRQjlTqmscCeUnIgIdZIAqF376f5ridcARA2iDHN0\ndBQN10Klo/NcT+Hh4fz0009a+3bs2EGDBg1wcXFh6tSpJR5cRTVunPr7d9+pR2EDxCTHUO/7eoB6\nig5BEISyQudHT927d0cul/P77+qumjExMXh7e2NpaYmjoyNRUVH8/PPPep9Btrw/enr3XVi9Wv1a\nqYScPgIOXziQmJZIU+emnBp5ynABCoD6EVNMTAyvvPKKoUMRhBJRKtOMX7hwgdatW2u2N23ahEql\n0vT+6NKlS64ah/CUJMGcOfBfnuXy5adJQpIknmQ9wcPOg5MjThouSEEz0+vevXuxsrIydDiCUCbo\nnCgSEhKoUaOGZvvAgQO0bdsWV1dXZDIZPXr04OrVq3oJsiJYtEi9Sp1CAXPnqte8BohPjeetX98i\nPTudTnU7IZfp/E8ilDCFQsGOHTtQKBT06dOH+vXrGzokQSgTdG7Mrlq1KvHx8QBkZGRw8uRJrXaJ\nnDEXQm5nz8KkSerXFy48XYhoz9U9BG4M1JSb7j/dANEJoH6UGhYWJsZFCEIedE4Ufn5+/Pzzz3Ts\n2JEdO3aQlpZGly5dNMdv3ryJk5OTXoIsz44dU9ckAN77YhdLY/ZCDCglJT+f+xkA32q+7BmwBzdb\nMamiodSoUUOMixCEfOjcmH38+HE6d+6sqTV06tSJgwcPao6/9NJLNGzYkE2bNukn0v+Ul8bs5GTY\ntg3ee0+97eMDNhNace7eGezN7ZGQUEkqvn7tawY2GigeOQmCoFelMo6iVatWnDt3jgMHDlC1alX6\n9eunOZaQkEDnzp3p3bt3kYKoaGJitMdHtJr4NVHV53E17iGd6nZi/6D9hgtOQKVS6TQljSAIamJk\ndgmLiFA/atq6FV55BTZsgM8uv8PB6wcZ2HAgvb1707FuR0OHWSnlzNEUFxdHYGCgaIcQKpXifHYW\nOlEkJycTEhJCdHQ0AHXr1qVz585YW1sXKYDCKuuJIigIvv0WataEU6eges1MLOdZUteuLlFjowwd\nXqX17NrVYtU5oTIqtSk8fvrpJyZOnEhqaqrWfmtraxYtWsSIESOKFERFolKBVeMDKPr0wG3l02lh\nRRuEYYiZXgWh+HSuUezatYtevXpRt25dxo0bh6+vLwARERF8//333Lhxg+3bt/PGG2/oN+AyXKPI\nygIrK6DZEjI7fcjHrT7GwsQCY5kxH7f6GHMTc0OHWOncunWLyMhIUYsQKr1SefTUpk0bEhMTCQ8P\nz/WYKSUlhebNm2Nvb8+xY8fyvcb+/fsJCgpCqVQyYsQIpkyZkme506dP07JlS7Zs2cKbb76pHXAZ\nThS7d0OPD45j0uFzsurs5cGkBzhaiAnkDCnn/4qoRQiVXalN4TFs2LA82yKsra0ZNmwY58+fz/d8\npVLJ2LFj2b9/PxEREWzcuJHInEUZnis3ZcoUXn/99TKbEPISGgo9Rp2F91qTVWcvNa1qYmUqpoAw\nNJlMJpKEIBSTzolCkqQCf+Fe9Mt46tQpPD09cXd3x8TEhH79+rFz585c5b7//nveeustqlWrpmto\nBhcWBrNmAS/9CsB3r3/P7Y9uU8W4iiHDqlSUSiX3chYhFwShROncmN24cWPWrFnDmDFjck2Wlpqa\nypo1a2jcuHG+59+5cwc3t6cjj11dXQkPD89VZufOnYSGhnL69Ol8k8+sWbM0rwMCAggICND1bZSo\n7dth505YuyMGxtcFuXoZ2De8eojG61KU06PJxsYGJycnUYMQBCAsLIywsLASuZbOiWLSpEm8+eab\nvPLKK4wbN46X/pvV7vLly3z//fdcu3aN3377Ld/zdfnlDQoKYsGCBZpnafk9eno2URjKiRMwbRpE\nR4PJ6O5kyZX41fBjeeByalcV60mUBtGjSRDy9/wf0bNnzy7ytXROFL169WLJkiVMnjyZcTkr7/zH\n0tKSH374gV69euV7vouLC7GxsZrt2NhYXF1dtcqcPXtWM+JboVCwb98+TExM9N6TqrCePAF/f/V6\nEv5DQzhqfxkzIzNOjTiFiZGJocOrFJKSkggNDRVrVwtCKSj0gLukpCT++OMPzYA7Dw8POnfujK2t\nbYHnZWdn4+XlxaFDh3B2dqZZs2Zs3LgRHx+fPMsPHz6cHj16lLleT5IEX38NH38MYyclssTSAYDt\n72ynl3f+iVIoWcnJycTHx4tahCDoSK8D7rKysti5cyfXr1/H0dGRnj170rdv38LfyNiYJUuW0KVL\nF5RKJe+99x4+Pj4sX74cgNGjRxc+egNo0gTOnQOqRrPEsi4AXT27iiRRymxtbV/4x4kgCCWjwBpF\nUlIS7dq14/Lly5p9VatW5eDBgzRp0qRUAnyeIWsUUVHg7a1+/e3B7Yw//ibNXZpzeNhhzIzNDBKT\nIAiCLvQ2jmLu3LlcvnyZwMBAvvvuOz788ENSU1MZNWpUkW5WniUnP00Sq1eD23/NK8sDl4skoUcK\nhYKTJ0+WqzE1glDRFPjo6ffff6dLly7s2rVLs8/d3Z2JEydy+/btXI3RFdmRI+rvfn4wbBhszz1W\nUChBz/doEgTBcAqsUcTGxtK9e3etfYGB6qU7b926pb+oyqCcP2hXrjRsHJVBXmtXiwZrQTCcAmsU\nGRkZ2Nvba+2zs7PTHKvMZvw5w9AhVEh37tzh0KFDYlyEIJQhhZpmHMTkatmqbBYc+4qIBxEA1Heo\nb+CIKhaxdrUglD0vTBSLFi3SWgc7MzMTgBkzZuDomHtm1GfbMyqiLy9/yNZbPwKw8o2VYurwEmZk\nZCSShCCUMQV2jy3KusIqlapYAb2IobrHfr3uChOXHsSh50ISMuK5Of6mmKqjmLKzszE2LnSlVhCE\nItDbgDt9f+iXF4onCibeaACvQ0IGTGgxQSSJYsjp0RQTE0Pv3r0r/eNMQSjrxJ9zLzB5MmyPCoNX\noMqdTtxdvJWq5jaGDqvcenbt6i5duogkIQjlgEgUL7B+PWR4qKtry3svxs5CTBtRFGKmV0Eov0Si\nKMD27RAXB037XScRePVVQ0dUfsXHx2vGRYjGakEoXwo9e6yhlVZj9vXr4Fk/G97qB77bALgVdIta\ntrX0fm9BEISSptfZYyujuDjw7LENZr6l2bem5xqRJARBqJTEep3P2bEDnJ2Bl1cBMKX5LJI/SWao\n31DDBlZOKJVK7ty5Y+gwBEEoQeLR0zN2/vUPvaZvhhaLwfwhAFmfZmEsFxUvXeT0aLK2tqZz586i\nsVoQypBSffQUHR1NSEgI9+/fZ8CAAdSpU4fMzEzu3buHk5MTZmbld8rtLw5/C+1/1GyfH31eJAkd\niB5NglCxFepTcPLkyXz99deoVCpkMhktW7akTp06pKWl4ePjw9y5c5kwYYK+YtWrPVf3cDzrR3hc\njYfT7yMWT9PNw4cPOXTokFi7WhAqMJ3bKJYvX85XX33F2LFjOXjwoFYVxtbWlp49e7J79269BKlv\n35/8gcCN6unT+UcsaVoYJiYmNGzYkC5duogkIQgVlM41iqVLl9KrVy8WL16MQqHIdbxhw4YcPny4\nRIMrDYt+eMjHirHqjdVhmMa1w9TUsDGVJ5aWltSvX/Fm0LW3tycpKcnQYQhCodnZ2ZGYmFii19Q5\nUVy9epUxY8bke7xatWp5JpCy7mBYKjQAN8W7rA9uh7MzmIsJYSu9pKQksfyqUC7po31Q50dPVapU\n4fHjx/kej4mJoWrVqiUSlCHMHN6Stm3B09PQkZRNCoWCo0ePig9PQaiEdE4UTZs2Zfv27XkeS09P\nZ926dbRu3brEAisN167BwYOGjqJsUyqVnDlzhr179+Lk5GTocARBMACdE8XkyZM5fvw4gwYN4uLF\niwDExcWxf/9+2rVrR2xsLB9//LHeAtWHc+eAjlMBkMvE2MPnibWrBUGAQg64W7FiBePGjdOscpfD\nzMyMZcuWMWzYsJKOL5eSGnCXlgYuDW6QNMQDgAeTHuBokXvFvsrq3r17HDx4sNKOizDUAlmCUFz5\n/d8tzv/pQo/MjouLY+vWrURGRiJJEvXr16dv3764uLgUKYDCKqlf4I4fryTUegQAY1/9iO8DFxX7\nmhWJSqUiPT0dCwsLQ4diECJRlIyIiAiGDh3K6dOnDR1Kmff777+zYcMGraWni0IfiQKpnCmJkJ88\nkSR6DpeYZiF9tHeKpFQpSyAyoSIp678atWvXlszNzSUrKyvJyclJGjRokJScnKxV5q+//pLat28v\nWVtbS7a2tlKPHj2kiIgIrTLJycnS+PHjpVq1aklWVlaSh4eHFBQUJCkUijzvGxAQIFWrVk2ytraW\nvL29pRUrVhQY55tvvilt3ry5eG/WwKKjo6WAgADJwsJC8vb2lkJCQvIt+/rrr0tWVlaaL1NTU6lh\nw4ZaZRYvXizVqVNHsrS0lHx8fKSrV69qjjVo0EC6ePFiseLN7/9ucf5Pl+3fhjwU9xc4LDpM8vqq\nmcRke8l4klsJRVW+ZWZmGjqEMqesJwp3d3fp0KFDkiRJ0r1796TGjRtLkyZN0hw/fvy4ZGVlJX33\n3XdSamqqlJiYKM2YMUOys7OTbty4IUmSJGVkZEhNmjSRXnvtNSkyMlKSJEm6f/++NHfuXGnv3r15\n3vfixYua/y/h4eGSmZmZ9M8//+RZ9u7du5K9vb2UkZFRpPeYnZ1dpPNKWosWLaSJEydK6enp0rZt\n26SqVatKDx480OncgIAA6f/+7/802z/99JPUqFEjzc/7xo0bUmJioub4559/Lo0dO7ZY8Ro0UQQE\nBEjt27fP9yvnuL4V580G7Q+SmIX6a0B36YMNC0swsvInOztbOn36tLRlyxZJqRS1qmeVp0QhSZI0\nadIkqVu3bprtNm3aSP/73/9ynde1a1dpyJAhkiSpP7ScnJykx48fFymG8PBwycHBQbp7926ex9eu\nXSt17txZa9/8+fMlDw8PydraWvL19ZW2b9+uObZ69WqpVatW0oQJEyQHBwfp008/lTIyMqSJEydK\ntWrVkpycnKT3339fSktLkyRJkpKSkqTu3btL1apVk+zs7KTAwEDp9u3bRXov+YmKipLMzMyk1NRU\nzb62bdtKP/744wvPjY6OloyMjKRbt25JkiRJSqVScnV1lUJDQ/M956+//pLq1KlTrJj1kSh0HnAX\nHR2d6xlXdnY2cXFxSJKEo6NjmZ7CIUuZxdLTS3G1ceXBlllknHyPeUsNHZXhPLt2dbdu3ZDLRa+v\nwggKgvPni3cNPz9YvLjo5+f8Lt6+fZv9+/fz1lvq9VOePHnCiRMnmDt3bq5z+vbty7Rp0wAICQmh\na9euhW6HCgwM5NChQ8hkMjZt2kTNmjXzLHfp0iW8vLy09nl6enLs2DFq1KjBli1bGDRoENevX9d0\nvT516hQDBgzg/v37ZGZmMmXKFKKjo7lw4QLGxsYMGDCAOXPmMG/ePFQqFe+99x5bt24lOzubd999\nl7Fjx+bbjT8wMJC//vorz2P+/v7s2rUr1/4rV65Qt25drc+2xo0bc+XKlRf+nIKDg2nbti21aqnX\n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\n", "text": [ "" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "Positive features\n", "power_points 1.222950\n", "is_home 0.692184\n", "pass_70 0.178619\n", "op_passes 0.140863\n", "fouls 0.138612\n", "opp_op_corners 0.122122\n", "opp_avg_points 0.055252\n", "opp_op_fouls 0.039738\n", "dtype: float64\n", "\n", "Dropped features\n", "avg_goals 0\n", "op_bad_passes 0\n", "corners 0\n", "op_shots 0\n", "op_cards 0\n", "opp_pass_op_ratio 0\n", "pass_ratio 0\n", "passes 0\n", "dtype: float64\n", "\n", "Negative features\n", "opp_power_points -0.550147\n", "opp_pass_70 -0.151549\n", "opp_op_passes -0.123470\n", "opp_fouls -0.121738\n", "op_corners -0.108831\n", "avg_points -0.052359\n", "op_fouls -0.038220\n", "bad_passes -0.028956\n", "dtype: float64\n" ] } ], "prompt_number": 11 }, { "cell_type": "markdown", "metadata": {}, "source": [ "## On to the world cup!\n", "\n", "Now that we've got a model that we like, let's look at predicting the world cup. We can build the same statistics (features) for the world cup games that we did for the club games. In this case, however, we don't have the targets; that is, we don't know who won (for some of the previous games, we do know who won, but let's predict them all equally as if we didn't know).\n", "\n", "features.get_wc_features() will return build features from the world cup games." ] }, { "cell_type": "code", "collapsed": true, "input": [ "import world_cup\n", "import features\n", "reload(match_stats)\n", "reload(features)\n", "reload(world_cup)\n", "\n", "wc_data = world_cup.prepare_data(features.get_wc_features(history_size))\n", "wc_labeled = world_cup.prepare_data(features.get_features(history_size))\n", "wc_labeled = wc_labeled[wc_labeled['competitionid'] == 4]\n", "wc_power_train = game_summaries[game_summaries['competitionid'] == 4].copy()" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "\r", "Waiting on bqjob_r771c340a8483b8a6_0000014722cd55df_4 ... (0s) Current status: DONE \n", "\r", "Waiting on bqjob_r5df9ca3d043b572b_0000014722cd5dbe_5 ... (0s) Current status: DONE " ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\n" ] } ], "prompt_number": 12 }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Predicting the world cup\n", "\n", "Once we have the model and the features, we can start predicting.\n", "\n", "### Home Team Advantage\n", "There are a couple of differences between the world cup and club data. For one, while home team advantage is important in club games, who is really at home? Is it only Brazil? What about other south american teams? Some models give the 'is home' status to only Brazil, others give partial status to other teams from the same continent, since historical data shows that teams from the same continent tend to outperform.\n", "\n", "We use a slightly modified model that is, however, somewhat subjective. We assing a value to is_home between 0.0 to 1.0 depending on the fan support (both numbers and enthusiasm) that a team enjoys. This is a result of noticing, in the early rounds, that the teams that had the more entusiastic supporters did better. For example, Chile's fans were deafining in support of their team, but Spain's fans barely showed up (Chile upset spain 2-0). There were a number of other cases like this; many involving south american sides, but many involving other teams that had sent a lot of supporters (Mexico, for example). Some teams, like the USA, had a lot of fans, but they were more reserved... they got a lower score. This factor was set based on first-hand reports from the group games.\n" ] }, { "cell_type": "code", "collapsed": false, "input": [ "import pandas as pd\n", "wc_home = pd.read_csv('wc_home.csv')\n", "\n", "def add_home_override(df, home_map):\n", " for ii in xrange(len(df)):\n", " team = df.iloc[ii]['teamid']\n", " if team in home_map:\n", " df['is_home'].iloc[ii] = home_map[team]\n", " else:\n", " # If we don't know, assume not at home.\n", " df['is_home'].iloc[ii] = 0.0\n", " \n", "home_override = {}\n", "for ii in xrange(len(wc_home)):\n", " row = wc_home.iloc[ii]\n", " home_override[row['teamid']] = row['is_home']\n", "\n", "# Add home team overrides.\n", "add_home_override(wc_data, home_override) " ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 13 }, { "cell_type": "markdown", "metadata": {}, "source": [ "### World Cup Power Rankings\n", "\n", "The lattice of teams playing eachother in the world cup is pretty sparese. Many teams haven't played eachother for decades. Many European teams rarely play South American ones, and even more rarely play Asian ones. We can use the same technique as we did for the club games, but we have to be prepared for failure.\n", "\n", "We'll output the power rankings from the previous games. We should eyeball them to make sure they make sense." ] }, { "cell_type": "code", "collapsed": false, "input": [ "# When training power data, since the games span multiple competitions, just set is_home to 0.5\n", "# Otherwise when we looked at games from the 2010 world cup, we'd think Brazil was still at\n", "# home instead of South Africa.\n", "wc_power_train['is_home'] = 0.5\n", "wc_power_data = power.add_power(wc_data, wc_power_train, power_cols)\n", "\n", "wc_results = world_cup.predict_model(power_model, wc_power_data, \n", " match_stats.get_non_feature_columns())" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "New season 2013\n", "New season 2009\n", "New season 6" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\n", "QC check did not pass for 45 out of 50 parameters\n", "Try increasing solver accuracy or number of iterations, decreasing alpha, or switch solvers" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\n", "Could not trim params automatically due to failed QC check. Trimming using trim_mode == 'size' will still work.\n", "[u'Australia: 0.000', u'USA: 0.017', u'Nigeria: 0.204', u\"C\\xf4te d'Ivoire: 0.244\", u'Costa Rica: 0.254', u'Algeria: 0.267', u'Paraguay: 0.277', u'Greece: 0.284', u'Switzerland: 0.291', u'Ecuador: 0.342', u'Uruguay: 0.367', u'Japan: 0.406', u'Mexico: 0.409', u'Chile: 0.413', u'England: 0.460', u'Portugal: 0.487', u'Ghana: 0.519', u'France: 0.648', u'Spain: 0.736', u'Argentina: 0.793', u'Italy: 0.798', u'Brazil: 0.898', u'Netherlands: 0.918', u'Germany: 1.000']\n" ] } ], "prompt_number": 14 }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Predicting games\n", "\n", "Now's the moment we've been waiting for. Let's predict some world cup games. Let's start with predicting the ones that have already happenned.\n", "\n", "We will output 4 columns:\n", "\n", "* team_name: Team we're predicting\n", "* op_team_name: Team that the team we're predicting is playing against\n", "* predicted: Precentage chance (we believe) that the team will win.\n", "* points: If the game has been played, what actually happenned. (if the game hasn't been played, we'll show a NaN here). 3 points is a win, 1 point is a draw, 0 points is a loss. Note that for games in the knockout phase that went into penalty kicks, we'll mark that as a draw.\n", "\n", "*But wait! These predictions are different from the ones you published!*\n", "\n", "There are three reasons why the prediction numbers might be different from the numbers you may have seen as published predictions:\n", "\n", "1. We've updated our code several times to fix bugs and improve accuracy. Our original model, for example, didn't account for extra time causing inflated statistics.\n", "1. Model building is non-deterministic. Since we pick a random subset of the data to use as our training set, the results will change from run to run. Sometimes fairly significantly.\n", "1. When we predicted the round of 16, we used the trailing 3 games to predict (since each team had played 3 games in the current world cup). For the quarterfinals, we used the trailing 4 games; for the semis, 5, and for the finals, we used all 6. The code below will predict based on the last 6 games; for many teams, we don't have 6 games of history, and even if we do, that history will be from previous world cups. *To see a more apples-to-apples comparison, set the history_size variable to 3 and rerun the notebook.*\n" ] }, { "cell_type": "code", "collapsed": false, "input": [ "pd.set_option('display.max_rows', 5000)\n", "pd.set_option('display.max_columns', 500)\n", "pd.set_option('display.width', 1000)\n", "\n", "wc_with_points = wc_power_data.copy()\n", "wc_with_points.index = pd.Index(\n", " zip(wc_with_points['matchid'], wc_with_points['teamid']))\n", "wc_labeled.index = pd.Index(\n", " zip(wc_labeled['matchid'], wc_labeled['teamid']))\n", "wc_with_points['points'] = wc_labeled['points']\n", "\n", "wc_pred = world_cup.extract_predictions(wc_with_points, \n", " wc_results['predicted'])\n", "\n", "# Reverse our predictions to show the most recent first.\n", "wc_pred.reindex(index=wc_pred.index[::-1])\n", "# Show our predictions for the games that have already happenned.\n", "wc_pred[wc_pred['points'] >= 0.0]\n" ], "language": "python", "metadata": {}, "outputs": [ { "html": [ "
\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
team_nameop_team_namepredictedexpectedwinnerpoints
2 Netherlands Argentina 45.187866 Argentina draw 1
3 Germany Brazil 46.087340 Brazil Germany 3
4 Costa Rica Netherlands 20.699193 Netherlands draw 1
5 Germany France 70.918295 Germany Germany 3
6 Switzerland Argentina 13.489369 Argentina Argentina 0
7 Algeria Germany 5.281820 Germany Germany 0
8 Nigeria France 6.469553 France France 0
9 Greece Costa Rica 44.465888 Costa Rica draw 1
10 Mexico Netherlands 25.852847 Netherlands Netherlands 0
11 Chile Brazil 29.159847 Brazil draw 1
12 Germany USA 92.599808 Germany Germany 3
13 Ghana Portugal 49.394952 Portugal Portugal 0
14 France Ecuador 84.520092 France draw 1
15 Uruguay Italy 24.142942 Italy Uruguay 3
16 Spain Australia 92.766946 Spain Spain 3
17 Chile Netherlands 36.429691 Netherlands Netherlands 0
18 Portugal USA 73.875672 Portugal draw 1
19 Ghana Germany 14.933490 Germany draw 1
20 France Switzerland 84.456472 France France 3
21 England Uruguay 61.402920 England Uruguay 0
22 Netherlands Australia 90.897030 Netherlands Netherlands 3
23 Mexico Brazil 18.745963 Brazil draw 1
24 USA Ghana 29.331909 Ghana USA 3
25 Portugal Germany 15.955012 Germany Germany 0
26 Japan C\u00f4te d'Ivoire 41.934990 C\u00f4te d'Ivoire C\u00f4te d'Ivoire 0
27 Italy England 73.812329 Italy Italy 3
28 Netherlands Spain 49.682892 Spain Netherlands 3
29 Spain Netherlands 53.295771 Spain Spain 3
30 Germany Uruguay 78.982270 Germany Germany 3
31 Spain Germany 41.830024 Germany Spain 3
32 Spain Paraguay 89.878445 Spain Spain 3
33 Germany Argentina 45.202720 Argentina Germany 3
34 Brazil Netherlands 63.908731 Brazil Netherlands 0
35 Portugal Spain 20.960764 Spain Spain 0
36 Japan Paraguay 63.519259 Japan draw 1
37 Mexico Argentina 27.152356 Argentina Argentina 0
38 England Germany 17.248992 Germany Germany 0
39 Ghana USA 70.933824 Ghana Ghana 3
40 Brazil Portugal 88.601369 Brazil draw 1
41 Germany Ghana 89.953475 Germany Germany 3
42 France Italy 39.809571 Italy draw 1
43 Portugal Germany 12.435939 Germany Germany 0
\n", "
" ], "metadata": {}, "output_type": "pyout", "prompt_number": 15, "text": [ " team_name op_team_name predicted expected winner points\n", "2 Netherlands Argentina 45.187866 Argentina draw 1\n", "3 Germany Brazil 46.087340 Brazil Germany 3\n", "4 Costa Rica Netherlands 20.699193 Netherlands draw 1\n", "5 Germany France 70.918295 Germany Germany 3\n", "6 Switzerland Argentina 13.489369 Argentina Argentina 0\n", "7 Algeria Germany 5.281820 Germany Germany 0\n", "8 Nigeria France 6.469553 France France 0\n", "9 Greece Costa Rica 44.465888 Costa Rica draw 1\n", "10 Mexico Netherlands 25.852847 Netherlands Netherlands 0\n", "11 Chile Brazil 29.159847 Brazil draw 1\n", "12 Germany USA 92.599808 Germany Germany 3\n", "13 Ghana Portugal 49.394952 Portugal Portugal 0\n", "14 France Ecuador 84.520092 France draw 1\n", "15 Uruguay Italy 24.142942 Italy Uruguay 3\n", "16 Spain Australia 92.766946 Spain Spain 3\n", "17 Chile Netherlands 36.429691 Netherlands Netherlands 0\n", "18 Portugal USA 73.875672 Portugal draw 1\n", "19 Ghana Germany 14.933490 Germany draw 1\n", "20 France Switzerland 84.456472 France France 3\n", "21 England Uruguay 61.402920 England Uruguay 0\n", "22 Netherlands Australia 90.897030 Netherlands Netherlands 3\n", "23 Mexico Brazil 18.745963 Brazil draw 1\n", "24 USA Ghana 29.331909 Ghana USA 3\n", "25 Portugal Germany 15.955012 Germany Germany 0\n", "26 Japan C\u00f4te d'Ivoire 41.934990 C\u00f4te d'Ivoire C\u00f4te d'Ivoire 0\n", "27 Italy England 73.812329 Italy Italy 3\n", "28 Netherlands Spain 49.682892 Spain Netherlands 3\n", "29 Spain Netherlands 53.295771 Spain Spain 3\n", "30 Germany Uruguay 78.982270 Germany Germany 3\n", "31 Spain Germany 41.830024 Germany Spain 3\n", "32 Spain Paraguay 89.878445 Spain Spain 3\n", "33 Germany Argentina 45.202720 Argentina Germany 3\n", "34 Brazil Netherlands 63.908731 Brazil Netherlands 0\n", "35 Portugal Spain 20.960764 Spain Spain 0\n", "36 Japan Paraguay 63.519259 Japan draw 1\n", "37 Mexico Argentina 27.152356 Argentina Argentina 0\n", "38 England Germany 17.248992 Germany Germany 0\n", "39 Ghana USA 70.933824 Ghana Ghana 3\n", "40 Brazil Portugal 88.601369 Brazil draw 1\n", "41 Germany Ghana 89.953475 Germany Germany 3\n", "42 France Italy 39.809571 Italy draw 1\n", "43 Portugal Germany 12.435939 Germany Germany 0" ] } ], "prompt_number": 15 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's look at the stats for the teams in the final. We can compare them by eyeball to see which one we think will win:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "final = wc_power_data[wc_power_data['matchid'] == '731830']\n", "final" ], "language": "python", "metadata": {}, "outputs": [ { "html": [ "
\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
matchidteamidop_teamidcompetitionidseasonidis_hometeam_nameop_team_nametimestampavg_pointsavg_goalsop_avg_goalspass_70pass_80op_pass_70op_pass_80expected_goalsop_expected_goalspassesbad_passespass_ratiocornersfoulscardsshotsop_passesop_bad_passesop_cornersop_foulsop_cardsop_shotsgoals_op_ratioshots_op_ratiopass_op_ratiopower_points
0 731830 632 357 4 2013 0.7 Argentina Germany 2014-07-13 20:00:00.000000 2.666667 1.333333 0.500000 0.531302 0.173338 0.410517 0.141505 1.27091 0.703133 4.643395 0.954973 0.823198 0.070890 0.162126 1.000000 0.163520 3.179610 0.924281 0.047631 0.096118 1.833333 0.118528 1.083333 1.475854 1.110942 0.792664
1 731830 357 632 4 2013 0.2 Germany Argentina 2014-07-13 20:00:00.000000 2.666667 2.833333 0.666667 0.808822 0.248188 0.427337 0.166366 2.11032 1.000115 5.643403 1.030747 0.837985 0.053194 0.152780 0.666667 0.147986 3.623266 1.008787 0.047043 0.122868 1.166667 0.132571 2.666667 1.525363 1.086513 1.000000
\n", "
" ], "metadata": {}, "output_type": "pyout", "prompt_number": 16, "text": [ " matchid teamid op_teamid competitionid seasonid is_home team_name op_team_name timestamp avg_points avg_goals op_avg_goals pass_70 pass_80 op_pass_70 op_pass_80 expected_goals op_expected_goals passes bad_passes pass_ratio corners fouls cards shots op_passes op_bad_passes op_corners op_fouls op_cards op_shots goals_op_ratio shots_op_ratio pass_op_ratio power_points\n", "0 731830 632 357 4 2013 0.7 Argentina Germany 2014-07-13 20:00:00.000000 2.666667 1.333333 0.500000 0.531302 0.173338 0.410517 0.141505 1.27091 0.703133 4.643395 0.954973 0.823198 0.070890 0.162126 1.000000 0.163520 3.179610 0.924281 0.047631 0.096118 1.833333 0.118528 1.083333 1.475854 1.110942 0.792664\n", "1 731830 357 632 4 2013 0.2 Germany Argentina 2014-07-13 20:00:00.000000 2.666667 2.833333 0.666667 0.808822 0.248188 0.427337 0.166366 2.11032 1.000115 5.643403 1.030747 0.837985 0.053194 0.152780 0.666667 0.147986 3.623266 1.008787 0.047043 0.122868 1.166667 0.132571 2.666667 1.525363 1.086513 1.000000" ] } ], "prompt_number": 16 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now let's look at the games that made up the decisions:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "op = game_summaries\n", "\n", "def countryStats(d, name):\n", " pred = d['team_name'] == name\n", " return d[pred]\n", "\n", "fr = countryStats(op, 'France')\n", "ge = countryStats(op, 'Germany')\n", "ar = countryStats(op, 'Argentina')\n", "br = countryStats(op, 'Brazil')\n", "ne = countryStats(op, 'Netherlands')\n", "ge[:6]" ], "language": "python", "metadata": {}, "outputs": [ { "html": [ "
\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
matchidteamidpassesbad_passespass_ratiocornersfoulscardsgoalsshotsis_hometeam_namepass_80pass_70expected_goalson_targetlengthop_teamidop_passesop_bad_passesop_pass_ratioop_cornersop_foulsop_cardsop_goalsop_shotsop_team_nameop_pass_80op_pass_70op_expected_goalsop_on_targetcompetitionidseasonidshots_op_ratiogoals_op_ratiopass_op_ratiopointstimestamp
3 731827 357 5.098901 0.989011 0.836036 0.054945 0.120879 0 7 0.153846 0 Germany 0.384615 0.912088 3.358086 0.109890 91 614 4.494505 0.934066 0.826263 0.076923 0.153846 1 1 0.197802 Brazil 0.318681 0.736264 1.846013 0.087912 4 2013 0.777778 7 1.011828 3 1404859864586
29 731824 357 3.670213 1.223404 0.748373 0.031915 0.159574 2 1 0.095745 0 Germany 0.095745 0.489362 0.937786 0.031915 94 368 3.627660 1.170213 0.754425 0.053191 0.191489 0 0 0.138298 France 0.234043 0.382979 1.199728 0.053191 4 2013 0.692308 1 0.991978 3 1404500184917
38 731820 357 6.000000 1.147541 0.838488 0.081967 0.163934 1 2 0.229508 1 Germany 0.213115 0.713115 3.414730 0.098361 122 1215 2.467213 1.254098 0.661538 0.032787 0.090164 1 1 0.081967 Algeria 0.057377 0.172131 1.044178 0.032787 4 2013 2.800000 2 1.267482 3 1404171338462
74 731811 357 7.574468 0.861702 0.896725 0.031915 0.159574 1 1 0.138298 0 Germany 0.308511 1.180851 1.156981 0.063830 94 596 3.526882 0.795699 0.813896 0.021505 0.096774 2 0 0.043011 USA 0.118280 0.376344 0.000000 0.000000 4 2013 3.215426 1 1.101769 3 1403808875580
111 731795 357 5.989362 1.127660 0.840299 0.074468 0.180851 0 2 0.127660 1 Germany 0.223404 0.755319 1.511423 0.042553 94 1219 3.425532 1.074468 0.759434 0.031915 0.117021 1 2 0.191489 Ghana 0.148936 0.478723 1.337382 0.063830 4 2013 0.666667 1 1.106480 1 1403387662729
143 731779 357 5.527473 0.835165 0.867990 0.043956 0.131868 0 4 0.142857 1 Germany 0.263736 0.802198 2.282916 0.054945 91 359 4.197802 0.824176 0.834973 0.065934 0.087912 2 0 0.142857 Portugal 0.120879 0.417582 0.573390 0.032967 4 2013 1.000000 4 1.039543 3 1402944761781
\n", "
" ], "metadata": {}, "output_type": "pyout", "prompt_number": 17, "text": [ " matchid teamid passes bad_passes pass_ratio corners fouls cards goals shots is_home team_name pass_80 pass_70 expected_goals on_target length op_teamid op_passes op_bad_passes op_pass_ratio op_corners op_fouls op_cards op_goals op_shots op_team_name op_pass_80 op_pass_70 op_expected_goals op_on_target competitionid seasonid shots_op_ratio goals_op_ratio pass_op_ratio points timestamp\n", "3 731827 357 5.098901 0.989011 0.836036 0.054945 0.120879 0 7 0.153846 0 Germany 0.384615 0.912088 3.358086 0.109890 91 614 4.494505 0.934066 0.826263 0.076923 0.153846 1 1 0.197802 Brazil 0.318681 0.736264 1.846013 0.087912 4 2013 0.777778 7 1.011828 3 1404859864586\n", "29 731824 357 3.670213 1.223404 0.748373 0.031915 0.159574 2 1 0.095745 0 Germany 0.095745 0.489362 0.937786 0.031915 94 368 3.627660 1.170213 0.754425 0.053191 0.191489 0 0 0.138298 France 0.234043 0.382979 1.199728 0.053191 4 2013 0.692308 1 0.991978 3 1404500184917\n", "38 731820 357 6.000000 1.147541 0.838488 0.081967 0.163934 1 2 0.229508 1 Germany 0.213115 0.713115 3.414730 0.098361 122 1215 2.467213 1.254098 0.661538 0.032787 0.090164 1 1 0.081967 Algeria 0.057377 0.172131 1.044178 0.032787 4 2013 2.800000 2 1.267482 3 1404171338462\n", "74 731811 357 7.574468 0.861702 0.896725 0.031915 0.159574 1 1 0.138298 0 Germany 0.308511 1.180851 1.156981 0.063830 94 596 3.526882 0.795699 0.813896 0.021505 0.096774 2 0 0.043011 USA 0.118280 0.376344 0.000000 0.000000 4 2013 3.215426 1 1.101769 3 1403808875580\n", "111 731795 357 5.989362 1.127660 0.840299 0.074468 0.180851 0 2 0.127660 1 Germany 0.223404 0.755319 1.511423 0.042553 94 1219 3.425532 1.074468 0.759434 0.031915 0.117021 1 2 0.191489 Ghana 0.148936 0.478723 1.337382 0.063830 4 2013 0.666667 1 1.106480 1 1403387662729\n", "143 731779 357 5.527473 0.835165 0.867990 0.043956 0.131868 0 4 0.142857 1 Germany 0.263736 0.802198 2.282916 0.054945 91 359 4.197802 0.824176 0.834973 0.065934 0.087912 2 0 0.142857 Portugal 0.120879 0.417582 0.573390 0.032967 4 2013 1.000000 4 1.039543 3 1402944761781" ] } ], "prompt_number": 17 }, { "cell_type": "markdown", "metadata": {}, "source": [ "OK now that we've looked at the data every which way possible, let's predict the final results:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "wc_pred[~(wc_pred['points'] >= 0)][[\n", " 'team_name', 'op_team_name', 'predicted']]" ], "language": "python", "metadata": {}, "outputs": [ { "html": [ "
\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
team_nameop_team_namepredicted
0 Argentina Germany 43.224980
1 Netherlands Brazil 37.168067
\n", "
" ], "metadata": {}, "output_type": "pyout", "prompt_number": 18, "text": [ " team_name op_team_name predicted\n", "0 Argentina Germany 43.224980\n", "1 Netherlands Brazil 37.168067" ] } ], "prompt_number": 18 }, { "cell_type": "code", "collapsed": false, "input": [], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 18 } ], "metadata": {} } ] }