├── notebooks ├── footyscripts │ ├── __init__.py │ └── footyviz.py ├── images │ └── pitch-lines.png ├── README.md ├── how to get full play-by-play data for the WC2014.ipynb └── player value appreciation in Portuguese vs. English clubs.ipynb ├── .gitignore ├── README ├── datasets ├── james-vs-barcelona-player-data.csv ├── GER_[3]-0_SRB_player_data.csv ├── BRA_0-[5]_GER_player_data.csv ├── AJA_[1]-0_PSV_player_data.csv └── open_xt_12x8_v1.json └── scripts └── footyviz.py /notebooks/footyscripts/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .ipynb_checkpoints 2 | notebooks/output/* 3 | notebooks/cache/* 4 | .vscode 5 | *.pyc 6 | datasets/external/* -------------------------------------------------------------------------------- /notebooks/images/pitch-lines.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjtavares/football-crunching/HEAD/notebooks/images/pitch-lines.png -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | This repository was created due to requests to share the code and data used to make a visualization I shared on reddit (/r/soccer and /r/dataisbeautiful). Hopefully, it will become a usefull repository of analysis and data about The Beautiful Game. -------------------------------------------------------------------------------- /datasets/james-vs-barcelona-player-data.csv: -------------------------------------------------------------------------------- 1 | player,team,num,name 2 | 11,defense,, 3 | 12,defense,, 4 | 13,defense,, 5 | 14,defense,, 6 | 15,defense,, 7 | 16,defense,, 8 | 17,defense,, 9 | 18,defense,, 10 | 19,defense,, 11 | 20,attack,, 12 | 21,attack,, 13 | 22,attack,, 14 | 23,attack,, 15 | 24,attack,, 16 | 25,attack,, 17 | -------------------------------------------------------------------------------- /datasets/GER_[3]-0_SRB_player_data.csv: -------------------------------------------------------------------------------- 1 | player,coords,player_num,player_obj,team,num,name,edgecolor,bgcolor 2 | 12,"(136.7259154955094, 166.22695610141977)",10.0,12,attack,,,black,white 3 | 1583,"(343.77343151714865, 51.91094646567434)",2.0,13,attack,,,black,white 4 | 2248,"(148.21786597279606, 166.84364103805248)",,14,defense,,,white,red 5 | 3802,"(142.8434015524876, 191.71472194218805)",,15,defense,,,white,red 6 | 4563,"(222.12243059774264, 132.0547213685634)",,16,defense,,,white,red 7 | 5867,"(312.24806696292546, 56.81699122099323)",,17,defense,,,white,red 8 | 6501,"(294.21750336437725, 39.77842997777177)",,18,defense,,,white,red 9 | 7502,"(305.08243042847624, 27.46527258165132)",,19,attack,,,black,white 10 | 9617,"(180.0, 266.0)",,20,attack,,,black,white 11 | 10678,"(185.09546430530065, 179.4068611404931)",,21,attack,,,black,white 12 | 367,"(389.7102803738318, 217.58878504672896)",,367,attack,,,black,white 13 | 2256,"(352.65625, 48.875)",,2256,defense,,,white,red 14 | 3652,"(21.0, 209.0)",,3652,defense,,,white,red 15 | 7300,"(525.9540131642823, 134.37566110704734)",,7300,attack,,,black,white 16 | 7301,"(467.872414972532, 132.18923619925005)",,7301,defense,,,white,red 17 | 7302,"(515.5298219098647, 80.98513960333622)",,7302,defense,,,white,red 18 | 8695,"(356.1584373638937, 121.37524525520615)",,8695,attack,,,black,white 19 | -------------------------------------------------------------------------------- /datasets/BRA_0-[5]_GER_player_data.csv: -------------------------------------------------------------------------------- 1 | player,coords,player_num,player_obj,team,num,name,edgecolor,bgcolor 2 | 12,"(390.82329814922076, 230.3681266177904)",5.0,12,attack,,,red,black 3 | 1923,"(192.70181613677647, 287.0911467596564)",6.0,1923,attack,,,red,black 4 | 3235,"(187.2173482005435, 323.5288842347714)",8.0,3235,attack,,,red,black 5 | 6439,"(218.60259895696848, 226.14081165461812)",,6439,attack,,,red,black 6 | 7600,"(174.46875, 274.625)",,7600,defense,,,blue,yellow 7 | 14761,"(215.71575859162712, 215.28004377345493)",,14761,defense,,,blue,yellow 8 | 16656,"(202.12091085738695, 337.46147632768816)",,16656,defense,,,blue,yellow 9 | 18912,"(190.5326064508669, 132.68650155862397)",,18912,defense,,,blue,yellow 10 | 21118,"(236.0, 151.0)",,21118,defense,,,blue,yellow 11 | 23899,"(342.6033758740193, 193.39115877679416)",,23899,defense,,,blue,yellow 12 | 25686,"(398.52750470586994, 214.27410783699221)",,25686,defense,,,blue,yellow 13 | 37572,"(287.10170479200815, 271.7485391685611)",,37572,attack,,,red,black 14 | 42428,"(198.4123749772249, 33.94978367111291)",,42428,attack,,,red,black 15 | 44136,"(278.972381708952, 56.237396503233526)",,44136,defense,,,blue,yellow 16 | 44137,"(341.40901702115536, 286.95268565748404)",,44137,defense,,,blue,yellow 17 | 48457,"(42.0, 246.0)",,48457,defense,,,blue,yellow 18 | 53809,"(350.0, 62.0)",,53809,attack,,,red,black 19 | 53810,"(429.0, 119.0)",,53810,attack,,,red,black 20 | 53811,"(356.0, 213.0)",,53811,attack,,,red,black 21 | 53812,"(359.0, 325.0)",,53812,attack,,,red,black 22 | 58367,"(265.0889494896123, 260.6871208118323)",,58367,defense,,,blue,yellow 23 | -------------------------------------------------------------------------------- /datasets/AJA_[1]-0_PSV_player_data.csv: -------------------------------------------------------------------------------- 1 | player,coords,player_num,player_obj,team,num,name,edgecolor,bgcolor 2 | 12,"(100.87270667830711, 358.9748447267472)",6.0,12,attack,,,red,white 3 | 2226,"(133.86573275787046, 251.93117519975567)",25.0,2226,attack,,,red,white 4 | 2479,"(146.78908512980206, 436.60298289276943)",10.0,2479,attack,,,red,white 5 | 2724,"(304.7370770398743, 453.89102084058067)",3.0,2724,attack,,,red,white 6 | 3724,"(336.67233172282727, 409.580526187905)",,3724,attack,,,red,white 7 | 4598,"(171.2732462779248, 309.6478339555086)",,4598,defense,,,orange,darkblue 8 | 4599,"(105.08270317163756, 310.1181738180273)",,4599,defense,,,orange,darkblue 9 | 4600,"(141.823473755787, 373.3303688574185)",,4600,defense,,,orange,darkblue 10 | 4601,"(194.04748324585225, 430.7146940198011)",,4601,defense,,,orange,darkblue 11 | 7745,"(184.70756950369795, 372.7604552055377)",,7745,defense,,,orange,darkblue 12 | 10076,"(271.3344218055786, 431.20759895156465)",,10076,defense,,,orange,darkblue 13 | 11521,"(140.0, 192.0)",,11521,defense,,,orange,darkblue 14 | 11522,"(178.5973943587801, 145.000517500412)",,11522,attack,,,red,white 15 | 14190,"(291.6829268292683, 201.5609756097561)",,14190,defense,,,orange,darkblue 16 | 14191,"(265.9268292682927, 349.8292682926829)",,14191,defense,,,orange,darkblue 17 | 14192,"(244.02439024390245, 334.3414634146341)",,14192,attack,,,red,white 18 | 14193,"(368.2439024390244, 207.70731707317074)",,14193,attack,,,red,white 19 | 14194,"(324.5609756097561, 189.8048780487805)",,14194,attack,,,red,white 20 | 16101,"(364.5121951219512, 301.7317073170732)",,16101,attack,,,red,white 21 | 16102,"(315.0, 429.0)",,16102,defense,,,orange,darkblue 22 | 19091,"(20.448323588800143, 260.14957692856564)",,19091,defense,,,orange,darkblue 23 | -------------------------------------------------------------------------------- /notebooks/README.md: -------------------------------------------------------------------------------- 1 | List of Notebooks 2 | ----------------- 3 | 4 | # Getting and Working with Data 5 | 6 | * [how to get full world cup 2014 play-by-play data](http://nbviewer.ipython.org/github/rjtavares/football-crunching/blob/master/notebooks/how%20to%20get%20full%20play-by-play%20data%20for%20the%20WC2014.ipynb): Good football data is hard to come by. Basic stat counts are easily available, but full play data (i.e. a play broken down in its individual components: interceptions and tackles, runs, passes and shots, etc.) is very rare. And that's the most important unit in a team sport like football. So imagine my surprise and great joy when I came across a fantastic dataset of full play-by-play data for all World Cup matches. 7 | 8 | * [working with positional data](working%20with%20positional%20data.ipynb): This notebook shows how to plot positional data using matplotlib and some things you can do with full positional data that you can't with event based data. 9 | 10 | * [using Voronoi Diagrams](using%20voronoi%20diagrams.ipynb): Applied to football, a Voronoi diagram will partition the pitch into zones that have a single player as the closest to each point in that zone. This notebook shows how you can, having positional data, calculate and plot Voronoi Diagrams. 11 | 12 | 13 | # Analysis 14 | 15 | * [an exploratory analysis of the world cup final](http://nbviewer.ipython.org/github/rjtavares/football-crunching/blob/master/notebooks/an%20exploratory%20data%20analysis%20of%20the%20world%20cup%20final.ipynb): This notebook shows how you can use play-by-play data to analyse a football match, showing custom measures and visualizations to better understand the sport. 16 | 17 | 18 | # Metrics and Statistics 19 | 20 | * [how likely is it to score from 45 meters](http://nbviewer.ipython.org/github/rjtavares/football-crunching/blob/master/notebooks/how%20likely%20is%20it%20to%20score%20from%2045%20meters.ipynb): we use a logistic regression to model the probability of scoring based on the distance from the goal 21 | 22 | Other links: [Blog](https://medium.com/football-crunching) | [Twitter](https://twitter.com/lastrowview) | [Youtube](https://www.youtube.com/channel/UCz9ZhIX4DO16O_OMSC1_Udw) -------------------------------------------------------------------------------- /datasets/open_xt_12x8_v1.json: -------------------------------------------------------------------------------- 1 | [ 2 | [ 3 | 0.00638303, 4 | 0.00779616, 5 | 0.00844854, 6 | 0.00977659, 7 | 0.01126267, 8 | 0.01248344, 9 | 0.01473596, 10 | 0.0174506, 11 | 0.02122129, 12 | 0.02756312, 13 | 0.03485072, 14 | 0.0379259 15 | ], 16 | [ 17 | 0.00750072, 18 | 0.00878589, 19 | 0.00942382, 20 | 0.0105949, 21 | 0.01214719, 22 | 0.0138454, 23 | 0.01611813, 24 | 0.01870347, 25 | 0.02401521, 26 | 0.02953272, 27 | 0.04066992, 28 | 0.04647721 29 | ], 30 | [ 31 | 0.0088799, 32 | 0.00977745, 33 | 0.01001304, 34 | 0.01110462, 35 | 0.01269174, 36 | 0.01429128, 37 | 0.01685596, 38 | 0.01935132, 39 | 0.0241224, 40 | 0.02855202, 41 | 0.05491138, 42 | 0.06442595 43 | ], 44 | [ 45 | 0.00941056, 46 | 0.01082722, 47 | 0.01016549, 48 | 0.01132376, 49 | 0.01262646, 50 | 0.01484598, 51 | 0.01689528, 52 | 0.0199707, 53 | 0.02385149, 54 | 0.03511326, 55 | 0.10805102, 56 | 0.25745362 57 | ], 58 | [ 59 | 0.00941056, 60 | 0.01082722, 61 | 0.01016549, 62 | 0.01132376, 63 | 0.01262646, 64 | 0.01484598, 65 | 0.01689528, 66 | 0.0199707, 67 | 0.02385149, 68 | 0.03511326, 69 | 0.10805102, 70 | 0.25745362 71 | ], 72 | [ 73 | 0.0088799, 74 | 0.00977745, 75 | 0.01001304, 76 | 0.01110462, 77 | 0.01269174, 78 | 0.01429128, 79 | 0.01685596, 80 | 0.01935132, 81 | 0.0241224, 82 | 0.02855202, 83 | 0.05491138, 84 | 0.06442595 85 | ], 86 | [ 87 | 0.00750072, 88 | 0.00878589, 89 | 0.00942382, 90 | 0.0105949, 91 | 0.01214719, 92 | 0.0138454, 93 | 0.01611813, 94 | 0.01870347, 95 | 0.02401521, 96 | 0.02953272, 97 | 0.04066992, 98 | 0.04647721 99 | ], 100 | [ 101 | 0.00638303, 102 | 0.00779616, 103 | 0.00844854, 104 | 0.00977659, 105 | 0.01126267, 106 | 0.01248344, 107 | 0.01473596, 108 | 0.0174506, 109 | 0.02122129, 110 | 0.02756312, 111 | 0.03485072, 112 | 0.0379259 113 | ] 114 | ] 115 | -------------------------------------------------------------------------------- /notebooks/footyscripts/footyviz.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import matplotlib.pyplot as plt 3 | from matplotlib.patches import Ellipse 4 | 5 | x_size = 105.0 6 | y_size = 68.0 7 | 8 | type_names = {1: 'PASS', 2:'OFFSIDE PASS', 3: 'DRIBBLE', 4:'FOUL (1-ON, 0-BY)', 5: 'PLAY_ACTORS', 9 | 6:'CORNER (1-WON, 0-GRANTED)', 7: 'TACKLE', 8:'INTERCEPTION', 10 | 10: 'SAVE/BLOCK', 11: 'GK GRAB BALL', 12: 'INTERCEPTION (NO CONTROL)', 11 | 13: 'SHOT OFF GOAL', 14: 'SHOT HIT POST', 15: 'SHOT ON GOAL', 16: 'GOAL', 12 | 17: 'YELLOW CARD', 18: 'SUBSTITUTION (OFF)', 19: 'SUBSTITUTION (ON)', 34:'????????', 13 | 41: 'GK PUNCH', 42: 'something awesome', 43: '???????????', 44:'HEADING DUEL', 14 | 45: 'TACKLE (MISSED)', 49: 'WON CONTROL OF BALL', 50:'LOST CONTROL OF BALL', 51:'INTERCEPTION (MISSED)', 15 | 52: 'gk action', 55:'offside defender', 56:'??????????', 59: 'gk action', 16 | 61: 'LOST CONTROL OF BALL', 74: 'CLEAR BALL (OUT OF PITCH)', 100: 'RECEPTION', 101: 'RUN WITH BALL', 102: 'LINEUP'} 17 | 18 | def draw_events(events, alpha=1, base_color='black', goal_color='red', mirror_away=False, arrows=True): 19 | for i, event in events.iterrows(): 20 | side = event['side'] 21 | if mirror_away: 22 | mirror = side=='A' 23 | else: 24 | mirror = False 25 | if mirror: 26 | x = x_size-event['x'] 27 | y = y_size-event['y'] 28 | dx = -(event['to_x']-event['x']) 29 | dy = -(event['to_y']-event['y']) 30 | else: 31 | x = event['x'] 32 | y = event['y'] 33 | dx = event['to_x']-event['x'] 34 | dy = event['to_y']-event['y'] 35 | 36 | if event['type']==16: 37 | color = goal_color 38 | else: 39 | color = base_color 40 | 41 | if pd.notnull(event['to_x']): 42 | if event['type']==101: 43 | style='dotted' 44 | head_width=1*arrows 45 | head_length=1*arrows 46 | else: 47 | style='solid' 48 | head_width=2*arrows 49 | head_length=2*arrows 50 | 51 | plt.arrow(x, y, dx, dy, head_width=head_width, head_length=head_length, linestyle=style, 52 | color=color, alpha=alpha, length_includes_head=True) 53 | else: 54 | plt.scatter(x,y, marker='x', color=color, alpha=alpha) 55 | 56 | def draw_pitch(): 57 | #set up field 58 | fig = plt.figure(figsize=(x_size/10, y_size/10)) 59 | fig.patch.set_facecolor('#78AB46') 60 | 61 | axes = fig.add_subplot(1, 1, 1, axisbg='#78AB46') 62 | 63 | axes.xaxis.set_visible(False) 64 | axes.yaxis.set_visible(False) 65 | 66 | plt.xlim([-5,x_size+5]) 67 | plt.ylim([-5,y_size+5]) 68 | 69 | box_height = ((16.5*2 + 7.32)/y_size)/1.15 70 | box_width = (16.5/x_size)/1.15 71 | 72 | team_colors = {'H': 'red', 73 | 'A': 'white'} 74 | 75 | r1 = plt.Rectangle((0.04338, 0.0641), (0.95652-0.04338), (0.9359-0.0641), 76 | edgecolor="white", facecolor="none", alpha=1, transform=axes.transAxes) #pitch 77 | 78 | r2 = plt.Line2D([0.5, 0.5], [0.9359, 0.0641], 79 | c='w', transform=axes.transAxes) #half-way line 80 | 81 | r3 = plt.Rectangle((0.04338, (1-box_height)/2), box_width, box_height, 82 | ec='w', fc='none', transform=axes.transAxes) #penalty area 83 | 84 | r4 = plt.Rectangle((0.95652-box_width, (1-box_height)/2), box_width, box_height, 85 | ec='w', fc='none', transform=axes.transAxes) #penalty area 86 | 87 | r5 = Ellipse((0.5, 0.5), 9.15*2/x_size, 9.15*2/y_size, 88 | ec='w', fc='none', transform=axes.transAxes) #middle circle 89 | 90 | fig.lines.extend([r1, r2, r3, r4, r5]) 91 | 92 | return fig, axes -------------------------------------------------------------------------------- /scripts/footyviz.py: -------------------------------------------------------------------------------- 1 | from matplotlib import pyplot as plt 2 | from matplotlib.patches import Ellipse 3 | from matplotlib.collections import PatchCollection 4 | import matplotlib.patheffects as path_effects 5 | import numpy as np 6 | 7 | from scipy.spatial import Voronoi 8 | from shapely.geometry import Polygon 9 | 10 | X_SIZE = 105 11 | Y_SIZE = 68 12 | 13 | BOX_HEIGHT = (16.5*2 + 7.32)/Y_SIZE*100 14 | BOX_WIDTH = 16.5/X_SIZE*100 15 | 16 | GOAL = 7.32/Y_SIZE*100 17 | 18 | GOAL_AREA_HEIGHT = 5.4864*2/Y_SIZE*100 + GOAL 19 | GOAL_AREA_WIDTH = 5.4864/X_SIZE*100 20 | 21 | SCALERS = np.array([X_SIZE/100, Y_SIZE/100]) 22 | pitch_polygon = Polygon(((0,0), (0,100), (100,100), (100,0))) 23 | 24 | def draw_pitch(dpi=100, pitch_color='#a8bc95', fig=None, ax=None, size=1): 25 | """Sets up field 26 | Returns matplotlib fig and axes objects. 27 | """ 28 | if fig is None: 29 | figsize=(12.8*size, 7.2*size) 30 | fig = plt.figure(figsize=figsize, dpi=dpi) 31 | fig.patch.set_facecolor(pitch_color) 32 | 33 | if ax is None: 34 | ax = fig.add_subplot(1, 1, 1) 35 | ax.set_axis_off() 36 | ax.set_facecolor(pitch_color) 37 | ax.xaxis.set_visible(False) 38 | ax.yaxis.set_visible(False) 39 | 40 | ax.set_xlim(0,100) 41 | ax.set_ylim(0,100) 42 | 43 | plt.xlim([-13.32, 113.32]) 44 | plt.ylim([-5, 105]) 45 | 46 | fig.tight_layout(pad=3) 47 | 48 | draw_patches(ax) 49 | 50 | return fig, ax 51 | 52 | def draw_patches(axes): 53 | """ 54 | Draws basic field shapes on an axes 55 | """ 56 | #pitch 57 | axes.add_patch(plt.Rectangle((0, 0), 100, 100, 58 | edgecolor="white", facecolor="none")) 59 | 60 | #half-way line 61 | axes.add_line(plt.Line2D([50, 50], [100, 0], 62 | c='w')) 63 | 64 | #penalty areas 65 | axes.add_patch(plt.Rectangle((100-BOX_WIDTH, (100-BOX_HEIGHT)/2), BOX_WIDTH, BOX_HEIGHT, 66 | ec='w', fc='none')) 67 | axes.add_patch(plt.Rectangle((0, (100-BOX_HEIGHT)/2), BOX_WIDTH, BOX_HEIGHT, 68 | ec='w', fc='none')) 69 | 70 | #goal areas 71 | axes.add_patch(plt.Rectangle((100-GOAL_AREA_WIDTH, (100-GOAL_AREA_HEIGHT)/2), GOAL_AREA_WIDTH, GOAL_AREA_HEIGHT, 72 | ec='w', fc='none')) 73 | axes.add_patch(plt.Rectangle((0, (100-GOAL_AREA_HEIGHT)/2), GOAL_AREA_WIDTH, GOAL_AREA_HEIGHT, 74 | ec='w', fc='none')) 75 | 76 | #goals 77 | axes.add_patch(plt.Rectangle((100, (100-GOAL)/2), 1, GOAL, 78 | ec='w', fc='none')) 79 | axes.add_patch(plt.Rectangle((0, (100-GOAL)/2), -1, GOAL, 80 | ec='w', fc='none')) 81 | 82 | 83 | #halfway circle 84 | axes.add_patch(Ellipse((50, 50), 2*9.15/X_SIZE*100, 2*9.15/Y_SIZE*100, 85 | ec='w', fc='none')) 86 | 87 | return axes 88 | 89 | def draw_frame(df, t, fig=None, ax=None, size=1, dpi=100, fps=20, label='player_num', show_players=True, 90 | highlight_color=None, highlight_player=None, text_size=8, text_color='white', flip=False, voronoi=False, **anim_args): 91 | """ 92 | Draws players from time t (in seconds) from a DataFrame df 93 | """ 94 | fig, ax = draw_pitch(dpi=dpi, fig=fig, ax=ax, size=size) 95 | 96 | dfFrame = get_frame(df, t, fps=fps) 97 | 98 | if show_players: 99 | fig, ax, dfFrame = add_players(fig, ax, dfFrame, 100 | label=label, highlight_color=highlight_color, highlight_player= highlight_player, 101 | text_size=text_size, text_color=text_color) 102 | if voronoi == True: 103 | fig, ax, dfFrame = add_voronoi(fig, ax, dfFrame) 104 | return fig, ax, dfFrame 105 | 106 | def add_players(fig, ax, dfFrame, label='player_num', highlight_color=None, highlight_player=None, text_size=8, text_color='white'): 107 | for pid in dfFrame.index: 108 | if pid==0: 109 | #se for bola 110 | try: 111 | z = dfFrame.loc[pid]['z'] 112 | except: 113 | z = 0 114 | size = 1.2+z 115 | lw = 0.9 116 | color='black' 117 | edge='white' 118 | zorder = 100 119 | else: 120 | #se for jogador 121 | size = 3 122 | lw = 2 123 | edge = dfFrame.loc[pid]['edgecolor'] 124 | 125 | if pid == highlight_player: 126 | color = highlight_color 127 | else: 128 | color = dfFrame.loc[pid]['bgcolor'] 129 | if dfFrame.loc[pid]['team']=='attack': 130 | zorder = 21 131 | else: 132 | zorder = 20 133 | 134 | ax.add_artist(Ellipse((dfFrame.loc[pid]['x'], 135 | dfFrame.loc[pid]['y']), 136 | size/X_SIZE*100, size/Y_SIZE*100, 137 | edgecolor=edge, 138 | linewidth=lw, 139 | facecolor=color, 140 | alpha=0.8, 141 | zorder=zorder)) 142 | 143 | if text_color is not None: 144 | try: 145 | s = dfFrame.loc[pid][label] 146 | if isinstance(s, str)==False: 147 | s = str(int(label)) 148 | except: 149 | s = '' 150 | text = plt.text(dfFrame.loc[pid]['x'],dfFrame.loc[pid]['y'],s, 151 | horizontalalignment='center', verticalalignment='center', 152 | fontsize=text_size, color=text_color, zorder=22, alpha=0.8) 153 | 154 | text.set_path_effects([path_effects.Stroke(linewidth=1, foreground=text_color, alpha=0.8), 155 | path_effects.Normal()]) 156 | 157 | return fig, ax, dfFrame 158 | 159 | 160 | def add_voronoi(fig, ax, dfFrame): 161 | polygons = {} 162 | vor, dfVor = calculate_voronoi(dfFrame) 163 | for index, region in enumerate(vor.regions): 164 | if not -1 in region: 165 | if len(region)>0: 166 | try: 167 | pl = dfVor[dfVor['region']==index] 168 | polygon = Polygon([vor.vertices[i] for i in region]/SCALERS).intersection(pitch_polygon) 169 | polygons[pl.index[0]] = polygon 170 | color = pl['bgcolor'].values[0] 171 | x, y = polygon.exterior.xy 172 | plt.fill(x, y, c=color, alpha=0.30) 173 | except IndexError: 174 | pass 175 | except AttributeError: 176 | pass 177 | 178 | plt.scatter(dfVor['x'], dfVor['y'], c=dfVor['bgcolor'], alpha=0.2) 179 | 180 | return fig, ax, dfFrame 181 | 182 | def calculate_voronoi(dfFrame): 183 | dfTemp = dfFrame.copy().drop(0, errors='ignore') 184 | 185 | values = np.vstack((dfTemp[['x', 'y']].values*SCALERS, 186 | [-1000,-1000], 187 | [+1000,+1000], 188 | [+1000,-1000], 189 | [-1000,+1000] 190 | )) 191 | 192 | vor = Voronoi(values) 193 | 194 | dfTemp['region'] = vor.point_region[:-4] 195 | 196 | return vor, dfTemp 197 | 198 | def get_frame(df, t, fps=20): 199 | dfFrame = df.loc[int(t*fps)].set_index('player') 200 | return dfFrame -------------------------------------------------------------------------------- /notebooks/how to get full play-by-play data for the WC2014.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "name": "", 4 | "signature": "sha256:ff8679825ca5378d17b005803647000fd6aae74bab00fe8322dde6bf52c31947" 5 | }, 6 | "nbformat": 3, 7 | "nbformat_minor": 0, 8 | "worksheets": [ 9 | { 10 | "cells": [ 11 | { 12 | "cell_type": "heading", 13 | "level": 1, 14 | "metadata": {}, 15 | "source": [ 16 | "How to get full play-by-play data for the WC2014" 17 | ] 18 | }, 19 | { 20 | "cell_type": "markdown", 21 | "metadata": {}, 22 | "source": [ 23 | "Good football data is hard to come by. Basic stat counts are easily available, but full play data (i.e. a play broken down in its individual components: interceptions and tackles, runs, passes and shots, etc.) is very rare. And that's the most important unit in a team sport like football. So imagine my surprise and great joy when I came across a fantastic dataset of full play-by-play data for all World Cup matches.\n", 24 | "\n", 25 | "After spending some time in the wonderful world of web scraping, one becomes aware of hints that something worthwhile is going on. Whenever I see a pretty interactive chart on a web-page like the great [Huff Post Data's World Cup page](http://data.huffingtonpost.com/2014/world-cup), my spider sense starts tingling.\n", 26 | "\n", 27 | "The first thing to do is to make sure that the site is using only html5 and js. Check.\n", 28 | "\n", 29 | "OK, so how is the website sending the data to the browser? Developer tools in Chrome is your friend: network tab, filter \"json\".\n", 30 | "\n", 31 | "*Bingo.*\n", 32 | "\n", 33 | "The website was sending the full dataset to the browser." 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "collapsed": false, 39 | "input": [ 40 | "#imports\n", 41 | "import requests\n", 42 | "import json\n", 43 | "import mechanize\n", 44 | "from bs4 import BeautifulSoup\n", 45 | "import time\n", 46 | "\n", 47 | "#initializes the browser\n", 48 | "br = mechanize.Browser()\n", 49 | "br.set_handle_refresh(mechanize._http.HTTPRefreshProcessor(), max_time=10)\n", 50 | "br.addheaders = [('User-agent', 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.1) Gecko/2008071615 Fedora/3.0.1-1.fc9 Firefox/3.0.1')]\n" 51 | ], 52 | "language": "python", 53 | "metadata": {}, 54 | "outputs": [], 55 | "prompt_number": 2 56 | }, 57 | { 58 | "cell_type": "markdown", 59 | "metadata": {}, 60 | "source": [ 61 | "Looking at a match page, you see that all links are listed in a handy menu. Looking at the html code, you can see that they are inside a span tag with class set to \"matchup\".\n", 62 | "\n", 63 | "So the following code gets you all the links:" 64 | ] 65 | }, 66 | { 67 | "cell_type": "code", 68 | "collapsed": false, 69 | "input": [ 70 | "starting_link = 'http://data.huffingtonpost.com/2014/world-cup/matches/belgium-vs-usa-731822'\n", 71 | "response = mechanize.urlopen(starting_link)\n", 72 | "soup = BeautifulSoup(response)\n", 73 | "\n", 74 | "links_html = soup.find_all(\"span\", class_=\"matchup\")\n", 75 | "\n", 76 | "links = []\n", 77 | "\n", 78 | "for link_html in links_html:\n", 79 | " a = link_html.find_all('a')\n", 80 | " for l in a:\n", 81 | " link = l.get('href')\n", 82 | " link = link.split('/')[-1]\n", 83 | "\n", 84 | " links.append(link)\n", 85 | " \n", 86 | "links[:5]" 87 | ], 88 | "language": "python", 89 | "metadata": {}, 90 | "outputs": [ 91 | { 92 | "metadata": {}, 93 | "output_type": "pyout", 94 | "prompt_number": 9, 95 | "text": [ 96 | "['brazil-vs-chile-731815',\n", 97 | " 'colombia-vs-uruguay-731816',\n", 98 | " 'netherlands-vs-mexico-731817',\n", 99 | " 'costa-rica-vs-greece-731818',\n", 100 | " 'france-vs-nigeria-731819']" 101 | ] 102 | } 103 | ], 104 | "prompt_number": 9 105 | }, 106 | { 107 | "cell_type": "markdown", 108 | "metadata": {}, 109 | "source": [ 110 | "With this information we can get all the match data trough a simple request, which gives back easily readable json." 111 | ] 112 | }, 113 | { 114 | "cell_type": "code", 115 | "collapsed": false, 116 | "input": [ 117 | "def get_match_data(match):\n", 118 | " match_id = match.split('-')[-1]\n", 119 | " response = mechanize.urlopen('http://data.huffingtonpost.com/2014/world-cup/matches/%s.json' % match_id)\n", 120 | "\n", 121 | " match_data = json.loads(response.read())\n", 122 | " \n", 123 | " return match_data\n", 124 | "\n", 125 | "match_data = get_match_data(links[0]) # test\n", 126 | "\n", 127 | "match_data.keys()" 128 | ], 129 | "language": "python", 130 | "metadata": {}, 131 | "outputs": [ 132 | { 133 | "metadata": {}, 134 | "output_type": "pyout", 135 | "prompt_number": 10, 136 | "text": [ 137 | "[u'team_stats', u'events', u'summary']" 138 | ] 139 | } 140 | ], 141 | "prompt_number": 10 142 | }, 143 | { 144 | "cell_type": "markdown", 145 | "metadata": {}, 146 | "source": [ 147 | "Unfortunately, the data includes IDs only. The page has names, though, so there must be some conversion taking place. At this point, I was scared that I to look through all script files and javascript code to see where the conversion took place.\n", 148 | "\n", 149 | "However, the first (and obvious) step was enough: simply searching for a player's name in the main page source showed that variables HPIN.teams and HPIN.players contained the names and IDs, plus a bunch of other information (like position, birth date and even preferred foot). The script tag that defined the variables has no class or id, so we could only identify it by its position." 150 | ] 151 | }, 152 | { 153 | "cell_type": "code", 154 | "collapsed": false, 155 | "input": [ 156 | "def get_match_names(match):\n", 157 | " response = mechanize.urlopen('http://data.huffingtonpost.com/2014/world-cup/matches/%s' % links[0]) #example page\n", 158 | " soup = BeautifulSoup(response)\n", 159 | "\n", 160 | " data = {}\n", 161 | "\n", 162 | " data_script = soup.findAll(\"script\")[1] #gets the second script block. Hopefully all pages follow the same format\n", 163 | " data_lines = data_script.text.split('\\n')\n", 164 | "\n", 165 | " for line in data_lines[1:]:\n", 166 | " try:\n", 167 | " #format of a variable is HPIN.variable = [list of dictionaries]\n", 168 | " #this tries to convert it to \n", 169 | " line_data = line.split(' = ')\n", 170 | " name = line_data[0].split('.')[1]\n", 171 | " value = json.loads(line_data[1][:-1])\n", 172 | " data[name] = value\n", 173 | " except:\n", 174 | " print \"error parsing string: \", line #should only occur on blank lines - yeah, I know, lazy exception handling...\n", 175 | " \n", 176 | " return data\n", 177 | "\n", 178 | "names = get_match_names(links[0])\n", 179 | "names.keys()" 180 | ], 181 | "language": "python", 182 | "metadata": {}, 183 | "outputs": [ 184 | { 185 | "output_type": "stream", 186 | "stream": "stdout", 187 | "text": [ 188 | "error parsing string: \n" 189 | ] 190 | }, 191 | { 192 | "metadata": {}, 193 | "output_type": "pyout", 194 | "prompt_number": 11, 195 | "text": [ 196 | "[u'statCategories',\n", 197 | " u'awayTeam',\n", 198 | " u'callbackPath',\n", 199 | " u'homeTeam',\n", 200 | " u'teams',\n", 201 | " u'players',\n", 202 | " u'imageCallbackPath',\n", 203 | " u'imageCallbackInterval',\n", 204 | " u'twitterUrl']" 205 | ] 206 | } 207 | ], 208 | "prompt_number": 11 209 | }, 210 | { 211 | "cell_type": "markdown", 212 | "metadata": {}, 213 | "source": [ 214 | "Alright, so now we have all the match links, a function that returns the events and stats from each match, and a function that returns the players and team names. Let's put it all together. First, create a dictionary:" 215 | ] 216 | }, 217 | { 218 | "cell_type": "code", 219 | "collapsed": false, 220 | "input": [ 221 | "data = {}" 222 | ], 223 | "language": "python", 224 | "metadata": {}, 225 | "outputs": [] 226 | }, 227 | { 228 | "cell_type": "markdown", 229 | "metadata": {}, 230 | "source": [ 231 | "Then, execute a loop that will get the data from all the matches and add it to the dictionary. The `if` statement ensures you don't have to reprocess a match in the case you have to run the cell again (e.g. due a network error)." 232 | ] 233 | }, 234 | { 235 | "cell_type": "code", 236 | "collapsed": true, 237 | "input": [ 238 | "for match in links:\n", 239 | " if match not in data:\n", 240 | " print match\n", 241 | " time.sleep(60)\n", 242 | "\n", 243 | " match_data = get_match_data(match)\n", 244 | " match_names = get_match_names(match)\n", 245 | " data[match] = {'data': match_data, 'names': match_names}\n", 246 | "\n", 247 | " print match, \" done\" \n", 248 | " else:\n", 249 | " print match, \" already processed\"\n" 250 | ], 251 | "language": "python", 252 | "metadata": {}, 253 | "outputs": [ 254 | { 255 | "output_type": "stream", 256 | "stream": "stdout", 257 | "text": [ 258 | "brazil-vs-chile-731815\n", 259 | "error parsing string: " 260 | ] 261 | }, 262 | { 263 | "output_type": "stream", 264 | "stream": "stdout", 265 | "text": [ 266 | " \n", 267 | "brazil-vs-chile-731815 done\n", 268 | "colombia-vs-uruguay-731816\n", 269 | "error parsing string: " 270 | ] 271 | }, 272 | { 273 | "output_type": "stream", 274 | "stream": "stdout", 275 | "text": [ 276 | " \n", 277 | "colombia-vs-uruguay-731816 done\n", 278 | "netherlands-vs-mexico-731817\n", 279 | "error parsing string: " 280 | ] 281 | }, 282 | { 283 | "output_type": "stream", 284 | "stream": "stdout", 285 | "text": [ 286 | " \n", 287 | "netherlands-vs-mexico-731817 done\n", 288 | "costa-rica-vs-greece-731818\n", 289 | "error parsing string: " 290 | ] 291 | }, 292 | { 293 | "output_type": "stream", 294 | "stream": "stdout", 295 | "text": [ 296 | " \n", 297 | "costa-rica-vs-greece-731818 done\n", 298 | "france-vs-nigeria-731819\n", 299 | "error parsing string: " 300 | ] 301 | }, 302 | { 303 | "output_type": "stream", 304 | "stream": "stdout", 305 | "text": [ 306 | " \n", 307 | "france-vs-nigeria-731819 done\n", 308 | "germany-vs-algeria-731820\n", 309 | "error parsing string: " 310 | ] 311 | }, 312 | { 313 | "output_type": "stream", 314 | "stream": "stdout", 315 | "text": [ 316 | " \n", 317 | "germany-vs-algeria-731820 done\n", 318 | "argentina-vs-switzerland-731821\n", 319 | "error parsing string: " 320 | ] 321 | }, 322 | { 323 | "output_type": "stream", 324 | "stream": "stdout", 325 | "text": [ 326 | " \n", 327 | "argentina-vs-switzerland-731821 done\n", 328 | "belgium-vs-usa-731822\n", 329 | "error parsing string: " 330 | ] 331 | }, 332 | { 333 | "output_type": "stream", 334 | "stream": "stdout", 335 | "text": [ 336 | " \n", 337 | "belgium-vs-usa-731822 done\n", 338 | "france-vs-germany-731824\n", 339 | "error parsing string: " 340 | ] 341 | }, 342 | { 343 | "output_type": "stream", 344 | "stream": "stdout", 345 | "text": [ 346 | " \n", 347 | "france-vs-germany-731824 done\n", 348 | "brazil-vs-colombia-731823\n", 349 | "error parsing string: " 350 | ] 351 | }, 352 | { 353 | "output_type": "stream", 354 | "stream": "stdout", 355 | "text": [ 356 | " \n", 357 | "brazil-vs-colombia-731823 done\n", 358 | "argentina-vs-belgium-731826\n", 359 | "error parsing string: " 360 | ] 361 | }, 362 | { 363 | "output_type": "stream", 364 | "stream": "stdout", 365 | "text": [ 366 | " \n", 367 | "argentina-vs-belgium-731826 done\n", 368 | "netherlands-vs-costa-rica-731825\n", 369 | "error parsing string: " 370 | ] 371 | }, 372 | { 373 | "output_type": "stream", 374 | "stream": "stdout", 375 | "text": [ 376 | " \n", 377 | "netherlands-vs-costa-rica-731825 done\n", 378 | "brazil-vs-germany-731827\n", 379 | "error parsing string: " 380 | ] 381 | }, 382 | { 383 | "output_type": "stream", 384 | "stream": "stdout", 385 | "text": [ 386 | " \n", 387 | "brazil-vs-germany-731827 done\n", 388 | "netherlands-vs-argentina-731828\n", 389 | "error parsing string: " 390 | ] 391 | }, 392 | { 393 | "output_type": "stream", 394 | "stream": "stdout", 395 | "text": [ 396 | " \n", 397 | "netherlands-vs-argentina-731828 done\n", 398 | "brazil-vs-netherlands-731829\n", 399 | "error parsing string: " 400 | ] 401 | }, 402 | { 403 | "output_type": "stream", 404 | "stream": "stdout", 405 | "text": [ 406 | " \n", 407 | "brazil-vs-netherlands-731829 done\n", 408 | "germany-vs-argentina-731830\n", 409 | "error parsing string: " 410 | ] 411 | }, 412 | { 413 | "output_type": "stream", 414 | "stream": "stdout", 415 | "text": [ 416 | " \n", 417 | "germany-vs-argentina-731830 done\n", 418 | "brazil-vs-croatia-731767\n", 419 | "error parsing string: " 420 | ] 421 | }, 422 | { 423 | "output_type": "stream", 424 | "stream": "stdout", 425 | "text": [ 426 | " \n", 427 | "brazil-vs-croatia-731767 done\n", 428 | "mexico-vs-cameroon-731768\n", 429 | "error parsing string: " 430 | ] 431 | }, 432 | { 433 | "output_type": "stream", 434 | "stream": "stdout", 435 | "text": [ 436 | " \n", 437 | "mexico-vs-cameroon-731768 done\n", 438 | "brazil-vs-mexico-731783\n", 439 | "error parsing string: " 440 | ] 441 | }, 442 | { 443 | "output_type": "stream", 444 | "stream": "stdout", 445 | "text": [ 446 | " \n", 447 | "brazil-vs-mexico-731783 done\n", 448 | "cameroon-vs-croatia-731784\n", 449 | "error parsing string: " 450 | ] 451 | }, 452 | { 453 | "output_type": "stream", 454 | "stream": "stdout", 455 | "text": [ 456 | " \n", 457 | "cameroon-vs-croatia-731784 done\n", 458 | "croatia-vs-mexico-731800\n", 459 | "error parsing string: " 460 | ] 461 | }, 462 | { 463 | "output_type": "stream", 464 | "stream": "stdout", 465 | "text": [ 466 | " \n", 467 | "croatia-vs-mexico-731800 done\n", 468 | "cameroon-vs-brazil-731799\n", 469 | "error parsing string: " 470 | ] 471 | }, 472 | { 473 | "output_type": "stream", 474 | "stream": "stdout", 475 | "text": [ 476 | " \n", 477 | "cameroon-vs-brazil-731799 done\n", 478 | "spain-vs-netherlands-731769\n", 479 | "error parsing string: " 480 | ] 481 | }, 482 | { 483 | "output_type": "stream", 484 | "stream": "stdout", 485 | "text": [ 486 | " \n", 487 | "spain-vs-netherlands-731769 done\n", 488 | "chile-vs-australia-731770\n", 489 | "error parsing string: " 490 | ] 491 | }, 492 | { 493 | "output_type": "stream", 494 | "stream": "stdout", 495 | "text": [ 496 | " \n", 497 | "chile-vs-australia-731770 done\n", 498 | "australia-vs-netherlands-731786\n", 499 | "error parsing string: " 500 | ] 501 | }, 502 | { 503 | "output_type": "stream", 504 | "stream": "stdout", 505 | "text": [ 506 | " \n", 507 | "australia-vs-netherlands-731786 done\n", 508 | "spain-vs-chile-731785\n", 509 | "error parsing string: " 510 | ] 511 | }, 512 | { 513 | "output_type": "stream", 514 | "stream": "stdout", 515 | "text": [ 516 | " \n", 517 | "spain-vs-chile-731785 done\n", 518 | "netherlands-vs-chile-731802\n", 519 | "error parsing string: " 520 | ] 521 | }, 522 | { 523 | "output_type": "stream", 524 | "stream": "stdout", 525 | "text": [ 526 | " \n", 527 | "netherlands-vs-chile-731802 done\n", 528 | "australia-vs-spain-731801\n", 529 | "error parsing string: " 530 | ] 531 | }, 532 | { 533 | "output_type": "stream", 534 | "stream": "stdout", 535 | "text": [ 536 | " \n", 537 | "australia-vs-spain-731801 done\n", 538 | "colombia-vs-greece-731771\n", 539 | "error parsing string: " 540 | ] 541 | }, 542 | { 543 | "output_type": "stream", 544 | "stream": "stdout", 545 | "text": [ 546 | " \n", 547 | "colombia-vs-greece-731771 done\n", 548 | "ivory-coast-vs-japan-731772\n", 549 | "error parsing string: " 550 | ] 551 | }, 552 | { 553 | "output_type": "stream", 554 | "stream": "stdout", 555 | "text": [ 556 | " \n", 557 | "ivory-coast-vs-japan-731772 done\n", 558 | "colombia-vs-ivory-coast-731787\n", 559 | "error parsing string: " 560 | ] 561 | }, 562 | { 563 | "output_type": "stream", 564 | "stream": "stdout", 565 | "text": [ 566 | " \n", 567 | "colombia-vs-ivory-coast-731787 done\n", 568 | "japan-vs-greece-731788\n", 569 | "error parsing string: " 570 | ] 571 | }, 572 | { 573 | "output_type": "stream", 574 | "stream": "stdout", 575 | "text": [ 576 | " \n", 577 | "japan-vs-greece-731788 done\n", 578 | "japan-vs-colombia-731803\n", 579 | "error parsing string: " 580 | ] 581 | }, 582 | { 583 | "output_type": "stream", 584 | "stream": "stdout", 585 | "text": [ 586 | " \n", 587 | "japan-vs-colombia-731803 done\n", 588 | "greece-vs-ivory-coast-731804\n", 589 | "error parsing string: " 590 | ] 591 | }, 592 | { 593 | "output_type": "stream", 594 | "stream": "stdout", 595 | "text": [ 596 | " \n", 597 | "greece-vs-ivory-coast-731804 done\n", 598 | "uruguay-vs-costa-rica-731773\n", 599 | "error parsing string: " 600 | ] 601 | }, 602 | { 603 | "output_type": "stream", 604 | "stream": "stdout", 605 | "text": [ 606 | " \n", 607 | "uruguay-vs-costa-rica-731773 done\n", 608 | "england-vs-italy-731774\n", 609 | "error parsing string: " 610 | ] 611 | }, 612 | { 613 | "output_type": "stream", 614 | "stream": "stdout", 615 | "text": [ 616 | " \n", 617 | "england-vs-italy-731774 done\n", 618 | "uruguay-vs-england-731789\n", 619 | "error parsing string: " 620 | ] 621 | }, 622 | { 623 | "output_type": "stream", 624 | "stream": "stdout", 625 | "text": [ 626 | " \n", 627 | "uruguay-vs-england-731789 done\n", 628 | "italy-vs-costa-rica-731790\n", 629 | "error parsing string: " 630 | ] 631 | }, 632 | { 633 | "output_type": "stream", 634 | "stream": "stdout", 635 | "text": [ 636 | " \n", 637 | "italy-vs-costa-rica-731790 done\n", 638 | "italy-vs-uruguay-731805\n", 639 | "error parsing string: " 640 | ] 641 | }, 642 | { 643 | "output_type": "stream", 644 | "stream": "stdout", 645 | "text": [ 646 | " \n", 647 | "italy-vs-uruguay-731805 done\n", 648 | "costa-rica-vs-england-731806\n", 649 | "error parsing string: " 650 | ] 651 | }, 652 | { 653 | "output_type": "stream", 654 | "stream": "stdout", 655 | "text": [ 656 | " \n", 657 | "costa-rica-vs-england-731806 done\n", 658 | "switzerland-vs-ecuador-731775\n", 659 | "error parsing string: " 660 | ] 661 | }, 662 | { 663 | "output_type": "stream", 664 | "stream": "stdout", 665 | "text": [ 666 | " \n", 667 | "switzerland-vs-ecuador-731775 done\n", 668 | "france-vs-honduras-731776\n", 669 | "error parsing string: " 670 | ] 671 | }, 672 | { 673 | "output_type": "stream", 674 | "stream": "stdout", 675 | "text": [ 676 | " \n", 677 | "france-vs-honduras-731776 done\n", 678 | "switzerland-vs-france-731791\n", 679 | "error parsing string: " 680 | ] 681 | }, 682 | { 683 | "output_type": "stream", 684 | "stream": "stdout", 685 | "text": [ 686 | " \n", 687 | "switzerland-vs-france-731791 done\n", 688 | "honduras-vs-ecuador-731792\n", 689 | "error parsing string: " 690 | ] 691 | }, 692 | { 693 | "output_type": "stream", 694 | "stream": "stdout", 695 | "text": [ 696 | " \n", 697 | "honduras-vs-ecuador-731792 done\n", 698 | "ecuador-vs-france-731808\n", 699 | "error parsing string: " 700 | ] 701 | }, 702 | { 703 | "output_type": "stream", 704 | "stream": "stdout", 705 | "text": [ 706 | " \n", 707 | "ecuador-vs-france-731808 done\n", 708 | "honduras-vs-switzerland-731807\n", 709 | "error parsing string: " 710 | ] 711 | }, 712 | { 713 | "output_type": "stream", 714 | "stream": "stdout", 715 | "text": [ 716 | " \n", 717 | "honduras-vs-switzerland-731807 done\n", 718 | "argentina-vs-bosnia-herz-731777\n", 719 | "error parsing string: " 720 | ] 721 | }, 722 | { 723 | "output_type": "stream", 724 | "stream": "stdout", 725 | "text": [ 726 | " \n", 727 | "argentina-vs-bosnia-herz-731777 done\n", 728 | "iran-vs-nigeria-731778\n", 729 | "error parsing string: " 730 | ] 731 | }, 732 | { 733 | "output_type": "stream", 734 | "stream": "stdout", 735 | "text": [ 736 | " \n", 737 | "iran-vs-nigeria-731778 done\n", 738 | "argentina-vs-iran-731793\n", 739 | "error parsing string: " 740 | ] 741 | }, 742 | { 743 | "output_type": "stream", 744 | "stream": "stdout", 745 | "text": [ 746 | " \n", 747 | "argentina-vs-iran-731793 done\n", 748 | "nigeria-vs-bosnia-herz-731794\n", 749 | "error parsing string: " 750 | ] 751 | }, 752 | { 753 | "output_type": "stream", 754 | "stream": "stdout", 755 | "text": [ 756 | " \n", 757 | "nigeria-vs-bosnia-herz-731794 done\n", 758 | "nigeria-vs-argentina-731809\n", 759 | "error parsing string: " 760 | ] 761 | }, 762 | { 763 | "output_type": "stream", 764 | "stream": "stdout", 765 | "text": [ 766 | " \n", 767 | "nigeria-vs-argentina-731809 done\n", 768 | "bosnia-herz-vs-iran-731810\n", 769 | "error parsing string: " 770 | ] 771 | }, 772 | { 773 | "output_type": "stream", 774 | "stream": "stdout", 775 | "text": [ 776 | " \n", 777 | "bosnia-herz-vs-iran-731810 done\n", 778 | "germany-vs-portugal-731779\n", 779 | "error parsing string: " 780 | ] 781 | }, 782 | { 783 | "output_type": "stream", 784 | "stream": "stdout", 785 | "text": [ 786 | " \n", 787 | "germany-vs-portugal-731779 done\n", 788 | "ghana-vs-usa-731780\n", 789 | "error parsing string: " 790 | ] 791 | }, 792 | { 793 | "output_type": "stream", 794 | "stream": "stdout", 795 | "text": [ 796 | " \n", 797 | "ghana-vs-usa-731780 done\n", 798 | "germany-vs-ghana-731795\n", 799 | "error parsing string: " 800 | ] 801 | }, 802 | { 803 | "output_type": "stream", 804 | "stream": "stdout", 805 | "text": [ 806 | " \n", 807 | "germany-vs-ghana-731795 done\n", 808 | "usa-vs-portugal-731796\n", 809 | "error parsing string: " 810 | ] 811 | }, 812 | { 813 | "output_type": "stream", 814 | "stream": "stdout", 815 | "text": [ 816 | " \n", 817 | "usa-vs-portugal-731796 done\n", 818 | "portugal-vs-ghana-731812\n", 819 | "error parsing string: " 820 | ] 821 | }, 822 | { 823 | "output_type": "stream", 824 | "stream": "stdout", 825 | "text": [ 826 | " \n", 827 | "portugal-vs-ghana-731812 done\n", 828 | "usa-vs-germany-731811\n", 829 | "error parsing string: " 830 | ] 831 | }, 832 | { 833 | "output_type": "stream", 834 | "stream": "stdout", 835 | "text": [ 836 | " \n", 837 | "usa-vs-germany-731811 done\n", 838 | "belgium-vs-algeria-731781\n", 839 | "error parsing string: " 840 | ] 841 | }, 842 | { 843 | "output_type": "stream", 844 | "stream": "stdout", 845 | "text": [ 846 | " \n", 847 | "belgium-vs-algeria-731781 done\n", 848 | "russia-vs-south-korea-731782\n", 849 | "error parsing string: " 850 | ] 851 | }, 852 | { 853 | "output_type": "stream", 854 | "stream": "stdout", 855 | "text": [ 856 | " \n", 857 | "russia-vs-south-korea-731782 done\n", 858 | "belgium-vs-russia-731797\n", 859 | "error parsing string: " 860 | ] 861 | }, 862 | { 863 | "output_type": "stream", 864 | "stream": "stdout", 865 | "text": [ 866 | " \n", 867 | "belgium-vs-russia-731797 done\n", 868 | "south-korea-vs-algeria-731798\n", 869 | "error parsing string: " 870 | ] 871 | }, 872 | { 873 | "output_type": "stream", 874 | "stream": "stdout", 875 | "text": [ 876 | " \n", 877 | "south-korea-vs-algeria-731798 done\n", 878 | "algeria-vs-russia-731814\n", 879 | "error parsing string: " 880 | ] 881 | }, 882 | { 883 | "output_type": "stream", 884 | "stream": "stdout", 885 | "text": [ 886 | " \n", 887 | "algeria-vs-russia-731814 done\n", 888 | "south-korea-vs-belgium-731813\n", 889 | "error parsing string: " 890 | ] 891 | }, 892 | { 893 | "output_type": "stream", 894 | "stream": "stdout", 895 | "text": [ 896 | " \n", 897 | "south-korea-vs-belgium-731813 done\n", 898 | "brazil-vs-chile-731815 already processed\n", 899 | "colombia-vs-uruguay-731816 already processed\n", 900 | "netherlands-vs-mexico-731817 already processed\n", 901 | "costa-rica-vs-greece-731818 already processed\n", 902 | "france-vs-nigeria-731819 already processed\n", 903 | "germany-vs-algeria-731820 already processed\n", 904 | "argentina-vs-switzerland-731821 already processed\n", 905 | "belgium-vs-usa-731822 already processed\n", 906 | "france-vs-germany-731824 already processed\n", 907 | "brazil-vs-colombia-731823 already processed\n", 908 | "argentina-vs-belgium-731826 already processed\n", 909 | "netherlands-vs-costa-rica-731825 already processed\n", 910 | "brazil-vs-germany-731827 already processed\n", 911 | "netherlands-vs-argentina-731828 already processed\n", 912 | "brazil-vs-netherlands-731829 already processed\n", 913 | "germany-vs-argentina-731830 already processed\n", 914 | "brazil-vs-croatia-731767 already processed\n", 915 | "mexico-vs-cameroon-731768 already processed\n", 916 | "brazil-vs-mexico-731783 already processed\n", 917 | "cameroon-vs-croatia-731784 already processed\n", 918 | "croatia-vs-mexico-731800 already processed\n", 919 | "cameroon-vs-brazil-731799 already processed\n", 920 | "spain-vs-netherlands-731769 already processed\n", 921 | "chile-vs-australia-731770 already processed\n", 922 | "australia-vs-netherlands-731786 already processed\n", 923 | "spain-vs-chile-731785 already processed\n", 924 | "netherlands-vs-chile-731802 already processed\n", 925 | "australia-vs-spain-731801 already processed\n", 926 | "colombia-vs-greece-731771 already processed\n", 927 | "ivory-coast-vs-japan-731772 already processed\n", 928 | "colombia-vs-ivory-coast-731787 already processed\n", 929 | "japan-vs-greece-731788 already processed\n", 930 | "japan-vs-colombia-731803 already processed\n", 931 | "greece-vs-ivory-coast-731804 already processed\n", 932 | "uruguay-vs-costa-rica-731773 already processed\n", 933 | "england-vs-italy-731774 already processed\n", 934 | "uruguay-vs-england-731789 already processed\n", 935 | "italy-vs-costa-rica-731790 already processed\n", 936 | "italy-vs-uruguay-731805 already processed\n", 937 | "costa-rica-vs-england-731806 already processed\n", 938 | "switzerland-vs-ecuador-731775 already processed\n", 939 | "france-vs-honduras-731776 already processed\n", 940 | "switzerland-vs-france-731791 already processed\n", 941 | "honduras-vs-ecuador-731792 already processed\n", 942 | "ecuador-vs-france-731808 already processed\n", 943 | "honduras-vs-switzerland-731807 already processed\n", 944 | "argentina-vs-bosnia-herz-731777 already processed\n", 945 | "iran-vs-nigeria-731778 already processed\n", 946 | "argentina-vs-iran-731793 already processed\n", 947 | "nigeria-vs-bosnia-herz-731794 already processed\n", 948 | "nigeria-vs-argentina-731809 already processed\n", 949 | "bosnia-herz-vs-iran-731810 already processed\n", 950 | "germany-vs-portugal-731779 already processed\n", 951 | "ghana-vs-usa-731780 already processed\n", 952 | "germany-vs-ghana-731795 already processed\n", 953 | "usa-vs-portugal-731796 already processed\n", 954 | "portugal-vs-ghana-731812 already processed\n", 955 | "usa-vs-germany-731811 already processed\n", 956 | "belgium-vs-algeria-731781 already processed\n", 957 | "russia-vs-south-korea-731782 already processed\n", 958 | "belgium-vs-russia-731797 already processed\n", 959 | "south-korea-vs-algeria-731798 already processed\n", 960 | "algeria-vs-russia-731814 already processed\n", 961 | "south-korea-vs-belgium-731813 already processed\n" 962 | ] 963 | } 964 | ], 965 | "prompt_number": 24 966 | }, 967 | { 968 | "cell_type": "code", 969 | "collapsed": false, 970 | "input": [ 971 | "print len(data.keys()) #make sure you have all 64 games" 972 | ], 973 | "language": "python", 974 | "metadata": {}, 975 | "outputs": [ 976 | { 977 | "output_type": "stream", 978 | "stream": "stdout", 979 | "text": [ 980 | "64\n" 981 | ] 982 | } 983 | ], 984 | "prompt_number": 26 985 | }, 986 | { 987 | "cell_type": "code", 988 | "collapsed": false, 989 | "input": [ 990 | "import pickle\n", 991 | "\n", 992 | "pickle.dump(data, open( \"wc2014.p\", \"wb\"))" 993 | ], 994 | "language": "python", 995 | "metadata": {}, 996 | "outputs": [], 997 | "prompt_number": 29 998 | }, 999 | { 1000 | "cell_type": "code", 1001 | "collapsed": false, 1002 | "input": [ 1003 | "data == pickle.load(open(\"wc2014.p\", \"rb\")) #because I'm a bit OCD and want to make sure the data was properly stored" 1004 | ], 1005 | "language": "python", 1006 | "metadata": {}, 1007 | "outputs": [ 1008 | { 1009 | "metadata": {}, 1010 | "output_type": "pyout", 1011 | "prompt_number": 32, 1012 | "text": [ 1013 | "True" 1014 | ] 1015 | } 1016 | ], 1017 | "prompt_number": 32 1018 | }, 1019 | { 1020 | "cell_type": "markdown", 1021 | "metadata": {}, 1022 | "source": [ 1023 | "##That's all folks!\n", 1024 | "\n", 1025 | "The boring part is over. Now, it's time to play :)\n", 1026 | "\n", 1027 | "Check out my [WC final analysis notebook](http://nbviewer.ipython.org/github/rjtavares/football-crunching/blob/master/notebooks/an%20exploratory%20data%20analysis%20of%20the%20world%20cup%20final.ipynb) for an example of what you can do with the data, and follow my github repository [Football Crunching](https://github.com/rjtavares/football-crunching) for more analysis in the future." 1028 | ] 1029 | } 1030 | ], 1031 | "metadata": {} 1032 | } 1033 | ] 1034 | } -------------------------------------------------------------------------------- /notebooks/player value appreciation in Portuguese vs. English clubs.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "name": "", 4 | "signature": "sha256:7214aec897a9642abbdd6aecb8de29f6831a58e2f030fe64355efd15259db272" 5 | }, 6 | "nbformat": 3, 7 | "nbformat_minor": 0, 8 | "worksheets": [ 9 | { 10 | "cells": [ 11 | { 12 | "cell_type": "code", 13 | "collapsed": false, 14 | "input": [ 15 | "import requests\n", 16 | "from bs4 import BeautifulSoup\n", 17 | "import pandas as pd\n", 18 | "import numpy as np\n", 19 | "import matplotlib.pyplot as plt\n", 20 | "from matplotlib.lines import Line2D\n", 21 | "\n", 22 | "%matplotlib inline\n", 23 | "\n", 24 | "links = {'porto': 'http://www.transfermarkt.co.uk/fc-porto/alletransfers/verein/720',\n", 25 | " 'benfica': 'http://www.transfermarkt.co.uk/benfica-lissabon/alletransfers/verein/294',\n", 26 | " 'sporting': 'http://www.transfermarkt.co.uk/sporting-lissabon/alletransfers/verein/336',\n", 27 | " 'man city': 'http://www.transfermarkt.co.uk/manchester-city/alletransfers/verein/281',\n", 28 | " 'man united': 'http://www.transfermarkt.co.uk/manchester-united/alletransfers/verein/985',\n", 29 | " 'chelsea': 'http://www.transfermarkt.co.uk/fc-chelsea/alletransfers/verein/631',\n", 30 | " }" 31 | ], 32 | "language": "python", 33 | "metadata": {}, 34 | "outputs": [], 35 | "prompt_number": 3 36 | }, 37 | { 38 | "cell_type": "code", 39 | "collapsed": false, 40 | "input": [ 41 | "transfers = []\n", 42 | "\n", 43 | "for club, link in links.items():\n", 44 | " response = requests.get(link)\n", 45 | "\n", 46 | " soup = BeautifulSoup(response.text).find_all('div', attrs={'class': 'box'})\n", 47 | "\n", 48 | " for box in soup:\n", 49 | " title_soup = box\n", 50 | " title = title_soup.find('div', attrs={'class': 'table-header'})\n", 51 | "\n", 52 | " if title:\n", 53 | " title = title.text.split('\\t')[4]\n", 54 | "\n", 55 | " for row in box.find_all('tr'):\n", 56 | " fields = title.split(' ')\n", 57 | " fields.append(club)\n", 58 | " for field in row.find_all('td'):\n", 59 | " if field[\"class\"][0] == 'rechts':\n", 60 | " fields.append(field.text)\n", 61 | " elif field[\"class\"][0] in ['redtext', 'greentext']:\n", 62 | " pass\n", 63 | " else:\n", 64 | " if field.a:\n", 65 | " try:\n", 66 | " fields.append(field.a[\"title\"])\n", 67 | " except:\n", 68 | " print \"error:\", field[\"class\"][0]\n", 69 | " else:\n", 70 | " try:\n", 71 | " fields.append(field[\"title\"])\n", 72 | " except:\n", 73 | " print \"error:\", field[\"class\"][0]\n", 74 | " if len(fields)==8:\n", 75 | " transfers.append(fields)\n", 76 | "\n", 77 | "print len(transfers)" 78 | ], 79 | "language": "python", 80 | "metadata": {}, 81 | "outputs": [ 82 | { 83 | "output_type": "stream", 84 | "stream": "stdout", 85 | "text": [ 86 | "6802\n" 87 | ] 88 | } 89 | ], 90 | "prompt_number": 4 91 | }, 92 | { 93 | "cell_type": "code", 94 | "collapsed": false, 95 | "input": [ 96 | "df = pd.DataFrame(transfers, columns=['type', 'season', 'club', 'position', 'player', 'transfer_club', 'transfer_club_alt', 'fee'])\n", 97 | "\n", 98 | "df.head()" 99 | ], 100 | "language": "python", 101 | "metadata": {}, 102 | "outputs": [ 103 | { 104 | "html": [ 105 | "
\n", 106 | "\n", 107 | " \n", 108 | " \n", 109 | " \n", 110 | " \n", 111 | " \n", 112 | " \n", 113 | " \n", 114 | " \n", 115 | " \n", 116 | " \n", 117 | " \n", 118 | " \n", 119 | " \n", 120 | " \n", 121 | " \n", 122 | " \n", 123 | " \n", 124 | " \n", 125 | " \n", 126 | " \n", 127 | " \n", 128 | " \n", 129 | " \n", 130 | " \n", 131 | " \n", 132 | " \n", 133 | " \n", 134 | " \n", 135 | " \n", 136 | " \n", 137 | " \n", 138 | " \n", 139 | " \n", 140 | " \n", 141 | " \n", 142 | " \n", 143 | " \n", 144 | " \n", 145 | " \n", 146 | " \n", 147 | " \n", 148 | " \n", 149 | " \n", 150 | " \n", 151 | " \n", 152 | " \n", 153 | " \n", 154 | " \n", 155 | " \n", 156 | " \n", 157 | " \n", 158 | " \n", 159 | " \n", 160 | " \n", 161 | " \n", 162 | " \n", 163 | " \n", 164 | " \n", 165 | " \n", 166 | " \n", 167 | " \n", 168 | " \n", 169 | " \n", 170 | " \n", 171 | " \n", 172 | " \n", 173 | " \n", 174 | " \n", 175 | " \n", 176 | " \n", 177 | "
typeseasonclubpositionplayertransfer_clubtransfer_club_altfee
0 Arrivals 14/15 porto Right Wing Adri\u00e1n Atl\u00e9tico Madrid Atl\u00e9tico Madrid 9,68 Mill. \u00a3
1 Arrivals 14/15 porto Centre Back Bruno Martins Indi Feyenoord Rotterdam Feyenoord 6,78 Mill. \u00a3
2 Arrivals 14/15 porto Attacking Midfield Ot\u00e1vio Sport Club Internacional Internacional 6,16 Mill. \u00a3
3 Arrivals 14/15 porto Attacking Midfield Yacine Brahimi Granada CF Granada CF 5,72 Mill. \u00a3
4 Arrivals 14/15 porto Centre Forward Vincent Aboubakar FC Lorient FC Lorient 2,64 Mill. \u00a3
\n", 178 | "

5 rows \u00d7 8 columns

\n", 179 | "
" 180 | ], 181 | "metadata": {}, 182 | "output_type": "pyout", 183 | "prompt_number": 5, 184 | "text": [ 185 | " type season club position player \\\n", 186 | "0 Arrivals 14/15 porto Right Wing Adri\u00e1n \n", 187 | "1 Arrivals 14/15 porto Centre Back Bruno Martins Indi \n", 188 | "2 Arrivals 14/15 porto Attacking Midfield Ot\u00e1vio \n", 189 | "3 Arrivals 14/15 porto Attacking Midfield Yacine Brahimi \n", 190 | "4 Arrivals 14/15 porto Centre Forward Vincent Aboubakar \n", 191 | "\n", 192 | " transfer_club transfer_club_alt fee \n", 193 | "0 Atl\u00e9tico Madrid Atl\u00e9tico Madrid 9,68 Mill. \u00a3 \n", 194 | "1 Feyenoord Rotterdam Feyenoord 6,78 Mill. \u00a3 \n", 195 | "2 Sport Club Internacional Internacional 6,16 Mill. \u00a3 \n", 196 | "3 Granada CF Granada CF 5,72 Mill. \u00a3 \n", 197 | "4 FC Lorient FC Lorient 2,64 Mill. \u00a3 \n", 198 | "\n", 199 | "[5 rows x 8 columns]" 200 | ] 201 | } 202 | ], 203 | "prompt_number": 5 204 | }, 205 | { 206 | "cell_type": "code", 207 | "collapsed": false, 208 | "input": [ 209 | "df.groupby('club').agg(pd.Series.count)" 210 | ], 211 | "language": "python", 212 | "metadata": {}, 213 | "outputs": [ 214 | { 215 | "html": [ 216 | "
\n", 217 | "\n", 218 | " \n", 219 | " \n", 220 | " \n", 221 | " \n", 222 | " \n", 223 | " \n", 224 | " \n", 225 | " \n", 226 | " \n", 227 | " \n", 228 | " \n", 229 | " \n", 230 | " \n", 231 | " \n", 232 | " \n", 233 | " \n", 234 | " \n", 235 | " \n", 236 | " \n", 237 | " \n", 238 | " \n", 239 | " \n", 240 | " \n", 241 | " \n", 242 | " \n", 243 | " \n", 244 | " \n", 245 | " \n", 246 | " \n", 247 | " \n", 248 | " \n", 249 | " \n", 250 | " \n", 251 | " \n", 252 | " \n", 253 | " \n", 254 | " \n", 255 | " \n", 256 | " \n", 257 | " \n", 258 | " \n", 259 | " \n", 260 | " \n", 261 | " \n", 262 | " \n", 263 | " \n", 264 | " \n", 265 | " \n", 266 | " \n", 267 | " \n", 268 | " \n", 269 | " \n", 270 | " \n", 271 | " \n", 272 | " \n", 273 | " \n", 274 | " \n", 275 | " \n", 276 | " \n", 277 | " \n", 278 | " \n", 279 | " \n", 280 | " \n", 281 | " \n", 282 | " \n", 283 | " \n", 284 | " \n", 285 | " \n", 286 | " \n", 287 | " \n", 288 | " \n", 289 | " \n", 290 | " \n", 291 | " \n", 292 | " \n", 293 | " \n", 294 | " \n", 295 | " \n", 296 | " \n", 297 | " \n", 298 | " \n", 299 | " \n", 300 | " \n", 301 | " \n", 302 | "
typeseasonpositionplayertransfer_clubtransfer_club_altfee
club
benfica 1149 1149 1149 1149 1149 1149 1149
chelsea 1187 1187 1187 1187 1187 1187 1187
man city 1279 1279 1279 1279 1279 1279 1279
man united 1253 1253 1253 1253 1253 1253 1253
porto 1053 1053 1053 1053 1053 1053 1053
sporting 881 881 881 881 881 881 881
\n", 303 | "

6 rows \u00d7 7 columns

\n", 304 | "
" 305 | ], 306 | "metadata": {}, 307 | "output_type": "pyout", 308 | "prompt_number": 6, 309 | "text": [ 310 | " type season position player transfer_club transfer_club_alt \\\n", 311 | "club \n", 312 | "benfica 1149 1149 1149 1149 1149 1149 \n", 313 | "chelsea 1187 1187 1187 1187 1187 1187 \n", 314 | "man city 1279 1279 1279 1279 1279 1279 \n", 315 | "man united 1253 1253 1253 1253 1253 1253 \n", 316 | "porto 1053 1053 1053 1053 1053 1053 \n", 317 | "sporting 881 881 881 881 881 881 \n", 318 | "\n", 319 | " fee \n", 320 | "club \n", 321 | "benfica 1149 \n", 322 | "chelsea 1187 \n", 323 | "man city 1279 \n", 324 | "man united 1253 \n", 325 | "porto 1053 \n", 326 | "sporting 881 \n", 327 | "\n", 328 | "[6 rows x 7 columns]" 329 | ] 330 | } 331 | ], 332 | "prompt_number": 6 333 | }, 334 | { 335 | "cell_type": "code", 336 | "collapsed": false, 337 | "input": [ 338 | "mults = {'Mill.': 1, 'Th.': 0.001}\n", 339 | "\n", 340 | "def translate_fee(fee):\n", 341 | " if 'Loan' in fee or 'loan' in fee:\n", 342 | " return np.NAN\n", 343 | " elif fee in ['Free transfer', '0', '-']:\n", 344 | " return 0\n", 345 | " elif fee in ['?', 'Tauschgesch\u00e4ft', 'Spielertausch']:\n", 346 | " return np.NAN\n", 347 | " else:\n", 348 | " try:\n", 349 | " value, mult, currency = fee.split(' ')\n", 350 | " return float(value.replace(',', '.')) * mults[mult]\n", 351 | " except:\n", 352 | " return np.NAN\n", 353 | " \n", 354 | " \n", 355 | "df['fee_value'] = df['fee'].map(translate_fee)\n", 356 | "\n", 357 | "countries = {'porto': 'pt',\n", 358 | " 'benfica': 'pt',\n", 359 | " 'sporting': 'pt',\n", 360 | " 'man city': 'en',\n", 361 | " 'man united': 'en',\n", 362 | " 'chelsea': 'en',\n", 363 | " }\n", 364 | "\n", 365 | "df['country'] = df['club'].map(countries.get)\n", 366 | "\n", 367 | "df.groupby('club').agg(pd.Series.count)" 368 | ], 369 | "language": "python", 370 | "metadata": {}, 371 | "outputs": [ 372 | { 373 | "output_type": "stream", 374 | "stream": "stderr", 375 | "text": [ 376 | "-c:8: UnicodeWarning: Unicode equal comparison failed to convert both arguments to Unicode - interpreting them as being unequal\n" 377 | ] 378 | }, 379 | { 380 | "html": [ 381 | "
\n", 382 | "\n", 383 | " \n", 384 | " \n", 385 | " \n", 386 | " \n", 387 | " \n", 388 | " \n", 389 | " \n", 390 | " \n", 391 | " \n", 392 | " \n", 393 | " \n", 394 | " \n", 395 | " \n", 396 | " \n", 397 | " \n", 398 | " \n", 399 | " \n", 400 | " \n", 401 | " \n", 402 | " \n", 403 | " \n", 404 | " \n", 405 | " \n", 406 | " \n", 407 | " \n", 408 | " \n", 409 | " \n", 410 | " \n", 411 | " \n", 412 | " \n", 413 | " \n", 414 | " \n", 415 | " \n", 416 | " \n", 417 | " \n", 418 | " \n", 419 | " \n", 420 | " \n", 421 | " \n", 422 | " \n", 423 | " \n", 424 | " \n", 425 | " \n", 426 | " \n", 427 | " \n", 428 | " \n", 429 | " \n", 430 | " \n", 431 | " \n", 432 | " \n", 433 | " \n", 434 | " \n", 435 | " \n", 436 | " \n", 437 | " \n", 438 | " \n", 439 | " \n", 440 | " \n", 441 | " \n", 442 | " \n", 443 | " \n", 444 | " \n", 445 | " \n", 446 | " \n", 447 | " \n", 448 | " \n", 449 | " \n", 450 | " \n", 451 | " \n", 452 | " \n", 453 | " \n", 454 | " \n", 455 | " \n", 456 | " \n", 457 | " \n", 458 | " \n", 459 | " \n", 460 | " \n", 461 | " \n", 462 | " \n", 463 | " \n", 464 | " \n", 465 | " \n", 466 | " \n", 467 | " \n", 468 | " \n", 469 | " \n", 470 | " \n", 471 | " \n", 472 | " \n", 473 | " \n", 474 | " \n", 475 | " \n", 476 | " \n", 477 | " \n", 478 | " \n", 479 | " \n", 480 | " \n", 481 | " \n", 482 | " \n", 483 | "
typeseasonpositionplayertransfer_clubtransfer_club_altfeefee_valuecountry
club
benfica 1149 1149 1149 1149 1149 1149 1149 439 1149
chelsea 1187 1187 1187 1187 1187 1187 1187 510 1187
man city 1279 1279 1279 1279 1279 1279 1279 547 1279
man united 1253 1253 1253 1253 1253 1253 1253 564 1253
porto 1053 1053 1053 1053 1053 1053 1053 396 1053
sporting 881 881 881 881 881 881 881 358 881
\n", 484 | "

6 rows \u00d7 9 columns

\n", 485 | "
" 486 | ], 487 | "metadata": {}, 488 | "output_type": "pyout", 489 | "prompt_number": 7, 490 | "text": [ 491 | " type season position player transfer_club transfer_club_alt \\\n", 492 | "club \n", 493 | "benfica 1149 1149 1149 1149 1149 1149 \n", 494 | "chelsea 1187 1187 1187 1187 1187 1187 \n", 495 | "man city 1279 1279 1279 1279 1279 1279 \n", 496 | "man united 1253 1253 1253 1253 1253 1253 \n", 497 | "porto 1053 1053 1053 1053 1053 1053 \n", 498 | "sporting 881 881 881 881 881 881 \n", 499 | "\n", 500 | " fee fee_value country \n", 501 | "club \n", 502 | "benfica 1149 439 1149 \n", 503 | "chelsea 1187 510 1187 \n", 504 | "man city 1279 547 1279 \n", 505 | "man united 1253 564 1253 \n", 506 | "porto 1053 396 1053 \n", 507 | "sporting 881 358 881 \n", 508 | "\n", 509 | "[6 rows x 9 columns]" 510 | ] 511 | } 512 | ], 513 | "prompt_number": 7 514 | }, 515 | { 516 | "cell_type": "code", 517 | "collapsed": false, 518 | "input": [ 519 | "pivot = pd.pivot_table(df, values='fee_value', rows=['player', 'club', 'country'], cols='type', aggfunc=np.mean).dropna().reset_index()\n", 520 | "pivotPT = pivot[pivot.country=='pt']\n", 521 | "pivotEN = pivot[pivot.country=='en']\n", 522 | "pivot.describe()" 523 | ], 524 | "language": "python", 525 | "metadata": {}, 526 | "outputs": [ 527 | { 528 | "html": [ 529 | "
\n", 530 | "\n", 531 | " \n", 532 | " \n", 533 | " \n", 534 | " \n", 535 | " \n", 536 | " \n", 537 | " \n", 538 | " \n", 539 | " \n", 540 | " \n", 541 | " \n", 542 | " \n", 543 | " \n", 544 | " \n", 545 | " \n", 546 | " \n", 547 | " \n", 548 | " \n", 549 | " \n", 550 | " \n", 551 | " \n", 552 | " \n", 553 | " \n", 554 | " \n", 555 | " \n", 556 | " \n", 557 | " \n", 558 | " \n", 559 | " \n", 560 | " \n", 561 | " \n", 562 | " \n", 563 | " \n", 564 | " \n", 565 | " \n", 566 | " \n", 567 | " \n", 568 | " \n", 569 | " \n", 570 | " \n", 571 | " \n", 572 | " \n", 573 | " \n", 574 | " \n", 575 | " \n", 576 | " \n", 577 | " \n", 578 | " \n", 579 | " \n", 580 | "
typeArrivalsDepartures
count 1048.000000 1048.000000
mean 2.128931 2.245259
std 4.964249 6.073548
min 0.000000 0.000000
25% 0.000000 0.000000
50% 0.000000 0.000000
75% 1.760000 1.580000
max 40.480000 82.720000
\n", 581 | "

8 rows \u00d7 2 columns

\n", 582 | "
" 583 | ], 584 | "metadata": {}, 585 | "output_type": "pyout", 586 | "prompt_number": 8, 587 | "text": [ 588 | "type Arrivals Departures\n", 589 | "count 1048.000000 1048.000000\n", 590 | "mean 2.128931 2.245259\n", 591 | "std 4.964249 6.073548\n", 592 | "min 0.000000 0.000000\n", 593 | "25% 0.000000 0.000000\n", 594 | "50% 0.000000 0.000000\n", 595 | "75% 1.760000 1.580000\n", 596 | "max 40.480000 82.720000\n", 597 | "\n", 598 | "[8 rows x 2 columns]" 599 | ] 600 | } 601 | ], 602 | "prompt_number": 8 603 | }, 604 | { 605 | "cell_type": "code", 606 | "collapsed": false, 607 | "input": [ 608 | "dfClubs = pivot.groupby('club').agg(np.sum)\n", 609 | "dfClubs['Profit %']=(dfClubs['Departures']/dfClubs['Arrivals']-1)*100\n", 610 | "dfClubs" 611 | ], 612 | "language": "python", 613 | "metadata": {}, 614 | "outputs": [ 615 | { 616 | "html": [ 617 | "
\n", 618 | "\n", 619 | " \n", 620 | " \n", 621 | " \n", 622 | " \n", 623 | " \n", 624 | " \n", 625 | " \n", 626 | " \n", 627 | " \n", 628 | " \n", 629 | " \n", 630 | " \n", 631 | " \n", 632 | " \n", 633 | " \n", 634 | " \n", 635 | " \n", 636 | " \n", 637 | " \n", 638 | " \n", 639 | " \n", 640 | " \n", 641 | " \n", 642 | " \n", 643 | " \n", 644 | " \n", 645 | " \n", 646 | " \n", 647 | " \n", 648 | " \n", 649 | " \n", 650 | " \n", 651 | " \n", 652 | " \n", 653 | " \n", 654 | " \n", 655 | " \n", 656 | " \n", 657 | " \n", 658 | " \n", 659 | " \n", 660 | " \n", 661 | " \n", 662 | " \n", 663 | " \n", 664 | " \n", 665 | " \n", 666 | " \n", 667 | " \n", 668 | " \n", 669 | " \n", 670 | " \n", 671 | "
typeArrivalsDeparturesProfit %
club
benfica 173.825667 408.708 135.125231
chelsea 767.142000 453.531 -40.880437
man city 539.957000 251.616 -53.400734
man united 436.694000 400.773 -8.225668
porto 206.609000 641.992 210.727993
sporting 106.892000 196.411 83.747147
\n", 672 | "

6 rows \u00d7 3 columns

\n", 673 | "
" 674 | ], 675 | "metadata": {}, 676 | "output_type": "pyout", 677 | "prompt_number": 9, 678 | "text": [ 679 | "type Arrivals Departures Profit %\n", 680 | "club \n", 681 | "benfica 173.825667 408.708 135.125231\n", 682 | "chelsea 767.142000 453.531 -40.880437\n", 683 | "man city 539.957000 251.616 -53.400734\n", 684 | "man united 436.694000 400.773 -8.225668\n", 685 | "porto 206.609000 641.992 210.727993\n", 686 | "sporting 106.892000 196.411 83.747147\n", 687 | "\n", 688 | "[6 rows x 3 columns]" 689 | ] 690 | } 691 | ], 692 | "prompt_number": 9 693 | }, 694 | { 695 | "cell_type": "code", 696 | "collapsed": false, 697 | "input": [ 698 | "from matplotlib import style\n", 699 | "print style.available" 700 | ], 701 | "language": "python", 702 | "metadata": {}, 703 | "outputs": [ 704 | { 705 | "output_type": "stream", 706 | "stream": "stdout", 707 | "text": [ 708 | "[u'dark_background', u'bmh', u'grayscale', u'ggplot', u'fivethirtyeight']\n" 709 | ] 710 | } 711 | ], 712 | "prompt_number": 10 713 | }, 714 | { 715 | "cell_type": "code", 716 | "collapsed": false, 717 | "input": [ 718 | "style.use('ggplot')\n", 719 | "\n", 720 | "fig, ax = plt.subplots(figsize = (7.5,7.5))\n", 721 | "\n", 722 | "plt.title('Transfer Fee of players when arrived vs. left the club')\n", 723 | "\n", 724 | "plt.scatter(pivotPT.Arrivals, pivotPT.Departures, color = \"#8EBA42\", alpha=0.5)\n", 725 | "plt.scatter(pivotEN.Arrivals, pivotEN.Departures, color = \"#E24A33\", alpha=0.5)\n", 726 | "\n", 727 | "ax.set_ylim(-1, 90)\n", 728 | "ax.set_xlim(90, -1)\n", 729 | "ax.set_ylabel(u'Departure Fee (\u00a3M)')\n", 730 | "ax.set_xlabel(u'Arrival Fee (\u00a3M)')\n", 731 | "ax.arrow(70, 70, -4, 4, head_width=1, head_length=1, color=\"black\")\n", 732 | "ax.arrow(70, 70, 4, -4, head_width=1, head_length=1, color=\"black\")\n", 733 | "ax.annotate(\"increase in value\", (67, 75.5))\n", 734 | "ax.annotate(\"decrease in value\", (85, 63.5), )\n", 735 | "ax.legend(['Portuguese Clubs', 'English Clubs'], loc='lower left')\n", 736 | "ax.add_line(Line2D((0,90), (0,90), color=\"gray\", alpha=0.3))" 737 | ], 738 | "language": "python", 739 | "metadata": {}, 740 | "outputs": [ 741 | { 742 | "metadata": {}, 743 | "output_type": "pyout", 744 | "prompt_number": 16, 745 | "text": [ 746 | "" 747 | ] 748 | }, 749 | { 750 | "metadata": {}, 751 | "output_type": "display_data", 752 | "png": "iVBORw0KGgoAAAANSUhEUgAAAdYAAAHkCAYAAACDqA78AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzs3XlcVPX+P/DXGfZl2MFEMHBFMdQ067oAIiomZlfN0iys\nTK+2mNZV0yyt2+L3ZqZmee3aomVXK8XUUskF11/hiiIKorgriIrKMszy+f3BZS7DMgw4+7yej4cP\nmTMzZ97nMwOv+XzO55wjCSEEiIiIyChkli6AiIjInjBYiYiIjIjBSkREZEQMViIiIiNisBIRERkR\ng5WIiMiIGKw2YPHixQgLC4OTkxPeffddS5djVsba9vj4eLz44otGrMy62dv2jh07Fv379zfLa8lk\nMqxatcosr1Wf/Px8yGQy7Nu3r1HPO3bsGHr06AEPDw+0atXqnuuYM2cO2rZte8/raYqIiAi8//77\njXqOtXzunS1dgDWQyfR/v4iIiMCZM2fMVI2uy5cv47XXXsNnn32GYcOGwdvb2+ivUdf2u7u7o7S0\n1Oiv1RjG3HZJkiBJkhGrs272tr2LFy+GRqOxdBlWb9q0afDz88OpU6fg5eWF7777Ds8++2yDbXfx\n4kW0bNkSO3fuRGxsrJmq1a8pn2Fr+dwzWAFcvXpV+/PevXsxfPhwHD58GM2bNwdQO3gqKirg6upq\nltrOnDkDIQSGDBmCZs2aNXk9SqUSLi4u9d6/ZMkSDB8+XHvbGj6cxtp2a9LQ++DIVCoVnJ11/yRV\ntZdcLrdQVbbl9OnTSElJQcuWLZv0fJ4vyDg4FAwgJCRE+8/f3x8AEBwcrLN88eLFGD16NPz8/JCS\nkgIAmDVrFjp27AgvLy+0bNkSEydOxO3bt7Xr/eabb+Di4oJ9+/bhwQcfhJeXF7p3744DBw5oH6NU\nKjF16lSEh4fD3d0doaGhGDVqFIDKYZiqb48tW7aETCbD+fPnAQBpaWno1asXPD09ERYWhueffx43\nbtzQrrdq6Gzx4sWIiIiAu7s7FApFvW3g6+urs73BwcHa+xYvXoyoqCh4eHigXbt2+OCDD6BWq3W2\nYc6cOWjVqhU8PDzQqVMnLFu2rMF2//XXX9GtWze4u7ujWbNmeOmll7S9ZH3bXlNERATeeustjBs3\nDr6+vggODsasWbP0/pFIS0tDfHw8AgMD4efnh/j4eGRkZOi038CBA2s9LyEhAePGjdNZT2PeBw8P\nDygUCuzZswe9evWCj48PfHx80KVLF2zdurXOWsvKyuDm5obff/9duywuLg7u7u4oKysDAJSWlsLV\n1RVpaWnaxwgh8N5776F58+YIDAxESkoKSkpKdNb9n//8B126dIGHhwciIyPx+uuv64xUVA2tNbSe\nmhYuXIiuXbtCLpejefPmGDVqlM4X2J07d0Imk+HXX39F79694eHhgX//+991tld5ebnOUHBaWhqc\nnZ1x6dIlnddcvXo1vLy8cPfuXQDAtWvXMHbsWISEhMDHxwe9e/fG7t27dZ6zY8cOxMTEwMPDA507\nd8aOHTv0bldubi5kMhn279+vs/yPP/6ATCZDXl4eAODf//43OnToAA8PDwQGBiIuLq5WvY2lb3uq\nho7z8vLw9ttvQyaToW/fvnj22WcBVHYOZDIZnn/++TrXXRXEffv2hUwmqzWM/MsvvyAqKgre3t7o\n27cvTp8+rXP/wYMHMWDAAMjlcoSEhGD48OH1/r5WUalUmDt3Llq3bg13d3eEhYXh1VdfrffxdQ0N\njxs3Dn379tVZplarMWPGDAQHB8PX1xcTJkzQ+7fPJATp2LFjh5AkSVy6dEm7TJIkERgYKJYsWSLO\nnDkjTp8+LYQQ4h//+IfYs2ePOHfunNi2bZuIiooSKSkp2ud9/fXXQiaTibi4OLFnzx5x8uRJMWjQ\nIBEZGSnUarUQQoj58+eLsLAwkZ6eLi5cuCAyMjLEwoULhRBC3L17V6xdu1ZIkiSOHDkirl27JtRq\ntdi2bZvw9PQUn332mTh9+rTIyMgQffv2FXFxcdrXTklJET4+PmLYsGEiMzNTHD9+XPuaNUmSJL77\n7rs673vnnXfE/fffL1JTU0V+fr749ddfRcuWLcXs2bN1Xqtz584iLS1N5Ofni9WrVws/Pz+xfPny\netv56NGjwsnJSUydOlWcOnVK/Pbbb6Jly5bimWee0bvtdbn//vuFj4+PeOedd0ROTo5YuXKl8PLy\n0rajEELEx8eLF198UXt73bp14scffxQ5OTnixIkTYty4cSIgIEAUFRUJIYTYv3+/kMlk4uzZs9rn\n5ObmCplMJv78808hhGjy+1BeXi78/f3F66+/Lk6fPi1Onz4tUlNTxe7du+ttr9jYWPHmm28KIYQo\nLS0Vrq6uolmzZmLr1q1CCCE2b94sXF1dRVlZmRBCiLi4OOHn56dt361bt4qAgACd9+3rr78W/v7+\n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\nAABJRU5ErkJggg==\n", 753 | "text": [ 754 | "" 755 | ] 756 | } 757 | ], 758 | "prompt_number": 16 759 | }, 760 | { 761 | "cell_type": "code", 762 | "collapsed": false, 763 | "input": [ 764 | "pivot[pivot.Arrivals == 0].groupby('club').agg({'player': pd.Series.count,\n", 765 | " 'Departures': np.mean})" 766 | ], 767 | "language": "python", 768 | "metadata": {}, 769 | "outputs": [ 770 | { 771 | "html": [ 772 | "
\n", 773 | "\n", 774 | " \n", 775 | " \n", 776 | " \n", 777 | " \n", 778 | " \n", 779 | " \n", 780 | " \n", 781 | " \n", 782 | " \n", 783 | " \n", 784 | " \n", 785 | " \n", 786 | " \n", 787 | " \n", 788 | " \n", 789 | " \n", 790 | " \n", 791 | " \n", 792 | " \n", 793 | " \n", 794 | " \n", 795 | " \n", 796 | " \n", 797 | " \n", 798 | " \n", 799 | " \n", 800 | " \n", 801 | " \n", 802 | " \n", 803 | " \n", 804 | " \n", 805 | " \n", 806 | " \n", 807 | " \n", 808 | " \n", 809 | " \n", 810 | " \n", 811 | " \n", 812 | " \n", 813 | " \n", 814 | " \n", 815 | " \n", 816 | " \n", 817 | " \n", 818 | "
playerDepartures
club
benfica 82 0.874476
chelsea 91 0.677357
man city 110 0.411364
man united 132 0.892356
porto 50 2.031920
sporting 75 1.635387
\n", 819 | "

6 rows \u00d7 2 columns

\n", 820 | "
" 821 | ], 822 | "metadata": {}, 823 | "output_type": "pyout", 824 | "prompt_number": 17, 825 | "text": [ 826 | " player Departures\n", 827 | "club \n", 828 | "benfica 82 0.874476\n", 829 | "chelsea 91 0.677357\n", 830 | "man city 110 0.411364\n", 831 | "man united 132 0.892356\n", 832 | "porto 50 2.031920\n", 833 | "sporting 75 1.635387\n", 834 | "\n", 835 | "[6 rows x 2 columns]" 836 | ] 837 | } 838 | ], 839 | "prompt_number": 17 840 | }, 841 | { 842 | "cell_type": "code", 843 | "collapsed": false, 844 | "input": [ 845 | "pivot['Profit'] = pivot['Departures']-pivot['Arrivals']\n", 846 | "pivot.sort('Profit').head(10)" 847 | ], 848 | "language": "python", 849 | "metadata": {}, 850 | "outputs": [ 851 | { 852 | "html": [ 853 | "
\n", 854 | "\n", 855 | " \n", 856 | " \n", 857 | " \n", 858 | " \n", 859 | " \n", 860 | " \n", 861 | " \n", 862 | " \n", 863 | " \n", 864 | " \n", 865 | " \n", 866 | " \n", 867 | " \n", 868 | " \n", 869 | " \n", 870 | " \n", 871 | " \n", 872 | " \n", 873 | " \n", 874 | " \n", 875 | " \n", 876 | " \n", 877 | " \n", 878 | " \n", 879 | " \n", 880 | " \n", 881 | " \n", 882 | " \n", 883 | " \n", 884 | " \n", 885 | " \n", 886 | " \n", 887 | " \n", 888 | " \n", 889 | " \n", 890 | " \n", 891 | " \n", 892 | " \n", 893 | " \n", 894 | " \n", 895 | " \n", 896 | " \n", 897 | " \n", 898 | " \n", 899 | " \n", 900 | " \n", 901 | " \n", 902 | " \n", 903 | " \n", 904 | " \n", 905 | " \n", 906 | " \n", 907 | " \n", 908 | " \n", 909 | " \n", 910 | " \n", 911 | " \n", 912 | " \n", 913 | " \n", 914 | " \n", 915 | " \n", 916 | " \n", 917 | " \n", 918 | " \n", 919 | " \n", 920 | " \n", 921 | " \n", 922 | " \n", 923 | " \n", 924 | " \n", 925 | " \n", 926 | " \n", 927 | " \n", 928 | " \n", 929 | " \n", 930 | " \n", 931 | " \n", 932 | " \n", 933 | " \n", 934 | " \n", 935 | " \n", 936 | " \n", 937 | " \n", 938 | " \n", 939 | " \n", 940 | " \n", 941 | " \n", 942 | " \n", 943 | " \n", 944 | " \n", 945 | " \n", 946 | " \n", 947 | " \n", 948 | " \n", 949 | " \n", 950 | " \n", 951 | " \n", 952 | " \n", 953 | " \n", 954 | " \n", 955 | " \n", 956 | " \n", 957 | " \n", 958 | "
typeplayerclubcountryArrivalsDeparturesProfit
52 Andriy Shevchenko chelsea en 40.48 0.00-40.48
854 Rio Ferdinand man united en 40.48 0.00-40.48
661 Michael Essien chelsea en 33.44 0.00-33.44
254 Dimitar Berbatov man united en 33.44 4.40-29.04
471 Joleon Lescott man city en 24.20 0.00-24.20
394 Hern\u00e1n Crespo chelsea en 22.88 0.00-22.88
866 Robinho man city en 37.84 15.84-22.00
745 Owen Hargreaves man united en 22.00 0.00-22.00
517 J\u00f4 man city en 21.12 0.00-21.12
290 Emmanuel Adebayor man city en 25.52 5.63-19.89
\n", 959 | "

10 rows \u00d7 6 columns

\n", 960 | "
" 961 | ], 962 | "metadata": {}, 963 | "output_type": "pyout", 964 | "prompt_number": 18, 965 | "text": [ 966 | "type player club country Arrivals Departures Profit\n", 967 | "52 Andriy Shevchenko chelsea en 40.48 0.00 -40.48\n", 968 | "854 Rio Ferdinand man united en 40.48 0.00 -40.48\n", 969 | "661 Michael Essien chelsea en 33.44 0.00 -33.44\n", 970 | "254 Dimitar Berbatov man united en 33.44 4.40 -29.04\n", 971 | "471 Joleon Lescott man city en 24.20 0.00 -24.20\n", 972 | "394 Hern\u00e1n Crespo chelsea en 22.88 0.00 -22.88\n", 973 | "866 Robinho man city en 37.84 15.84 -22.00\n", 974 | "745 Owen Hargreaves man united en 22.00 0.00 -22.00\n", 975 | "517 J\u00f4 man city en 21.12 0.00 -21.12\n", 976 | "290 Emmanuel Adebayor man city en 25.52 5.63 -19.89\n", 977 | "\n", 978 | "[10 rows x 6 columns]" 979 | ] 980 | } 981 | ], 982 | "prompt_number": 18 983 | }, 984 | { 985 | "cell_type": "code", 986 | "collapsed": false, 987 | "input": [ 988 | "pivot.sort('Profit', ascending=False).head(10)" 989 | ], 990 | "language": "python", 991 | "metadata": {}, 992 | "outputs": [ 993 | { 994 | "html": [ 995 | "
\n", 996 | "\n", 997 | " \n", 998 | " \n", 999 | " \n", 1000 | " \n", 1001 | " \n", 1002 | " \n", 1003 | " \n", 1004 | " \n", 1005 | " \n", 1006 | " \n", 1007 | " \n", 1008 | " \n", 1009 | " \n", 1010 | " \n", 1011 | " \n", 1012 | " \n", 1013 | " \n", 1014 | " \n", 1015 | " \n", 1016 | " \n", 1017 | " \n", 1018 | " \n", 1019 | " \n", 1020 | " \n", 1021 | " \n", 1022 | " \n", 1023 | " \n", 1024 | " \n", 1025 | " \n", 1026 | " \n", 1027 | " \n", 1028 | " \n", 1029 | " \n", 1030 | " \n", 1031 | " \n", 1032 | " \n", 1033 | " \n", 1034 | " \n", 1035 | " \n", 1036 | " \n", 1037 | " \n", 1038 | " \n", 1039 | " \n", 1040 | " \n", 1041 | " \n", 1042 | " \n", 1043 | " \n", 1044 | " \n", 1045 | " \n", 1046 | " \n", 1047 | " \n", 1048 | " \n", 1049 | " \n", 1050 | " \n", 1051 | " \n", 1052 | " \n", 1053 | " \n", 1054 | " \n", 1055 | " \n", 1056 | " \n", 1057 | " \n", 1058 | " \n", 1059 | " \n", 1060 | " \n", 1061 | " \n", 1062 | " \n", 1063 | " \n", 1064 | " \n", 1065 | " \n", 1066 | " \n", 1067 | " \n", 1068 | " \n", 1069 | " \n", 1070 | " \n", 1071 | " \n", 1072 | " \n", 1073 | " \n", 1074 | " \n", 1075 | " \n", 1076 | " \n", 1077 | " \n", 1078 | " \n", 1079 | " \n", 1080 | " \n", 1081 | " \n", 1082 | " \n", 1083 | " \n", 1084 | " \n", 1085 | " \n", 1086 | " \n", 1087 | " \n", 1088 | " \n", 1089 | " \n", 1090 | " \n", 1091 | " \n", 1092 | " \n", 1093 | " \n", 1094 | " \n", 1095 | " \n", 1096 | " \n", 1097 | " \n", 1098 | " \n", 1099 | " \n", 1100 | "
typeplayerclubcountryArrivalsDeparturesProfit
172 Cristiano Ronaldo man united en 15.400 82.72 67.320
284 Eliaquim Mangala porto pt 5.720 47.36 41.640
309 Falcao porto pt 4.780 41.36 36.580
427 James Rodr\u00edguez porto pt 6.470 39.60 33.130
213 David Beckham man united en 0.000 33.00 33.000
401 Hulk porto pt 16.720 48.40 31.680
81 Axel Witsel benfica pt 7.920 35.20 27.280
844 Ricardo Carvalho porto pt 0.000 26.40 26.400
336 F\u00e1bio Coentr\u00e3o benfica pt 0.792 26.40 25.608
799 Pepe porto pt 1.760 26.40 24.640
\n", 1101 | "

10 rows \u00d7 6 columns

\n", 1102 | "
" 1103 | ], 1104 | "metadata": {}, 1105 | "output_type": "pyout", 1106 | "prompt_number": 19, 1107 | "text": [ 1108 | "type player club country Arrivals Departures Profit\n", 1109 | "172 Cristiano Ronaldo man united en 15.400 82.72 67.320\n", 1110 | "284 Eliaquim Mangala porto pt 5.720 47.36 41.640\n", 1111 | "309 Falcao porto pt 4.780 41.36 36.580\n", 1112 | "427 James Rodr\u00edguez porto pt 6.470 39.60 33.130\n", 1113 | "213 David Beckham man united en 0.000 33.00 33.000\n", 1114 | "401 Hulk porto pt 16.720 48.40 31.680\n", 1115 | "81 Axel Witsel benfica pt 7.920 35.20 27.280\n", 1116 | "844 Ricardo Carvalho porto pt 0.000 26.40 26.400\n", 1117 | "336 F\u00e1bio Coentr\u00e3o benfica pt 0.792 26.40 25.608\n", 1118 | "799 Pepe porto pt 1.760 26.40 24.640\n", 1119 | "\n", 1120 | "[10 rows x 6 columns]" 1121 | ] 1122 | } 1123 | ], 1124 | "prompt_number": 19 1125 | }, 1126 | { 1127 | "cell_type": "code", 1128 | "collapsed": false, 1129 | "input": [], 1130 | "language": "python", 1131 | "metadata": {}, 1132 | "outputs": [] 1133 | } 1134 | ], 1135 | "metadata": {} 1136 | } 1137 | ] 1138 | } --------------------------------------------------------------------------------