Paso 1: Importando las librerías necesarias¶

Lo primero es cargar todas las herramientas que vamos a usar a lo largo del notebook.

In [1]:
import numpy as np        # Operaciones numéricas eficientes
import pandas as pd       # Manejo de datos en tablas (DataFrames)

import matplotlib.pyplot as plt   # Visualizaciones básicas
import seaborn as sns             # Visualizaciones estadísticas más elaboradas

from sklearn.preprocessing import StandardScaler      # Para normalizar la escala de los datos
from sklearn.model_selection import train_test_split  # Para dividir datos en entrenamiento y prueba

from sklearn.svm import SVC  # El modelo de clasificación SVM

from sklearn.metrics import accuracy_score, confusion_matrix, classification_report
# accuracy_score    → fracción de predicciones correctas
# confusion_matrix  → detalle de aciertos y errores por clase
# classification_report → precisión, recall y F1-score por clase

Paso 2: Cargando el dataset¶

El archivo CSV está almacenado en Google Drive. Lo montamos y lo cargamos con pandas.

In [2]:
# Montamos Google Drive para poder acceder al archivo CSV
from google.colab import drive
drive.mount('/content/drive')

resources_path = '/content/drive/My Drive'
data = pd.read_csv(resources_path+'/resources/train.csv')
data
Mounted at /content/drive
Out[2]:
battery_power blue clock_speed dual_sim fc four_g int_memory m_dep mobile_wt n_cores ... px_height px_width ram sc_h sc_w talk_time three_g touch_screen wifi price_range
0 842 0 2.2 0 1 0 7 0.6 188 2 ... 20 756 2549 9 7 19 0 0 1 1
1 1021 1 0.5 1 0 1 53 0.7 136 3 ... 905 1988 2631 17 3 7 1 1 0 2
2 563 1 0.5 1 2 1 41 0.9 145 5 ... 1263 1716 2603 11 2 9 1 1 0 2
3 615 1 2.5 0 0 0 10 0.8 131 6 ... 1216 1786 2769 16 8 11 1 0 0 2
4 1821 1 1.2 0 13 1 44 0.6 141 2 ... 1208 1212 1411 8 2 15 1 1 0 1
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
1995 794 1 0.5 1 0 1 2 0.8 106 6 ... 1222 1890 668 13 4 19 1 1 0 0
1996 1965 1 2.6 1 0 0 39 0.2 187 4 ... 915 1965 2032 11 10 16 1 1 1 2
1997 1911 0 0.9 1 1 1 36 0.7 108 8 ... 868 1632 3057 9 1 5 1 1 0 3
1998 1512 0 0.9 0 4 1 46 0.1 145 5 ... 336 670 869 18 10 19 1 1 1 0
1999 510 1 2.0 1 5 1 45 0.9 168 6 ... 483 754 3919 19 4 2 1 1 1 3

2000 rows × 21 columns

Paso 3: Análisis Exploratorio de los Datos (EDA)¶

Antes de entrenar cualquier modelo, es fundamental entender los datos. En esta sección exploraremos el dataset para conocer su estructura, distribuciones y relaciones entre variables.

Un buen EDA puede ahorrarte horas de depuración más adelante. ¡No te lo saltes!

In [3]:
# ¿Cuántas filas y columnas tiene el dataset?
data.shape
Out[3]:
(2000, 21)
In [4]:
# Tipos de datos por columna y conteo de valores nulos
# Si hay columnas con valores nulos, habría que tratarlas antes de entrenar
data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 2000 entries, 0 to 1999
Data columns (total 21 columns):
 #   Column         Non-Null Count  Dtype  
---  ------         --------------  -----  
 0   battery_power  2000 non-null   int64  
 1   blue           2000 non-null   int64  
 2   clock_speed    2000 non-null   float64
 3   dual_sim       2000 non-null   int64  
 4   fc             2000 non-null   int64  
 5   four_g         2000 non-null   int64  
 6   int_memory     2000 non-null   int64  
 7   m_dep          2000 non-null   float64
 8   mobile_wt      2000 non-null   int64  
 9   n_cores        2000 non-null   int64  
 10  pc             2000 non-null   int64  
 11  px_height      2000 non-null   int64  
 12  px_width       2000 non-null   int64  
 13  ram            2000 non-null   int64  
 14  sc_h           2000 non-null   int64  
 15  sc_w           2000 non-null   int64  
 16  talk_time      2000 non-null   int64  
 17  three_g        2000 non-null   int64  
 18  touch_screen   2000 non-null   int64  
 19  wifi           2000 non-null   int64  
 20  price_range    2000 non-null   int64  
