"""
# Predicting 3D World position from Car bounding boxes.

This kernel aims to find a model which, given a 2D bounding box (defined as the center and its height/width), can predict the position of a car in the real world (distance, height and lateral displacement).

Here I test several baseline models and, later, fine tune the best one.

The dataset I'm going to be using is a derived one (built by me): https://www.kaggle.com/alvaroibrain/carworldpositions, which contains the features mentioned above. The dataset has been made from the Baidu one using YOLO for finding the bounding boxes and then, finding the matches between the BBoxes and the real world points.

Hope this approach can serve you to improve your predictions.
"""
import numpy as np
import seaborn as sns
import pandas as pd
import joblib

import sklearn
from sklearn import preprocessing
from sklearn import model_selection
from sklearn import tree
from sklearn import ensemble
from sklearn import neighbors

from sklearn import multioutput
from sklearn import metrics
from sklearn import svm
from sklearn.gaussian_process.kernels import DotProduct, WhiteKernel, Matern, RBF

from matplotlib import pyplot as plt

sns.set(style="dark")
IMAGE_WIDTH = 3384
IMAGE_HEIGHT = 2710
def print_metrics(test_set, predictions):
    print("Test metrics")
    print("-"*20)
    
    wx_error = sklearn.metrics.mean_absolute_error(test_set['wx'], predictions[:, 0])
    wy_error = sklearn.metrics.mean_absolute_error(test_set['wy'], predictions[:, 1])
    wz_error = sklearn.metrics.mean_absolute_error(test_set['wz'], predictions[:, 2])
    
    wx_r2 = sklearn.metrics.r2_score(test_set['wx'], predictions[:, 0])
    wy_r2 = sklearn.metrics.r2_score(test_set['wy'], predictions[:, 1])
    wz_r2 = sklearn.metrics.r2_score(test_set['wz'], predictions[:, 2])

    print(f"- WX error {wx_error}")
    print(f"- WY error {wy_error}")
    print(f"- WZ error {wz_error}")

    print("")
    print(f"- WX R2 score {wx_r2}")
    print(f"- WY R2 score {wy_r2}")
    print(f"- WZ R2 score {wz_r2}")
    
    return wx_error, wy_error, wz_error, wx_r2, wy_r2, wz_r2
          
def plot_predictions(test_set, predictions, fig, ax):
          
    line_kws = {'color':'black', 'linestyle':'--', 'linewidth':2}
    scatter_kws = {'s':1}
    
    ax[0].set_title('World X')
    ax[1].set_title('World Y')
    ax[2].set_title('World Z')

    ax[0] = sns.regplot(test_set['wx'], predictions[:, 0], ax=ax[0], line_kws=line_kws, scatter_kws=scatter_kws)
    ax[1] = sns.regplot(test_set['wy'], predictions[:, 1], ax=ax[1], line_kws=line_kws, scatter_kws=scatter_kws)
    ax[2] = sns.regplot(test_set['wz'], predictions[:, 2], ax=ax[2], line_kws=line_kws, scatter_kws=scatter_kws)
dataf = pd.read_csv('../input/carworldpositions/data_points.csv').drop('Unnamed: 0', axis=1)
dataf.describe()
# Remove outliers
dataf = dataf[dataf['wx'] < 100]
dataf = dataf[dataf['wy'] < 100]
dataf = dataf[dataf['wz'] < 100]
sns.pairplot(dataf)
# Normalization (and centering) for some of the algorithms (knn, svm or nn)
dataf['Cx'] /= IMAGE_WIDTH
dataf['Cy'] /= IMAGE_HEIGHT

scaler = sklearn.preprocessing.StandardScaler()
dataf[['wx', 'wy', 'wz']] = scaler.fit_transform(dataf[['wx', 'wy', 'wz']])
# Train test split
mask = np.random.random(len(dataf)) > 0.7

dtrain = dataf[mask]
dtest = dataf[~mask]

# Predictor and target variable names
predictors = ['Cx', 'Cy', 'bh', 'bw']
targets = ['wx', 'wy', 'wz']
"""
# Model selection

We will test baseline models to choose one to do the fine-tuning
"""
# DataFrame to store the results
test_metrics = pd.DataFrame(columns=['Method', 'WX_Error', 'WY_Error', 'WZ_Error', 'WX_r2', 'WY_r2', 'WZ_r2'])
"""
## Random Forest
"""
rf = sklearn.ensemble.RandomForestRegressor(n_estimators=200)
rf = rf.fit(X=dtrain[predictors], y=dtrain[targets])
preds = rf.predict(dtest[predictors])
results = print_metrics(dtest, preds)
test_metrics = test_metrics.append(pd.Series(('RF', *results), index=test_metrics.columns), ignore_index=True)
fig, ax = plt.subplots(3, figsize=(15,16))
plot_predictions(dtest, preds, fig, ax)
"""
## Boosting
"""
bt = sklearn.ensemble.GradientBoostingRegressor()

