# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

# + [markdown] id="peUFBQDh_Yo_"
# # Support Vector Machines
#
# En el siguiente notebook veremos el algoritmo SVM (Support Vector Machine), para el cual veremos teoría e implementación a la vez:

# + [markdown] id="OZVA5ffX_YpB"
# ### Configurando el entorno

# + id="ayuPpKv5_YpC"
import sys
import sklearn
assert sklearn.__version__ >= "0.20"
import numpy as np
import os


# Establecemos una semilla:
np.random.seed(42)

# Para las representaciones:
# %matplotlib inline
import matplotlib as mpl
import matplotlib.pyplot as plt
mpl.rc('axes', labelsize=14)
mpl.rc('xtick', labelsize=12)
mpl.rc('ytick', labelsize=12)

# Ruta para guardar las imágenes:
PROJECT_ROOT_DIR = "."
CHAPTER_ID = "svm"
IMAGES_PATH = os.path.join(PROJECT_ROOT_DIR, "images", CHAPTER_ID)
os.makedirs(IMAGES_PATH, exist_ok=True)

def save_fig(fig_id, tight_layout=True, fig_extension="png", resolution=300):
    path = os.path.join(IMAGES_PATH, fig_id + "." + fig_extension)
    print("Saving figure", fig_id)
    if tight_layout:
        plt.tight_layout()
    plt.savefig(path, format=fig_extension, dpi=resolution)


# + [markdown] id="95v-QLEa_YpG"
# # Ejemplo básico de clasificación

# + [markdown] id="2Szjy-6V_YpH"
# Con el siguiente código podremos crear nuestro modelo SVM lineal de clasificación:

# +
from sklearn.svm import SVC
from sklearn import datasets

iris = datasets.load_iris()
X = iris["data"][:, (2, 3)]  # petal length, petal width
y = iris["target"]

# + id="1EjTbRbt_YpI" outputId="d855be49-512f-417d-9742-0510ce657c9a"
setosa_or_versicolor = (y == 0) | (y == 1)
X = X[setosa_or_versicolor]
y = y[setosa_or_versicolor]

# SVM Classifier model
svm_clf = SVC(kernel="linear", C=float("inf"))
svm_clf.fit(X, y)
# -

# A continuación, dibujaremos los márgenes de decisión y los que utilizará el SVM, pero no hay que saber hacer esto, simplemente es con fines educativos:

# + id="XlFQexfx_YpM" outputId="2cbfd52f-6d3f-4667-aef0-3a99c4a96f23"
# Bad models
x0 = np.linspace(0, 5.5, 200)
pred_1 = 5*x0 - 20
pred_2 = x0 - 1.8
pred_3 = 0.1 * x0 + 0.5

def plot_svc_decision_boundary(svm_clf, xmin, xmax):
    w = svm_clf.coef_[0]
    b = svm_clf.intercept_[0]

    # At the decision boundary, w0*x0 + w1*x1 + b = 0
    # => x1 = -w0/w1 * x0 - b/w1
    x0 = np.linspace(xmin, xmax, 200)
    decision_boundary = -w[0]/w[1] * x0 - b/w[1]

    margin = 1/w[1]
    gutter_up = decision_boundary + margin
    gutter_down = decision_boundary - margin

    svs = svm_clf.support_vectors_
    plt.scatter(svs[:, 0], svs[:, 1], s=180, facecolors='#FFAAAA')
    plt.plot(x0, decision_boundary, "k-", linewidth=2)
    plt.plot(x0, gutter_up, "k--", linewidth=2)
    plt.plot(x0, gutter_down, "k--", linewidth=2)

plt.figure(figsize=(12,2.7))

plt.subplot(121)
plt.plot(x0, pred_1, "g--", linewidth=2)
plt.plot(x0, pred_2, "m-", linewidth=2)
plt.plot(x0, pred_3, "r-", linewidth=2)
plt.plot(X[:, 0][y==1], X[:, 1][y==1], "bs", label="Iris versicolor")
plt.plot(X[:, 0][y==0], X[:, 1][y==0], "yo", label="Iris setosa")
plt.xlabel("Petal length", fontsize=14)
plt.ylabel("Petal width", fontsize=14)
plt.legend(loc="upper left", fontsize=14)
plt.axis([0, 5.5, 0, 2])

plt.subplot(122)
plot_svc_decision_boundary(svm_clf, 0, 5.5)
plt.plot(X[:, 0][y==1], X[:, 1][y==1], "bs")
plt.plot(X[:, 0][y==0], X[:, 1][y==0], "yo")
plt.xlabel("Petal length", fontsize=14)
plt.axis([0, 5.5, 0, 2])