dtypes: float64(2), int64(19)
memory usage: 328.3 KB

Estadísticas descriptivas¶

¿Cómo se distribuyen los valores de cada variable? El siguiente resumen nos da la media, desviación estándar, mínimos y máximos de cada columna. Es muy útil para detectar valores atípicos o escalas muy dispares entre variables.

In [5]:
# Resumen estadístico: media, desviación estándar, percentiles de cada variable
# Fíjate en las diferencias de escala entre columnas (ej: ram vs blue)
data.describe()
Out[5]:
battery_power blue clock_speed dual_sim fc four_g int_memory m_dep mobile_wt n_cores ... px_height px_width ram sc_h sc_w talk_time three_g touch_screen wifi price_range
count 2000.000000 2000.0000 2000.000000 2000.000000 2000.000000 2000.000000 2000.000000 2000.000000 2000.000000 2000.000000 ... 2000.000000 2000.000000 2000.000000 2000.000000 2000.000000 2000.000000 2000.000000 2000.000000 2000.000000 2000.000000
mean 1238.518500 0.4950 1.522250 0.509500 4.309500 0.521500 32.046500 0.501750 140.249000 4.520500 ... 645.108000 1251.515500 2124.213000 12.306500 5.767000 11.011000 0.761500 0.503000 0.507000 1.500000
std 439.418206 0.5001 0.816004 0.500035 4.341444 0.499662 18.145715 0.288416 35.399655 2.287837 ... 443.780811 432.199447 1084.732044 4.213245 4.356398 5.463955 0.426273 0.500116 0.500076 1.118314
min 501.000000 0.0000 0.500000 0.000000 0.000000 0.000000 2.000000 0.100000 80.000000 1.000000 ... 0.000000 500.000000 256.000000 5.000000 0.000000 2.000000 0.000000 0.000000 0.000000 0.000000
25% 851.750000 0.0000 0.700000 0.000000 1.000000 0.000000 16.000000 0.200000 109.000000 3.000000 ... 282.750000 874.750000 1207.500000 9.000000 2.000000 6.000000 1.000000 0.000000 0.000000 0.750000
50% 1226.000000 0.0000 1.500000 1.000000 3.000000 1.000000 32.000000 0.500000 141.000000 4.000000 ... 564.000000 1247.000000 2146.500000 12.000000 5.000000 11.000000 1.000000 1.000000 1.000000 1.500000
75% 1615.250000 1.0000 2.200000 1.000000 7.000000 1.000000 48.000000 0.800000 170.000000 7.000000 ... 947.250000 1633.000000 3064.500000 16.000000 9.000000 16.000000 1.000000 1.000000 1.000000 2.250000
max 1998.000000 1.0000 3.000000 1.000000 19.000000 1.000000 64.000000 1.000000 200.000000 8.000000 ... 1960.000000 1998.000000 3998.000000 19.000000 18.000000 20.000000 1.000000 1.000000 1.000000 3.000000

8 rows × 21 columns

Matriz de correlación¶

La matriz de correlación nos muestra qué tan relacionadas están las variables entre sí. Un valor cercano a 1 o -1 indica una correlación fuerte; cercano a 0 indica poca o ninguna relación lineal.

Esto también nos ayuda a identificar si alguna variable tiene una relación directa con price_range.

In [6]:
# Calculamos la correlación entre todas las variables numéricas del dataset
correlation_matrix = data.corr()

plt.figure(figsize=(12, 10))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt=".2f", linewidths=0.5)
plt.title("Matriz de Correlación de Variables Numéricas")
plt.show()
No description has been provided for this image

Distribución de variables categóricas¶

Varias variables son binarias (0 o 1). Vamos a graficar cuántos registros tienen cada valor para detectar posibles desequilibrios en los datos.

In [7]:
df_info = data.describe()
print(df_info)

# Graficamos la frecuencia de cada valor en las variables categóricas (binarias)
# Un desequilibrio fuerte (ej: 95% ceros) podría afectar al modelo
categorical_vars = ['blue', 'dual_sim', 'four_g', 'three_g', 'touch_screen', 'wifi', 'price_range']
plt.figure(figsize=(15, 10))

for i, var in enumerate(categorical_vars, 1):
    plt.subplot(3, 3, i)
    sns.countplot(x=data[var], hue=data[var], palette="viridis", legend=False)
    plt.title(f"Frecuencia de {var}")
    plt.xlabel(var)
    plt.ylabel("Frecuencia")

