Paso 1: Importando las librerías necesarias¶
Lo primero es cargar todas las herramientas que vamos a usar a lo largo del notebook.
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.
# 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
| 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!
# ¿Cuántas filas y columnas tiene el dataset?
data.shape
(2000, 21)
# 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.
# 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()
| 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.
# 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()
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.
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]
¿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.
# 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)
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:
- Dividir los datos en entrenamiento y prueba
- Escalar las variables para que todas tengan el mismo peso
- Entrenar el SVM con los datos de entrenamiento escalados
# 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.
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)
# 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
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)
# 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
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
# 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%
# 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))
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
StandardScalersolo 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,kernely 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!