{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Are the Ratings Predictions of Goals Scored?","metadata":{}},{"cell_type":"markdown","source":"In this competition of predicting football match results primarily based on the data of past ten matches of the two sides, the rating feature remains a mystery for many participants. Octosport, the host of this competition, states in the description of the dataset that:\n\n> The rating features are calculated by Octosport. Ratings are meant to give information on the team's relative strength for a given match. For instance, we would expect that a team with a rating of 10.5 beats a team with a rating of 2.3.\n\nIn a [discussion](https://www.kaggle.com/code/curiosityquotient/investigation-into-rating-feature-octosport/comments), it is further revealed that:\n\n> ...ratings are pairwise and can be seen as a combination of a team's offensive strength and an opponent's defensive strength which is why they are varying in time depending on the fixture.\n\nA research of football prediction models shows that this kind of formulation may be more likely associated with the Poisson models, which use the assessments of the offensive and defensive strength to come up with the expected mean goals of each side, then use Poisson distribution to turn out the probabilities of every possible score lines and thereby the probabilities of home win, draw or away win. Do the rating features here operate in this way? Let's see what the data tell us.","metadata":{}},{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2022-04-25T18:59:40.154054Z","iopub.execute_input":"2022-04-25T18:59:40.154482Z","iopub.status.idle":"2022-04-25T18:59:40.186974Z","shell.execute_reply.started":"2022-04-25T18:59:40.154372Z","shell.execute_reply":"2022-04-25T18:59:40.186016Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#import modules\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nimport plotly.express as px\n\n%matplotlib inline\n\n#loading the data\ntrain = pd.read_csv('/kaggle/input/football-match-probability-prediction/train.csv')\ntest = pd.read_csv('/kaggle/input/football-match-probability-prediction/test.csv')","metadata":{"execution":{"iopub.status.busy":"2022-04-25T18:59:40.188354Z","iopub.execute_input":"2022-04-25T18:59:40.188866Z","iopub.status.idle":"2022-04-25T18:59:52.012176Z","shell.execute_reply.started":"2022-04-25T18:59:40.188833Z","shell.execute_reply":"2022-04-25T18:59:52.011083Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"In this analysis I only investigate the train set. In the original dataset, the ratings of past ten matches of the home team and its opponents and the away team and its opponents are listed in 40 columns in a 'wide' form. In this investigation of the relationship between ratings and final score lines, it may be more convenient to 'stack' them into a 'long' form.","metadata":{}},{"cell_type":"code","source":"history_columns=['id']+[col for col in train.columns if col[-1].isdigit()]","metadata":{"execution":{"iopub.status.busy":"2022-04-25T18:59:52.013588Z","iopub.execute_input":"2022-04-25T18:59:52.013821Z","iopub.status.idle":"2022-04-25T18:59:52.019326Z","shell.execute_reply.started":"2022-04-25T18:59:52.013793Z","shell.execute_reply":"2022-04-25T18:59:52.018419Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"stubname=['home_team_history_match_date',\n           'home_team_history_is_play_home',\n           'home_team_history_is_cup',\n           'home_team_history_goal',\n           'home_team_history_opponent_goal',\n           'home_team_history_rating',\n           'home_team_history_opponent_rating',\n           'home_team_history_coach',\n           'home_team_history_league_id',\n           'away_team_history_match_date',\n           'away_team_history_is_play_home',\n           'away_team_history_is_cup',\n           'away_team_history_goal',\n           'away_team_history_opponent_goal',\n           'away_team_history_rating',\n           'away_team_history_opponent_rating',\n           'away_team_history_coach',\n           'away_team_history_league_id']\n\nhistory_train=pd.wide_to_long(train[history_columns], stubnames=stubname, i='id', j='match', sep='_')\nhistory_train=history_train.reset_index().sort_values(by=['id','match'])\nhistory_train","metadata":{"execution":{"iopub.status.busy":"2022-04-25T18:59:52.022021Z","iopub.execute_input":"2022-04-25T18:59:52.022280Z","iopub.status.idle":"2022-04-25T19:00:18.780364Z","shell.execute_reply.started":"2022-04-25T18:59:52.022250Z","shell.execute_reply":"2022-04-25T19:00:18.779339Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"history_train.duplicated(subset=stubname).value_counts()","metadata":{"execution":{"iopub.status.busy":"2022-04-25T19:00:18.781668Z","iopub.execute_input":"2022-04-25T19:00:18.781972Z","iopub.status.idle":"2022-04-25T19:00:20.237858Z","shell.execute_reply.started":"2022-04-25T19:00:18.781908Z","shell.execute_reply":"2022-04-25T19:00:20.236374Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"I originally thought that there might be many duplicated records, but the counts do not go this way. So I leave them as they are.","metadata":{}},{"cell_type":"code","source":"rating_columns=[col for col in history_train if 'rating' in col]\nhistory_train[rating_columns].describe()","metadata":{"execution":{"iopub.status.busy":"2022-04-25T19:52:03.909490Z","iopub.execute_input":"2022-04-25T19:52:03.909787Z","iopub.status.idle":"2022-04-25T19:52:04.253900Z","shell.execute_reply.started":"2022-04-25T19:52:03.909758Z","shell.execute_reply":"2022-04-25T19:52:04.252387Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The distributions of ratings are very skewed, ranging from 0.21 to more than 57, with mean about 6.5 and medium about 6.","metadata":{}},{"cell_type":"code","source":"# adding features for rating gaps and goal differences\nhistory_train['home_rating_gap']= history_train['home_team_history_rating']-history_train['home_team_history_opponent_rating']\nhistory_train['home_goal_difference']= history_train['home_team_history_goal']-history_train['home_team_history_opponent_goal']\nhistory_train['away_rating_gap']= history_train['away_team_history_rating']-history_train['away_team_history_opponent_rating']\nhistory_train['away_goal_difference']= history_train['away_team_history_goal']-history_train['away_team_history_opponent_goal']","metadata":{"execution":{"iopub.status.busy":"2022-04-25T19:00:20.500549Z","iopub.execute_input":"2022-04-25T19:00:20.501165Z","iopub.status.idle":"2022-04-25T19:00:20.522448Z","shell.execute_reply.started":"2022-04-25T19:00:20.501133Z","shell.execute_reply":"2022-04-25T19:00:20.521885Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sns.scatterplot(data=history_train, x='home_rating_gap', y='home_goal_difference')","metadata":{"execution":{"iopub.status.busy":"2022-04-25T19:00:20.525228Z","iopub.execute_input":"2022-04-25T19:00:20.525586Z","iopub.status.idle":"2022-04-25T19:00:25.538483Z","shell.execute_reply.started":"2022-04-25T19:00:20.525554Z","shell.execute_reply":"2022-04-25T19:00:25.537588Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"A scatter plot on rating gaps and also the final goal differences between the home teams and their past opponents generally form a cloud of tilted ellipse. There is a general trend that a large rating gap results in a large goal difference, but there are also a wide range of possibilities, the teams with equal rating can end up in draws, a win or a lose up to 10 goals in margin. A similar pattern exists for the away teams and their opponents.","metadata":{}},{"cell_type":"code","source":"sns.scatterplot(data=history_train, x='away_rating_gap', y=history_train.away_goal_difference)","metadata":{"execution":{"iopub.status.busy":"2022-04-25T19:00:25.542421Z","iopub.execute_input":"2022-04-25T19:00:25.542988Z","iopub.status.idle":"2022-04-25T19:00:30.244840Z","shell.execute_reply.started":"2022-04-25T19:00:25.542950Z","shell.execute_reply":"2022-04-25T19:00:30.244155Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Let's go further in the relationship between rating and goals scored in the past matches, this time only for the home team. A scatter plot again shows a wide range of possibilities. A team with a rating of 5 can end up scoring 0 to about 7 goals, while a team with a rating of 30 can end up scoring 0 to more than 20 goals. The rating and goals scored of the opponent teams has a similar distribution.","metadata":{}},{"cell_type":"code","source":"sns.jointplot(data=history_train, x='home_team_history_rating', y='home_team_history_goal')","metadata":{"execution":{"iopub.status.busy":"2022-04-25T19:00:30.246052Z","iopub.execute_input":"2022-04-25T19:00:30.247065Z","iopub.status.idle":"2022-04-25T19:00:38.270393Z","shell.execute_reply.started":"2022-04-25T19:00:30.247015Z","shell.execute_reply":"2022-04-25T19:00:38.269588Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sns.jointplot(data=history_train, x='home_team_history_opponent_rating', y='home_team_history_opponent_goal')","metadata":{"execution":{"iopub.status.busy":"2022-04-25T19:00:38.271620Z","iopub.execute_input":"2022-04-25T19:00:38.271817Z","iopub.status.idle":"2022-04-25T19:00:47.348711Z","shell.execute_reply.started":"2022-04-25T19:00:38.271789Z","shell.execute_reply":"2022-04-25T19:00:47.347661Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"On the surface the wide range of possibilities may make one think the rating only have limited predictive power. But if we look into the 'cross sections', i.e. observing the distributions of goals scored for a given value of rating, it is more apparent that rating makes a difference. As the values of ratings are floats, we can only make observations by binning. In this investigation I use plus and minus 0.5 as the lower and upper limits.","metadata":{}},{"cell_type":"code","source":"r5=history_train.loc[(history_train.home_team_history_rating > 4.5) & (history_train.home_team_history_rating <5.5)].home_team_history_goal\nsns.displot(data=r5, discrete=True)","metadata":{"execution":{"iopub.status.busy":"2022-04-25T19:00:47.350669Z","iopub.execute_input":"2022-04-25T19:00:47.350967Z","iopub.status.idle":"2022-04-25T19:00:47.952387Z","shell.execute_reply.started":"2022-04-25T19:00:47.350938Z","shell.execute_reply":"2022-04-25T19:00:47.951237Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"So teams with a rating of 5 (binned as having ratings between 4.5 and 5.5) mostly score 1 or 0 goal. Teams with a rating of 10 mostly score 1 to 2 goals.","metadata":{}},{"cell_type":"code","source":"r10=history_train.loc[(history_train.home_team_history_rating > 9.5) & (history_train.home_team_history_rating <10.5)].home_team_history_goal\nsns.displot(data=r10, discrete=True)","metadata":{"execution":{"iopub.status.busy":"2022-04-25T19:00:47.954096Z","iopub.execute_input":"2022-04-25T19:00:47.954406Z","iopub.status.idle":"2022-04-25T19:00:48.197509Z","shell.execute_reply.started":"2022-04-25T19:00:47.954371Z","shell.execute_reply":"2022-04-25T19:00:48.196649Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"r15=history_train.loc[(history_train.home_team_history_rating > 14.5) & (history_train.home_team_history_rating <15.5)].home_team_history_goal\nsns.displot(data=r15, discrete=True)","metadata":{"execution":{"iopub.status.busy":"2022-04-25T19:00:48.199000Z","iopub.execute_input":"2022-04-25T19:00:48.199368Z","iopub.status.idle":"2022-04-25T19:00:48.478831Z","shell.execute_reply.started":"2022-04-25T19:00:48.199338Z","shell.execute_reply":"2022-04-25T19:00:48.477686Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"And teams with a rating of 15 mostly score 2 or 3 goals, teams with a rating of 20 mostly score 3 or 2 goals. The distributions of goals scored look like Poisson distribution, though in the case of 20 it is less well shaped, apparently due to the smaller size of sample.","metadata":{}},{"cell_type":"code","source":"r20=history_train.loc[(history_train.home_team_history_rating > 19.5) & (history_train.home_team_history_rating <20.5)].home_team_history_goal\nsns.displot(data=r20, discrete=True)","metadata":{"execution":{"iopub.status.busy":"2022-04-25T19:00:48.480356Z","iopub.execute_input":"2022-04-25T19:00:48.480558Z","iopub.status.idle":"2022-04-25T19:00:48.770221Z","shell.execute_reply.started":"2022-04-25T19:00:48.480531Z","shell.execute_reply":"2022-04-25T19:00:48.769440Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"If the goals scored form a Poisson distribution, its single parameter lambda can be interpreted as the expected goals scored given a rating. So let's see the mean goals scored for each bin of rating...","metadata":{}},{"cell_type":"code","source":"rating_goal_means=[history_train.loc[(history_train.home_team_history_rating > i-0.5) \n                                     & (history_train.home_team_history_rating < i+0.5)].home_team_history_goal.mean() for i in range (1,21)]","metadata":{"execution":{"iopub.status.busy":"2022-04-25T19:00:48.771384Z","iopub.execute_input":"2022-04-25T19:00:48.771710Z","iopub.status.idle":"2022-04-25T19:00:49.141993Z","shell.execute_reply.started":"2022-04-25T19:00:48.771684Z","shell.execute_reply":"2022-04-25T19:00:49.141296Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"px.scatter(x=np.linspace(1,20,20), y=rating_goal_means, trendline=\"ols\", labels={\n                     \"x\": \"rating\",\n                     \"y\": \"mean goals\"})","metadata":{"execution":{"iopub.status.busy":"2022-04-25T19:00:49.143316Z","iopub.execute_input":"2022-04-25T19:00:49.144190Z","iopub.status.idle":"2022-04-25T19:00:51.201276Z","shell.execute_reply.started":"2022-04-25T19:00:49.144152Z","shell.execute_reply":"2022-04-25T19:00:51.200563Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**The means goals scored nearly fall into a straight line! What's more interesting, we can say in a rough way that each additional 5 points increase in rating results in one more expected goal!!**","metadata":{}},{"cell_type":"code","source":"opponent_goal_means=[history_train.loc[(history_train.home_team_history_opponent_rating > i-0.5) \n                                       & (history_train.home_team_history_opponent_rating < i+0.5)].home_team_history_opponent_goal.mean() \n                     for i in range (1,21)]\npx.scatter(x=np.linspace(1,20,20), y=opponent_goal_means, trendline=\"ols\", labels={\n                     \"x\": \"rating\",\n                     \"y\": \"mean goals\"})","metadata":{"execution":{"iopub.status.busy":"2022-04-25T19:00:51.202426Z","iopub.execute_input":"2022-04-25T19:00:51.202644Z","iopub.status.idle":"2022-04-25T19:00:51.620051Z","shell.execute_reply.started":"2022-04-25T19:00:51.202616Z","shell.execute_reply":"2022-04-25T19:00:51.619005Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The mean goals scored by the opponents also have a similar pattern.","metadata":{}},{"cell_type":"code","source":"home_goal_means=[history_train.loc[(history_train.home_team_history_is_play_home==1) \n                                    & (history_train.home_team_history_rating > i-0.5) \n                                    & (history_train.home_team_history_rating < i+0.5)].home_team_history_goal.mean() \n                  for i in range (1,21)]\naway_goal_means=[history_train.loc[(history_train.home_team_history_is_play_home==0) \n                                   & (history_train.home_team_history_rating > i-0.5) \n                                   & (history_train.home_team_history_rating < i+0.5)].home_team_history_goal.mean() \n                 for i in range (1,21)]","metadata":{"execution":{"iopub.status.busy":"2022-04-25T19:00:51.621937Z","iopub.execute_input":"2022-04-25T19:00:51.622277Z","iopub.status.idle":"2022-04-25T19:00:52.270766Z","shell.execute_reply.started":"2022-04-25T19:00:51.622239Z","shell.execute_reply":"2022-04-25T19:00:52.269481Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"fig=px.scatter(x=np.linspace(1,20,20), y=home_goal_means, trendline=\"ols\", labels={\n                     \"x\": \"rating\",\n                     \"y\": \"mean goals\"})\nfig.add_scatter(x=np.linspace(1,20,20), y=away_goal_means, mode=\"markers\",\n                marker=dict(size=8, color=\"Red\"),\n               name=\"play away\")\nfig.show()","metadata":{"execution":{"iopub.status.busy":"2022-04-25T21:31:07.914894Z","iopub.execute_input":"2022-04-25T21:31:07.915763Z","iopub.status.idle":"2022-04-25T21:31:08.005271Z","shell.execute_reply.started":"2022-04-25T21:31:07.915722Z","shell.execute_reply":"2022-04-25T21:31:08.004168Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"How about home court advantage? We can say the mean goals scored for a team playing home or playing away for a given rating have the same underlying pattern, but the deviances are more notable in higher ratings when playing away, perhaps due to the fact that teams playing away rarely have high ratings, and thus have far smaller samples for such groups. And distributions of ratings show that teams playing home generally do have higher ratings than playing away.","metadata":{}},{"cell_type":"code","source":"play_home_ratings=history_train.loc[history_train.home_team_history_is_play_home==1].home_team_history_rating.dropna()\nplay_home_ratings.describe()","metadata":{"execution":{"iopub.status.busy":"2022-04-25T19:00:52.341822Z","iopub.execute_input":"2022-04-25T19:00:52.342091Z","iopub.status.idle":"2022-04-25T19:00:52.454983Z","shell.execute_reply.started":"2022-04-25T19:00:52.342060Z","shell.execute_reply":"2022-04-25T19:00:52.453688Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"play_away_ratings=history_train.loc[history_train.home_team_history_is_play_home==0].home_team_history_rating.dropna()\nplay_away_ratings.describe()","metadata":{"execution":{"iopub.status.busy":"2022-04-25T19:00:52.456659Z","iopub.execute_input":"2022-04-25T19:00:52.456882Z","iopub.status.idle":"2022-04-25T19:00:52.573486Z","shell.execute_reply.started":"2022-04-25T19:00:52.456859Z","shell.execute_reply":"2022-04-25T19:00:52.571880Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from plotly.subplots import make_subplots\nfig2 = make_subplots(rows=2, subplot_titles=['play home','play away'])\nfig2.add_histogram(x=play_home_ratings, row=1, col=1) \nfig2.add_histogram(x=play_away_ratings, row=2, col=1) \nfig2.show()","metadata":{"execution":{"iopub.status.busy":"2022-04-25T19:11:45.225525Z","iopub.execute_input":"2022-04-25T19:11:45.225789Z","iopub.status.idle":"2022-04-25T19:11:45.486453Z","shell.execute_reply.started":"2022-04-25T19:11:45.225765Z","shell.execute_reply":"2022-04-25T19:11:45.485161Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}