plt.tight_layout()
plt.show()
       battery_power       blue  clock_speed     dual_sim           fc  \
count    2000.000000  2000.0000  2000.000000  2000.000000  2000.000000   
mean     1238.518500     0.4950     1.522250     0.509500     4.309500   
std       439.418206     0.5001     0.816004     0.500035     4.341444   
min       501.000000     0.0000     0.500000     0.000000     0.000000   
25%       851.750000     0.0000     0.700000     0.000000     1.000000   
50%      1226.000000     0.0000     1.500000     1.000000     3.000000   
75%      1615.250000     1.0000     2.200000     1.000000     7.000000   
max      1998.000000     1.0000     3.000000     1.000000    19.000000   

            four_g   int_memory        m_dep    mobile_wt      n_cores  ...  \
count  2000.000000  2000.000000  2000.000000  2000.000000  2000.000000  ...   
mean      0.521500    32.046500     0.501750   140.249000     4.520500  ...   
std       0.499662    18.145715     0.288416    35.399655     2.287837  ...   
min       0.000000     2.000000     0.100000    80.000000     1.000000  ...   
25%       0.000000    16.000000     0.200000   109.000000     3.000000  ...   
50%       1.000000    32.000000     0.500000   141.000000     4.000000  ...   
75%       1.000000    48.000000     0.800000   170.000000     7.000000  ...   
max       1.000000    64.000000     1.000000   200.000000     8.000000  ...   

         px_height     px_width          ram         sc_h         sc_w  \
count  2000.000000  2000.000000  2000.000000  2000.000000  2000.000000   
mean    645.108000  1251.515500  2124.213000    12.306500     5.767000   
std     443.780811   432.199447  1084.732044     4.213245     4.356398   
min       0.000000   500.000000   256.000000     5.000000     0.000000   
25%     282.750000   874.750000  1207.500000     9.000000     2.000000   
50%     564.000000  1247.000000  2146.500000    12.000000     5.000000   
75%     947.250000  1633.000000  3064.500000    16.000000     9.000000   
max    1960.000000  1998.000000  3998.000000    19.000000    18.000000   

         talk_time      three_g  touch_screen         wifi  price_range  
count  2000.000000  2000.000000   2000.000000  2000.000000  2000.000000  
mean     11.011000     0.761500      0.503000     0.507000     1.500000  
std       5.463955     0.426273      0.500116     0.500076     1.118314  
min       2.000000     0.000000      0.000000     0.000000     0.000000  
25%       6.000000     1.000000      0.000000     0.000000     0.750000  
50%      11.000000     1.000000      1.000000     1.000000     1.500000  
75%      16.000000     1.000000      1.000000     1.000000     2.250000  
max      20.000000     1.000000      1.000000     1.000000     3.000000  

[8 rows x 21 columns]
No description has been provided for this image

¿Está balanceado el dataset?¶

Un dataset desbalanceado puede llevar a modelos sesgados. Por ejemplo, si el 90% de los datos pertenece a una sola clase, el modelo podría predecir siempre esa clase y tener 90% de exactitud... ¡sin aprender nada útil!

Vamos a revisar si las 4 clases de price_range están bien distribuidas.

In [8]:
# Visualizamos la distribución de la variable objetivo
plt.figure(figsize=(8, 6))
sns.countplot(x=data["price_range"], hue=data["price_range"], legend=False, palette="pastel")
plt.title("Distribución de la variable objetivo (price_range)")
plt.xlabel("Rango de precios")
plt.ylabel("Frecuencia")
plt.show()

# Calculamos el porcentaje de registros por clase
distribution = data["price_range"].value_counts(normalize=True) * 100

print("Distribución de price_range en porcentaje:")
print(distribution)
No description has been provided for this image
Distribución de price_range en porcentaje:
price_range
1    25.0
2    25.0
3    25.0
0    25.0
Name: proportion, dtype: float64

¡Perfecto! El dataset está perfectamente balanceado: cada rango de precio representa aproximadamente el 25% de los datos. Esto significa que la exactitud (accuracy) es una métrica confiable para evaluar el modelo.

Paso 5: Entrenamiento del modelo SVM¶

¡Llegó el momento de entrenar! El proceso tiene tres etapas:

  1. Dividir los datos en entrenamiento y prueba
  2. Escalar las variables para que todas tengan el mismo peso
  3. Entrenar el SVM con los datos de entrenamiento escalados