save_fig("large_margin_classification_plot")
plt.show()
# -

# Lo que vemos a la izquierda es la representación de los datos de las flores Iris con posibles fronteras de división, superpuestas a los datos.
#
# A la derecha, tenemos el hiperplano (en este caso, una recta) óptimo, que maximizará la distancia entre clases.

# + [markdown] id="IfE9PMH1_YpP"
# ### Sensibilidad a la escala de las varaibles
#
# Como hemos comentado, un cambio en la escala puede suponer que un algoritmo funcione o no, ya que no será capaz de extraer el verdadero valor de cada variable. A continuación, se recoge un par de gráficos en los que se comapra (no os aprendáis el código de la representación, es para representar las imágenes que nos permitan explicar el caso).

# + id="KO1bHNnA_YpQ" outputId="4dbfdc61-5f99-43e9-8c63-9a7b49c60138"
Xs = np.array([[1, 50], [5, 20], [3, 80], [5, 60]]).astype(np.float64)
ys = np.array([0, 0, 1, 1])
svm_clf = SVC(kernel="linear", C=100)
svm_clf.fit(Xs, ys)

plt.figure(figsize=(12,3.2))
plt.subplot(121)
plt.plot(Xs[:, 0][ys==1], Xs[:, 1][ys==1], "bo")
plt.plot(Xs[:, 0][ys==0], Xs[:, 1][ys==0], "ms")
plot_svc_decision_boundary(svm_clf, 0, 6)
plt.xlabel("$x_0$", fontsize=20)
plt.ylabel("$x_1$  ", fontsize=20, rotation=0)
plt.title("Unscaled", fontsize=16)
plt.axis([0, 6, 0, 90])

from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(Xs)
svm_clf.fit(X_scaled, ys)

plt.subplot(122)
plt.plot(X_scaled[:, 0][ys==1], X_scaled[:, 1][ys==1], "bo")
plt.plot(X_scaled[:, 0][ys==0], X_scaled[:, 1][ys==0], "ms")
plot_svc_decision_boundary(svm_clf, -2, 2)
plt.xlabel("$x_0$", fontsize=20)
plt.title("Scaled", fontsize=16)
plt.axis([-2, 2, -2, 2])

save_fig("sensitivity_to_feature_scales_plot")

# -

# Como puedes comprobar, si no escalamos los datos, el hiperplano óptimo cambia, por lo que las decisiones serán distintas y, en la mayoría de casos, eróneas.

# + [markdown] id="TBe-G-xa_YpU"
# # Sensibilidad de los outliers
#
# Así como el escalado es importante, los outliers no se quedan atrás, pudiendo hacer que un algoritmo no funcione si se nos ha colado algún outlier:

# + id="RIL-Iwy2_YpU" outputId="3a57927c-4b6d-44ed-c199-24213fd6a039"
X_outliers = np.array([[3.4, 1.3], [3.2, 0.8]])
y_outliers = np.array([0, 0])
Xo1 = np.concatenate([X, X_outliers[:1]], axis=0)
yo1 = np.concatenate([y, y_outliers[:1]], axis=0)
Xo2 = np.concatenate([X, X_outliers[1:]], axis=0)
yo2 = np.concatenate([y, y_outliers[1:]], axis=0)

svm_clf2 = SVC(kernel="linear", C=10**9)
svm_clf2.fit(Xo2, yo2)

plt.figure(figsize=(12,2.7))

plt.subplot(121)
plt.plot(Xo1[:, 0][yo1==1], Xo1[:, 1][yo1==1], "bs")
plt.plot(Xo1[:, 0][yo1==0], Xo1[:, 1][yo1==0], "yo")
plt.text(0.3, 1.0, "Impossible!", fontsize=24, color="red")
plt.xlabel("Petal length", fontsize=14)
plt.ylabel("Petal width", fontsize=14)
plt.annotate("Outlier",
             xy=(X_outliers[0][0], X_outliers[0][1]),
             xytext=(2.5, 1.7),
             ha="center",
             arrowprops=dict(facecolor='black', shrink=0.1),
             fontsize=16,
            )
plt.axis([0, 5.5, 0, 2])

plt.subplot(122)
plt.plot(Xo2[:, 0][yo2==1], Xo2[:, 1][yo2==1], "bs")
plt.plot(Xo2[:, 0][yo2==0], Xo2[:, 1][yo2==0], "yo")
plot_svc_decision_boundary(svm_clf2, 0, 5.5)
plt.xlabel("Petal length", fontsize=14)
plt.annotate("Outlier",
             xy=(X_outliers[1][0], X_outliers[1][1]),
             xytext=(3.2, 0.08),
             ha="center",
             arrowprops=dict(facecolor='black', shrink=0.1),
             fontsize=16,
            )
