The Tabular Playground Series of this month integrates an interesting dataset, a dataset of Rocket League gameplay. This month's challenge is to, given a snapshot from a Rocket League match, predict the probability of each team scoring within the next 10 seconds of the game.
The dataset consists of sequences of snapshots of the state of a Rocket League match, including position and velocity of all players and the ball, as well as extra information. The data was taken from professional Rocket League matches. Each event consists of a chronological series of frames recorded at 10 frames per second. All events begin with a kickoff, and most end in one team scoring a goal, but some are truncated and end with no goal scored due to circumstances which can cause gameplay strategies to shift.
Files:
Columns Description from train/test files:
Position and Velocity data from ball and players:
Additional Data:
For i in [0,5]:
Note: All p{i} columns will be NaN if and only if the player is demolished (destroyed by an enemy player; will respawn within a few seconds).
Distribution of team players:
Note: The orientation vector of the player's car (which way the car is facing) does not necessarily match the player's velocity vector, and this dataset does not capture orientation data.
For i in [0, 6):
Note: The orb (x, y) locations are roughly [(-61.4, -81.9),(61.4, -81.9),(-71.7, 0),(71.7, 0),(-61.4, 81.9),(61.4, 81.9)] with z = 0. (Players can also gain boost from small boost pads across the map).
Target Columns
dtypes = {'game_num': 'int32', 'event_id': 'int32',
'event_time': 'float16', 'ball_pos_x': 'float16',
'ball_pos_y': 'float16', 'ball_pos_z': 'float16',
'ball_vel_x': 'float16', 'ball_vel_y': 'float16',
'ball_vel_z': 'float16', 'p0_pos_x': 'float16',
'p0_pos_y': 'float16', 'p0_pos_z': 'float16',
'p0_vel_x': 'float16', 'p0_vel_y': 'float16',
'p0_vel_z': 'float16', 'p0_boost': 'float16',
'p1_pos_x': 'float16', 'p1_pos_y': 'float16',
'p1_pos_z': 'float16', 'p1_vel_x': 'float16',
'p1_vel_y': 'float16', 'p1_vel_z': 'float16',
'p1_boost': 'float16', 'p2_pos_x': 'float16',
'p2_pos_y': 'float16', 'p2_pos_z': 'float16',
'p2_vel_x': 'float16', 'p2_vel_y': 'float16',
'p2_vel_z': 'float16', 'p2_boost': 'float16',
'p3_pos_x': 'float16', 'p3_pos_y': 'float16',
'p3_pos_z': 'float16', 'p3_vel_x': 'float16',
'p3_vel_y': 'float16', 'p3_vel_z': 'float16',
'p3_boost': 'float16', 'p4_pos_x': 'float16',
'p4_pos_y': 'float16', 'p4_pos_z': 'float16',
'p4_vel_x': 'float16', 'p4_vel_y': 'float16',
'p4_vel_z': 'float16', 'p4_boost': 'float16',
'p5_pos_x': 'float16', 'p5_pos_y': 'float16',
'p5_pos_z': 'float16', 'p5_vel_x': 'float16',
'p5_vel_y': 'float16', 'p5_vel_z': 'float16',
'p5_boost': 'float16', 'boost0_timer': 'float16',
'boost1_timer': 'float16', 'boost2_timer': 'float16',
'boost3_timer': 'float16', 'boost4_timer': 'float16',
'boost5_timer': 'float16', 'player_scoring_next': 'int8',
'team_scoring_next': 'object',
'team_A_scoring_within_10sec': 'int8',
'team_B_scoring_within_10sec': 'int8'}
import pandas as pd
train_df = pd.DataFrame()
for i in range(10):
temp_df = pd.read_csv(f'../input/tabular-playground-series-oct-2022/train_{i}.csv', dtype=dtypes) # memory usage: 440.7+ MB, 264.4+ MB new
train_df = pd.concat([train_df, temp_df], ignore_index=True)
del temp_df
print(f'train {i}')
train 0 train 1 train 2 train 3 train 4 train 5 train 6 train 7 train 8 train 9
train_df.info(verbose=False)
<class 'pandas.core.frame.DataFrame'> RangeIndex: 21198036 entries, 0 to 21198035 Columns: 61 entries, game_num to team_B_scoring_within_10sec dtypes: float16(55), int32(2), int8(3), object(1) memory usage: 2.5+ GB
train_df = pd.concat([train0_df, train1_df, train2_df, train3_df, train4_df,
train5_df, train6_df, train7_df, train5_df, train6_df, train7_df])
train_df.reset_index(inplace=True)
train_df.to_hdf('train_complete_v1.hdf', key='df', mode='w')
Note: Each event consists of a chronological series of frames recorded at 10 frames per second. However, Rocket League is a fast game, but it do not involve relativistic particles. An adequate downsampling from the train dataset do not neccesarilly represent a mistake and can help to reduce model training / data analysis.
In this specific work the selected downsampling transform the 10 frames per second recorded to ~3 frames per second. Given the chronological order of the dataset rows, this can be done via df = df[::4].
import numpy as np
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
print('Number of games: ', len(train_df['game_num'].unique()))
print('Number of events: ', len(train_df['event_id'].unique()))
Number of games: 7365 Number of events: 30528
from time import sleep
from IPython.display import display, clear_output
def show_event(event_id):
f,ax=plt.subplots(figsize=(13,5))
temp2 = train_df[(train_df['event_id']==event_id)] #&(train_df['team_A_scoring_within_10sec']==1)]
def plot_obj(x):
t1 = temp2.iloc[x]
ax.text(-130,70, str(t1['event_time']) + ' sec')
for p in [0,1,2]:
ax.scatter(t1[f'p{p}_pos_y'],t1[f'p{p}_pos_x'], color='darkred')
ax.text(t1[f'p{p}_pos_y'],t1[f'p{p}_pos_x']+5, f'Player{p}')
ax.arrow(t1[f'p{p}_pos_y'],t1[f'p{p}_pos_x'],t1[f'p{p}_vel_y']//3,t1[f'p{p}_vel_x']/3, width= 1 ,color='darkred')
for p in [3,4,5]:
ax.scatter(t1[f'p{p}_pos_y'],t1[f'p{p}_pos_x'], color='navy')
ax.text(t1[f'p{p}_pos_y'],t1[f'p{p}_pos_x']+5, f'Player{p}')
ax.arrow(t1[f'p{p}_pos_y'],t1[f'p{p}_pos_x'],t1[f'p{p}_vel_y']//3,t1[f'p{p}_vel_x']/3, width= 1,color='navy')
ax.scatter(t1['ball_pos_y'],t1['ball_pos_x'], color='black',s=60)
ax.text(t1['ball_pos_y'],t1['ball_pos_x']+5, f'Ball')
ax.arrow(t1['ball_pos_y'],t1['ball_pos_x'],t1['ball_vel_y']/3,t1['ball_vel_x']/3, width= 1, color='black')
for i in range(temp2.shape[0]//4):
ax.clear()
ax.set_ylim([-100,100]); ax.set_xlim([-140,140])
ax.vlines([-105,105],color='green', ymin=-82, ymax=82)
#ax.hlines([0],color='black', xmin=-105, xmax=105, ls='dashed', lw=1)
ax.text(-125,0,'Score for\nTeam B')
ax.text(110,0,'Score for\nTeam A')
ax.hlines([-82,82],color='green', xmin=-105, xmax=105)
plot_obj(i*4)
display(f)
clear_output(wait = True)
show_event(event_id=1002)
def plot_player_positions(data_temp, t_title, cmin=0, cmax=1700):
# fill values in order to plot, do not consider it much
for i in range(0,6):
for p in 'xyz':
data_temp[f'p{i}_pos_{p}'].fillna(value=-150, inplace=True) # outside field
data_temp[f'p{i}_vel_{p}'].fillna(value=0, inplace=True)
data_temp[f'p{i}_boost'].fillna(value=0, inplace=True)
range_x = np.linspace(-105,105,106)
range_y = np.linspace(-82,82,83)
f,ax=plt.subplots(ncols=2, figsize=((105*2)//14,(82)//14))
h0 = ax[0].hist2d(pd.concat([data_temp['p0_pos_y'],data_temp['p1_pos_y'],data_temp['p2_pos_y']]),
pd.concat([data_temp['p0_pos_x'],data_temp['p1_pos_x'],data_temp['p2_pos_x']]),
bins=[range_x,range_y],cmax=cmax, cmin=cmin)
h1 = ax[1].hist2d(pd.concat([data_temp['p3_pos_y'],data_temp['p4_pos_y'],data_temp['p5_pos_y']]),
pd.concat([data_temp['p3_pos_x'],data_temp['p4_pos_x'],data_temp['p5_pos_x']]),
bins=[range_x,range_y],cmax=cmax, cmin=cmin)
#f.colorbar(h0[3],ax=ax[0])
#f.colorbar(h1[3],ax=ax[1])
for i in range(2):
ax[i].set_xlabel('y axis')
ax[i].set_ylabel('x axis')
ax[0].set_title('Team A players')
ax[1].set_title('Team B players')
f.suptitle(t_title, fontsize=16)
plt.show()
data_A = train_df[(train_df['team_A_scoring_within_10sec']==1)].copy()
data_B = train_df[(train_df['team_B_scoring_within_10sec']==1)].copy()
plot_player_positions(data_A, 'Team A scoring')
# Team B winner
plot_player_positions(data_B, 'Team B scoring')
plot_player_positions(data_N,cmin=0, cmax=45000)
data = train_df[train_df['boost5_timer']==0].copy()
plot_player_positions(data, 'Boost5 position', cmin=0, cmax=40000)
Then this is the distribution of orbs.
### Estimation of boost position
x= [-61.4, 61.4, -71.7, 71.7, -61.4, 61.4]
y= [-81.9, -81.9, 0, 0, 81.9, 81.9]
f,ax=plt.subplots()
ax.plot(y,x,marker='o',lw=0)
for i in range(6):
ax.text(y[i],x[i],f'boost {i}',fontsize=20)
# define distance from center to ball position to avoid kickoff snapshots
train_df['dist_center'] = np.linalg.norm(train_df[['ball_pos_x','ball_pos_y','ball_pos_z']], axis=1)
# set scoring scenarios
data_A = train_df[(train_df['team_A_scoring_within_10sec']==1)&(train_df['ball_pos_x']!=0)&(train_df['dist_center']>10)]
data_B = train_df[(train_df['team_B_scoring_within_10sec']==1)&(train_df['ball_pos_x']!=0)&(train_df['dist_center']>10)]
def contour_ball_positions(data_A, data_B):#,cmin=0,cmax=1700):
range_x = np.linspace(-105,105,106)
range_y = np.linspace(-82,82,83)
range_z = np.linspace(0,80,42)
fig,ax=plt.subplots(figsize=(15,10), ncols=2, nrows=2)
for cnt, data_temp in enumerate([data_A, data_B]):
h = np.histogram2d(data_temp['ball_pos_y'], data_temp['ball_pos_x'],bins=[range_x,range_y])
Y, X = np.meshgrid(h[1][:-1], h[2][:-1], indexing='ij')
cp = ax[0][cnt].contourf(Y, X, h[0], levels=6)
fig.colorbar(cp, ax=ax[0][cnt])
h = np.histogram2d(data_temp['ball_pos_y'], data_temp['ball_pos_z'],bins=[range_x,range_y])
Y, Z = np.meshgrid(h[1][:-1], h[2][:-1], indexing='ij')
cp = ax[1][cnt].contourf(Y, Z, h[0], levels=5)
fig.colorbar(cp, ax=ax[1][cnt])
for i in range(2):
for j in range(2):
ax[i][j].set_xlim([-105,105])
ax[0][0].set_ylim([-82,82])
ax[0][1].set_ylim([-82,82])
ax[1][0].set_ylim([0,80])
ax[1][1].set_ylim([0,80])
ax[0][0].set_title('Team A scoring')
ax[0][1].set_title('Team B scoring')
return fig, ax
f, ax = contour_ball_positions(data_A, data_B)
# (x**2)/a**2 + (y**2)/b**2 + (z)**2/c**2 = r**2+y_shift
def plot_sphr(a,b,c,y_shift):
x_r_pos = lambda y: np.sqrt(1 - (y - y_shift)**2/b**2)*a
x_r_neg = lambda y: -np.sqrt(1 - (y - y_shift)**2/b**2)*a
z_r_pos = lambda y: np.sqrt(1 - (y - y_shift)**2/b**2)*c
#z_r_neg = lambda y: -np.sqrt(1 - (y)**2/b**2)*c**2
# inverted
x_r_pos_inv = lambda y: np.sqrt(1 - (y + y_shift)**2/b**2)*a
x_r_neg_inv = lambda y: -np.sqrt(1 - (y + y_shift)**2/b**2)*a
z_r_pos_inv = lambda y: np.sqrt(1 - (y + y_shift)**2/b**2)*c
y0 = np.linspace(-b+y_shift, b+y_shift,20)
y1 = np.linspace(-b-y_shift, b-y_shift,20)
#for cnt, y in enumerate([y0,y1]):
ax[0][0].plot(y0, x_r_pos(y0), color='darkred')
ax[0][0].plot(y0, x_r_neg(y0), color='darkred')
ax[1][0].plot(y0, z_r_pos(y0), color='darkred')
ax[0][1].plot(y1, x_r_pos_inv(y1), color='darkred')
ax[0][1].plot(y1, x_r_neg_inv(y1), color='darkred')
ax[1][1].plot(y1, z_r_pos_inv(y1), color='darkred')
plot_sphr(a=196, b=75, c=30.25, y_shift = 105)
plot_sphr(a=36, b=40, c=18, y_shift = 105)
plot_sphr(a=22, b=16, c=13, y_shift = 105)
ax[0][1].text(-100, 0, s='A0', color='white', fontsize=20)
ax[0][1].text( -80, 0, s='A1', color='white', fontsize=20)
ax[0][1].text( -50, 0, s='A2', color='white', fontsize=20)
ax[0][0].text(90,0, s='A3', color='white', fontsize=20)
ax[0][0].text(70, 0, s='A4', color='white', fontsize=20)
ax[0][0].text(40, 0, s='A5', color='white', fontsize=20)
for i in range(2):
ax[0][i].set_ylabel('x axis')
ax[1][i].set_ylabel('z axis')
for j in range(2):
ax[j][i].set_xlabel('y axis')
plt.tight_layout()
Note: Center point (0,0) is also a representative position to score both teams. This is that a score can occur at kickoff.
Each event is composed by a collection of snapshots recorded with a regular rate, but entire time series has variable length for each event.
f,ax=plt.subplots(figsize=(10,5))
temp = train_df[['event_id','event_time']].groupby('event_id').first()['event_time'].abs()
temp.hist(bins=100, ax=ax)
ax.set_title('Event time distribution')
ax.set_xlabel('duration [seconds]')
print('Maximum Duration for a particular event: ', f'{temp.max()} secs')
Maximum Duration for a particular event: 920.0 secs
Considering the target features, lets check the distribution of the snapshots results.
temp2 = train_df[['event_id','team_A_scoring_within_10sec','team_B_scoring_within_10sec']].groupby('event_id').last()
temp2 = temp2.sum(axis=1)
f,ax=plt.subplots(figsize=(10,3))
ax.scatter(temp.values, temp2.values, marker='.')
ax.set_xlabel('event duration [sec]')
ax.set_ylabel('Score or not')
ax.set_title('Event Final result distribution')
Text(0.5, 1.0, 'Event Final result distribution')
Note: as the 'positive' cases in which a Team scores are registered in the last 10 seconds, the longer the event, the more imbalance the target distribution of the snapshots.
counts = train_df[['team_A_scoring_within_10sec','team_B_scoring_within_10sec']].value_counts()
counts = pd.DataFrame(counts)
nrows = train_df.shape[0]
counts / nrows
| 0 | ||
|---|---|---|
| team_A_scoring_within_10sec | team_B_scoring_within_10sec | |
| 0 | 0 | 0.887607 |
| 1 | 0 | 0.057083 |
| 0 | 1 | 0.055310 |
Our Dataset has a representative imbalance data, ~88.7% of the snapshots represent an scenario in which there is no score within the next 10 secs. To attenuate this, the possible next steps are:
In the case of this work, Data Agumentation was selected, this was possible due to:
Since a players strategy need to be independent of coordinate system, reflection of players and associated features are possible. But remember, that score zones are defined at an specific side of the field, then, reflections around X-axis of associated features are going to also interchange the name of those features with the oponent Team. Team_A features <-> Team_B features.
Inside game physics, it is expected that there is some kind of gravity that control player/ball pos z parameter.
, then: , in this notation .
def check_acc(event_id=1003, parameter='ball_vel_z' ):
data_temp = train_df[train_df['event_id']==event_id]
event_time = data_temp['event_time'] - data_temp.iloc[0]['event_time']
#data_temp
delta_time = event_time - event_time.shift(1)
ball_vel_z_shift = data_temp[parameter].shift(1)
acc = (data_temp[parameter] - ball_vel_z_shift)/delta_time
d = acc[~(acc.isnull())&~(acc.isin([np.inf, -np.inf]))]
return d
c = pd.DataFrame()
for cnt, el in enumerate(train_df['event_id'].unique()):
t = check_acc(event_id=el, parameter='ball_vel_z' )
c = pd.concat([c, t], ignore_index=True)
del t
# Freq distribution
r = c[0].value_counts() # 26274 values
r = pd.DataFrame(r)
t = r.sort_values(by=0, ascending=False).iloc[1:4]
s = t.index.values * t[0].values
print('Estimated ball gravity', s.sum()/ t[0].values.sum())
Estimated ball gravity -10.868541850875738
######## ball
f, ax=plt.subplots()
range_x = np.linspace(-17, -4, 300)
c.hist(bins=range_x, ax=ax)
ax.set_title('Estimated gravity distribution')
plt.show()
######## player 0
f, ax=plt.subplots()
range_x = np.linspace(-17, -4, 400)
c.hist(bins=range_x, ax=ax)
ax.set_title('Estimated gravity distribution')
plt.show()
data_temp = train_df[train_df['event_id']==1003].copy()
data_temp['event_time'] -= data_temp.iloc[0]['event_time']
f,ax=plt.subplots(figsize=(15,4))
data_temp.plot('event_time', 'ball_pos_z', ax=ax)
ax.set_ylabel('pos_z')
ax.set_title('Variation of ball position at z-axis on event 1003')
ax.hlines([0],xmin=-10,xmax=140,color='darkgreen')
<matplotlib.collections.LineCollection at 0x7f431caf9e10>
f,ax=plt.subplots()
data_temp = train_df[train_df['event_time']>-10]
data_temp = data_temp.groupby(['event_id']).last()
data_temp['player_scoring_next'].value_counts().plot(kind='bar', ax=ax)
ax.set_title('Distribution of players scoring')
ax.set_xlabel('Player')
ax.set_ylabel('Number of Events')
Text(0, 0.5, 'Number of Events')
data_temp = train_df[train_df['event_time']>-10]
data_temp = data_temp.groupby(['event_id']).last().groupby('team_scoring_next')
data_temp.size().plot(kind='pie', autopct='%.2f')
<AxesSubplot:ylabel='None'>
import random
events = train_df['event_id'].unique()
l_events = random.choices(events, k=4)
f,ax=plt.subplots(ncols=2, nrows=2, figsize=(10,6))
for i, event in enumerate(l_events):
t = train_df[train_df['event_id']==event]['event_time']
t = (t - t.shift(1))
range_x = np.linspace(0.05, 0.15, 20)
ax[i//2][i%2].hist(t, bins=range_x)
ax[i//2][i%2].set_title(f'Event: {event}')
f.suptitle('Time frame recorded frequency distribution', fontsize=16)
f.tight_layout()
train_df['dem_p_A'] = train_df[['p0_pos_x', 'p1_pos_x','p2_pos_x']].isnull().astype(int).sum(axis=1)
train_df['dem_p_B'] = train_df[['p1_pos_x', 'p2_pos_x','p3_pos_x']].isnull().astype(int).sum(axis=1)
train_df['enc'] = -1*train_df['team_A_scoring_within_10sec'] + train_df['team_B_scoring_within_10sec']
t = train_df[(train_df['event_time']>-10)][['event_id','dem_p_A','dem_p_B','enc']].groupby('event_id').sum()
t.loc[t['enc']<0, 'enc'] = -1 ; t.loc[t['enc']>0, 'enc'] = 1
t.loc[:,['dem_p_A','dem_p_B']] = np.ceil(t[['dem_p_A','dem_p_B']]*0.02).astype('int8')
f,ax=plt.subplots(ncols=3, figsize=(10,4))
titles = ['None','A','B']
for cnt, i in enumerate([0,-1,1]):
t[t['enc']==i][['dem_p_A','dem_p_B']].value_counts().plot(kind='bar', ax=ax[cnt])
ax[cnt].set_title(f'Team scoring: {titles[cnt]}')
f.tight_layout()
As in any model, an adequate selection of features represent a high adantage. In the case of this work the following features where selected:
The following columns were ignored:
Since the target columns represent one-hot enconded columns of the results, a new target column need to be included in order to represent the three possible outcomes.
The following set of features represent a great improvement in evaluation metrics of the model.

Let: , . Then:
To find the time in which the distance to the ball is minimum the function above can be minimized, then t is truncated at 1 if t>1.
In our model, the direct implementation of position and velocity columns seems to not help as much as angle positioning according to evaluation metrics.
As reference points, the 'soccer' goal vertices are used.
import numpy as np
import matplotlib.pyplot as plt
range_x = np.linspace(-20,20,20*3)
range_z = np.linspace(0,15, 15*3)
t = train_df[(train_df['team_A_scoring_within_10sec']==1)&(train_df['ball_pos_y']>102)]
f,ax=plt.subplots(ncols=2, figsize=(18,5))
ax[0].hist2d(t['ball_pos_x'],t['ball_pos_z'], bins=[range_x, range_z], cmax=100)
t = train_df[(train_df['team_B_scoring_within_10sec']==1)&(train_df['ball_pos_y']<-102)]
ax[1].hist2d(t['ball_pos_x'],t['ball_pos_z'], bins=[range_x, range_z], cmax=100)
for i in range(2):
ax[i].vlines([-16.6,16.6], ymin=0, ymax=15, color='darkred')
ax[i].hlines([1.6,11.3], xmin=-20, xmax=20, color='darkred')
ax[0].set_title('Team A side') ; ax[1].set_title('Team B side')
f.suptitle('Rocket League goal vertices', fontsize=20)
plt.show()
print('Vertices: ', (16.6, 1.6), (-16.6, 1.6), (16.6, 11.4), (-16.6, 11.4))
####################33### xy plane
temp = np.array([-16.6, 100.6])-train_df[['ball_pos_x','ball_pos_y']]
temp = -temp.iloc[:,0] / np.linalg.norm(temp, axis=1)
train_df['alpha_1'] = np.degrees(np.arccos(temp)).astype('float16')
temp = np.array([16.6, 100.6])-train_df[['ball_pos_x','ball_pos_y']]
temp = -temp.iloc[:,0] / np.linalg.norm(temp, axis=1)
train_df['alpha_2'] = np.degrees(np.arccos(temp)).astype('float16')
temp = np.array([-16.6, -100.6])-train_df[['ball_pos_x','ball_pos_y']]
temp = -temp.iloc[:,0] / np.linalg.norm(temp, axis=1)
train_df['beta_1'] = -np.degrees(np.arccos(temp)).astype('float16')
temp = np.array([16.6, -100.6])-train_df[['ball_pos_x','ball_pos_y']]
temp = -temp.iloc[:,0] / np.linalg.norm(temp, axis=1)
train_df['beta_2'] = -np.degrees(np.arccos(temp)).astype('float16')
Vertices: (16.6, 1.6) (-16.6, 1.6) (16.6, 11.4) (-16.6, 11.4)
import matplotlib
f, ax=plt.subplots(figsize=(10,5))
t = train_df.iloc[1600]
ax.scatter(t['ball_pos_y'],t['ball_pos_x'],color='red')
ax.plot([t['ball_pos_y'], 100.6],[t['ball_pos_x'],-16.6], color='navy') ; ax.plot([t['ball_pos_y'], 100.6],[t['ball_pos_x'], 16.6], color='navy')
ax.plot([t['ball_pos_y'], -100.6],[t['ball_pos_x'],-16.6], color='navy') ; ax.plot([t['ball_pos_y'], -100.6],[t['ball_pos_x'], 16.6], color='navy')
ax.set_xlim([-102, 102])
ax.set_ylim([-82, 82])
print('alpha 1:', t['alpha_1'])
print('alpha 2:', t['alpha_2'])
print('beta 1:', t['beta_1'])
print('beta 2:', t['beta_2'])
ax.add_patch(matplotlib.patches.Arc((-25, -40), 40, 60, angle=0,
theta1=-185, theta2=-90,
edgecolor='black', lw=1.1))
ax.add_patch(matplotlib.patches.Arc((-25, -20), 40, 60, angle=0,
theta1=-185, theta2=-90,
edgecolor='black', lw=1.1))
ax.add_patch(matplotlib.patches.Arc((75, -40), 40, 60, angle=0,
theta1=-90, theta2=30,
edgecolor='black', lw=1.1))
ax.add_patch(matplotlib.patches.Arc((75, -20), 40, 60, angle=0,
theta1=-90, theta2=50,
edgecolor='black', lw=1.1))
ax.text(85, -70, 'alpha_1') ; ax.text(80, 10, 'alpha_2')
ax.text(-25, -70, 'beta_1') ; ax.text(-25, -40, 'beta_2')
ax.vlines([-25],ymin=-75, ymax=-30, color='black')
ax.vlines([75],ymin=-75, ymax=-35, color='black')
ax.arrow(t['ball_pos_y'],t['ball_pos_x'],t['ball_vel_y']//2,t['ball_vel_x']//2, width= 1,color='black')
ax.set_title('Angle Direction on xy axis (alpha & beta)')
alpha 1: 143.2 alpha 2: 154.0 beta 1: -111.8 beta 2: -121.56
Text(0.5, 1.0, 'Angle Direction on xy axis (alpha & beta)')
Alpha and Beta angles are state to reference from the xy data, similarly a sigma/gamma angles can reference data from yz plane.
Finally, each snapshot row on the train dataset have 118 features and 3 target columns.
The selected model architecture was a 4 layer model,and categorical_crossentropy loss.
In order to obtain the better evaluation metrics, the following preprocessing was applied:
import pandas as pd
temp = pd.read_hdf('../input/tbsoct2022-pro-data/train_0.25_v2.hdf', mode='r', key='df')
import matplotlib.pyplot as plt
f,ax=plt.subplots()
temp[['team_A_scoring_within_10sec','team_B_scoring_within_10sec']].value_counts().plot(kind='pie',autopct='%1.1f%%')
ax.set_title('Now we have a Balanced Dataset', fontsize=20)
Text(0.5, 1.0, 'Now we have a Balanced Dataset')
GroupShuffleSplit can do the trick, and complete events are stated for train and test datasetStandardScalet preprocessing was applied to Data.# trayectory of a ball
g = 12.39 # gravity
def dits_ball_hor(v_y, v_z, z, g=g):
t1 = ( v_z + np.sqrt(v_z**2 + 2*z*g) )/ g
return abs(v_y*t1)
dits_ball_hor(v_y=-4, v_z=25, z=100, g=g)
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
# Feature Enginerring
def feature_eng(df):
# I set the player far away from both zone score
for i in range(6):
df[f'p{i}_pos_x'].fillna(value= 85, inplace=True)
df[f'p{i}_pos_y'].fillna(value= 0, inplace=True)
df[f'p{i}_pos_z'].fillna(value= 0, inplace=True)
for p in 'xyz':
df[f'p{i}_vel_{p}'].fillna(value=0, inplace=True)
df[f'p{i}_boost'].fillna(value=0, inplace=True)
############################################### aximuth angle
df['azimuth'] = np.degrees(np.arctan(df['ball_vel_x']/df['ball_vel_y'])) + 90
df.loc[(df['ball_vel_y']<0),'azimuth'] -= 180
# filla
df['azimuth'].fillna(value=0, inplace=True)
df['azimuth'] = df['azimuth'].astype('float16')
print('azimuth')
############################################### ball velocity
df['ball_vel'] = np.linalg.norm(df[['ball_vel_x', 'ball_vel_y','ball_vel_z']], axis=1).astype('float16')
#df['bal_vel_xy'] = np.linalg.norm(df[['ball_vel_x', 'ball_vel_y']], axis=1).astype('float16')
temp_mod = np.linalg.norm(df[['ball_vel_x', 'ball_vel_y']], axis=1).astype('float16')
for i in range(6):
df[f'p{i}_vel'] = np.linalg.norm(df[[f'p{i}_vel_x', f'p{i}_vel_y',f'p{i}_vel_z']], axis=1).astype('float16')
print('ball & players vel')
############################################### elevation
#df['elevation'] = (df['ball_vel_x']**2 + df['ball_vel_y']**2)/(df['bal_vel_xy'] * df['ball_vel'])
df['elevation'] = (df['ball_vel_x']**2 + df['ball_vel_y']**2)/(temp_mod * df['ball_vel'])
# fill nan values in order to fill elevation with 0 ( cos-1(1)=0 )
df['elevation'].fillna(value=1, inplace=True)
df.loc[(df['elevation']>1),'elevation'] = 1
df.loc[(df['elevation']<-1),'elevation'] = -1
df['elevation'] = np.degrees(np.arccos(df['elevation']))
df.loc[(df['ball_vel_x']==0) & (df['ball_vel_y']==0) & (df['ball_vel_z']!=0), 'elevation'] = 90
df.loc[(df['ball_vel_x']==0) & (df['ball_vel_y']==0) & (df['ball_vel_z']==0), 'elevation'] = 0
df.loc[df['ball_vel_z']<0,'elevation'] *= -1
df['elevation'] = df['elevation'].astype('float16')
print('elevation')
del temp_mod
############################################### distance player-ball
for i in range(6):
df[f'dist_p{i}_ball'] = np.linalg.norm(df[['ball_pos_x','ball_pos_y','ball_pos_z']].to_numpy() -
df[[f'p{i}_pos_x',f'p{i}_pos_y',f'p{i}_pos_z']].to_numpy(), axis=1)
print('dist_p[0-5]_ball')
############################################### dist center ball
df['dist_ball'] = np.linalg.norm(df[['ball_pos_x','ball_pos_y','ball_pos_z']], axis=1).astype('float16')
for i in range(6):
df[f'dist_p{i}_center'] = np.linalg.norm(df[[f'p{i}_pos_x',f'p{i}_pos_y',f'p{i}_pos_z']], axis=1).astype('float16')
print('dist_center, ball & players')
################################################ angle from zone scoring
### xy plane
temp = np.array([-16.6, 100.6])-df[['ball_pos_x','ball_pos_y']]
temp = -temp.iloc[:,0] / np.linalg.norm(temp, axis=1)
df['alpha_1'] = (np.degrees(np.arccos(temp))).astype('float16')
temp = np.array([ 16.6, 100.6])-df[['ball_pos_x','ball_pos_y']]
temp = -temp.iloc[:,0] / np.linalg.norm(temp, axis=1)
df['alpha_2'] = np.degrees(np.arccos(temp)).astype('float16')
temp = np.array([-16.6,-100.6])-df[['ball_pos_x','ball_pos_y']]
temp = -temp.iloc[:,0] / np.linalg.norm(temp, axis=1)
df['beta_1'] = -np.degrees(np.arccos(temp)).astype('float16')
temp = np.array([ 16.6,-100.6])-df[['ball_pos_x','ball_pos_y']]
temp = -temp.iloc[:,0] / np.linalg.norm(temp, axis=1)
df['beta_2'] = -np.degrees(np.arccos(temp)).astype('float16')
##### yz plane
temp = np.array([ 100.6, 11.3]) - df[['ball_pos_y','ball_pos_z']]
temp = -temp.iloc[:,1]/np.linalg.norm(temp, axis=1)
df['sigma_2'] = (np.degrees(np.arccos(temp))-90).astype('float16')
temp = np.array([-100.6, 11.3]) - df[['ball_pos_y','ball_pos_z']]
temp = -temp.iloc[:,1]/np.linalg.norm(temp, axis=1)
df['gamma_2'] = (np.degrees(np.arccos(temp))-90).astype('float16')
print('scoring zone angles')
def new_features(df):
for i in range(6):
temp = np.array([-16.6, 100.6])-df[[f'p{i}_pos_x',f'p{i}_pos_y']]
temp = -temp.iloc[:,0] / np.linalg.norm(temp, axis=1)
df[f'p{i}_alpha_1'] = (np.degrees(np.arccos(temp))).astype('float16')
temp = np.array([ 16.6, 100.6])-df[[f'p{i}_pos_x',f'p{i}_pos_y']]
temp = -temp.iloc[:,0] / np.linalg.norm(temp, axis=1)
df[f'p{i}_alpha_2'] = (np.degrees(np.arccos(temp))).astype('float16')
temp = np.array([-16.6,-100.6])-df[[f'p{i}_pos_x',f'p{i}_pos_y']]
temp = -temp.iloc[:,0] / np.linalg.norm(temp, axis=1)
df[f'p{i}_beta_1'] = -np.degrees(np.arccos(temp)).astype('float16')
temp = np.array([ 16.6,-100.6])-df[[f'p{i}_pos_x',f'p{i}_pos_y']]
temp = -temp.iloc[:,0] / np.linalg.norm(temp, axis=1)
df[f'p{i}_beta_2'] = -np.degrees(np.arccos(temp)).astype('float16')
del temp
print(i)
def new_features2(df):
for i in range(6):
temp = df[[f'p{i}_vel_x',f'p{i}_vel_y',f'p{i}_vel_z']] - df[['ball_vel_x','ball_vel_y','ball_vel_z']].to_numpy()
df[f'dif_p{i}'] = np.linalg.norm(temp, axis=1).astype('float16')
del temp
### minimum distance time
diff_pos = df[[f'p{i}_pos_x',f'p{i}_pos_y',f'p{i}_pos_z']].to_numpy() - df[['ball_pos_x','ball_pos_y','ball_pos_z']].to_numpy()
diff_vel = df[[f'p{i}_vel_x',f'p{i}_vel_y',f'p{i}_vel_z']].to_numpy() - df[['ball_vel_x','ball_vel_y','ball_vel_z']].to_numpy()
df[f't_p{i}'] = - (diff_pos * diff_vel).sum(axis=1)
df[f't_p{i}'] /= (diff_vel*diff_vel).sum(axis=1)
df[f't_p{i}'] = df[f't_p{i}'].astype('float16')
del diff_pos, diff_vel
print(i)
temp = df[['t_p0','t_p1','t_p2','t_p3','t_p4','t_p5']]
time = temp.where(temp > 0).min(axis=1).fillna(0)
time.loc[time>1.0] = 1.0
# determine minimum distance on the next sec at much
for i in range(6):
diff_pos = df[[f'p{i}_pos_x',f'p{i}_pos_y',f'p{i}_pos_z']].to_numpy() - df[['ball_pos_x','ball_pos_y','ball_pos_z']].to_numpy()
diff_vel = df[[f'p{i}_vel_x',f'p{i}_vel_y',f'p{i}_vel_z']].to_numpy() - df[['ball_vel_x','ball_vel_y','ball_vel_z']].to_numpy()
df[f'mdist_p{i}'] = np.linalg.norm(diff_pos + diff_vel * time.to_numpy()[:, np.newaxis], axis=1).astype('float16')
del diff_pos, diff_vel
print(i)
del time, temp
cols = ['t_p0','t_p1','t_p2','t_p3','t_p4','t_p5']
df.drop(cols, inplace=True, axis=1)
#feature_eng(df)
def reflexion_y_axis(df, pos_vel=True):
t = df[(df['team_A_scoring_within_10sec']==1)|(df['team_B_scoring_within_10sec']==1)].copy()
# 'ball_pos_x',
t['ball_pos_x'] *= -1
if pos_vel:
# 'ball_vel_x' * -1
t['ball_vel_x'] *= -1
# 'p[0-6]_[pos/vel]_[x]' * -1
for i in range(6):
for el in ['pos','vel']:
t[f'p{i}_{el}_x'] *= -1
t['sec_ball_pos_x'] *= -1
for i in range(6):
t[f'sec_p{i}_pos_x'] *= -1
# 'boost1_timer' <-> 'boost0_timer', 'boost5_timer' <-> 'boost4_timer'
t.rename(columns={'boost1_timer': 'boost0_timer', 'boost0_timer': 'boost1_timer',
'boost5_timer': 'boost4_timer', 'boost4_timer': 'boost5_timer'}, inplace=True)
# azimuth reflexion around y-axis and angle scoring reflection
for angle in ['azimuth','beta_1', 'beta_2', 'alpha_1', 'alpha_2']:
t[angle] = t.apply(lambda x: 180-x[angle] - 360*(x[angle]<0), axis=1)
return t
# reflection around x-axis
# Possible Features 'player_scoring_next', 'team_scoring_next'
def reflexion_x_axis(df, pos_vel=True):
t = df[(df['team_A_scoring_within_10sec']==1)|(df['team_B_scoring_within_10sec']==1)].copy()
# 'ball_pos_y'
t['ball_pos_y'] *= -1
# ball_vel_y' * -1
if pos_vel:
t['ball_vel_y'] *= -1
# 'p[0-5]_[pos/vel]_y' * -1
for i in range(6):
for el in ['pos','vel']:
t[f'p{i}_{el}_y'] *= -1
t['sec_ball_pos_y'] *= -1
for i in range(6):
t[f'sec_p{i}_pos_y'] *= -1
# 'boost1_timer' <-> 'boost5_timer', 'boost0_timer' <-> 'boost4_timer'
t.rename(columns={'boost1_timer': 'boost5_timer', 'boost5_timer': 'boost1_timer',
'boost0_timer': 'boost4_timer', 'boost4_timer': 'boost0_timer'}, inplace=True)
# 'azimuth' * -1 and scoring zones angles
for angle in ['azimuth','beta_1', 'beta_2', 'alpha_1', 'alpha_2']:
t[angle] *= -1
# 'dist_side0' <-> 'dist_side1'
t.rename(columns={'dist_side0': 'dist_side1', 'dist_side1':'dist_side0'}, inplace=True)
# 'ball_pos_side' * -1
t['ball_pos_side'] *= -1
# 'p[0-5]_pos_side' *-1
for i in range(6):
t[f'p{i}_pos_side'] *= -1
# interchange fixed angles
for i in range(2):
t.rename(columns={f'alpha_{i}':f'beta_{i}', f'beta_{i}':f'alpha_{i}'}, inplace=True)
t.rename(columns={f'sigma_{i}':f'gamma_{i}', f'gamma_{i}':f'sigma_{i}'}, inplace=True)
### change Team [0-2] <-> [3-5]
for i in range(3):
t.rename(columns={f'p{i}_vel' :f'p{i+3}_vel', f'p{i+3}_vel' :f'p{i}_vel'}, inplace=True)
#t.rename(columns={f'p{i}_dist':f'p{i+3}_dist',f'p{i+3}_dist':f'p{i}_dist'}, inplace=True)
t.rename(columns={f'dist_p{i}_center':f'dist_p{i+3}_center',f'dist_p{i+3}_center':f'dist_p{i}_center'}, inplace=True)
# 'p{i}_[pos/vel]_[xyz]' <-> 'p{i+3}_[pos/vel]_[xyz]'
for i in range(3):
if pos_vel:
for el in ['pos','vel']:
for x in 'xyz':
t.rename(columns={f'p{i}_{el}_{x}': f'p{i+3}_{el}_{x}', f'p{i+3}_{el}_{x}':f'p{i}_{el}_{x}'}, inplace=True)
# 'p{i}_boost' <-> 'p{i+3}_boost'
t.rename(columns={f'p{i}_boost': f'p{i+3}_boost', f'p{i+3}_boost':f'p{i}_boost'}, inplace=True)
# 'p{i}_pos_side' <-> 'p{i+3}_pos_side'
t.rename(columns={f'p{i}_pos_side': f'p{i+3}_pos_side', f'p{i+3}_pos_side':f'p{i}_pos_side'}, inplace=True)
# 'dist_p{i}_base' <-> 'dist_p{i+3}_base'
t.rename(columns={f'dist_p{i}_base': f'dist_p{i+3}_base', f'dist_p{i+3}_base':f'dist_p{i}_base'}, inplace=True)
# 'dist_p{i}_ball' <-> 'dist_p{i+3}_ball'
t.rename(columns={f'dist_p{i}_ball': f'dist_p{i+3}_ball', f'dist_p{i+3}_ball':f'dist_p{i}_ball'}, inplace=True)
# 'sec_p{i}_pos_[xyz]' <-> 'sec_p{i+3}_pos_[xyz]'
for i in range(3):
for x in 'xyz':
t.rename(columns={f'sec_p{i}_pos_{x}': f'sec_p{i+3}_pos_{x}', f'sec_p{i+3}_pos_{x}':f'sec_p{i}_pos_{x}'}, inplace=True)
# 'team_A_scoring_within_10sec' <->'team_B_scoring_within_10sec'
t.rename(columns={'team_A_scoring_within_10sec':'team_B_scoring_within_10sec',
'team_B_scoring_within_10sec':'team_A_scoring_within_10sec'}, inplace=True)
return t
### Reflexion
t1 = reflexion_y_axis(df, pos_vel=True)
t2 = reflexion_x_axis(df, pos_vel=True)
df_refl = pd.concat([t1,t2,df], ignore_index=True)
del t1, t2, df
new_ord = lambda a, b: {f'p{a}_pos_x':f'p{b}_pos_x',
f'p{a}_pos_y':f'p{b}_pos_y',
f'p{a}_pos_z':f'p{b}_pos_z',
f'p{a}_vel_x':f'p{b}_vel_x',
f'p{a}_vel_y':f'p{b}_vel_y',
f'p{a}_vel_z':f'p{b}_vel_z',
f'p{a}_boost':f'p{b}_boost',
f'dist_p{a}_base':f'dist_p{b}_base',
f'p{a}_pos_side':f'p{b}_pos_side',
f'dist_p{a}_ball':f'dist_p{b}_ball',
f'sec_p{a}_pos_x':f'sec_p{b}_pos_x',
f'sec_p{a}_pos_y':f'sec_p{b}_pos_y',
f'sec_p{a}_pos_z':f'sec_p{b}_pos_z',
f'p{a}_vel':f'p{b}_vel',
f'p{a}_dist':f'p{b}_dist'}
new_mod = lambda a, b: {f'p{a}_boost':f'p{b}_boost',
f'dist_p{a}_base':f'dist_p{b}_base',
f'p{a}_pos_side':f'p{b}_pos_side',
f'dist_p{a}_ball':f'dist_p{b}_ball',
f'sec_p{a}_pos_x':f'sec_p{b}_pos_x',
f'sec_p{a}_pos_y':f'sec_p{b}_pos_y',
f'sec_p{a}_pos_z':f'sec_p{b}_pos_z',
f'p{a}_vel':f'p{b}_vel',
f'dist_p{a}_center':f'dist_p{b}_center'}
# mantain core permutation only [0,1,2], [3,4,5]
per_teamA = [[0,2,1], [1,0,2], [1,2,0], [2,1,0], [2,0,1]]
per_teamB = [[3,5,4], [4,3,5], [4,5,3], [5,4,3], [5,3,4]]
def aux_func(temp_l, c):
c0 = temp_l.index(c[0])
c1 = temp_l.index(c[1])
temp_l[c0],temp_l[c1] = temp_l[c1],temp_l[c0]
return temp_l
def permutation_player(df, pos_vel=True):
#n_ord = new_ord if pos_vel else new_mod
n_ord = new_ord
T = pd.DataFrame()
for na,p_ta in enumerate(per_teamA):
for nb,p_tb in enumerate(per_teamB):
base_a = [0,1,2] ; base_b=[3,4,5]
t = df.copy()
for i in range(3):
cols_ta, cols_tb = {},{}
s_ta = (i, p_ta[i]) ; s_tb = (i+3, p_tb[i])
if (i != p_ta[i]) and (base_a != p_ta):
base_a = aux_func(base_a, s_ta)
cols_ta = {**n_ord(i, p_ta[i]),**n_ord(p_ta[i], i)}
print('change',base_a)
if (i+3 != p_tb[i]) and (base_b != p_tb):
base_b = aux_func(base_b, s_tb)
cols_tb = {**n_ord(i+3, p_tb[i]),**n_ord(p_tb[i], i+3)}
print('change',base_b)
cols = {**cols_ta, **cols_tb}
t.rename(columns=cols, inplace=True)
cols_ta, cols_tb = {},{}
if (base_a != p_ta):
base_a = aux_func(base_a, (base_a[0], base_a[-1]) )
cols_ta = {**n_ord(base_a[0], base_a[-1]),**n_ord(base_a[-1], base_a[0])}
print('Last change: ', base_a)
if (base_b != p_tb):
base_b = aux_func(base_b, (base_b[0], base_b[-1]) )
cols_tb = {**n_ord(base_b[0], base_b[-1]),**n_ord(base_b[-1], base_b[0])}
print('Last change: ', base_b)
cols = {**cols_ta, **cols_tb}
t.rename(columns=cols, inplace=True)
print((na,nb), base_a, base_b)
print((na,nb), p_ta, p_tb, '\n')
T = pd.concat([T,t],ignore_index=True)
del t
return T
### Player Permutation
temp_df = df_refl[(df_refl['team_A_scoring_within_10sec']==1)|(df_refl['team_B_scoring_within_10sec']==1)]
df_per = permutation_player(temp_df[::6], pos_vel=False)
df = pd.concat([df_refl, df_per], ignore_index=True)
del temp_df, df_refl, df_per
df['team_Null'] = (~((df['team_A_scoring_within_10sec']==1) | (df['team_B_scoring_within_10sec']==1))).astype('int8')
df['target_enc'] = (df['team_A_scoring_within_10sec'] + 2* df['team_B_scoring_within_10sec']).astype('int8')
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
df = pd.read_hdf('../input/tbsoct2022-pro-data/train_0.25_v2.hdf', key='df', mode='r')
cols = ['bal_vel_xy','gamma_1', 'sigma_1',
'p0_pos_side','p1_pos_side','p2_pos_side','p3_pos_side','p4_pos_side','p5_pos_side','ball_pos_side',
'dist_p0_base', 'dist_p1_base', 'dist_p2_base',
'dist_p3_base', 'dist_p4_base', 'dist_p5_base',
'sec_p0_pos_x', 'sec_p0_pos_y', 'sec_p0_pos_z',
'sec_p1_pos_x', 'sec_p1_pos_y', 'sec_p1_pos_z',
'sec_p2_pos_x', 'sec_p2_pos_y', 'sec_p2_pos_z',
'sec_p3_pos_x', 'sec_p3_pos_y', 'sec_p3_pos_z',
'sec_p4_pos_x', 'sec_p4_pos_y', 'sec_p4_pos_z',
'sec_p5_pos_x', 'sec_p5_pos_y', 'sec_p5_pos_z',
'sec_ball_pos_x', 'sec_ball_pos_y', 'sec_ball_pos_z',
'dist_side0','dist_side1']
temp_df.drop(cols, inplace=True, axis=1)
new_features(temp_df)
new_features2(temp_df)
## better aproach
col_var3 = ['ball_pos_x','ball_pos_y','ball_pos_z', 'ball_vel_x', 'ball_vel_y', 'ball_vel_z',
'p0_pos_x','p0_pos_y', 'p0_pos_z', 'p0_vel_x','p0_vel_y','p0_vel_z',
'p1_pos_x','p1_pos_y', 'p1_pos_z', 'p1_vel_x','p1_vel_y','p1_vel_z',
'p2_pos_x','p2_pos_y', 'p2_pos_z', 'p2_vel_x','p2_vel_y','p2_vel_z',
'p3_pos_x','p3_pos_y', 'p3_pos_z', 'p3_vel_x','p3_vel_y','p3_vel_z',
'p4_pos_x','p4_pos_y', 'p4_pos_z', 'p4_vel_x','p4_vel_y','p4_vel_z',
'p5_pos_x','p5_pos_y', 'p5_pos_z', 'p5_vel_x','p5_vel_y','p5_vel_z',
'p0_boost', 'p1_boost', 'p2_boost', 'p3_boost', 'p4_boost', 'p5_boost',
'boost0_timer', 'boost1_timer', 'boost2_timer', 'boost3_timer','boost4_timer', 'boost5_timer',
'azimuth', 'elevation', 'ball_vel',
'p0_vel', 'p1_vel', 'p2_vel', 'p3_vel', 'p4_vel', 'p5_vel',
'dist_ball',
'dist_p0_ball','dist_p1_ball', 'dist_p2_ball', 'dist_p3_ball', 'dist_p4_ball','dist_p5_ball',
'dist_p0_center', 'dist_p1_center','dist_p2_center', 'dist_p3_center', 'dist_p4_center', 'dist_p5_center',
'alpha_1', 'alpha_2', 'beta_1', 'beta_2',
'sigma_2', 'gamma_2',
'p0_alpha_1', 'p0_alpha_2','p0_beta_1','p0_beta_2',
'p1_alpha_1', 'p1_alpha_2','p1_beta_1','p1_beta_2',
'p2_alpha_1', 'p2_alpha_2','p2_beta_1','p2_beta_2',
'p3_alpha_1', 'p3_alpha_2','p3_beta_1','p3_beta_2',
'p4_alpha_1', 'p4_alpha_2','p4_beta_1','p4_beta_2',
'p5_alpha_1', 'p5_alpha_2','p5_beta_1','p5_beta_2',
'dif_p0','dif_p1','dif_p2','dif_p3','dif_p4','dif_p5',
'mdist_p0', 'mdist_p1', 'mdist_p2','mdist_p3', 'mdist_p4', 'mdist_p5']
from sklearn.model_selection import GroupShuffleSplit
import numpy as np
group = temp_df['event_id'] # game_num', 'event_id'
y_k = temp_df['target_enc']
y = temp_df[['team_Null','team_A_scoring_within_10sec','team_B_scoring_within_10sec']]
X = temp_df[col_var3]
#del df
# change test_size consume more memory?
splitter = GroupShuffleSplit(test_size=.05, n_splits=2, random_state = 7)
split = splitter.split(X,y, groups=group)
train_idx, val_idx = next(split)
import tensorflow as tf
import tensorflow_addons as tfa
from tensorflow.keras import layers
# all 121
def simple_model(X_train, y_train, X_val, y_val, ep = 2, bs=1024, lr=0.0001): #, lambd = 0.001):
model = tf.keras.Sequential([layers.Dense(256, input_shape=[118], activation=tfa.activations.mish),
layers.BatchNormalization(),
layers.Dropout(rate=0.3),
layers.Dense(512, activation=tfa.activations.mish),
layers.BatchNormalization(),
layers.Dense(128, activation=tfa.activations.mish),
layers.Dense(3, activation='softmax')])
optimizer = tf.keras.optimizers.Adam(learning_rate=lr)
model.compile(optimizer=optimizer, loss='categorical_crossentropy',metrics=['accuracy'])
callback = tf.keras.callbacks.EarlyStopping(monitor='val_loss',
min_delta=0.001, patience=5,
restore_best_weights=True )
history = model.fit(X_train,y_train,
validation_data=(X_val, y_val),
batch_size=bs, epochs=ep, callbacks=[callback])
return model, history
from sklearn.preprocessing import StandardScaler
from sklearn.compose import make_column_transformer
from tensorflow.keras import mixed_precision
preprocessor = make_column_transformer( (StandardScaler(), col_var3) )
tf.keras.backend.clear_session()
X_tr = X.iloc[train_idx] ; y_tr = y.iloc[train_idx]
X_vl = X.iloc[val_idx] ; y_vl = y.iloc[val_idx]
X_tr = preprocessor.fit_transform(X_tr)
X_vl = preprocessor.transform(X_vl)
model, history = simple_model(X_tr, y_tr, X_vl, y_vl, ep=30, bs=1024, lr=0.0001*7)#, lambd = 0.001)
Epoch 1/30 3232/3232 [==============================] - 129s 40ms/step - loss: 0.5667 - accuracy: 0.7592 - val_loss: 0.4779 - val_accuracy: 0.8010 Epoch 2/30 3232/3232 [==============================] - 125s 39ms/step - loss: 0.4734 - accuracy: 0.8037 - val_loss: 0.4604 - val_accuracy: 0.8087 Epoch 3/30 3232/3232 [==============================] - 122s 38ms/step - loss: 0.4591 - accuracy: 0.8095 - val_loss: 0.4532 - val_accuracy: 0.8109 Epoch 4/30 3232/3232 [==============================] - 122s 38ms/step - loss: 0.4515 - accuracy: 0.8128 - val_loss: 0.4475 - val_accuracy: 0.8143 Epoch 5/30 3232/3232 [==============================] - 127s 39ms/step - loss: 0.4470 - accuracy: 0.8145 - val_loss: 0.4428 - val_accuracy: 0.8150 Epoch 6/30 3232/3232 [==============================] - 128s 40ms/step - loss: 0.4435 - accuracy: 0.8163 - val_loss: 0.4424 - val_accuracy: 0.8162 Epoch 7/30 3232/3232 [==============================] - 139s 43ms/step - loss: 0.4410 - accuracy: 0.8173 - val_loss: 0.4404 - val_accuracy: 0.8169 Epoch 8/30 3232/3232 [==============================] - 127s 39ms/step - loss: 0.4390 - accuracy: 0.8183 - val_loss: 0.4391 - val_accuracy: 0.8179 Epoch 9/30 3232/3232 [==============================] - 135s 42ms/step - loss: 0.4373 - accuracy: 0.8190 - val_loss: 0.4374 - val_accuracy: 0.8190 Epoch 10/30 3232/3232 [==============================] - 142s 44ms/step - loss: 0.4358 - accuracy: 0.8196 - val_loss: 0.4367 - val_accuracy: 0.8189 Epoch 11/30 3232/3232 [==============================] - 129s 40ms/step - loss: 0.4346 - accuracy: 0.8201 - val_loss: 0.4367 - val_accuracy: 0.8179 Epoch 12/30 3232/3232 [==============================] - 124s 38ms/step - loss: 0.4333 - accuracy: 0.8208 - val_loss: 0.4358 - val_accuracy: 0.8192 Epoch 13/30 3232/3232 [==============================] - 122s 38ms/step - loss: 0.4321 - accuracy: 0.8212 - val_loss: 0.4359 - val_accuracy: 0.8191 Epoch 14/30 3232/3232 [==============================] - 119s 37ms/step - loss: 0.4316 - accuracy: 0.8215 - val_loss: 0.4360 - val_accuracy: 0.8188 Epoch 15/30 3232/3232 [==============================] - 119s 37ms/step - loss: 0.4304 - accuracy: 0.8220 - val_loss: 0.4346 - val_accuracy: 0.8198 Epoch 16/30 3232/3232 [==============================] - 122s 38ms/step - loss: 0.4297 - accuracy: 0.8224 - val_loss: 0.4349 - val_accuracy: 0.8194 Epoch 17/30 3232/3232 [==============================] - 119s 37ms/step - loss: 0.4289 - accuracy: 0.8227 - val_loss: 0.4338 - val_accuracy: 0.8199 Epoch 18/30 3232/3232 [==============================] - 118s 36ms/step - loss: 0.4279 - accuracy: 0.8231 - val_loss: 0.4339 - val_accuracy: 0.8201 Epoch 19/30 3232/3232 [==============================] - 121s 38ms/step - loss: 0.4274 - accuracy: 0.8235 - val_loss: 0.4335 - val_accuracy: 0.8204 Epoch 20/30 3232/3232 [==============================] - 121s 37ms/step - loss: 0.4265 - accuracy: 0.8239 - val_loss: 0.4338 - val_accuracy: 0.8206 Epoch 21/30 3232/3232 [==============================] - 118s 37ms/step - loss: 0.4260 - accuracy: 0.8241 - val_loss: 0.4337 - val_accuracy: 0.8208 Epoch 22/30 3232/3232 [==============================] - 123s 38ms/step - loss: 0.4252 - accuracy: 0.8244 - val_loss: 0.4349 - val_accuracy: 0.8200 Epoch 23/30 3232/3232 [==============================] - 121s 38ms/step - loss: 0.4247 - accuracy: 0.8248 - val_loss: 0.4339 - val_accuracy: 0.8202 Epoch 24/30 3232/3232 [==============================] - 121s 37ms/step - loss: 0.4240 - accuracy: 0.8250 - val_loss: 0.4331 - val_accuracy: 0.8203
t = pd.DataFrame(history.history)
f,ax=plt.subplots(ncols=2, figsize=(15,4))
t[['loss','val_loss']].plot(ax=ax[0])
t[['accuracy','val_accuracy']].plot(ax=ax[1])
<AxesSubplot:>
pred = model.predict(X_vl)
from sklearn.metrics import accuracy_score, roc_auc_score
print('\n',accuracy_score(y_k.iloc[val_idx], np.argmax(pred, axis=1)))
print(roc_auc_score(y_vl, pred))
0.8216992872496889 0.9313930444049507
from sklearn.metrics import mean_absolute_error
ind1 = y_vl['team_A_scoring_within_10sec']==1 #.iloc[:,0]
ind2 = y_vl['team_B_scoring_within_10sec']==1
ind3 = y_vl['team_Null']==1
df_temp = pd.DataFrame(pred)
print('Team A winners: ',mean_absolute_error(y_vl.loc[ind1,'team_A_scoring_within_10sec'], df_temp.loc[ind1.values,1]))
print('Team B winners: ',mean_absolute_error(y_vl.loc[ind2,'team_B_scoring_within_10sec'], df_temp.loc[ind2.values,2]))
print('Team Null : ',mean_absolute_error(y_vl.loc[ind3,'team_Null'], df_temp.loc[ind3.values,0]))
Team A winners: 0.31683043 Team B winners: 0.33541942 Team Null : 0.107504554
# Team A scoring
temp = X.iloc[val_idx].loc[ind1].iloc[(df_temp.loc[ind1.values,1]<0.5).values]
temp['ball_pos_y'].hist(bins=200)
temp['ball_pos_z'].hist(bins=200)
Permutation importance is a technique that use column permutations, it determines how much this changes can affect the quality of the predictions.
The worse the prediction, the higher the importance of that specific feature.
from sklearn.metrics import mean_absolute_error
#define function to swap columns
def swap_columns(df, col1, col2):
col_list = list(df.columns)
x, y = col_list.index(col1), col_list.index(col2)
col_list[y], col_list[x] = col_list[x], col_list[y]
return df[col_list]
def check_Permutation_Importance(model, val_y_data, val_X_data):
#n = len(val_y_data)
#t_val_y = val_y_data.values.reshape(-1,1)
# cache of previous permutations
res = {}; ans = {}
for cnt, c in enumerate(val_X_data.columns):
print(cnt)
base = 0; total = 0
for p in val_X_data.columns:
cond1 = res.get(f'({c},{p})') is None
cond2 = res.get(f'({p},{c})') is None
if (cond1 and cond2):
temp = swap_columns(val_X_data, c, p)
#temp = preprocessor.transform(temp)
# mean squared error
#r = np.sum((t_val_y - model.predict(temp))**2)/n
r = mean_absolute_error(val_y_data, model.predict(temp))
if (c==p): base = r
# save cache
res[f"({c},{p})"] = r
else:
if res.get(f"({c},{p})") is None:
r = res[f"({p},{c})"]
else:
r = res[f"({c},{p})"]
# evaluate change in mse
total += r - base
ans[c] = total
eval_df = pd.DataFrame(data={'Feature':list(ans.keys()), 'Importance':list(ans.values())})
eval_df['Importance'] = eval_df['Importance']/sum(ans.values())
eval_df.sort_values(by='Importance', inplace=True)
return eval_df
# Dependencie on 'TeamA' scoring
from sklearn.utils.random import sample_without_replacement
ind1 = y_vl['team_A_scoring_within_10sec']==1
y_temp = y_vl.loc[ind1].iloc[:1000]
X_temp = pd.DataFrame(preprocessor.transform(X.iloc[val_idx].loc[ind1].iloc[:1000]), columns= col_var3)
eval_df = check_Permutation_Importance(model, y_temp, X_temp)
eval_df.set_index('Feature').plot(kind='barh', figsize=(10,30))
<AxesSubplot:ylabel='Feature'>
The most important features are:
The worst features are:
dtypes = {'index':'int32','game_num': 'int32', 'event_id': 'int32', 'event_time': 'float16',
'ball_pos_x': 'float16', 'ball_pos_y': 'float16', 'ball_pos_z': 'float16',
'ball_vel_x': 'float16', 'ball_vel_y': 'float16', 'ball_vel_z': 'float16',
'p0_pos_x': 'float16', 'p0_pos_y': 'float16', 'p0_pos_z': 'float16',
'p0_vel_x': 'float16', 'p0_vel_y': 'float16', 'p0_vel_z': 'float16', 'p0_boost': 'float16',
'p1_pos_x': 'float16', 'p1_pos_y': 'float16', 'p1_pos_z': 'float16',
'p1_vel_x': 'float16', 'p1_vel_y': 'float16', 'p1_vel_z': 'float16', 'p1_boost': 'float16',
'p2_pos_x': 'float16', 'p2_pos_y': 'float16', 'p2_pos_z': 'float16',
'p2_vel_x': 'float16', 'p2_vel_y': 'float16', 'p2_vel_z': 'float16', 'p2_boost': 'float16',
'p3_pos_x': 'float16', 'p3_pos_y': 'float16', 'p3_pos_z': 'float16',
'p3_vel_x': 'float16', 'p3_vel_y': 'float16', 'p3_vel_z': 'float16', 'p3_boost': 'float16',
'p4_pos_x': 'float16', 'p4_pos_y': 'float16', 'p4_pos_z': 'float16',
'p4_vel_x': 'float16', 'p4_vel_y': 'float16', 'p4_vel_z': 'float16', 'p4_boost': 'float16',
'p5_pos_x': 'float16', 'p5_pos_y': 'float16', 'p5_pos_z': 'float16',
'p5_vel_x': 'float16', 'p5_vel_y': 'float16', 'p5_vel_z': 'float16', 'p5_boost': 'float16',
'boost0_timer': 'float16', 'boost1_timer': 'float16', 'boost2_timer': 'float16',
'boost3_timer': 'float16', 'boost4_timer': 'float16', 'boost5_timer': 'float16',
'player_scoring_next': 'int8', 'team_scoring_next': 'object',
'team_A_scoring_within_10sec': 'int8', 'team_B_scoring_within_10sec': 'int8'}
test_df = pd.read_csv('../input/tabular-playground-series-oct-2022/test.csv', dtype=dtypes)
feature_eng(test_df)
new_features(test_df)
new_features2(test_df)
X_eval = test_df[col_var3]
X_eval = preprocessor.transform(X_eval)
pred = model.predict(X_eval)
test_df['team_null'] = pred[:,0]
test_df['team_A_scoring_within_10sec'] = pred[:,1]
test_df['team_B_scoring_within_10sec'] = pred[:,2]
final = test_df[['id','team_A_scoring_within_10sec','team_B_scoring_within_10sec']].copy()
final
| id | team_A_scoring_within_10sec | team_B_scoring_within_10sec | |
|---|---|---|---|
| 0 | 0 | 0.023890 | 0.027807 |
| 1 | 1 | 0.003067 | 0.230918 |
| 2 | 2 | 0.019368 | 0.089581 |
| 3 | 3 | 0.060639 | 0.021571 |
| 4 | 4 | 0.013900 | 0.037073 |
| ... | ... | ... | ... |
| 701138 | 701138 | 0.183625 | 0.026933 |
| 701139 | 701139 | 0.014722 | 0.065710 |
| 701140 | 701140 | 0.077823 | 0.065097 |
| 701141 | 701141 | 0.010819 | 0.107058 |
| 701142 | 701142 | 0.050167 | 0.028904 |
701143 rows × 3 columns
final.to_csv('sub.csv', index=False)
A good approach for future development can be based on the analysis on a more refined trajectory prediction, i.e. the implementation of ball/player gravity.