In [9]:
# Separamos las características (X) de la variable objetivo (y)
X = data.drop(columns='price_range')
y = data['price_range']

# Dividimos en 80% para entrenamiento y 20% para evaluación final
# random_state=123 garantiza reproducibilidad: siempre obtendremos la misma división
X_train, X_test, y_train, y_test = train_test_split(
    X,
    y,
    train_size=0.8,
    random_state=123,
    shuffle=True
)

print(f"Datos de entrenamiento: {X_train.shape[0]} filas")
print(f"Datos de prueba:        {X_test.shape[0]} filas")
Datos de entrenamiento: 1600 filas
Datos de prueba:        400 filas

Escalado de los atributos¶

El SVM es muy sensible a la escala de los datos. Variables como ram (valores en miles de MB) pueden dominar sobre variables como blue (0 o 1), dándoles más peso del que merecen.

Con StandardScaler transformamos cada variable para que tenga media = 0 y desviación estándar = 1.

Importante: ajustamos el escalador (fit) solo con los datos de entrenamiento y luego lo aplicamos (transform) tanto al train como al test. Esto evita el data leakage: que información del conjunto de prueba "filtre" al modelo durante el entrenamiento.

In [10]:
scaler = StandardScaler()

# fit_transform: aprende la media y desviación estándar DEL TRAIN y escala
X_train_scaled = scaler.fit_transform(X_train)

# transform: aplica la misma escala (aprendida del train) al test
# Nunca usamos fit sobre el test para evitar data leakage
X_test_scaled = scaler.transform(X_test)
In [11]:
# Creamos el modelo SVM con kernel lineal
# kernel="linear" → busca un hiperplano lineal para separar las clases
# C=1 → parámetro de regularización: valores más altos permiten menos errores
#        pero pueden sobreajustar; valores más bajos generalizan mejor
modeloSVM = SVC(kernel="linear", C=1, random_state=1234)

# Entrenamos con los datos escalados de entrenamiento
# El modelo aprende los patrones que separan las 4 clases de precio
modeloSVM.fit(X_train_scaled, y_train)

modeloSVM
Out[11]:
SVC(C=1, kernel='linear', random_state=1234)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
SVC(C=1, kernel='linear', random_state=1234)
In [12]:
# Le pedimos al modelo que prediga el rango de precio para los datos de prueba
# El modelo nunca ha "visto" estos datos durante el entrenamiento
predictSVM = modeloSVM.predict(X_test_scaled)
predictSVM
Out[12]:
array([1, 3, 3, 0, 3, 3, 1, 0, 0, 1, 0, 1, 3, 1, 1, 3, 0, 3, 2, 1, 1, 0,
       0, 3, 2, 2, 3, 2, 2, 1, 0, 1, 0, 1, 2, 3, 1, 2, 1, 1, 2, 3, 2, 1,
       1, 0, 0, 2, 2, 0, 3, 1, 2, 1, 0, 2, 0, 0, 2, 3, 1, 0, 2, 1, 2, 2,
       0, 2, 3, 1, 3, 1, 0, 2, 0, 2, 0, 1, 1, 0, 0, 0, 3, 2, 2, 1, 2, 0,
       0, 2, 2, 2, 1, 2, 3, 0, 0, 1, 0, 3, 2, 1, 3, 3, 1, 1, 3, 2, 3, 3,
       2, 2, 3, 2, 3, 3, 3, 0, 1, 3, 2, 2, 3, 0, 1, 1, 3, 2, 2, 1, 3, 0,
       3, 1, 0, 2, 2, 3, 1, 3, 2, 2, 3, 1, 3, 2, 2, 0, 0, 1, 1, 3, 2, 0,
       3, 3, 3, 0, 0, 0, 2, 0, 1, 3, 1, 1, 0, 1, 1, 0, 1, 2, 1, 1, 2, 0,
       2, 0, 2, 2, 2, 3, 1, 0, 3, 3, 0, 1, 1, 2, 2, 1, 2, 3, 0, 3, 1, 2,
       3, 1, 1, 1, 0, 3, 2, 3, 1, 3, 3, 2, 3, 2, 3, 1, 1, 0, 0, 1, 2, 1,
       0, 1, 1, 2, 2, 0, 3, 1, 2, 1, 0, 3, 3, 2, 1, 2, 2, 2, 1, 0, 0, 2,
       3, 2, 2, 1, 1, 3, 2, 3, 0, 1, 2, 2, 0, 2, 3, 0, 3, 0, 3, 0, 2, 0,
       2, 2, 2, 1, 3, 1, 0, 0, 1, 0, 3, 1, 2, 2, 3, 3, 3, 2, 3, 3, 1, 1,
       1, 2, 2, 3, 2, 2, 3, 3, 1, 2, 2, 1, 2, 1, 1, 1, 3, 2, 2, 2, 3, 0,
       2, 3, 0, 0, 1, 2, 2, 3, 3, 0, 3, 2, 2, 0, 3, 3, 2, 1, 2, 1, 3, 0,
       2, 2, 3, 1, 0, 1, 3, 2, 0, 0, 3, 1, 0, 3, 2, 1, 3, 3, 0, 2, 0, 3,
       3, 0, 2, 2, 2, 3, 0, 0, 3, 2, 1, 2, 3, 3, 2, 1, 2, 0, 0, 2, 3, 0,
       1, 2, 3, 1, 3, 2, 1, 0, 1, 2, 2, 1, 0, 0, 1, 0, 0, 1, 3, 3, 2, 2,
       2, 1, 0, 0])