plt.axis([0, 5.5, 0, 2])

save_fig("sensitivity_to_outliers_plot")
plt.show()
# -

# Como podemos comprobar, en el caso del hiperplano óptimo su efecto es muy claro, ya que directamente hace que no se pueda llevar a cabo, al estar entre el resto de valores. En cambio, en el caso de la derecha, vemos cómo sí que se puede calcular el hiperplano óptimo, pero se ve muy afectado por el outlier amarillo.

# + [markdown] id="ZkGRGZU5_Ypm"
# # Clasificación no lineal
#
# No solo tenemos SVM lineales, sino que también podemos utilizar modelos SVM no lineales. Para ello, utilizaremos diferentes kernels.
#
#
# Por ejemplo, podemos utilizar el kernel polinómico:

# + id="Cok48GTd_Ypn" outputId="c13ef886-0e0e-44e6-8ece-28eda052ae07"
X1D = np.linspace(-4, 4, 9).reshape(-1, 1)
X2D = np.c_[X1D, X1D**2]
y = np.array([0, 0, 1, 1, 1, 1, 1, 0, 0])

plt.figure(figsize=(11, 4))

plt.subplot(121)
plt.grid(True, which='both')
plt.axhline(y=0, color='k')
plt.plot(X1D[:, 0][y==0], np.zeros(4), "bs")
plt.plot(X1D[:, 0][y==1], np.zeros(5), "g^")
plt.gca().get_yaxis().set_ticks([])
plt.xlabel(r"$x_1$", fontsize=20)
plt.axis([-4.5, 4.5, -0.2, 0.2])

plt.subplot(122)
plt.grid(True, which='both')
plt.axhline(y=0, color='k')
plt.axvline(x=0, color='k')
plt.plot(X2D[:, 0][y==0], X2D[:, 1][y==0], "bs")
plt.plot(X2D[:, 0][y==1], X2D[:, 1][y==1], "g^")
plt.xlabel(r"$x_1$", fontsize=20)
plt.ylabel(r"$x_2$", fontsize=20, rotation=0)
plt.gca().get_yaxis().set_ticks([0, 4, 8, 12, 16])
plt.plot([-4.5, 4.5], [6.5, 6.5], "r--", linewidth=3)
plt.axis([-4.5, 4.5, -1, 17])

plt.subplots_adjust(right=1)

save_fig("higher_dimensions_plot", tight_layout=False)
plt.show()

# + [markdown] id="LXaZ3GUR_YqF"
# # Regresión
#
# Finalmente, tenemos el algoritmo de regresión, que en lugar de maximizar la distancia entre los puntos más cercanos al hiperplano, lo que buscará será que se queden fuera de su rango de acción el menor número de muestras posibles:

# + id="qONUHlFt_YqF"
np.random.seed(42)
m = 50
X = 2 * np.random.rand(m, 1)
y = (4 + 3 * X + np.random.randn(m, 1)).ravel()

# + id="FNkCdFFl_YqK"
from sklearn.svm import LinearSVR

svm_reg1 = LinearSVR(epsilon=1.5, random_state=42)
svm_reg2 = LinearSVR(epsilon=0.5, random_state=42)
svm_reg1.fit(X, y)
svm_reg2.fit(X, y)

def find_support_vectors(svm_reg, X, y):
    y_pred = svm_reg.predict(X)
    off_margin = (np.abs(y - y_pred) >= svm_reg.epsilon)
    return np.argwhere(off_margin)

svm_reg1.support_ = find_support_vectors(svm_reg1, X, y)
svm_reg2.support_ = find_support_vectors(svm_reg2, X, y)

eps_x1 = 1
eps_y_pred = svm_reg1.predict([[eps_x1]])


# + id="WPJ5wupO_YqN" outputId="e01b2de9-0def-4845-f649-314b135a1ed3"
def plot_svm_regression(svm_reg, X, y, axes):
    x1s = np.linspace(axes[0], axes[1], 100).reshape(100, 1)
    y_pred = svm_reg.predict(x1s)
    plt.plot(x1s, y_pred, "k-", linewidth=2, label=r"$\hat{y}$")
    plt.plot(x1s, y_pred + svm_reg.epsilon, "k--")
    plt.plot(x1s, y_pred - svm_reg.epsilon, "k--")
    plt.scatter(X[svm_reg.support_], y[svm_reg.support_], s=180, facecolors='#FFAAAA')
    plt.plot(X, y, "bo")
    plt.xlabel(r"$x_1$", fontsize=18)
    plt.legend(loc="upper left", fontsize=18)
    plt.axis(axes)