mor = sklearn.multioutput.MultiOutputRegressor(bt)
mor = mor.fit(X=dtrain[predictors], y=dtrain[targets])
preds = mor.predict(dtest[predictors])
results = print_metrics(dtest, preds)
test_metrics = test_metrics.append(pd.Series(('BoostingT', *results), index=test_metrics.columns), 
                                   ignore_index=True)
fig, ax = plt.subplots(3, figsize=(15,16))
plot_predictions(dtest, preds, fig, ax)
"""
## KNN
"""
knn = neighbors.KNeighborsRegressor(10)
knn = knn.fit(X=dtrain[predictors], y=dtrain[targets])
preds = knn.predict(dtest[predictors])
results = print_metrics(dtest, preds)
test_metrics = test_metrics.append(pd.Series(('KNN', *results), index=test_metrics.columns), 
                                   ignore_index=True)
fig, ax = plt.subplots(3, figsize=(15,16))
plot_predictions(dtest, preds, fig, ax)
"""
## SVM
"""
svr = sklearn.svm.SVR(gamma='scale')

mor = sklearn.multioutput.MultiOutputRegressor(svr)
mor = mor.fit(X=dtrain[predictors], y=dtrain[targets])
preds = mor.predict(dtest[predictors])
results = print_metrics(dtest, preds)
test_metrics = test_metrics.append(pd.Series(('SVM', *results), index=test_metrics.columns), 
                                   ignore_index=True)
fig, ax = plt.subplots(3, figsize=(15,16))
plot_predictions(dtest, preds, fig, ax)
"""
# Neural net
"""
from tensorflow import keras as k
inp = k.layers.Input(shape=(len(predictors),))
h = k.layers.Dense(300, activation='linear')(inp)
h = k.layers.Dropout(.3)(h)
h = k.layers.Dense(500, activation='relu')(h)
h = k.layers.Dropout(.1)(h)
h = k.layers.Dense(300, activation='linear')(h)
h = k.layers.Dense(200, activation='linear')(h)
h = k.layers.Dense(200, activation='selu')(h)
out = k.layers.Dense(3, activation='linear')(h)

model = k.models.Model(inputs=inp, outputs=out)

model.compile(k.optimizers.Adam(), loss='mse')
h = model.fit(x=dtrain[predictors].values, y=dtrain[targets].values, 
              validation_split=.3, epochs=10, batch_size=16)
preds = model.predict(dtest[predictors].values)
results = print_metrics(dtest, preds)
test_metrics = test_metrics.append(pd.Series(('NN', *results), index=test_metrics.columns), 
                                   ignore_index=True)
fig, ax = plt.subplots(3, figsize=(15,16))
plot_predictions(dtest, preds, fig, ax)
"""
# Results
"""
test_metrics
"""
Random Forest seems to be the baseline winner. I will finetune it.

The X position (lateral displacement is well predicted), however, Y and Z have a little more variance. This needs to be improved.
"""
# Reload dataset without normalization
dataf = pd.read_csv('../input/carworldpositions/data_points.csv').drop('Unnamed: 0', axis=1)
# Remove outliers
dataf = dataf[dataf['wx'] < 100]
dataf = dataf[dataf['wy'] < 100]
dataf = dataf[dataf['wz'] < 100]

dtrain = dataf[mask]
dtest = dataf[~mask]
rf = sklearn.ensemble.RandomForestRegressor()
param_grid = {
    'n_estimators':[200, 400, 500, 800, 1000, 2000]
}

gs = sklearn.model_selection.GridSearchCV(rf, n_jobs=4, param_grid=param_grid)
gs = gs.fit(dtrain[predictors], dtrain[targets])
pd.concat([pd.DataFrame(gs.cv_results_["params"]),
           pd.DataFrame(gs.cv_results_["mean_test_score"], columns=["MSE"])],axis=1)

"""
200 trees seems OK
"""
rf = sklearn.ensemble.RandomForestRegressor(n_estimators=200)
rf = rf.fit(X=dtrain[predictors], y=dtrain[targets])
preds = rf.predict(dtest[predictors])
results = print_metrics(dtest, preds)
# Save the model if you need it
# joblib.dump(rf, 'pos_predictor.joblib')