Paso 6: Evaluando el rendimiento del modelo¶

La exactitud sola no siempre cuenta toda la historia. Vamos a usar tres herramientas de evaluación para tener una visión completa:

  • Accuracy: el porcentaje total de aciertos
  • Matriz de confusión: nos muestra dónde se equivoca el modelo y con qué clases las confunde
  • Classification report: precisión, recall y F1-score por cada clase de precio
In [13]:
# La exactitud (accuracy) indica qué fracción de las predicciones fueron correctas
# Ejemplo: 0.95 significa que el modelo acertó en el 95% de los casos
accuracy_svm = accuracy_score(y_test, predictSVM)
print(f"Exactitud del modelo SVM: {accuracy_svm:.2%}")
Exactitud del modelo SVM: 95.25%
In [14]:
# La matriz de confusión muestra, por cada clase real (filas),
# cuántas veces el modelo predijo cada clase (columnas).
# La diagonal principal son los aciertos; el resto son errores.
cm = confusion_matrix(y_test, predictSVM)
plt.figure(figsize=(8, 6))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
            xticklabels=[0, 1, 2, 3], yticklabels=[0, 1, 2, 3])
plt.title("Matriz de Confusión - SVM Lineal")
plt.xlabel("Predicción")
plt.ylabel("Valor Real")
plt.show()

# El reporte de clasificación desglosa el rendimiento por clase:
# - Precisión:  de todo lo que predije como clase X, ¿cuánto era realmente X?
# - Recall:     de todo lo que era clase X, ¿cuánto logré identificar?
# - F1-score:   media armónica entre precisión y recall (equilibrio entre ambas)
print("Reporte de Clasificación:")
print(classification_report(y_test, predictSVM))
No description has been provided for this image
Reporte de Clasificación:
              precision    recall  f1-score   support

           0       0.97      0.98      0.97        87
           1       0.95      0.93      0.94       100
           2       0.94      0.94      0.94       116
           3       0.96      0.97      0.96        97

    accuracy                           0.95       400
   macro avg       0.95      0.95      0.95       400
weighted avg       0.95      0.95      0.95       400

Conclusiones¶

¡Llegamos al final! Repasemos lo que construimos y aprendimos:

  • Seguimos un pipeline completo de Machine Learning: exploración de datos → preprocesamiento → entrenamiento → evaluación.
  • Usamos SVM con kernel lineal, que logró una exactitud superior al 95% — un resultado excelente para un modelo sin ajuste de hiperparámetros.
  • Aplicamos escalado correcto: ajustando el StandardScaler solo con los datos de entrenamiento para evitar data leakage.
  • Evaluamos el modelo más allá de la exactitud, usando la matriz de confusión y el classification report para entender el comportamiento por clase.

¿Qué sigue?¶

En el próximo artículo exploraremos cómo llevar este modelo al siguiente nivel:

  • Validación cruzada (cross-validation): evaluar el modelo en múltiples particiones para una estimación más robusta y confiable.
  • Búsqueda de hiperparámetros (GridSearchCV / RandomizedSearchCV): encontrar automáticamente los mejores valores de C, kernel y otros parámetros del SVM.

Estos pasos son más costosos en cómputo, pero pueden marcar una diferencia importante en el rendimiento final. ¡Hasta la próxima!