plt.figure(figsize=(9, 4))
plt.subplot(121)
plot_svm_regression(svm_reg1, X, y, [0, 2, 3, 11])
plt.title(r"$\epsilon = {}$".format(svm_reg1.epsilon), fontsize=18)
plt.ylabel(r"$y$", fontsize=18, rotation=0)
#plt.plot([eps_x1, eps_x1], [eps_y_pred, eps_y_pred - svm_reg1.epsilon], "k-", linewidth=2)
plt.annotate(
        '', xy=(eps_x1, eps_y_pred), xycoords='data',
        xytext=(eps_x1, eps_y_pred - svm_reg1.epsilon),
        textcoords='data', arrowprops={'arrowstyle': '<->', 'linewidth': 1.5}
    )
plt.text(0.91, 5.6, r"$\epsilon$", fontsize=20)
plt.subplot(122)
plot_svm_regression(svm_reg2, X, y, [0, 2, 3, 11])
plt.title(r"$\epsilon = {}$".format(svm_reg2.epsilon), fontsize=18)
save_fig("svm_regression_plot")
plt.show()
# -

# Como puedes observar, la distancia del hiperplano a los puntos es mayor en el primer caso, lo que permitirá generalizar más; mientras que en la derecha, se quedan fuera de la predicción muchos puntos.
#
# En cuanto a los de regresion no lineal, la idea es semejante, solo que extendida para que se ofrezca un valor numérico y no una clase:

# + id="lq9AAH1k_YqS"
np.random.seed(42)
m = 100
X = 2 * np.random.rand(m, 1) - 1
y = (0.2 + 0.1 * X + 0.5 * X**2 + np.random.randn(m, 1)/10).ravel()

# + id="C3i-8t4Y_YqX" outputId="c072cc54-264b-4dba-f6ea-6041e5eca204"
from sklearn.svm import SVR

svm_poly_reg1 = SVR(kernel="poly", degree=2, C=100, epsilon=0.1, gamma="scale")
svm_poly_reg2 = SVR(kernel="poly", degree=2, C=0.01, epsilon=0.1, gamma="scale")
svm_poly_reg1.fit(X, y)
svm_poly_reg2.fit(X, y)

# + id="stBMXBCl_YqZ" outputId="2595f8bd-7114-41c4-b0af-1753a4bfa708"
plt.figure(figsize=(9, 4))
plt.subplot(121)
plot_svm_regression(svm_poly_reg1, X, y, [-1, 1, 0, 1])
plt.title(r"$degree={}, C={}, \epsilon = {}$".format(svm_poly_reg1.degree, svm_poly_reg1.C, svm_poly_reg1.epsilon), fontsize=18)
plt.ylabel(r"$y$", fontsize=18, rotation=0)
plt.subplot(122)
plot_svm_regression(svm_poly_reg2, X, y, [-1, 1, 0, 1])
plt.title(r"$degree={}, C={}, \epsilon = {}$".format(svm_poly_reg2.degree, svm_poly_reg2.C, svm_poly_reg2.epsilon), fontsize=18)
save_fig("svm_with_polynomial_kernel_plot")
plt.show()
# -

from sklearn.svm import S

# ### Escalado

# +
# Escalado

from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
svm_clf.fit(X_scaled, y)

# SVM Classifier model:
from sklearn.svm import LinearSVC
from sklearn.svm import SVC
# SVM Regressor model:
from sklearn.svm import LinearSVR
from sklearn.svm import SVR

svm_clf = LinearSVC(C=1)
svm_clf.fit(X, y)

# SVC
svm_clf2 = SVC(kernel="linear", C=10**9)
svm_clf2.fit(Xo2, yo2)

# SVC Regressor model
from sklearn.svm import LinearSVR
svm_reg1 = LinearSVR(epsilon=1.5, random_state=42)
svm_reg1.fit(X, y)


from sklearn.svm import SVR
svm_reg1 = SVR(epsilon=1.5, random_state=42)
svm_reg1.fit(X, y)
# -



svm_clf.predict([[2.5, 1]])

# ### EJERCICIO
#
# Al igual que hemos hecho con los modelos anteriores, vamos a proponer un par de ejercicios para poder compararlo y ver que resultados nos ofrece. Así que vamos a recuperar el DataFrame del FIFA y vamos a:
#
# 1. Crea un modelo de clasificación basado en SVM que nos ayude a diferenciar entre jugadores zurdos y diestros

# +
import pandas as pd
import numpy as np

df = pd.read_csv("../../../data/FIFA20.csv")
df

# +
import random
import numpy as np

zurdos = df[df['preferred_foot']=='Left']

n_index = np.random.choice(df[df['preferred_foot']=='Right'].index, size=len(zurdos), replace=False)

diestros = df[df.index.isin(n_index)]

N_df = pd.concat([diestros ,zurdos] )

print(len(N_df))
N_df['preferred_foot'].value_counts()

df = N_df

# +
df['año'] = df['dob'].apply(lambda x: int(x[0:4]))
df['mes'] = df['dob'].apply(lambda x: int(x[5:7]))
df['dia'] = df['dob'].apply(lambda x: int(x[8:10]))

df = df.dropna(subset=["team_position"])

df['money_pos'] = df.groupby("team_position").apply(lambda x: x['value_eur']/x['value_eur'].mean()).values

def position(x):
    x = str(x)
    if x[0] == 'R' and x != 'RES':
        return 'R'
    elif x[0] == 'L':
        return 'L'
    else:
        return ''

df['team_position'] = df['team_position'].apply(lambda x: position(x))

# +
from sklearn.model_selection import train_test_split

feature_cols = ['overall', 'potential', 'value_eur', 'año', 'mes', 'money_pos', 'team_position', 'dia']
pred_col = 'preferred_foot'

X = df[feature_cols]
y = df[pred_col]

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state = 42)

# +
# Tratamiento categóricas:
X_train = pd.get_dummies(X_train)
X_test = pd.get_dummies(X_test)

for col in X_train.columns:
    if col not in X_test.columns:
        X_test[col] = 0
        
X_test = X_test[X_train.columns]

# +
from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
scaler.fit(X_train)
X_train_scaled = scaler.transform(X_train)

X_test_scaled = scaler.transform(X_test)

# +
# # Equivalente:
# scaler = StandardScaler()
# X_train_scaled = scaler.fit_transform(X_train)

# X_test_scaled = scaler.transform(X_test)

# +
from sklearn.svm import LinearSVC

model = LinearSVC(C=10, random_state=42)

model.fit(X_train_scaled, y_train)
# -

model.score(X_test_scaled, y_test)

# +
from sklearn.svm import SVC

model = SVC(random_state=42)

model.fit(X_train_scaled, y_train)
# -

model.score(X_test_scaled, y_test)

pd.Series(model.predict(X_test_scaled)).value_counts()

# 2. Crea un modelo de regresión que nos permita calcular el valor (value_eur) en función de las otras variables:

# +
import pandas as pd
import numpy as np

df = pd.read_csv("../../../data/FIFA20.csv")

# +
df['año'] = df['dob'].apply(lambda x: int(x[0:4]))
df['mes'] = df['dob'].apply(lambda x: int(x[5:7]))
df['dia'] = df['dob'].apply(lambda x: int(x[8:10]))

df = df.dropna(subset=["team_position"])

# df['money_pos'] = df.groupby("team_position").apply(lambda x: x['value_eur']/x['value_eur'].mean()).values

def position(x):
    x = str(x)
    if x[0] == 'R' and x != 'RES':
        return 'R'
    elif x[0] == 'L':
        return 'L'
    else:
        return ''

df['team_position'] = df['team_position'].apply(lambda x: position(x))

# +
from sklearn.model_selection import train_test_split

feature_cols = ['overall', 'potential', 'preferred_foot', 'wage_eur', 'año', 'mes', 'team_position', 'dia']
pred_col = 'value_eur'

X = df[feature_cols]
y = df[pred_col]

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state = 42)

# +
# Tratamiento categóricas:
X_train = pd.get_dummies(X_train)
X_test = pd.get_dummies(X_test)

for col in X_train.columns:
    if col not in X_test.columns:
        x_test[col] = 0
        
X_test = X_test[X_train.columns]

# +
from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
scaler.fit(X_train)
X_train_scaled = scaler.transform(X_train)

X_test_scaled = scaler.transform(X_test)

# +
# # Equivalente:
# scaler = StandardScaler()
# X_train_scaled = scaler.fit_transform(X_train)

# X_test_scaled = scaler.transform(X_test)

# +
from sklearn.svm import LinearSVR

model = LinearSVR(C=200000, epsilon=1.5, random_state=42)

model.fit(X_train_scaled, y_train)
# -

model.score(X_test_scaled, y_test)

pd.DataFrame({'real': y_test, 'pred': model.predict(X_test_scaled)})

# +
plt.scatter(y_test.index[:10], y_test[:10])
plt.scatter(y_test.index[:10], model.predict(X_test_scaled)[:10])

plt.grid();
# -





StarcoderdataJupyterScriptsDedupFiltered